From b9c2ff0398a463b3ced114466ea171a2a6f7e912 Mon Sep 17 00:00:00 2001 From: fajiao <1519100073@qq.com> Date: Wed, 12 Oct 2022 14:46:46 +0800 Subject: [PATCH] feat: commit some changes --- Cis.Application/Cb/Common/CbInfo.cs | 2 +- Cis.Application/Cb/Service/CbCameraService.cs | 3 +- Cis.Application/Cis.Application.xml | 121 ++++++++++- Cis.Application/Cm/Common/CmInfo.cs | 12 +- Cis.Application/Cm/Entity/CmMarkLabel.cs | 52 +++++ .../Cm/Service/CmMarkGroupService.cs | 42 +++- .../Cm/Service/CmMarkLabelService.cs | 55 +++++ Cis.Application/Core/Algo/MarkSearcherBase.cs | 6 + Cis.Application/Core/Api/IPtzApi.cs | 9 + Cis.Application/Core/Api/PtzServerApi.cs | 198 ++++++++++++++++++ .../Core/Center/CameraDataCenter.cs | 113 ++++++++++ Cis.Application/Core/Common/Options.cs | 22 ++ Cis.Application/Core/Entity/CameraCalcInfo.cs | 32 +++ .../Core/Service/MarkSearchService.cs | 22 ++ Cis.Application/GlobalUsings.cs | 1 + Cis.Application/Startup.cs | 14 +- Cis.Application/Sys/Common/SysInfo.cs | 2 +- .../Sys/Service/SysDictDataService.cs | 41 +++- .../Sys/Service/SysDictTypeService.cs | 43 +++- Cis.Application/Tb/Common/TbInfo.cs | 29 +++ Cis.Application/Tb/Entity/TbPtzCamera.cs | 28 +++ .../Tb/Service/TbPtzCameraService.cs | 51 +++++ Cis.Core/Cis.Core.csproj | 5 + Cis.Core/Cis.Core.xml | 168 +++++++-------- Cis.Core/CoreConfig.json | 49 ++++- .../{Util => Entity}/RespParamProvider.cs | 0 Cis.Web.Core/Startup.cs | 22 ++ Cis.Web.Entry/Cis.Web.Entry.csproj | 4 - Cis.Web.Entry/wwwroot/images/logo.png | Bin 0 -> 25808 bytes 29 files changed, 1034 insertions(+), 112 deletions(-) create mode 100644 Cis.Application/Cm/Entity/CmMarkLabel.cs create mode 100644 Cis.Application/Cm/Service/CmMarkLabelService.cs create mode 100644 Cis.Application/Core/Algo/MarkSearcherBase.cs create mode 100644 Cis.Application/Core/Api/IPtzApi.cs create mode 100644 Cis.Application/Core/Api/PtzServerApi.cs create mode 100644 Cis.Application/Core/Center/CameraDataCenter.cs create mode 100644 Cis.Application/Core/Common/Options.cs create mode 100644 Cis.Application/Core/Entity/CameraCalcInfo.cs create mode 100644 Cis.Application/Core/Service/MarkSearchService.cs create mode 100644 Cis.Application/Tb/Common/TbInfo.cs create mode 100644 Cis.Application/Tb/Entity/TbPtzCamera.cs create mode 100644 Cis.Application/Tb/Service/TbPtzCameraService.cs rename Cis.Core/{Util => Entity}/RespParamProvider.cs (100%) create mode 100644 Cis.Web.Entry/wwwroot/images/logo.png diff --git a/Cis.Application/Cb/Common/CbInfo.cs b/Cis.Application/Cb/Common/CbInfo.cs index f64368f..b612a81 100644 --- a/Cis.Application/Cb/Common/CbInfo.cs +++ b/Cis.Application/Cb/Common/CbInfo.cs @@ -22,7 +22,7 @@ public class CbInfo #region Database Info /// - /// 数据库名 + /// 数据库标识 /// public const string DbName = SqlSugarConst.DefaultConfigId; diff --git a/Cis.Application/Cb/Service/CbCameraService.cs b/Cis.Application/Cb/Service/CbCameraService.cs index 165cffb..156e370 100644 --- a/Cis.Application/Cb/Service/CbCameraService.cs +++ b/Cis.Application/Cb/Service/CbCameraService.cs @@ -1,5 +1,4 @@ using Newtonsoft.Json.Linq; -using System.Runtime.InteropServices; namespace Cis.Application.Cb; @@ -22,7 +21,7 @@ public class CbCameraService : IDynamicApiController, ITransient return entity; } - public async Task> GetList(string queryJson) + public async Task> GetList(string queryJson = "") { JObject queryObj = queryJson.ToJObject(); List list = await _cbCameraRep.AsQueryable() diff --git a/Cis.Application/Cis.Application.xml b/Cis.Application/Cis.Application.xml index 4107b98..45840b3 100644 --- a/Cis.Application/Cis.Application.xml +++ b/Cis.Application/Cis.Application.xml @@ -21,7 +21,7 @@ - 数据库名 + 数据库标识 @@ -86,7 +86,7 @@ - 数据库名 + 数据库标识 @@ -99,6 +99,16 @@ CmMarkGroup 表描述 + + + CmMarkLabel 表名 + + + + + CmMarkLabel 表描述 + + 标记分组表 @@ -119,11 +129,106 @@ 备注 + + + 标记标签表 + + + + + 名称 + + + + + Pan 位置 + + + + + Tilt 位置 + + + + + Zoom 位置 + + + + + 备注 + + + + + 相机 Id + + + + + 标记组 Id + + 标记分组服务 + + + 标记标签服务 + + + + + Ptz 信息 + + + + + Pan + + + + + Tilt + + + + + Zoom + + + + + Ptz Api 接口 + + + + + Ptz Api + + + + + PtzServer选项 + + + + + 服务类别 + + + + + 服务IP + + + + + 服务端口 + + 配置应用所需服务,在该方法中可以添加应用所需要的功能或服务 @@ -154,7 +259,7 @@ - 数据库名 + 数据库标识 @@ -257,5 +362,15 @@ 系统字典类型服务 + + + 数据库标识 + + + + + TbPtzCamera 表名 + + diff --git a/Cis.Application/Cm/Common/CmInfo.cs b/Cis.Application/Cm/Common/CmInfo.cs index f82d8e1..abfda16 100644 --- a/Cis.Application/Cm/Common/CmInfo.cs +++ b/Cis.Application/Cm/Common/CmInfo.cs @@ -22,7 +22,7 @@ public class CmInfo #region Database Info /// - /// 数据库名 + /// 数据库标识 /// public const string DbName = SqlSugarConst.DefaultConfigId; @@ -40,5 +40,15 @@ public class CmInfo /// public const string CmMarkGroupTbDesc = "标记分组表"; + /// + /// CmMarkLabel 表名 + /// + public const string CmMarkLabelTbName = "cm_mark_label"; + + /// + /// CmMarkLabel 表描述 + /// + public const string CmMarkLabelTbDesc = "标记标签表"; + #endregion Table Info } \ No newline at end of file diff --git a/Cis.Application/Cm/Entity/CmMarkLabel.cs b/Cis.Application/Cm/Entity/CmMarkLabel.cs new file mode 100644 index 0000000..bb0429a --- /dev/null +++ b/Cis.Application/Cm/Entity/CmMarkLabel.cs @@ -0,0 +1,52 @@ +namespace Cis.Application.Cm; + +/// +/// 标记标签表 +/// +[SugarTable(CmInfo.CmMarkLabelTbName, CmInfo.CmMarkLabelTbDesc)] +[Tenant(CmInfo.DbName)] +public class CmMarkLabel : EntityBase +{ + /// + /// 名称 + /// + [SugarColumn(ColumnDescription = "名称", Length = 64)] + public string Name { get; set; } + + /// + /// Pan 位置 + /// + [SugarColumn(ColumnDescription = "Pan位置")] + public double PanPosition { get; set; } + + /// + /// Tilt 位置 + /// + [SugarColumn(ColumnDescription = "Tilt位置")] + public double TiltPosition { get; set; } + + /// + /// Zoom 位置 + /// + [SugarColumn(ColumnDescription = "Zoom位置")] + public double ZoomPosition { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注", Length = 256)] + [MaxLength(256)] + public string Remark { get; set; } + + /// + /// 相机 Id + /// + [SugarColumn(ColumnDescription = "相机Id")] + public long CbCameraId { get; set; } + + /// + /// 标记组 Id + /// + [SugarColumn(ColumnDescription = "标记组Id")] + public long CmMarkGroupId { get; set; } +} \ No newline at end of file diff --git a/Cis.Application/Cm/Service/CmMarkGroupService.cs b/Cis.Application/Cm/Service/CmMarkGroupService.cs index faabdb0..2cf1302 100644 --- a/Cis.Application/Cm/Service/CmMarkGroupService.cs +++ b/Cis.Application/Cm/Service/CmMarkGroupService.cs @@ -1,4 +1,6 @@ -namespace Cis.Application.Cm; +using Newtonsoft.Json.Linq; + +namespace Cis.Application.Cm; /// /// 标记分组服务 @@ -12,4 +14,42 @@ public class CmMarkGroupService : IDynamicApiController, ITransient { _cmMarkGroupRep = cmMarkGroupRep; } + + public async Task Get(long id) + { + CmMarkGroup entity = await _cmMarkGroupRep.GetByIdAsync(id); + return entity; + } + + public async Task> GetList(string queryJson) + { + JObject queryObj = queryJson.ToJObject(); + List list = await _cmMarkGroupRep.AsQueryable() + .ToListAsync(); + return list; + } + + public async Task> GetPageList(string queryJson, string pagination) + { + Pagination pageObj = pagination.ToObject(); + JObject queryObj = queryJson.ToJObject(); + List list = await _cmMarkGroupRep.AsQueryable() + .ToPageListAsync(pageObj.Index, pageObj.Size); + return list; + } + + public async Task Add(CmMarkGroup entity) + { + await _cmMarkGroupRep.InsertAsync(entity); + } + + public async Task Update(CmMarkGroup entity) + { + await _cmMarkGroupRep.UpdateAsync(entity); + } + + public async Task Delete(CmMarkGroup entity) + { + await _cmMarkGroupRep.DeleteAsync(entity); + } } \ No newline at end of file diff --git a/Cis.Application/Cm/Service/CmMarkLabelService.cs b/Cis.Application/Cm/Service/CmMarkLabelService.cs new file mode 100644 index 0000000..3df7e79 --- /dev/null +++ b/Cis.Application/Cm/Service/CmMarkLabelService.cs @@ -0,0 +1,55 @@ +using Newtonsoft.Json.Linq; + +namespace Cis.Application.Cm; + +/// +/// 标记标签服务 +/// +[ApiDescriptionSettings(CmInfo.GroupName, Order = CmInfo.GroupOrder)] +public class CmMarkLabelService : IDynamicApiController, ITransient +{ + private readonly SqlSugarRepository _cmMarkLabelRep; + + public CmMarkLabelService(SqlSugarRepository cmMarkLabelRep) + { + _cmMarkLabelRep = cmMarkLabelRep; + } + + public async Task Get(long id) + { + CmMarkLabel entity = await _cmMarkLabelRep.GetByIdAsync(id); + return entity; + } + + public async Task> GetList(string queryJson = "") + { + JObject queryObj = queryJson.ToJObject(); + List list = await _cmMarkLabelRep.AsQueryable() + .ToListAsync(); + return list; + } + + public async Task> GetPageList(string queryJson, string pagination) + { + Pagination pageObj = pagination.ToObject(); + JObject queryObj = queryJson.ToJObject(); + List list = await _cmMarkLabelRep.AsQueryable() + .ToPageListAsync(pageObj.Index, pageObj.Size); + return list; + } + + public async Task Add(CmMarkLabel entity) + { + await _cmMarkLabelRep.InsertAsync(entity); + } + + public async Task Update(CmMarkLabel entity) + { + await _cmMarkLabelRep.UpdateAsync(entity); + } + + public async Task Delete(CmMarkLabel entity) + { + await _cmMarkLabelRep.DeleteAsync(entity); + } +} \ No newline at end of file diff --git a/Cis.Application/Core/Algo/MarkSearcherBase.cs b/Cis.Application/Core/Algo/MarkSearcherBase.cs new file mode 100644 index 0000000..033a34b --- /dev/null +++ b/Cis.Application/Core/Algo/MarkSearcherBase.cs @@ -0,0 +1,6 @@ +namespace Cis.Application.Core; + +public class MarkSearcherBase +{ + +} \ No newline at end of file diff --git a/Cis.Application/Core/Api/IPtzApi.cs b/Cis.Application/Core/Api/IPtzApi.cs new file mode 100644 index 0000000..e3461e5 --- /dev/null +++ b/Cis.Application/Core/Api/IPtzApi.cs @@ -0,0 +1,9 @@ +namespace Cis.Application.Core; + +/// +/// Ptz Api 接口 +/// +public interface IPtzApi +{ + +} \ No newline at end of file diff --git a/Cis.Application/Core/Api/PtzServerApi.cs b/Cis.Application/Core/Api/PtzServerApi.cs new file mode 100644 index 0000000..4f65872 --- /dev/null +++ b/Cis.Application/Core/Api/PtzServerApi.cs @@ -0,0 +1,198 @@ +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Cis.Application.Core; + +/// +/// Ptz Api +/// +public class PtzServerApi : IPtzApi, ISingleton +{ + #region Attr + + private TcpClient _tcpClient { get; set; } + + private NetworkStream _stream { get; set; } + + #endregion Attr + + public PtzServerApi() + { + PtzServerOptions options = App.GetOptions(); + _tcpClient = new TcpClient(options.Ip, options.Port); + //创建一个 networkstream 用来写入和读取数据 + _stream = _tcpClient.GetStream(); + } + + public RequestRealControl GetPtzInfo(int cameraId) + { + RequestRealControl realControl = new(); + try + { + string recieve_string = string.Empty; + realControl.token = 666; + + realControl.CameraInfo.cameraid = cameraId; + realControl.Status = true; + realControl.realControlType = RealControlType.PTZINFO_GET_; + byte[] data = StructToByte(realControl); + _stream.Write(data, 0, data.Length); + + byte[] recieve_byte = new byte[_tcpClient.ReceiveBufferSize]; + _stream.Read(recieve_byte, 0, (int)_tcpClient.ReceiveBufferSize); + //stream.BeginRead(recieve_byte, 0, (int)tcp_client.ReceiveBufferSize,EndRead, stream); + realControl = (RequestRealControl)BytetoStruct(recieve_byte, typeof(RequestRealControl)); + if (realControl.PTZPositionInfo.FT < 0) + { + realControl.PTZPositionInfo.FT = 3600 + realControl.PTZPositionInfo.FT; + } + } + catch (Exception) + { + realControl = default; + } + return realControl; + } + + public static byte[] StructToByte(object structObj) + { + //获取结构体大小 + int size = Marshal.SizeOf(structObj); + + byte[] data = new byte[size]; + + //分配内存空间 + IntPtr structPtr = Marshal.AllocHGlobal(size); + // 将结构体数据复制到内存空间 + Marshal.StructureToPtr(structObj, structPtr, false); + // 将内存空间的数据拷贝到byte数组 + Marshal.Copy(structPtr, data, 0, size); + //释放内存 + Marshal.FreeHGlobal(structPtr); + return data; + } + + public static object BytetoStruct(byte[] bytes, Type type) + { + object obj = new object(); + try + { + byte[] temp = bytes; + // 获取结构体大小 + int size = Marshal.SizeOf(type); + if (size > bytes.Length) + return null; + // 分配结构体内存空间 + IntPtr structPtr = Marshal.AllocHGlobal(size); + // 将byte数组内容拷贝到内存中 + Marshal.Copy(temp, 0, structPtr, size); + + // 将内存空间转化为目标结构体 + obj = Marshal.PtrToStructure(structPtr, type); + //释放内存 + Marshal.FreeHGlobal(structPtr); + } + catch (Exception) + { + } + return obj; + } + +} + +#region 与ptz服务交互使用结构体 + +//---------------------------------------------------------------------------------------- +// 与ptz服务交互使用结构体 +//---------------------------------------------------------------------------------------- +//注意这个属性不能少 +[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] +public struct RequestRealControl +{ + public int token; + + public CameraInfo CameraInfo; + + public RealControlType realControlType; + + //请求时 状态 true:开始;false:结束 答复时:true:成功;其他:失败 + public bool Status; + + public PresentInfo PresentInfo; + + public PTZPosInfo PTZPositionInfo; + //int数组,SizeConst表示数组的个数,在转换成 + //byte数组前必须先初始化数组,再使用,初始化 + //的数组长度必须和SizeConst一致,例test = new int[6]; + //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] + //public int[] test; +} + +[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] +public struct CameraInfo +{ + public int cameraid; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)] + public char[] ip; + + public ushort port; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 80)] + private char[] user; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 80)] + private char[] password; +} + +[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] +public struct PresentInfo +{ + public int presentNo;//预置位编号 + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + private char[] PresentName; // 预置位名称 +} + +[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] +public struct PTZPosInfo +{ + public int FP;//p信息 + public int FT;//T信息 + public int FZ;//Z信息 + public int TitlePosMin;//描述点垂直参数min + public int ZoomPosMin;//描述点变倍参数min + + public float FHorWidth;//水平宽度 精确到小数点后两位 *10000 + public float FVerWidth;//垂直宽度 精确到小数点后两位 *10000 + public float FFold;//zoom=1没变时的焦距 精确到小数点后两位 *100 + + public int CameraType; +} + +public enum RealControlType +{ + MOVE_UP_ = 1, + MOVE_DOWN_, + MOVE_LEFT_, + MOVE_RIGHT_, + MOVE_LEFTUP_, + MOVE_LEFTDOWN_, + MOVE_RIGHTUP_, + MOVE_RIGHTDOWN_, + + ZOOM_IN_, + ZOOM_OUT_, + FOCUS_NEAR_, + FOCUS_FAR_, + IRIS_OPEN_, + IRIS_CLOSE_, + + PRESET_SET_, + PRESET_CALL_, + + PTZINFO_GET_, + PTZINFO_SET_ +} + +#endregion 与ptz服务交互使用结构体 \ No newline at end of file diff --git a/Cis.Application/Core/Center/CameraDataCenter.cs b/Cis.Application/Core/Center/CameraDataCenter.cs new file mode 100644 index 0000000..5dc864b --- /dev/null +++ b/Cis.Application/Core/Center/CameraDataCenter.cs @@ -0,0 +1,113 @@ +using Cis.Application.Cb; +using Cis.Application.Cm; +using Cis.Application.Tb; +using System.Collections.Concurrent; + +namespace Cis.Application.Core; + +public class CameraDataCenter +{ + #region Attr + + private Thread _thread { get; set; } + private List _tbPtzCameraList { get; set; } + private ConcurrentDictionary _cameraPtzInfoDict { get; set; } + + private readonly SqlSugarRepository _cbCameraRep; + private readonly SqlSugarRepository _cmMarkLableRep; + private readonly SqlSugarRepository _tbPtzCameraRep; + private readonly PtzServerApi _ptzServerApi; + + #endregion Attr + + public CameraDataCenter() + { + _cbCameraRep = App.GetService>(); + _cmMarkLableRep = App.GetService>(); + _tbPtzCameraRep = App.GetService>(); + _ptzServerApi = App.GetService(); + Init(); + } + + private void Init() + { + // 初始化 tbPtzCameraList + _tbPtzCameraList = _tbPtzCameraRep.GetList(); + // 根据 Ip 去重 + _tbPtzCameraList = _tbPtzCameraList.Where((a, i) => _tbPtzCameraList.FindIndex(b => b.Ip == a.Ip) == i).ToList(); + + // 初始化 ptzInfoDict + _cameraPtzInfoDict = new ConcurrentDictionary(); + + // 初始化 thread + _thread = new Thread(WorkLoop) + { + IsBackground = true// 设置后台线程 + }; + _thread.Start(); + } + + private void LazyInit() + { + // 初始化 tbPtzCameraList + _tbPtzCameraList = new(); + + // 初始化 ptzInfoDict + _cameraPtzInfoDict = new ConcurrentDictionary(); + + // 初始化 thread + _thread = new Thread(WorkLoop) + { + IsBackground = true// 设置后台线程 + }; + _thread.Start(); + } + + #region Loop + + private void WorkLoop() + { + while (true) + { + GetPtzInfoByApi(); + Thread.Sleep(10000); + } + } + + private void GetPtzInfoByApi() + { + foreach (TbPtzCamera item in _tbPtzCameraList) + { + RequestRealControl rrc = _ptzServerApi.GetPtzInfo(item.CameraId); + CameraCalcInfo ptzInfo = new() + { + Pan = rrc.PTZPositionInfo.FP, + Tilt = rrc.PTZPositionInfo.FT, + Zoom = rrc.PTZPositionInfo.FZ + }; + _cameraPtzInfoDict[item.Ip] = ptzInfo; + } + } + + private void Calc() + { + } + + #endregion Loop + + #region external call + + public bool GetCamera(string ip) + { + return false; + } + + public bool ActiveCamera(string ip) + { + + + return false; + } + + #endregion external call +} \ No newline at end of file diff --git a/Cis.Application/Core/Common/Options.cs b/Cis.Application/Core/Common/Options.cs new file mode 100644 index 0000000..c121c2a --- /dev/null +++ b/Cis.Application/Core/Common/Options.cs @@ -0,0 +1,22 @@ +namespace Cis.Application.Core; + +/// +/// PtzServer选项 +/// +public class PtzServerOptions : IConfigurableOptions +{ + /// + /// 服务类别 + /// + public string Type { get; set; } + + /// + /// 服务IP + /// + public string Ip { get; set; } + + /// + /// 服务端口 + /// + public int Port { get; set; } +} \ No newline at end of file diff --git a/Cis.Application/Core/Entity/CameraCalcInfo.cs b/Cis.Application/Core/Entity/CameraCalcInfo.cs new file mode 100644 index 0000000..e34c9b6 --- /dev/null +++ b/Cis.Application/Core/Entity/CameraCalcInfo.cs @@ -0,0 +1,32 @@ +namespace Cis.Application.Core; + +/// +/// 相机 Ptz 信息 +/// +public class CameraCalcInfo +{ + /// + /// 图像的宽度 + /// + public int ImageWidth { get; set; } = 1920; + + /// + /// 图像的宽度 + /// + public int ImageHeight { get; set; } = 1080; + + /// + /// Pan + /// + public double Pan { get; set; } + + /// + /// Tilt + /// + public double Tilt { get; set; } + + /// + /// Zoom + /// + public double Zoom { get; set; } +} \ No newline at end of file diff --git a/Cis.Application/Core/Service/MarkSearchService.cs b/Cis.Application/Core/Service/MarkSearchService.cs new file mode 100644 index 0000000..0432a26 --- /dev/null +++ b/Cis.Application/Core/Service/MarkSearchService.cs @@ -0,0 +1,22 @@ +using Cis.Application.Cm; + +namespace Cis.Application.Core; + +[ApiDescriptionSettings(CmInfo.GroupName, Order = CmInfo.GroupOrder)] +public class MarkSearchService : IDynamicApiController, ITransient +{ + #region Attr + + private CameraDataCenter _cameraDataCenter { get; set; } + + #endregion Attr + + public MarkSearchService(CameraDataCenter cameraDataCenter) + { + _cameraDataCenter = cameraDataCenter; + } + + public void ActivateCamera() + { + } +} \ No newline at end of file diff --git a/Cis.Application/GlobalUsings.cs b/Cis.Application/GlobalUsings.cs index a7568c4..76b15eb 100644 --- a/Cis.Application/GlobalUsings.cs +++ b/Cis.Application/GlobalUsings.cs @@ -1,5 +1,6 @@ global using Cis.Core; global using Furion; +global using Furion.ConfigurableOptions; global using Furion.DependencyInjection; global using Furion.DynamicApiController; global using Microsoft.AspNetCore.Mvc; diff --git a/Cis.Application/Startup.cs b/Cis.Application/Startup.cs index d88de9c..19a7348 100644 --- a/Cis.Application/Startup.cs +++ b/Cis.Application/Startup.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Builder; +using Cis.Application.Core; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; @@ -12,8 +13,11 @@ public class Startup : AppStartup /// /// public void ConfigureServices(IServiceCollection services) - { - } + { + services.AddConfigurableOptions(); + + services.AddSingleton(new CameraDataCenter()); + } /// /// 配置应用请求处理管道 @@ -21,6 +25,6 @@ public class Startup : AppStartup /// /// public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - } + { + } } \ No newline at end of file diff --git a/Cis.Application/Sys/Common/SysInfo.cs b/Cis.Application/Sys/Common/SysInfo.cs index 3ffb68d..dbeea07 100644 --- a/Cis.Application/Sys/Common/SysInfo.cs +++ b/Cis.Application/Sys/Common/SysInfo.cs @@ -22,7 +22,7 @@ public class SysInfo #region Database Info /// - /// 数据库名 + /// 数据库标识 /// public const string DbName = SqlSugarConst.DefaultConfigId; diff --git a/Cis.Application/Sys/Service/SysDictDataService.cs b/Cis.Application/Sys/Service/SysDictDataService.cs index 0ac8c68..6042140 100644 --- a/Cis.Application/Sys/Service/SysDictDataService.cs +++ b/Cis.Application/Sys/Service/SysDictDataService.cs @@ -1,4 +1,6 @@ -namespace Cis.Application.Sys; +using Newtonsoft.Json.Linq; + +namespace Cis.Application.Sys; /// /// 系统字典值服务 @@ -13,4 +15,41 @@ public class SysDictDataService : IDynamicApiController, ITransient _sysDictDataRep = sysDictDataRep; } + public async Task Get(long id) + { + SysDictData entity = await _sysDictDataRep.GetByIdAsync(id); + return entity; + } + + public async Task> GetList(string queryJson) + { + JObject queryObj = queryJson.ToJObject(); + List list = await _sysDictDataRep.AsQueryable() + .ToListAsync(); + return list; + } + + public async Task> GetPageList(string queryJson, string pagination) + { + Pagination pageObj = pagination.ToObject(); + JObject queryObj = queryJson.ToJObject(); + List list = await _sysDictDataRep.AsQueryable() + .ToPageListAsync(pageObj.Index, pageObj.Size); + return list; + } + + public async Task Add(SysDictData entity) + { + await _sysDictDataRep.InsertAsync(entity); + } + + public async Task Update(SysDictData entity) + { + await _sysDictDataRep.UpdateAsync(entity); + } + + public async Task Delete(SysDictData entity) + { + await _sysDictDataRep.DeleteAsync(entity); + } } \ No newline at end of file diff --git a/Cis.Application/Sys/Service/SysDictTypeService.cs b/Cis.Application/Sys/Service/SysDictTypeService.cs index 74c1b17..4c9763b 100644 --- a/Cis.Application/Sys/Service/SysDictTypeService.cs +++ b/Cis.Application/Sys/Service/SysDictTypeService.cs @@ -1,4 +1,7 @@ -namespace Cis.Application.Sys; +using Cis.Application.Cm; +using Newtonsoft.Json.Linq; + +namespace Cis.Application.Sys; /// /// 系统字典类型服务 @@ -12,4 +15,42 @@ public class SysDictTypeService : IDynamicApiController, ITransient { _sysDictTypeRep = sysDictTypeRep; } + + public async Task Get(long id) + { + SysDictType entity = await _sysDictTypeRep.GetByIdAsync(id); + return entity; + } + + public async Task> GetList(string queryJson) + { + JObject queryObj = queryJson.ToJObject(); + List list = await _sysDictTypeRep.AsQueryable() + .ToListAsync(); + return list; + } + + public async Task> GetPageList(string queryJson, string pagination) + { + Pagination pageObj = pagination.ToObject(); + JObject queryObj = queryJson.ToJObject(); + List list = await _sysDictTypeRep.AsQueryable() + .ToPageListAsync(pageObj.Index, pageObj.Size); + return list; + } + + public async Task Add(SysDictType entity) + { + await _sysDictTypeRep.InsertAsync(entity); + } + + public async Task Update(SysDictType entity) + { + await _sysDictTypeRep.UpdateAsync(entity); + } + + public async Task Delete(SysDictType entity) + { + await _sysDictTypeRep.DeleteAsync(entity); + } } \ No newline at end of file diff --git a/Cis.Application/Tb/Common/TbInfo.cs b/Cis.Application/Tb/Common/TbInfo.cs new file mode 100644 index 0000000..135f94d --- /dev/null +++ b/Cis.Application/Tb/Common/TbInfo.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Cis.Application.Tb; + +public class TbInfo +{ + #region Database Info + + /// + /// 数据库标识 + /// + public const string DbName = "serverdb"; + + #endregion Database Info + + #region Table Info + + /// + /// TbPtzCamera 表名 + /// + public const string TbPtzCameraTbName = "tb_ptzcamera"; + + #endregion Table Info +} + diff --git a/Cis.Application/Tb/Entity/TbPtzCamera.cs b/Cis.Application/Tb/Entity/TbPtzCamera.cs new file mode 100644 index 0000000..6f75ea8 --- /dev/null +++ b/Cis.Application/Tb/Entity/TbPtzCamera.cs @@ -0,0 +1,28 @@ +namespace Cis.Application.Tb; + +[SugarTable(TbInfo.TbPtzCameraTbName)] +[Tenant(TbInfo.DbName)] +public class TbPtzCamera +{ + public int Id { get; set; } + + public int Type { get; set; } + + public string Ip { get; set; } + + public int Port { get; set; } + + public string User { get; set; } + + public string Pass { get; set; } + + public int CameraId { get; set; } + + public double PanPosition { get; set; } + + public double TitlePosition { get; set; } + + public double ZoomPosition { get; set; } + + public byte Request { get; set; } +} \ No newline at end of file diff --git a/Cis.Application/Tb/Service/TbPtzCameraService.cs b/Cis.Application/Tb/Service/TbPtzCameraService.cs new file mode 100644 index 0000000..03cb5bf --- /dev/null +++ b/Cis.Application/Tb/Service/TbPtzCameraService.cs @@ -0,0 +1,51 @@ +using Newtonsoft.Json.Linq; + +namespace Cis.Application.Tb; + +public class TbPtzCameraService : ITransient +{ + private readonly SqlSugarRepository _tbPtzCameraRep; + + public TbPtzCameraService(SqlSugarRepository tbPtzCameraRep) + { + _tbPtzCameraRep = tbPtzCameraRep; + } + + public async Task Get(int id) + { + TbPtzCamera entity = await _tbPtzCameraRep.GetByIdAsync(id); + return entity; + } + + public async Task> GetList(string queryJson = "") + { + JObject queryObj = !string.IsNullOrEmpty(queryJson) ? queryJson.ToJObject():default; + List list = await _tbPtzCameraRep.AsQueryable() + .ToListAsync(); + return list; + } + + public async Task> GetPageList(string pagination, string queryJson = "") + { + Pagination pageObj = pagination.ToObject(); + JObject queryObj = queryJson.ToJObject(); + List list = await _tbPtzCameraRep.AsQueryable() + .ToPageListAsync(pageObj.Index, pageObj.Size); + return list; + } + + public async Task Add(TbPtzCamera entity) + { + await _tbPtzCameraRep.InsertAsync(entity); + } + + public async Task Update(TbPtzCamera entity) + { + await _tbPtzCameraRep.UpdateAsync(entity); + } + + public async Task Delete(TbPtzCamera entity) + { + await _tbPtzCameraRep.DeleteAsync(entity); + } +} \ No newline at end of file diff --git a/Cis.Core/Cis.Core.csproj b/Cis.Core/Cis.Core.csproj index 6530dd8..27ffbae 100644 --- a/Cis.Core/Cis.Core.csproj +++ b/Cis.Core/Cis.Core.csproj @@ -22,11 +22,16 @@ + + + + + diff --git a/Cis.Core/Cis.Core.xml b/Cis.Core/Cis.Core.xml index 310f220..30c9dce 100644 --- a/Cis.Core/Cis.Core.xml +++ b/Cis.Core/Cis.Core.xml @@ -110,6 +110,90 @@ 页码数 + + + 全局规范化结果 + + + + + 异常返回值 + + + + + + + + 成功返回值 + + + + + + + + 验证失败返回值 + + + + + + + + 特定状态码返回值 + + + + + + + + + 返回 RESTful 风格结果集 + + + + + + + + + + 全局返回结果 + + + + + + 状态码 + + + + + 类型success、warning、error + + + + + 错误信息 + + + + + 数据 + + + + + 附加数据 + + + + + 时间戳 + + 缓存类型枚举 @@ -397,89 +481,5 @@ - - - 全局规范化结果 - - - - - 异常返回值 - - - - - - - - 成功返回值 - - - - - - - - 验证失败返回值 - - - - - - - - 特定状态码返回值 - - - - - - - - - 返回 RESTful 风格结果集 - - - - - - - - - - 全局返回结果 - - - - - - 状态码 - - - - - 类型success、warning、error - - - - - 错误信息 - - - - - 数据 - - - - - 附加数据 - - - - - 时间戳 - - diff --git a/Cis.Core/CoreConfig.json b/Cis.Core/CoreConfig.json index b6deb2e..471e3b9 100644 --- a/Cis.Core/CoreConfig.json +++ b/Cis.Core/CoreConfig.json @@ -10,7 +10,13 @@ //"ConnectionString": "DataSource=./cis.db", //"DbType": "PostgreSQL", //"ConnectionString": "HOST=127.0.0.1;PORT=5432;USER ID=pgsql;PASSWORD=123456;DATABASE=cis;", - "EnableInitDb": false, // 启用库表初始化 + "EnableInitDb": false // 启用库表初始化 + }, + { + "ConfigId": "serverdb", + "DbType": "MySql", + "ConnectionString": "Data Source=127.0.0.1;port=3306;User ID=root;Password=123456;Database=serverdb;pooling=true;sslmode=none;CharSet=utf8;", + "EnableInitDb": false // 启用库表初始化 } ] }, @@ -18,12 +24,17 @@ "CacheType": "Redis", // Memory、Redis "RedisConnectionString": "127.0.0.1:6379;password=123456;db=2" }, - "SnowId": { - "WorkerId": 5 // 取值范围0~63,默认1 + "PTZServer": { + "Type": "", + "Ip": "127.0.0.1", + "Port": "7022" + }, + "AppSettings": { + "InjectSpecificationDocument": true // 生产环境是否开启Swagger }, "SpecificationDocumentSettings": { - "DocumentTitle": "Swagger",//默认标题 - "DefaultGroupName": "Default"//默认分组名称 + "DocumentTitle": "Swagger", //默认标题 + "DefaultGroupName": "Default" //默认分组名称 }, "DynamicApiControllerSettings": { "DefaultRoutePrefix": "api", //默认路由前缀 @@ -33,8 +44,30 @@ "AsLowerCamelCase": true, //启用小驼峰命名(首字母小写) "UrlParameterization": true // 方法参数 }, - "AppSettings": { - "InjectSpecificationDocument": true // 生产环境是否开启Swagger + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "File": { + "Enabled": true, // 启用文件日志 + "FileName": "logs/{0:yyyyMMdd}_{1}.log", // 日志文件 + "Append": true, // 追加覆盖 + // "MinimumLevel": "Information", // 日志级别 + "FileSizeLimitBytes": 10485760, // 10M=10*1024*1024 + "MaxRollingFiles": 30 // 只保留30个文件 + }, + "Monitor": { + "GlobalEnabled": true, // 启用全局拦截日志 + "IncludeOfMethods": [], // 拦截特定方法,当GlobalEnabled=false有效 + "ExcludeOfMethods": [], // 排除特定方法,当GlobalEnabled=true有效 + "BahLogLevel": "Information", // Oops.Oh 和 Oops.Bah 业务日志输出级别 + "WithReturnValue": true, // 配置是否包含返回值,默认true + "ReturnValueThreshold": 0 // 配置返回值字符串阈值,默认0全量输出 + } + }, + "SnowId": { + "WorkerId": 5 // 取值范围0~63,默认1 }, "CorsAccessorSettings": { "WithExposedHeaders": [ @@ -43,4 +76,4 @@ "environment" ] } -} +} \ No newline at end of file diff --git a/Cis.Core/Util/RespParamProvider.cs b/Cis.Core/Entity/RespParamProvider.cs similarity index 100% rename from Cis.Core/Util/RespParamProvider.cs rename to Cis.Core/Entity/RespParamProvider.cs diff --git a/Cis.Web.Core/Startup.cs b/Cis.Web.Core/Startup.cs index 6f1206e..5248263 100644 --- a/Cis.Web.Core/Startup.cs +++ b/Cis.Web.Core/Startup.cs @@ -4,12 +4,16 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using System; +using System.IO; using Yitter.IdGenerator; namespace Cis.Web.Core; +[AppStartup(1000)] public class Startup : AppStartup { /// @@ -39,12 +43,30 @@ public class Startup : AppStartup .AddNewtonsoftJson(options => { options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); // 首字母小写(驼峰样式) + options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;// 设置本地时区 options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; // 时间格式化 options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // 忽略循环引用 options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; // 忽略空值 }) .AddInjectWithUnifyResult(); + // 日志记录 + if (App.GetConfig("Logging:File:Enabled")) // 日志写入文件 + { + Array.ForEach(new[] { LogLevel.Information, LogLevel.Warning, LogLevel.Error }, logLevel => + { + services.AddFileLogging(options => + { + options.FileNameRule = fileName => string.Format(fileName, DateTime.Now, logLevel.ToString()); // 每天创建一个文件 + options.WriteFilter = logMsg => logMsg.LogLevel == logLevel; // 日志级别 + options.HandleWriteError = (writeError) => // 写入失败时启用备用文件 + { + writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName)); + }; + }); + }); + } + // 配置雪花Id算法机器码 YitIdHelper.SetIdGenerator(new IdGeneratorOptions { diff --git a/Cis.Web.Entry/Cis.Web.Entry.csproj b/Cis.Web.Entry/Cis.Web.Entry.csproj index 2ddaf1d..6392984 100644 --- a/Cis.Web.Entry/Cis.Web.Entry.csproj +++ b/Cis.Web.Entry/Cis.Web.Entry.csproj @@ -10,8 +10,4 @@ - - - - diff --git a/Cis.Web.Entry/wwwroot/images/logo.png b/Cis.Web.Entry/wwwroot/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..eff5511ffb1210f5e940d040354df5a4e0834cf0 GIT binary patch literal 25808 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�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