diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3729ff0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/langguanApi.sln b/langguanApi.sln new file mode 100644 index 0000000..dfe9989 --- /dev/null +++ b/langguanApi.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34221.43 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "langguanApi", "langguanApi\langguanApi.csproj", "{99745E10-D0AE-46BC-B23E-A24483532FEF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {99745E10-D0AE-46BC-B23E-A24483532FEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99745E10-D0AE-46BC-B23E-A24483532FEF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99745E10-D0AE-46BC-B23E-A24483532FEF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99745E10-D0AE-46BC-B23E-A24483532FEF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9FA08BD4-39EF-4F6E-B3C9-990729D8ED1A} + EndGlobalSection +EndGlobal diff --git a/langguanApi.xml b/langguanApi.xml new file mode 100644 index 0000000..ae06537 --- /dev/null +++ b/langguanApi.xml @@ -0,0 +1,1255 @@ + + + + langguanApi + + + + + 缓存管理 + + + + + 缓存数据 + + + + + 缓存时间秒,0=int.max , + + + + + sub + + + + + 缓存天气信息 + + + + + redis 服务地址 + + + + + redis 端口 + + + + + redis 连接密码 + + + + + 自定义Key + + + + + 默认数据库索引 + + + + + 转换数据库连接字符串 + + + + + + + + + + + + + 列表 + + + + + + + 新加 + + + + + + + 删除 + + + + + + + 更新 + + + + + + + 获取指定设备的历史数据 + + + + + + + 首页view + + + + + + 注入生命周期 + + + + + Transient + + + + + Scoped + + + + + Singleton + + + + + + + + + + 注入类型 + + + + + 服务注入 + + + + + 服务注入 + + 注入类型 + + + + 服务注入 + + 服务的接口类型 + 注入的类型 + + + + 自动注入 + + + + + + + + + + + + 服务自动注入 + + 需要自动注入服务的服务集合 + 应用于每个Assembly的筛选函数 + 指定的注入类型不在可注入的范围内 + 指定注入的类型未实现任何服务 + 输入的参数错误:1、注入的类型未实现指定的服务。2、指定的服务不是Interface类型 + 自动注入服务后的服务集合 + + + + add redis service + + + + + + + + + + + + + 日志 + + + + + 构参 + + + + + + 重写 + + + + + + + Api返回结果 + + + + + code=0成功。1失败 + + + + + 结果 + + + + + 消息 + + + + + base model + + + + + //标记主键 + + + + + //指明数据库中字段名为CreateDateTime + + + + + 标记删除 + + + + + basemodel + + + + + + + + + + a34004=PM2.5浓度,a34002=PM10,a34001=tsp浓度 + + + + + 命令编码 + + + + + 心跳包 + + + + + 工控机向上位机上传分钟数据 + + + + + 工控机向上位机上传小时数据 + + + + + 工控机向上位机上传日数据 + + + + + 工控机向上位机上传实时数据 + + + + + 上位机向工控机返回应答 + + + + + CP指令 + + + + + 数据时间信息 + + + + + 污染物信息 + + + + + 解析 + + + + + + + 序列化 + + + + + + 数据段 + + + + + 默认应答系统编码 + + + + + 请求编号 + + + yyyyMMddHHmmssZZZ 取当前系统时间, 精确到毫秒值, 用来唯一标识一次命令交互 + + + + + 系统编号 + + + + + 命令编码 + 详见附录 2 + + + + + 访问密码 + 对接时提供给各个对接站点 + + + + + 设备唯一标识 + 对接时提供给各个对接站点 + + + + + 拆分包及应答标志 + + + + + 总包数 + PNUM 指示本次通讯中总共包含的包数,注:不分包时可以没有本字段,与标志位有关 + + + + + 包号 + PNO 指示当前数据包的包号,注: 不分包时可以没有本字段,与标志位有关 + + + + + 指令 + CP=&&数据区&&( 详见表 5 ) + + + + + 解析 + + + + + + + + 序列化 + + + + + + 设备信息 + + + + + HJ212_2017 + + + + + 数据帧头 + + + + + 数据长度 + + + + + 数据头 + + + + + + + + + + CRC校验 + + + + + + + + + + + + 判断数据是否通过校验 + + 原始数据 + 是否通过 + + + + 污染物因子编码 + + + + + 设备ID + + + + + PM2.5浓度 + + + + + PM10浓度 + + + + + TSP浓度 + + + + + 温度 + + + + + 湿度 + + + + + 大气压 + + + + + 风速 + + + + + 风向 + + + + + 检测仪器数据标记 + + + + + 正常(有效) + 在线监控(监测)仪器仪表工作正常 + + + + + 无效 + 在线监控(监测)仪器仪表停运 + + + + + 无效 + 在线监控(监测)仪器仪表处于维护期间产生的数据 + + + + + 有效 + 手工输入的设定值 + + + + + 无效 + 在线监控(监测)仪器仪表故障 + + + + + 无效 + 在线监控(监测)仪器仪表处于校准状态 + + + + + 无效 + 在线监控(监测)仪器仪表采样数值超过测量上限 + + + + + 无效 + 在线监控(监测)仪器仪表与数采仪通讯异常 + + + + + 无效(有效数据不足) + + + 按照5分钟、1小时均值计算要求,所获取的有效数据个数不足 + + + + + 通讯包 + + + + + 默认头 + + + + + 默认尾 + + + + + 包头 + + + + + 数据段长度 + + + + + 数据段 + + + + + CRC校验码 + + + + + 包尾 + + + + + CRC16校验 + + 需要校验的字符串 + CRC16 校验码 + + + + 解析 + + + + + + + + 序列化 + + + + + + 拆分包及应答标志 + + + + + 命令是否应答:1-应答,0-不应答 + + + + + 是否有数据包序号:1 - 数据包中包含包号和总包数两部分,0 - 数据包中不包含包号和总包数两部分 + + + + + 标准版本号 + 000000 表示标准 HJ/T212-2005 + 000001 表示本次标准修订版本号 + + + + + 解析 + + + + + + + 序列化 + + + + + + 获取取第index位 + + + index从0开始 + + + + + + + + 将第index位设为1 + + + index从0开始 + + + + + + + + 将第index位设为0 + + + index从0开始 + + + + + + + + 将第index位取反 + + + index从0开始 + + + + + + + + 污染物信息 + + + + + 约定的无效值 + + + + + 污染物因子编码 + + + + + 污染物实时采样数据 + + + 默认值为约定的无效值 + + + + + 污染物指定时问内平均值 + + + 默认值为约定的无效值 + + + + + 污染物指定时问内最大值 + + + 默认值为约定的无效值 + + + + + 污染物指定时问内最小值 + + + 默认值为约定的无效值 + + + + + 污染物指定时问内累计值 + + + 默认值为约定的无效值 + + + + + 检测仪器数据标记 + + + + + 回应代码集 + + + + + 执行成功 + + + + + 执行失败,但不知道原因 + + + + + 执行失败,命令请求条件错误 + + + + + 通讯超时 + + + + + 系统繁忙不能执行 + + + + + 系统时间异常 + + + + + 没有数据 + + + + + 心跳包 + + + + + 新加 + + + + + + + 分页取数据 + + + + + + + 构造 + + + + + + + 获取所有 + + + + + + 获取单个 + + + + + + + 创建 + + + + + + + 更新 + + + + + + + 删除 + + + + + + 根据id删除 + + + + + + 取列表 + + + + + + 取单条 + + + + + + + 新增 + + + + + + + 新增 + + + + + + + 更新 + + + + + + + + 删除 + + + + + + + 表达式取数据 + + + + + + + filter查找 + + + + + + + filterdefinition + + + + + + + 是否存在 + + + + + + + 分页取数据 + + + + + + + + 新加 + + + + + + + 是否存在 + + + + + + + 更新 + + + + + + + remove + + + + + + + 分页取数据 + + + + + + + 新加数据 + + + + + + + + 最近10个小时的数据 + + + + + + + 按设备号查询数据 + + + + + + + 实时的数据 + + + + + + 缓冲器 + + + + + 最大连接数 + + + + + 服务IP地址 + + + + + 服务端口号 + + + + + 客户端列表 + + + + + IP终端 + + + + + 服务端Socket + + + + + 启动服务 + + + + + + 收到数据事件 + + + + + 发送数据事件 + + + + + + + + + + + 引发收到数据事件 + + + + + 引发发送数据事件 + + + + + + + + 获取分包缓存 + + + + + + + + + + + 应答 + + + + + HomeService + + + + + HomeService + + + + + + + view + + + + + + ping service + + + + + + + + + + + + + + + + + + + + + + ping + + + + + + + 爬气象局的天气数据% + + + + + + 54511 + + + + + 北京 + + + + + 中国, 北京, 北京 + + + + + Precipitation + + + + + Temperature + + + + + Pressure + + + + + Humidity + + + + + 东北风 + + + + + WindDirectionDegree + + + + + WindSpeed + + + + + 微风 + + + + + + + + + + Location + + + + + Now + + + + + Alarm + + + + + 2024/01/15 10:05 + + + + + success + + + + + Code + + + + + Data + + + + + 暂存服务 + + + + diff --git a/langguanApi/.config/dotnet-tools.json b/langguanApi/.config/dotnet-tools.json new file mode 100644 index 0000000..b0e38ab --- /dev/null +++ b/langguanApi/.config/dotnet-tools.json @@ -0,0 +1,5 @@ +{ + "version": 1, + "isRoot": true, + "tools": {} +} \ No newline at end of file diff --git a/langguanApi/Common/CacheManager.cs b/langguanApi/Common/CacheManager.cs new file mode 100644 index 0000000..4a7637d --- /dev/null +++ b/langguanApi/Common/CacheManager.cs @@ -0,0 +1,41 @@ +using langguanApi.Common.Redis; +using langguanApi.Extensions.AutoDI; +using Newtonsoft.Json; +using StackExchange.Redis; + +namespace langguanApi.Common +{ + /// + /// 缓存管理 + /// + [ServiceInjection(InjectionType.Singleton)] + public class CacheManager + { + private readonly IDatabase _redisDatabase; + public CacheManager(RedisHelper redisHelper) + { + _redisDatabase = redisHelper._database; + } + /// + /// 缓存数据 + /// + /// + /// + /// + /// 缓存时间秒,0=int.max , + /// + public async Task GetConvertVale(string cacheKey, Func> dataFetcher, int seconds = 0) + { + var cacheData = await _redisDatabase.StringGetAsync(cacheKey); + if (cacheData.IsNull) + { + var temp = await dataFetcher(); + cacheData = JsonConvert.SerializeObject(temp); + seconds = seconds == 0 ? int.MaxValue : seconds; + await _redisDatabase.StringSetAsync(cacheKey, cacheData, TimeSpan.FromSeconds(seconds), When.NotExists); + return temp; + } + return JsonConvert.DeserializeObject(cacheData); + } + } +} diff --git a/langguanApi/Common/Redis/RedisHelper.cs b/langguanApi/Common/Redis/RedisHelper.cs new file mode 100644 index 0000000..22fa2b4 --- /dev/null +++ b/langguanApi/Common/Redis/RedisHelper.cs @@ -0,0 +1,20 @@ +using StackExchange.Redis; + +namespace langguanApi.Common.Redis +{ + public class RedisHelper + { + public IDatabase _database; + /// + /// sub + /// + public ISubscriber _subscriber; + public RedisHelper() + { + // var con = RedisOptions.Default.GetConnect(); + IConnectionMultiplexer connection = ConnectionMultiplexer.Connect(RedisOptions.Default.GetConnect()); + _subscriber = connection.GetSubscriber(); + _database = connection.GetDatabase(RedisOptions.Default.Index); + } + } +} diff --git a/langguanApi/Common/Redis/RedisKeylist.cs b/langguanApi/Common/Redis/RedisKeylist.cs new file mode 100644 index 0000000..295fbd5 --- /dev/null +++ b/langguanApi/Common/Redis/RedisKeylist.cs @@ -0,0 +1,11 @@ +namespace langguanApi.Common.Redis +{ + public class RedisKeylist + { + public static string UserLoginInfo = "UserLoginInfo"; + /// + /// 缓存天气信息 + /// + public static string Weather = "weather"; + } +} diff --git a/langguanApi/Common/Redis/RedisOptions.cs b/langguanApi/Common/Redis/RedisOptions.cs new file mode 100644 index 0000000..bb15439 --- /dev/null +++ b/langguanApi/Common/Redis/RedisOptions.cs @@ -0,0 +1,54 @@ +namespace langguanApi.Common.Redis +{ + public class RedisOptions + { + public static readonly RedisOptions Default = new RedisOptions(); + public RedisOptions() + { + this.Server = null; + this.Port = 6379; + this.Password = null; + this.Key = null; + this.Index = 0; + } + + /// + /// redis 服务地址 + /// + public string Server { get; set; } + + /// + /// redis 端口 + /// + public int Port { get; set; } + + /// + /// redis 连接密码 + /// + public string Password { get; set; } + + /// + /// 自定义Key + /// + public string Key { get; set; } + + /// + /// 默认数据库索引 + /// + public int Index { get; set; } + public string Connection { get; set; } + + /// + /// 转换数据库连接字符串 + /// + /// + /// + public string GetConnect() + { + if (string.IsNullOrWhiteSpace(Password)) + return $"{Server}"; + else + return $"{Server},password={Password}"; + } + } +} diff --git a/langguanApi/Controllers/DeviceController.cs b/langguanApi/Controllers/DeviceController.cs new file mode 100644 index 0000000..0807df7 --- /dev/null +++ b/langguanApi/Controllers/DeviceController.cs @@ -0,0 +1,67 @@ +using langguanApi.Model; +using langguanApi.Model.Dto; +using langguanApi.Service; +using Microsoft.AspNetCore.Mvc; + +namespace langguanApi.Controllers +{ + [Route("api/[controller]/[action]")] + [ApiController] + public class DeviceController : ControllerBase + { + private readonly DeviceService _deviceService; + /// + /// + /// + /// + public DeviceController(DeviceService deviceService) + { + _deviceService = deviceService; + } + /// + /// 列表 + /// + /// + /// + [HttpGet] + public async Task Getpage([FromQuery] reqpage input) + { + var result = await _deviceService.GetPage(input); + return Ok(result); + } + /// + ///新加 + /// + /// + /// + [HttpPost] + public async Task Addd([FromBody] DeviceDto input) + { + var result = await _deviceService.Add(input); + return Ok(result); + } + /// + /// 删除 + /// + /// + /// + [HttpDelete] + public async Task Remove(IEnumerable ids) + { + var result = await _deviceService.remove(ids); + return Ok(result); + } + /// + /// 更新 + /// + /// + /// + [HttpPut] + public async Task update([FromBody] DeviceDto input) + { + var result = await _deviceService.update(input); + return Ok(result); + } + + } +} diff --git a/langguanApi/Controllers/HJ212Controller.cs b/langguanApi/Controllers/HJ212Controller.cs new file mode 100644 index 0000000..316db85 --- /dev/null +++ b/langguanApi/Controllers/HJ212Controller.cs @@ -0,0 +1,30 @@ +using langguanApi.Service; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace langguanApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class HJ212Controller : ControllerBase + { + private readonly Hj212Service _hj212Service; + + public HJ212Controller(Hj212Service hj212Service) + { + _hj212Service = hj212Service; + } + + /// + /// 获取指定设备的历史数据 + /// + /// + /// + [HttpGet] + public async Task Get(string mn) + { + var resul = await _hj212Service.GetViewByDeviceMn(mn); + return Ok(resul); + } + } +} diff --git a/langguanApi/Controllers/HomeController.cs b/langguanApi/Controllers/HomeController.cs new file mode 100644 index 0000000..c1797f2 --- /dev/null +++ b/langguanApi/Controllers/HomeController.cs @@ -0,0 +1,45 @@ +using langguanApi.Model; +using langguanApi.Service; +using langguanApi.Service.HJ212; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Text; + +namespace langguanApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class HomeController : ControllerBase + { + private readonly HomeService _homeService; + public HomeController(HomeService homeService) + { + _homeService = homeService; + } + /// + /// 首页view + /// + /// + [HttpGet("view")] + public async Task View() + { + return Ok( await _homeService.View() ); + + } + [HttpGet("test")] + /// + /// test + /// + /// 数字 + /// 字符串 + /// + public async Task test(int num = 1, string key = "") + { + // string rawText = "数据报:##0250QN=20240424224800000;ST=22;CN=2011;PW=123456;MN=LGYC022024690001;Flag=5;CP=&&DataTime=20240424224800;a34001-Rtd=356.2"; + //NetPackage netPackage = NetPackage.Parse(rawText, null); + //((NetServer)Server).RaiseReceivedData(this, netPackage, rawText); + return Ok(); + } + + } +} diff --git a/langguanApi/Dockerfile b/langguanApi/Dockerfile new file mode 100644 index 0000000..bdf067d --- /dev/null +++ b/langguanApi/Dockerfile @@ -0,0 +1,21 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine AS base +WORKDIR /app +EXPOSE 80 + +#FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +#WORKDIR /src +#COPY ["langguanApi/langguanApi.csproj", "langguanApi/"] +#RUN dotnet restore "langguanApi/langguanApi.csproj" +COPY . . +#WORKDIR "/src/langguanApi" +#RUN dotnet build "langguanApi.csproj" -c Release -o /app/build +# +#FROM build AS publish +#RUN dotnet publish "langguanApi.csproj" -c Release -o /app/publish /p:UseAppHost=false +# +#FROM base AS final +#WORKDIR /app +#COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "langguanApi.dll"] \ No newline at end of file diff --git a/langguanApi/Extensions/AutoDI/InjectionType.cs b/langguanApi/Extensions/AutoDI/InjectionType.cs new file mode 100644 index 0000000..7b88f2b --- /dev/null +++ b/langguanApi/Extensions/AutoDI/InjectionType.cs @@ -0,0 +1,23 @@ +namespace langguanApi.Extensions.AutoDI +{ + /// + /// 注入生命周期 + /// + public enum InjectionType + { + /// + /// Transient + /// + Transient, + + /// + /// Scoped + /// + Scoped, + + /// + /// Singleton + /// + Singleton + } +} diff --git a/langguanApi/Extensions/AutoDI/ServiceInjectionAttribute.cs b/langguanApi/Extensions/AutoDI/ServiceInjectionAttribute.cs new file mode 100644 index 0000000..72ab0b2 --- /dev/null +++ b/langguanApi/Extensions/AutoDI/ServiceInjectionAttribute.cs @@ -0,0 +1,43 @@ +namespace langguanApi.Extensions.AutoDI +{ + [AttributeUsage(AttributeTargets.Class)] + public class ServiceInjectionAttribute : Attribute + { + /// + /// + /// + public Type InterfaceType { get; set; } + + /// + /// 注入类型 + /// + public InjectionType InjectionType { get; } + + /// + /// 服务注入 + /// + public ServiceInjectionAttribute() + { + InjectionType = InjectionType.Scoped; + } + + /// + /// 服务注入 + /// + /// 注入类型 + public ServiceInjectionAttribute(InjectionType injectionType) + { + InjectionType = injectionType; + } + /// + /// 服务注入 + /// + /// 服务的接口类型 + /// 注入的类型 + public ServiceInjectionAttribute(Type interfaceType, InjectionType injectionType) + { + InterfaceType = interfaceType; + InjectionType = injectionType; + } + } +} diff --git a/langguanApi/Extensions/AutoDI/ServicesAutoInjectionExtension.cs b/langguanApi/Extensions/AutoDI/ServicesAutoInjectionExtension.cs new file mode 100644 index 0000000..cb1719a --- /dev/null +++ b/langguanApi/Extensions/AutoDI/ServicesAutoInjectionExtension.cs @@ -0,0 +1,70 @@ +using System.Reflection; + +namespace langguanApi.Extensions.AutoDI +{ + /// + /// 自动注入 + /// + public static class AutoDIExtensions + { + /// + /// + /// + /// + /// + public static IServiceCollection ServicesAutoInjectionExtension(this IServiceCollection serviceCollection) + { + var directory = AppDomain.CurrentDomain.BaseDirectory; + var types = Directory.GetFiles(directory, "*.dll", SearchOption.TopDirectoryOnly) + .Select(Assembly.LoadFrom) + .SelectMany(a => a.GetTypes()); + Injection(serviceCollection, types); + return serviceCollection; + } + /// + /// 服务自动注入 + /// + /// 需要自动注入服务的服务集合 + /// 应用于每个Assembly的筛选函数 + /// 指定的注入类型不在可注入的范围内 + /// 指定注入的类型未实现任何服务 + /// 输入的参数错误:1、注入的类型未实现指定的服务。2、指定的服务不是Interface类型 + /// 自动注入服务后的服务集合 + public static IServiceCollection ServicesAutoInjection(this IServiceCollection serviceCollection, Func selector) + { + var directory = AppDomain.CurrentDomain.BaseDirectory; + var types = Directory.GetFiles(directory, "*.dll", SearchOption.TopDirectoryOnly) + .Select(Assembly.LoadFrom) + .Where(selector) + .SelectMany(a => a.GetTypes()); + + Injection(serviceCollection, types); + + return serviceCollection; + } + // 注入逻辑 + private static void Injection(IServiceCollection serviceCollection, IEnumerable types) + { + foreach (var type in types) + { + var attribute = type.GetCustomAttribute(); + if (attribute == null) continue; + switch (attribute.InjectionType) + { + case InjectionType.Transient: + serviceCollection.AddTransient(type); + break; + case InjectionType.Scoped: + serviceCollection.AddScoped(type); + break; + case InjectionType.Singleton: + serviceCollection.AddSingleton(type); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + } + } +} diff --git a/langguanApi/Extensions/RedisExtension.cs b/langguanApi/Extensions/RedisExtension.cs new file mode 100644 index 0000000..fd53aac --- /dev/null +++ b/langguanApi/Extensions/RedisExtension.cs @@ -0,0 +1,21 @@ +using langguanApi.Common.Redis; + +namespace langguanApi.Extensions +{ + public static class RedisExtension + { + /// + /// add redis service + /// + /// + /// + public static void AddRedis(this IServiceCollection services, Action redisOptions) + { + services.AddSingleton(); + if (redisOptions != null) + { + redisOptions.Invoke(RedisOptions.Default); + } + } + } +} diff --git a/langguanApi/Extensions/SocketExtension.cs b/langguanApi/Extensions/SocketExtension.cs new file mode 100644 index 0000000..62f34f7 --- /dev/null +++ b/langguanApi/Extensions/SocketExtension.cs @@ -0,0 +1,22 @@ +using langguanApi.Service; + +namespace langguanApi.Extensions +{ + public static class SocketExtension + { + /// + /// + /// + /// + public static void AddSocketService(this IServiceCollection services) + { + // services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + _ = serviceProvider.GetService().Start(); + serviceProvider.GetService().CreatTask(); + } + } +} diff --git a/langguanApi/Middleware/CustomerExceptionFilter.cs b/langguanApi/Middleware/CustomerExceptionFilter.cs new file mode 100644 index 0000000..58a073e --- /dev/null +++ b/langguanApi/Middleware/CustomerExceptionFilter.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace langguanApi.Middleware +{ + public class CustomerExceptionFilter : IAsyncExceptionFilter + { + /// + /// 日志 + /// + public ILogger _logger; + /// + /// 构参 + /// + /// + public CustomerExceptionFilter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + /// + /// 重写 + /// + /// + /// + public Task OnExceptionAsync(ExceptionContext context) + { + if (context.ExceptionHandled == false) + { + var json = new { cdoe = -1, msg = context.Exception.Message, data = context.Exception.Data }; + context.HttpContext.Response.StatusCode = 400; + context.Result = new JsonResult(json); + } + _logger.LogError($"请求出现异常,地址:{context.HttpContext?.Request?.Path},请求方式:{context.HttpContext.Request.Method},异常信息:{context.Exception.Message}"); + //记录异常已经处理 + context.ExceptionHandled = true; + return Task.CompletedTask; + } + } +} diff --git a/langguanApi/Model/Alert.cs b/langguanApi/Model/Alert.cs new file mode 100644 index 0000000..d6ee4e5 --- /dev/null +++ b/langguanApi/Model/Alert.cs @@ -0,0 +1,7 @@ +namespace langguanApi.Model +{ + public class Alert : BaseModel + { + public string DeviceMn { get; set; } + } +} diff --git a/langguanApi/Model/ApiResult.cs b/langguanApi/Model/ApiResult.cs new file mode 100644 index 0000000..23e2e36 --- /dev/null +++ b/langguanApi/Model/ApiResult.cs @@ -0,0 +1,21 @@ +namespace langguanApi.Model +{ + /// + /// Api返回结果 + /// + public class ApiResult + { + /// + /// code=0成功。1失败 + /// + public int code { get; set; } = 0; + /// + /// 结果 + /// + public object data { get; set; } + /// + /// 消息 + /// + public string msg { get; set; } + } +} diff --git a/langguanApi/Model/BaseModel.cs b/langguanApi/Model/BaseModel.cs new file mode 100644 index 0000000..4d977c4 --- /dev/null +++ b/langguanApi/Model/BaseModel.cs @@ -0,0 +1,40 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace langguanApi.Model +{ + /// + /// base model + /// + public class BaseModel + { + /// + /// //标记主键 + /// + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] //参数类型 , 无需赋值 + public string Id { get; set; } + + /// + /// //指明数据库中字段名为CreateDateTime + /// + [BsonElement(nameof(CreateDateTime))] //指明数据库中字段名为CreateDateTime + + public DateTime CreateDateTime { get; set; } + + /// + /// 标记删除 + /// + //[BsonElement(nameof(IsDelete))] + public bool IsDelete { get; set; }=false; + + /// + /// basemodel + /// + public BaseModel() + { + CreateDateTime = DateTime.Now; + IsDelete = false; + } + } +} diff --git a/langguanApi/Model/CommandNumber.cs b/langguanApi/Model/CommandNumber.cs new file mode 100644 index 0000000..3712305 --- /dev/null +++ b/langguanApi/Model/CommandNumber.cs @@ -0,0 +1,38 @@ +namespace langguanApi.Model +{ + /// + /// 命令编码 + /// + public enum CommandNumber + { + /// + /// 心跳包 + /// + HeartbeatPackage = 1062, + + /// + /// 工控机向上位机上传分钟数据 + /// + UploadMinuteData = 2051, + + /// + /// 工控机向上位机上传小时数据 + /// + UploadHourlyData = 2061, + + /// + /// 工控机向上位机上传日数据 + /// + UploadDailyData = 2031, + + /// + /// 工控机向上位机上传实时数据 + /// + UploadRealTimeData = 2011, + + /// + /// 上位机向工控机返回应答 + /// + DataResponse = 9014 + } +} diff --git a/langguanApi/Model/CpCommand.cs b/langguanApi/Model/CpCommand.cs new file mode 100644 index 0000000..217cd5b --- /dev/null +++ b/langguanApi/Model/CpCommand.cs @@ -0,0 +1,114 @@ +using IceCoffee.Common.Extensions; +using System.Reflection; + +namespace langguanApi.Model +{ + /// + /// CP指令 + /// + public class CpCommand + { + public ResponseCode ExeRtn { get; set; } + + /// + /// 数据时间信息 + /// + public DateTime DataTime { get; set; } + + /// + /// 污染物信息 + /// + public List PollutantInfo { get; set; } + + /// + /// 解析 + /// + /// + /// + public static CpCommand Parse(string cp) + { + var cpCommand = new CpCommand(); + + cpCommand.PollutantInfo = new List(); + + cpCommand.DataTime = DateTime.ParseExact(cp.GetMidStr("DataTime=", ";"), "yyyyMMddHHmmss", null); + cp = cp.Substring(24); + foreach (string project in cp.Split(';', StringSplitOptions.RemoveEmptyEntries)) + { + var pollutantInfo = new PollutantInfo(); + + string[] classes = project.Split(','); + foreach (string @class in classes) + { + string[] keyValue = @class.Split('='); + string key = keyValue[0]; + string value = keyValue[1]; + + string[] factorCodeType = key.Split('-'); + string factorCode = factorCodeType[0]; + string type = factorCodeType[1]; + + pollutantInfo.FactorCode = (FactorCode)Enum.Parse(typeof(FactorCode), factorCode); + + switch (type) + { + case nameof(Model.PollutantInfo.Rtd): + if (string.IsNullOrEmpty(value) == false && decimal.TryParse(value, out decimal rtd)) + { + pollutantInfo.Rtd = rtd; + } + break; + case nameof(Model.PollutantInfo.Avg): + if (string.IsNullOrEmpty(value) == false && decimal.TryParse(value, out decimal avg)) + { + pollutantInfo.Avg = avg; + } + break; + case nameof(Model.PollutantInfo.Max): + if (string.IsNullOrEmpty(value) == false && decimal.TryParse(value, out decimal max)) + { + pollutantInfo.Max = max; + } + break; + case nameof(Model.PollutantInfo.Min): + if (string.IsNullOrEmpty(value) == false && decimal.TryParse(value, out decimal min)) + { + pollutantInfo.Min = min; + } + break; + case nameof(Model.PollutantInfo.Cou): + if (string.IsNullOrEmpty(value) == false && decimal.TryParse(value, out decimal cou)) + { + pollutantInfo.Cou = cou; + } + break; + case nameof(Model.PollutantInfo.Flag): + pollutantInfo.Flag = (InstrumentationDataFlag)Enum.Parse(typeof(InstrumentationDataFlag), value); + break; + default: + throw new Exception("无效的CP指令字段名"); + } + } + + cpCommand.PollutantInfo.Add(pollutantInfo); + } + + return cpCommand; + //} + //catch (Exception ex) + //{ + // Console.WriteLine($"Error in CpCommand.Parse:{ex.Message}"); + // throw new Exception("Error in CpCommand.Parse", ex); + //} + } + + /// + /// 序列化 + /// + /// + public string Serialize() + { + return "ExeRtn=" + (int)ExeRtn; + } + } +} diff --git a/langguanApi/Model/DataSegment.cs b/langguanApi/Model/DataSegment.cs new file mode 100644 index 0000000..4b11695 --- /dev/null +++ b/langguanApi/Model/DataSegment.cs @@ -0,0 +1,152 @@ +using IceCoffee.Common.Extensions; +using System.Text; + +namespace langguanApi.Model +{ + /// + /// 数据段 + /// + public class DataSegment + { + /// + /// 默认应答系统编码 + /// + public const string ResponseST = "91"; + + /// + /// 请求编号 + /// + /// + /// yyyyMMddHHmmssZZZ 取当前系统时间, 精确到毫秒值, 用来唯一标识一次命令交互 + /// + public string QN { get; set; } + + /// + /// 系统编号 + /// + public string ST { get; set; } + + /// + /// 命令编码 + /// 详见附录 2 + /// + public CommandNumber CN { get; set; } + + /// + /// 访问密码 + /// 对接时提供给各个对接站点 + /// + public string PW { get; set; } + + /// + /// 设备唯一标识 + /// 对接时提供给各个对接站点 + /// + public string MN { get; set; } + + /// + /// 拆分包及应答标志 + /// + public PackageFlag PackageFlag { get; set; } + + /// + /// 总包数 + /// PNUM 指示本次通讯中总共包含的包数,注:不分包时可以没有本字段,与标志位有关 + /// + public int PNUM { get; set; } + + /// + /// 包号 + /// PNO 指示当前数据包的包号,注: 不分包时可以没有本字段,与标志位有关 + /// + public int PNO { get; set; } + + /// + /// 指令 + /// CP=&&数据区&&( 详见表 5 ) + /// + public CpCommand CpCommand { get; set; } + + /// + /// 解析 + /// + /// + /// + /// + public static DataSegment Parse(string data, Func unpackCacheFunc) + { + DataSegment dataSegment = new DataSegment(); + try + { + int outEnd; + dataSegment.QN = data.GetMidStr("QN=", ";", out outEnd); + + if (outEnd < 0) + { + outEnd = 0; + } + + dataSegment.ST = data.GetMidStr("ST=", ";", out outEnd, outEnd); + dataSegment.CN = (CommandNumber)int.Parse(data.GetMidStr("CN=", ";", out outEnd, outEnd)); + dataSegment.PW = data.GetMidStr("PW=", ";", out outEnd, outEnd); + dataSegment.MN = data.GetMidStr("MN=", ";", out outEnd, outEnd); + + string packageFlag = data.GetMidStr("Flag=", ";", out outEnd, outEnd); + if (string.IsNullOrEmpty(packageFlag) || int.TryParse(packageFlag, out _) == false || outEnd < 0) + { + outEnd = 0; + } + else + { + dataSegment.PackageFlag = PackageFlag.Parse(packageFlag); + } + + if (dataSegment.PackageFlag != null && dataSegment.PackageFlag.D == 1) + { + // 分包 + dataSegment.PNUM = int.Parse(data.GetMidStr("PNUM=", ";", out outEnd, outEnd)); + dataSegment.PNO = int.Parse(data.GetMidStr("PNO=", ";", out outEnd, outEnd)); + + string cp = data.GetMidStr("CP=&&", "&&", out outEnd, outEnd); + var cache = unpackCacheFunc.Invoke(); + if (dataSegment.PNO == 1)// 第一个包 + { + cache.Append(cp); + } + else if (dataSegment.PNUM == dataSegment.PNO)// 最后一个包 + { + cache.Append(cp.Substring(23)); + dataSegment.CpCommand = CpCommand.Parse(cache.ToString()); + cache.Clear(); + } + else// 中间的包 + { + cache.Append(cp.Substring(23));// 23 - DataTime=20170920100000; 留分号 + } + } + else + { + string cp = data.GetMidStr("CP=&&", "&&", out outEnd, outEnd); + // 过滤心跳包 + dataSegment.CpCommand = dataSegment.CN == CommandNumber.HeartbeatPackage ? new CpCommand() : CpCommand.Parse(cp); + } + return dataSegment; + } + catch (Exception ex) + { + Console.WriteLine($"Error in DataSegment.Parse:{ex.Message}"); + + } + return dataSegment; + } + + /// + /// 序列化 + /// + /// + public string Serialize() + { + return $"QN={QN};ST={ST};CN={(int)CN};PW={PW};MN={MN};Flag={PackageFlag.Serialize()};CP=&&{CpCommand.Serialize()}&&"; + } + } +} diff --git a/langguanApi/Model/Device.cs b/langguanApi/Model/Device.cs new file mode 100644 index 0000000..9c51adb --- /dev/null +++ b/langguanApi/Model/Device.cs @@ -0,0 +1,16 @@ +namespace langguanApi.Model +{ + /// + /// 设备信息 + /// + public class Device : BaseModel + { + public string deviceMN { get; set; } + public string Name { get; set; } + public string Ip { get; set; } + public double lng { get; set; } + public double lat { get; set; } + public string desricption { get; set; } + public int state { get; set; } + } +} diff --git a/langguanApi/Model/Dto/AddDevice.cs b/langguanApi/Model/Dto/AddDevice.cs new file mode 100644 index 0000000..76c3330 --- /dev/null +++ b/langguanApi/Model/Dto/AddDevice.cs @@ -0,0 +1,13 @@ +namespace langguanApi.Model.Dto +{ + public class DeviceDto + { + public string deviceMN { get; set; } + public string Name { get; set; } + public string Ip { get; set; } + public double lng { get; set; } + public double lat { get; set; } + public string desricption { get; set; } + public int state { get; set; } + } +} diff --git a/langguanApi/Model/Dto/HJ212_2017.cs b/langguanApi/Model/Dto/HJ212_2017.cs new file mode 100644 index 0000000..59a1633 --- /dev/null +++ b/langguanApi/Model/Dto/HJ212_2017.cs @@ -0,0 +1,143 @@ +using System.Text; + +namespace langguanApi.Model.Dto +{ + /// + /// HJ212_2017 + /// + public class HJ212_2017 + { + /// + /// 数据帧头 + /// + public string header { get; set; } + /// + /// 数据长度 + /// + public string data_length { get; set; } + + /// + /// 数据头 + /// + public Dictionary DATA_HEAD { get; set; } + /// + /// + /// + public Dictionary CP { get; set; } + /// + /// CRC校验 + /// + public string CRC { get; set; } + /// + /// + /// + /// + /// + public bool DecodeData(string Text) + { + try + { + if (Text.Length < 12 || !Text.StartsWith("##")) + { + Console.WriteLine("不是HJ212协议的报文!"); + return false; + } + Console.WriteLine($"开始解码数据:"); + Text = Text.ToUpper().Replace(";CP=", "").Replace(" ", "");//有些厂家协议不很标准,有的大小写不一致,此处强制转大写字母 + header = Text.Substring(0, 2); + data_length = Text.Substring(2, 4); + string[] data_0 = Text.Substring(6, Text.Length - 6).Split(new char[] { '&', '&' }, StringSplitOptions.RemoveEmptyEntries); + string[] data_1 = data_0[0].Split(new char[] { ';' }); + DATA_HEAD = new Dictionary(); + for (int h = 0; h < data_1.Length; h++) + { + string[] d_1 = data_1[h].Split(new char[] { '=' }); + DATA_HEAD.Add(d_1[0], d_1[1]); + } + string[] data_2 = data_0[1].Split(new char[] { ';' }); + CP = new Dictionary(); + for (int i = 0; i < data_2.Length; i++) + { + string[] d_6 = data_2[i].Split(new char[] { ',' }); + for (int j = 0; j < d_6.Length; j++) + { + string[] d_7 = d_6[j].Split(new char[] { '=' }); + CP.Add(d_7[0].Replace("-RTD", ""), d_7[1]); + } + } + CRC = data_0[2]; + return true; + } + catch (Exception ex) + { + //数据接收不完整 + Console.WriteLine($" 解码失败:err:{ex},数据有问题," + Text); + //throw; + return false; + } + } + + + /// + /// 判断数据是否通过校验 + /// + /// 原始数据 + /// 是否通过 + public bool Crc16(string Text) + { + try + { + string CRC = Text.Substring(Text.Length - 4, 4); + + Text = Text.Substring(Text.IndexOf("QN")); + Text = Text.Substring(0, Text.Length - 4); + + byte[] bytes = Encoding.ASCII.GetBytes(Text); + int crcRegister = 0xFFFF; + for (int i = 0; i < bytes.Length; i++) + { + crcRegister = (crcRegister >> 8) ^ bytes[i]; + for (int j = 0; j < 8; j++) + { + int check = crcRegister & 0x0001; + crcRegister >>= 1; + if (check == 0x0001) + { + crcRegister ^= 0xA001; + } + } + } + string result = string.Format("{0:X}", crcRegister);//转十六进制 + for (int i = result.Length; i < 4; i++)//补足 4 位 + { + result = "0" + result; + } + //LogApi.WriteLog("计算校验码:" + result); + if (result == CRC) + { + return true; + } + else + { + Console.WriteLine("校验码有误," + CRC); + Console.WriteLine("待校验数据:" + Text); + Console.WriteLine("计算校验码:" + result); + //LogApi.WriteLog("校验码有误:" + CRC); + //LogApi.WriteLog("待校验数据:" + Text); + //LogApi.WriteLog("计算校验码:" + result); + return false; + + + } + } + catch (Exception) + { + Console.WriteLine("数据校验:数据有问题:" + Text); + //数据接收不完整 + return false; + //throw; + } + + } + } +} diff --git a/langguanApi/Model/FactorCode.cs b/langguanApi/Model/FactorCode.cs new file mode 100644 index 0000000..5678686 --- /dev/null +++ b/langguanApi/Model/FactorCode.cs @@ -0,0 +1,422 @@ +namespace langguanApi.Model +{ + /// + /// 污染物因子编码 + /// + public enum FactorCode + { + #region VOCs 在线检测仪因子编码 + a24901, + a24904, + a24905, + a24042, + a25002, + a24036, + a24908, + a24909, + a24910, + a24012, + a24043, + a24084, + a24911, + a25003, + a24912, + a24913, + a24070, + a25004, + a25008, + a25038, + a25006, + a24044, + a25034, + a25033, + a25902, + a25014, + a25021, + a25901, + a25019, + a24068, + a25020, + a25903, + a25904, + a24914, + a24915, + a24001, + a24045, + a24002, + a24053, + a24038, + a24037, + a24079, + a24064, + a24919, + a24063, + a24902, + a24041, + a24039, + a24077, + a24074, + a24076, + a24907, + a24903, + a24011, + a24061, + a24906, + a31002, + a31005, + + #region 甲烷和非甲烷总烃分析仪因子编码 + a05002, + a99999, + a24088, + #endregion + + a24072, + a05009, + a05014, + a24099, + a24058, + a24046, + a24078, + a24008, + a24015, + a24916, + a31004, + a31003, + a24047, + a05013, + a31024, + a99009, + a24003, + a28006, + a31015, + a24016, + a31900, + a24111, + a24004, + a24018, + a24005, + a24017, + a24049, + a24917, + a24027, + a31010, + a31026, + a24007, + a24112, + a24054, + a24019, + a24050, + a31009, + a24034, + a25010, + a24009, + a24020, + a25012, + a25011, + a30003, + a30008, + a99051, + a29026, + a29017, + a31025, + a24110, + a25072, + a29015, + a31030, + a31027, + a24006, + a25013, + a25068, + a25015, + a24113, + a25059, + a31001, + a31016, + a31018, + a31020, + a24102, + a30001, + a25905, + a24918, + a28900, + a28010, + a30002, + a30023, + a28001, + a24059, + a24944, + #endregion + + #region 气象五参数在线监测仪因子编码 + a01030, + a01004, + a01007, + a01008, + a06001, + a01001, + #endregion + + #region 氮氧化物分析仪 + a21003, + a21004, + a21002, + a21029, + #endregion + + #region 常规六参因子编码 + a34004, + a34002, + a21026, + // a21004, + a21005, + #endregion + + #region 在线离子色谱仪因子编码 + a06010, + a06011, + a06013, + a06009, + a06012, + a06005, + a06006, + a06019, + a06008, + a21001, + a20109, + // a21026, + a20110, + a21024, + // a21004, + a06007, + a06015, + a06018, + a06021, + a06017, + a06022, + a06023, + a06024, + a06025, + a06026, + a06027, + a06028, + a06029, + a06030, + a06031, + a06032, + a06033, + a34007, + a34006, + a34047, + a34048, + a34049, + a20044, + a20072, + a20058, + a20033, + a20026, + a20104, + a20041, + a20064, + a20111, + a20055, + a20095, + a20004, + a20092, + a20101, + a20012, + a20007, + a20029, + a20068, + a20038, + a20061, + a20112, + a20113, + a20089, + a20114, + a20115, + a20086, + a20116, + a20117, + a20118, + a20119, + a20079, + a20120, + a20002, + a20121, + a20075, + a20052, + a20122, + a20123, + a21012, + a20124, + a20107, + a20125, + a20126, + a20127, + a20128, + a20129, + a20098, + a20020, + a01031, + a01032, + a01029, + a01033, + a01034, + a01035, + a01036, + a01037, + // a01001, + // a01004, + a01038, + a01039, + // a06001, + a01022, + a01023, + // a34002, + // a34004, + a01024, + a19006, + a01025, + a01026, + a01020, + a01027, + a01028, + // a01029, + // a01022, + // a01023, + a05024, + a19999, + a19998, + a19997, + a19996, + a19995, + a19994, + a19993, + #endregion + + #region 气监测因子编码表(引用 HJ 524-2009) + a00000, + //a01001, + a01002, + a01006, + //a01007, + //a01008, + a01010, + a01011, + a01012, + a01013, + a01014, + a01015, + a01016, + a01017, + a01901, + a01902, + a05001, + //a05002, + a05008, + //a05009, + //a05013, + a19001, + //a20007, + a20016, + a20025, + //a20026, + a20043, + //a20044, + a20057, + //a20058, + a20063, + a20091, + //a21001, + //a21002, + //a21003, + //a21004, + //a21005, + a21017, + a21018, + a21022, + //a21024, + //a21026, + a21028, + a23001, + //a24003, + //a24004, + //a24005, + //a24006, + //a24007, + //a24008, + //a24009, + //a24015, + //a24016, + //a24017, + //a24018, + //a24019, + //a24020, + //a24027, + //a24034, + //a24036, + //a24042, + //a24043, + //a24046, + //a24047, + //a24049, + //a24050, + //a24053, + //a24054, + //a24072, + //a24078, + a24087, + //a24088, + //a24099, + //a24110, + //a24111, + //a24112, + //a24113, + //a25002, + //a25003, + //a25004, + a25005, + //a25006, + a25007, + //a25008, + //a25010, + //a25011, + //a25012, + //a25013, + //a25014, + //a25015, + //a25019, + //a25020, + //a25021, + a25023, + //a25038, + a25044, + //a25072, + a26001, + //a29017, + //a29026, + //a30001, + //a30008, + a30022, + //a31001, + //a31002, + //a31024, + //a31025, + //a31030, + a34001, + //a34002, + //a34004, + a34005, + a34011, + a34013, + a34017, + a34038, + a34039, + a34040, + a99010, + a99049, + //a99051, + #endregion + } +} diff --git a/langguanApi/Model/HJ212.cs b/langguanApi/Model/HJ212.cs new file mode 100644 index 0000000..eadc538 --- /dev/null +++ b/langguanApi/Model/HJ212.cs @@ -0,0 +1,49 @@ +using Swashbuckle.AspNetCore.SwaggerUI; +using System.ComponentModel; +using System.Reflection.Metadata; + +namespace langguanApi.Model +{ + public class HJ212 : BaseModel + { + /// + /// 设备ID + /// + public string deviceMN { get; set; } + /// + /// PM2.5浓度 + /// + public double a34004 { get; set; } + /// + /// PM10浓度 + /// + public double a34002 { get; set; } + /// + /// TSP浓度 + /// + public double a34001 { get; set; } + /// + /// 温度 + /// + public double a01001 { get; set; } + /// + /// 湿度 + /// + public double a01002 { get; set; } + /// + /// 大气压 + /// + public double a01006 { get; set; } + /// + /// 风速 + /// + public double a01007 { get; set; } + /// + /// 风向 + /// + public double a01008 { get; set; } + public double lat { get; set; } + public double lng { get; set; } + + } +} diff --git a/langguanApi/Model/InstrumentationDataFlag.cs b/langguanApi/Model/InstrumentationDataFlag.cs new file mode 100644 index 0000000..b5126ed --- /dev/null +++ b/langguanApi/Model/InstrumentationDataFlag.cs @@ -0,0 +1,64 @@ +namespace langguanApi.Model +{ + /// + /// 检测仪器数据标记 + /// + public enum InstrumentationDataFlag + { + /// + /// 正常(有效) + /// 在线监控(监测)仪器仪表工作正常 + /// + N, + + /// + /// 无效 + /// 在线监控(监测)仪器仪表停运 + /// + F, + + /// + /// 无效 + /// 在线监控(监测)仪器仪表处于维护期间产生的数据 + /// + M, + + /// + /// 有效 + /// 手工输入的设定值 + /// + S, + + /// + /// 无效 + /// 在线监控(监测)仪器仪表故障 + /// + D, + + /// + /// 无效 + /// 在线监控(监测)仪器仪表处于校准状态 + /// + C, + + /// + /// 无效 + /// 在线监控(监测)仪器仪表采样数值超过测量上限 + /// + T, + + /// + /// 无效 + /// 在线监控(监测)仪器仪表与数采仪通讯异常 + /// + B, + + /// + /// 无效(有效数据不足) + /// + /// + /// 按照5分钟、1小时均值计算要求,所获取的有效数据个数不足 + /// + H + } +} diff --git a/langguanApi/Model/NetPackage.cs b/langguanApi/Model/NetPackage.cs new file mode 100644 index 0000000..6d2ceff --- /dev/null +++ b/langguanApi/Model/NetPackage.cs @@ -0,0 +1,119 @@ +using System.Text; + +namespace langguanApi.Model +{ + /// + /// 通讯包 + /// + public class NetPackage + { + /// + /// 默认头 + /// + public const string FixedHead = "##"; + /// + /// 默认尾 + /// + public const string FixedTail = "\r\n"; + + /// + /// 包头 + /// + public string Head { get; set; } + + /// + /// 数据段长度 + /// + public int DataSegmentLength { get; set; } + + /// + /// 数据段 + /// + public DataSegment DataSegment { get; set; } + + /// + /// CRC校验码 + /// + public string CrcCode { get; set; } + + /// + /// 包尾 + /// + public string Tail { get; set; } + + /// + /// CRC16校验 + /// + /// 需要校验的字符串 + /// CRC16 校验码 + private static string CRC16(string arg) + { + char[] puchMsg = arg.ToCharArray(); + uint i, j, crc_reg, check; + crc_reg = 0xFFFF; + for (i = 0; i < puchMsg.Length; i++) + { + crc_reg = (crc_reg >> 8) ^ puchMsg[i]; + for (j = 0; j < 8; j++) + { + check = crc_reg & 0x0001; + crc_reg >>= 1; + if (check == 0x0001) + { + crc_reg ^= 0xA001; + } + } + } + + return crc_reg.ToString("X2").PadLeft(4, '0'); + } + + /// + /// 解析 + /// + /// + /// + /// + public static NetPackage Parse(string line, Func unpackCacheFunc = null) + { + try + { + NetPackage netPackage = new NetPackage(); + netPackage.Head = line.Substring(0, 2); + netPackage.DataSegmentLength = int.Parse(line.Substring(2, 4)); + + string dataSegment = line.Substring(6, netPackage.DataSegmentLength); + + string crcCode = line.Substring(6 + netPackage.DataSegmentLength, 4); + string calcCrcCode = CRC16(dataSegment); + if (crcCode != calcCrcCode) + { + throw new Exception("CRC校验失败 " + line); + } + + netPackage.DataSegment = DataSegment.Parse(dataSegment, unpackCacheFunc); + netPackage.CrcCode = crcCode; + netPackage.Tail = line.Substring(10 + netPackage.DataSegmentLength); + + return netPackage; + } + catch (Exception ex) + { + throw new Exception("Error in NetPackage.Parse", ex); + } + } + + /// + /// 序列化 + /// + /// + public string Serialize() + { + string dataSegment = DataSegment.Serialize(); + DataSegmentLength = dataSegment.Length; + CrcCode = CRC16(dataSegment); + + return $"{Head}{DataSegmentLength.ToString().PadLeft(4, '0')}{dataSegment}{CrcCode}{Tail}"; + } + } +} diff --git a/langguanApi/Model/PackageFlag.cs b/langguanApi/Model/PackageFlag.cs new file mode 100644 index 0000000..99d962c --- /dev/null +++ b/langguanApi/Model/PackageFlag.cs @@ -0,0 +1,117 @@ +namespace langguanApi.Model +{ + /// + /// 拆分包及应答标志 + /// + public class PackageFlag + { + public byte V5 { get; set; } + public byte V4 { get; set; } + public byte V3 { get; set; } + public byte V2 { get; set; } + public byte V1 { get; set; } + public byte V0 { get; set; } + + /// + /// 命令是否应答:1-应答,0-不应答 + /// + public byte A { get; set; } + + /// + /// 是否有数据包序号:1 - 数据包中包含包号和总包数两部分,0 - 数据包中不包含包号和总包数两部分 + /// + public byte D { get; set; } + + /// + /// 标准版本号 + /// 000000 表示标准 HJ/T212-2005 + /// 000001 表示本次标准修订版本号 + /// + public string Version + { + get + { + return $"{V5}{V4}{V3}{V2}{V1}{V0}"; + } + } + + /// + /// 解析 + /// + /// + /// + public static PackageFlag Parse(string data) + { + byte flag = byte.Parse(data); + return new PackageFlag() + { + + V5 = GetBit(flag, 7), + V4 = GetBit(flag, 6), + V3 = GetBit(flag, 5), + V2 = GetBit(flag, 4), + V1 = GetBit(flag, 3), + V0 = GetBit(flag, 2), + D = GetBit(flag, 1), + A = GetBit(flag, 0) + }; + } + + /// + /// 序列化 + /// + /// + public string Serialize() + { + return Convert.ToInt32($"{V5}{V4}{V3}{V2}{V1}{V0}{D}{A}", 2).ToString(); + } + + /// + /// 获取取第index位 + /// + /// + /// index从0开始 + /// + /// + /// + /// + private static byte GetBit(byte b, int index) + { + // (byte)((from & (0xFF << (index * 8))) >> (index * 8)) + return ((b & (1 << index)) > 0) ? (byte)1 : (byte)0; + } + + /// + /// 将第index位设为1 + /// + /// + /// index从0开始 + /// + /// + /// + /// + private static byte SetBit(byte b, int index) { return (byte)(b | (1 << index)); } + + /// + /// 将第index位设为0 + /// + /// + /// index从0开始 + /// + /// + /// + /// + private static byte ClearBit(byte b, int index) { return (byte)(b & (byte.MaxValue - (1 << index))); } + + /// + /// 将第index位取反 + /// + /// + /// index从0开始 + /// + /// + /// + /// + private static byte ReverseBit(byte b, int index) { return (byte)(b ^ (byte)(1 << index)); } + } +} diff --git a/langguanApi/Model/PollutantInfo.cs b/langguanApi/Model/PollutantInfo.cs new file mode 100644 index 0000000..d05d021 --- /dev/null +++ b/langguanApi/Model/PollutantInfo.cs @@ -0,0 +1,63 @@ +namespace langguanApi.Model +{ + /// + /// 污染物信息 + /// + public class PollutantInfo + { + /// + /// 约定的无效值 + /// + public const decimal InvaildValue = -9999; + + /// + /// 污染物因子编码 + /// + public FactorCode FactorCode { get; set; } + + /// + /// 污染物实时采样数据 + /// + /// + /// 默认值为约定的无效值 + /// + public decimal Rtd { get; set; } = InvaildValue; + + /// + /// 污染物指定时问内平均值 + /// + /// + /// 默认值为约定的无效值 + /// + public decimal Avg { get; set; } = InvaildValue; + + /// + /// 污染物指定时问内最大值 + /// + /// + /// 默认值为约定的无效值 + /// + public decimal Max { get; set; } = InvaildValue; + + /// + /// 污染物指定时问内最小值 + /// + /// + /// 默认值为约定的无效值 + /// + public decimal Min { get; set; } = InvaildValue; + + /// + /// 污染物指定时问内累计值 + /// + /// + /// 默认值为约定的无效值 + /// + public decimal Cou { get; set; } = InvaildValue; + + /// + /// 检测仪器数据标记 + /// + public InstrumentationDataFlag Flag { get; set; } + } +} diff --git a/langguanApi/Model/ReqPaing.cs b/langguanApi/Model/ReqPaing.cs new file mode 100644 index 0000000..dccc6f7 --- /dev/null +++ b/langguanApi/Model/ReqPaing.cs @@ -0,0 +1,13 @@ +namespace langguanApi.Model +{ + public class ReqPaing + { + public int pageSize { get; set; } = 10; + public int current { get; set; } = 1; + } + public class reqpage : ReqPaing + { + public string key { get; set; + } + } +} diff --git a/langguanApi/Model/ResponseCode.cs b/langguanApi/Model/ResponseCode.cs new file mode 100644 index 0000000..a8cedaf --- /dev/null +++ b/langguanApi/Model/ResponseCode.cs @@ -0,0 +1,48 @@ +namespace langguanApi.Model +{ + /// + /// 回应代码集 + /// + public enum ResponseCode + { + /// + /// 执行成功 + /// + ExecSucceeded = 1, + + /// + /// 执行失败,但不知道原因 + /// + ExecutionFailed_DoNotKnowReason = 2, + + /// + /// 执行失败,命令请求条件错误 + /// + ExecutionFailed_InvalidCommand = 3, + + /// + /// 通讯超时 + /// + CommunicationTimeout = 4, + + /// + /// 系统繁忙不能执行 + /// + SystemBusy = 5, + + /// + /// 系统时间异常 + /// + InvalidSystemTime = 6, + + /// + /// 没有数据 + /// + NoneData = 100, + + /// + /// 心跳包 + /// + HeartbeatPackage = 300 + } +} diff --git a/langguanApi/Model/columnView.cs b/langguanApi/Model/columnView.cs new file mode 100644 index 0000000..414b947 --- /dev/null +++ b/langguanApi/Model/columnView.cs @@ -0,0 +1,15 @@ +namespace langguanApi.Model +{ + public class columnView + { + /// + /// + /// + public string hour { get; set; } + /// + /// a34004=PM2.5浓度,a34002=PM10,a34001=tsp浓度 + /// + public string type { get; set; } + public double value { get; set; } + } +} diff --git a/langguanApi/Program.cs b/langguanApi/Program.cs new file mode 100644 index 0000000..4959dc1 --- /dev/null +++ b/langguanApi/Program.cs @@ -0,0 +1,94 @@ +using langguanApi.Common.Redis; +using langguanApi.Extensions; +using langguanApi.Extensions.AutoDI; +using langguanApi.Middleware; +using Microsoft.Extensions.Configuration; +using Microsoft.OpenApi.Models; +using System.Text.Json; + +var builder = WebApplication.CreateBuilder(args); + + +// Add services to the container. + +builder.Services.AddControllers(options => +{ + options.Filters.Add(); +}).AddNewtonsoftJson(option => +{ + option.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver(); + option.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; + +}).AddJsonOptions(option => +{ + option.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; +}); +//swagger +builder.Services.AddSwaggerGen( + options => + { + options.SwaggerDoc("v1", new OpenApiInfo() + { + Title = "Title", + Version = "v1", + Description = "Description", + }); + var path = Path.Combine(AppContext.BaseDirectory, "langguanApi.xml"); + options.IncludeXmlComments(path, true); + options.OrderActionsBy(_ => _.RelativePath); + }); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); + +//redis +var redisoptions = builder.Configuration.GetSection("Redis").Get(); +if (redisoptions != null) +{ + builder.Services.AddRedis(options => + { + options.Port = redisoptions.Port; + options.Server = redisoptions.Server; + options.Index = redisoptions.Index; + options.Password = redisoptions.Password; + options.Key = redisoptions.Key; + }); +} +//Զע +builder.Services.ServicesAutoInjectionExtension(); +builder.Services.AddSocketService(); +//cross domain +builder.Services.AddCors(options => +{ + options.AddPolicy("CorsPolicy", builder => + { + builder.AllowAnyOrigin(); + builder.AllowAnyMethod(); + builder.AllowAnyHeader(); + }); +}); + +var app = builder.Build(); +ServiceLocator.Instance = app.Services; + + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} +app.UseSwagger(); +app.UseSwaggerUI(); +app.UseCors("CorsPolicy"); +//app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); +/// +/// ݴ +/// +public static class ServiceLocator +{ + public static IServiceProvider Instance { get; set; } +} diff --git a/langguanApi/Properties/launchSettings.json b/langguanApi/Properties/launchSettings.json new file mode 100644 index 0000000..e51933f --- /dev/null +++ b/langguanApi/Properties/launchSettings.json @@ -0,0 +1,40 @@ +{ + "profiles": { + "langguanApi": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5254" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "environmentVariables": { + "ASPNETCORE_URLS": "http://+:80" + }, + "publishAllPorts": true + } + }, + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:9471", + "sslPort": 0 + } + } +} \ No newline at end of file diff --git a/langguanApi/Service/AlertService.cs b/langguanApi/Service/AlertService.cs new file mode 100644 index 0000000..b7e598f --- /dev/null +++ b/langguanApi/Service/AlertService.cs @@ -0,0 +1,43 @@ +using langguanApi.Extensions.AutoDI; +using langguanApi.Model; +using langguanApi.Model.Dto; +using System.Linq.Expressions; + +namespace langguanApi.Service +{ + [ServiceInjection(InjectionType.Transient)] + public class AlertService : BaseService + { + public AlertService(IConfiguration config) : base(config, nameof(Device)) + { + } + /// + /// 新加 + /// + /// + /// + public async Task Add(Alert input) + { + if (input != null) + { + await base.CreateAsync(input); + return new ApiResult { code = 0, msg = "" }; + } + return new ApiResult { code = -1, msg = "" }; ; + } + /// + /// 分页取数据 + /// + /// + /// + public async Task GetPage(reqpage input) + { + Expression> exp = filter => filter.DeviceMn.Contains(input.key) && filter.IsDelete == false; + return await base.GetPager(new ReqPaing() + { + pageSize = input.pageSize, + current = input.current + }, exp); + } + } +} diff --git a/langguanApi/Service/BaseService.cs b/langguanApi/Service/BaseService.cs new file mode 100644 index 0000000..e9480f9 --- /dev/null +++ b/langguanApi/Service/BaseService.cs @@ -0,0 +1,201 @@ +using langguanApi.Model; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.RazorPages; +using MongoDB.Driver; +using System.Linq.Expressions; + +namespace langguanApi.Service +{ + public class BaseService where T : BaseModel + { + private readonly IMongoCollection _collection; //数据表操作对象 + /// + /// 构造 + /// + /// + /// + public BaseService(IConfiguration config, string tableName) + { + + var client = new MongoClient(config.GetSection("ConnectionStrings:MongoDBConn").Value); //获取链接字符串 + var database = client.GetDatabase(config.GetSection("ConnectionStrings:DBName").Value); + _collection = database.GetCollection(tableName); + if (_collection == null) + { + database.CreateCollection(tableName); + } + } + /// + /// 获取所有 + /// + /// + public List Get() + { + return _collection.Find(T => true).ToList(); + } + /// + /// 获取单个 + /// + /// + /// + public T Get(string id) + { + return _collection.Find(T => T.Id == id).FirstOrDefault(); + } + /// + /// 创建 + /// + /// + /// + public T Create(T T) + { + _collection.InsertOne(T); + return T; + } + + /// + /// 更新 + /// + /// + /// + public void Update(string id, T TIn) + { + _collection.ReplaceOne(T => T.Id == id, TIn); + } + + /// + /// 删除 + /// + /// + public void Remove(T TIn) + { + _collection.DeleteOne(T => T.Id == TIn.Id); + } + + /// + /// 根据id删除 + /// + /// + public void Remove(string id) + { + _collection.DeleteOne(T => T.Id == id); + } + #region 异步操作 + /// + /// 取列表 + /// + /// + public async Task> GetAsync() + { + return await _collection.Find(T => true).ToListAsync(); + } + /// + /// 取单条 + /// + /// + /// + public async Task GetAsync(string id) + { + return await _collection.Find(T => T.Id == id).FirstOrDefaultAsync(); + } + /// + /// 新增 + /// + /// + /// + public async Task CreateAsync(T T) + { + await _collection.InsertOneAsync(T); + return T; + } + /// + /// 新增 + /// + /// + /// + public async Task CreateManyAsync(IEnumerable T) + { + await _collection.InsertManyAsync(T); + } + /// + /// 更新 + /// + /// + /// + /// + public async Task UpdateAsync(string id, T TIn) + { + await _collection.ReplaceOneAsync(T => T.Id == id, TIn); + } + /// + /// 删除 + /// + /// + /// + public async Task RemoveAsync(string id) + { + await _collection.DeleteOneAsync(T => T.Id == id); + } + + #endregion + /// + /// 表达式取数据 + /// + /// + /// + public Task> GetListWithExp(Expression> expression) + { + // var temp = _collection.AsQueryable().Where(expression).ToList(); + return Task.FromResult(_collection.AsQueryable().Where(expression)); + } + /// + /// filter查找 + /// + /// + /// + public async Task> FindListByFilter(Expression> filter) + { + FilterDefinition filters = Builders.Filter.Where(filter); + return await _collection.Find(filters).ToListAsync(); + + } + /// + /// filterdefinition + /// + /// + /// + public async Task> FindListyFilter(FilterDefinition filter) + { + return await _collection.Find(filter).ToListAsync(); + } + /// + /// 是否存在 + /// + /// + /// + public Task Exist(Expression> expression) + { + var result = _collection.AsQueryable().Where(expression).Any(); + return Task.FromResult(result); + } + /// + /// 分页取数据 + /// + /// + /// + /// + public async Task GetPager(ReqPaing req, Expression> exp = null) + { + req.pageSize = req.pageSize == 0 ? 10 : req.pageSize; + var query = await GetListWithExp(exp); + var total = query.Count(); + var items = query.OrderByDescending(s => s.CreateDateTime) + .Skip(req.pageSize * (req.current - 1)).Take(req.pageSize).ToList(); + return new ApiResult() + { + code = 0, + data = new { total, items } + }; + } + } +} diff --git a/langguanApi/Service/DeviceService.cs b/langguanApi/Service/DeviceService.cs new file mode 100644 index 0000000..50deeca --- /dev/null +++ b/langguanApi/Service/DeviceService.cs @@ -0,0 +1,99 @@ +using langguanApi.Extensions.AutoDI; +using langguanApi.Model; +using langguanApi.Model.Dto; +using Mapster; +using System.Linq.Expressions; + +namespace langguanApi.Service +{ + [ServiceInjection(InjectionType.Scoped)] + public class DeviceService : BaseService + { + public DeviceService(IConfiguration config) : base(config, nameof(Device)) + { + } + + /// + /// 新加 + /// + /// + /// + public async Task Add(DeviceDto input) + { + if (await Exist(input)) + { + return new ApiResult { code = 1, msg = $"已经存在名称为:{input.Name}" }; + } + var entity = input.Adapt(); + if (entity != null) + { + await base.CreateAsync(entity); + return new ApiResult { code = 0, msg = "" }; + } + return new ApiResult { code = -1, msg = "" }; ; + } + /// + /// 是否存在 + /// + /// + /// + public async Task Exist(DeviceDto input) + { + var entity = input.Adapt(); + Expression> exp = filter => filter.deviceMN == entity.deviceMN; + return await base.Exist(exp); + } + /// + /// 更新 + /// + /// + /// + public async Task update(DeviceDto input) + { + var entity = input.Adapt(); + await base.UpdateAsync(entity.Id, entity); + return new ApiResult { code = 0, msg = "" }; + } + /// + /// remove + /// + /// + /// + public async Task remove(IEnumerable ids) + { + if (ids.Any()) + { + foreach (var item in ids) + { + var entity = await base.GetAsync(item); + entity.IsDelete = true; + await base.UpdateAsync(entity.Id, entity); + } + return new ApiResult { code = 0, msg = "" }; + } + return new ApiResult { code = -1, msg = "" }; + } + // 通过devicemn获取设备信息 + public async Task GetByDeviceMN(string deviceMN) + { + Expression> exp = filter => filter.deviceMN == deviceMN && filter.IsDelete == false; + return (await base.GetListWithExp(exp)).FirstOrDefault(); + } + + + /// + /// 分页取数据 + /// + /// + /// + public async Task GetPage(reqpage input) + { + Expression> exp = filter => filter.Name.Contains(input.key) && filter.IsDelete == false; + return await base.GetPager(new ReqPaing() + { + pageSize = input.pageSize, + current = input.current + }, exp); + } + } +} diff --git a/langguanApi/Service/HJ212/NetServer.cs b/langguanApi/Service/HJ212/NetServer.cs new file mode 100644 index 0000000..2a0f426 --- /dev/null +++ b/langguanApi/Service/HJ212/NetServer.cs @@ -0,0 +1,58 @@ +using IceCoffee.FastSocket.Tcp; +using langguanApi.Model; +using System.Net; + +namespace langguanApi.Service.HJ212 +{ + public class NetServer : TcpServer + { + /// + /// 收到数据事件 + /// + public event Action ReceivedData; + /// + /// 发送数据事件 + /// + public event Action SendData; + + public NetServer(IPAddress address, int port, TcpServerOptions options = null) + : base(address, port, options ?? new TcpServerOptions() { KeepAlive = true }) + { + } + public NetServer(string address, int port, TcpServerOptions options = null) + : base(address, port, options ?? new TcpServerOptions() { KeepAlive = true }) + { + } + public NetServer(IPEndPoint endPoint, TcpServerOptions options = null) + : base(endPoint, options ?? new TcpServerOptions() { KeepAlive = true }) + { + } + /// + /// + /// + /// + protected override TcpSession CreateSession() + { + return new NetSession(this); + } + + /// + /// 引发收到数据事件 + /// + internal void RaiseReceivedData(NetSession netSession, NetPackage netPackage, string rawText) + { + ReceivedData?.Invoke(netSession, netPackage, rawText); + } + + /// + /// 引发发送数据事件 + /// + /// + /// + /// + internal void RaiseSendData(NetSession netSession, NetPackage netPackage, string rawText) + { + SendData?.Invoke(netSession, netPackage, rawText); + } + } +} diff --git a/langguanApi/Service/HJ212/NetSession.cs b/langguanApi/Service/HJ212/NetSession.cs new file mode 100644 index 0000000..95c4a2e --- /dev/null +++ b/langguanApi/Service/HJ212/NetSession.cs @@ -0,0 +1,87 @@ +using IceCoffee.FastSocket.Tcp; +using langguanApi.Model; +using System.Text; + +namespace langguanApi.Service.HJ212 +{ + public class NetSession : TcpSession + { + private StringBuilder _unpackCache; + + public NetSession(TcpServer server) : base(server) + { + } + + protected override void OnClosed() + { + base.OnClosed(); + _unpackCache?.Clear(); + } + + /// + /// 获取分包缓存 + /// + /// + private StringBuilder GetUnpackCache() + { + return _unpackCache ??= new StringBuilder(); + } + /// + /// + /// + protected override void OnReceived() + { + if (ReadBuffer.IndexOf(35) != 0L)// '#' + { + return; + // throw new Exception("异常TCP连接 IP: " + RemoteIPEndPoint); + } + + string rawText = null; + while (ReadBuffer.CanReadLine) + { + try + { + byte[] data = ReadBuffer.ReadLine(); + rawText = Encoding.UTF8.GetString(data); + NetPackage netPackage = NetPackage.Parse(rawText, GetUnpackCache); + ((NetServer)Server).RaiseReceivedData(this, netPackage, rawText); + + if (netPackage.DataSegment.PackageFlag != null && netPackage.DataSegment.PackageFlag.A == 1) + { + Response(netPackage); + } + } + catch (Exception ex) + { + Console.WriteLine($"error :OnReceived:{ex.Message},data:{ex.Data}"); + } + } + } + + /// + /// 应答 + /// + private void Response(NetPackage netPackage) + { + try + { + netPackage.DataSegment.ST = DataSegment.ResponseST; + netPackage.DataSegment.CN = CommandNumber.DataResponse; + netPackage.DataSegment.PackageFlag.A = 0; + netPackage.DataSegment.PackageFlag.D = 0; + netPackage.DataSegment.CpCommand.ExeRtn = ResponseCode.ExecSucceeded; + + string rawText = netPackage.Serialize(); + byte[] data = Encoding.UTF8.GetBytes(rawText); + SendAsync(data); + + ((NetServer)Server).RaiseSendData(this, netPackage, rawText); + } + catch (Exception ex) + { + throw new Exception("Error in NetSession", ex); + } + } + } +} diff --git a/langguanApi/Service/HJ212SocketServer.cs b/langguanApi/Service/HJ212SocketServer.cs new file mode 100644 index 0000000..98834b6 --- /dev/null +++ b/langguanApi/Service/HJ212SocketServer.cs @@ -0,0 +1,140 @@ +using System.Net.Sockets; +using System.Net; +using System.Text; +using IceCoffee.FastSocket.Tcp; +using langguanApi.Model.Dto; +using Newtonsoft.Json; +using langguanApi.Model; +using langguanApi.Service.HJ212; +using langguanApi.Extensions.AutoDI; + +namespace langguanApi.Service +{ + + public class HJ212SocketServer + { + private Hj212Service _hj212Service; + public HJ212SocketServer(Hj212Service hj212Service) + { + _hj212Service = hj212Service; + } + /// + /// 缓冲器 + /// + private byte[] result = new byte[1024]; + /// + /// 最大连接数 + /// + private int maxClientCount; + /// + /// 服务IP地址 + /// + private string ip; + /// + /// 服务端口号 + /// + private int port => 5001; + // 编码 + // private string code; + /// + /// 客户端列表 + /// + private List ClientSockets; + /// + /// IP终端 + /// + private IPEndPoint ipEndPoint; + /// + /// 服务端Socket + /// + private Socket ServerSocket; + private static NetServer server; + private static IceCoffee.FastSocket.Tcp.TcpClient client; + /// + /// 启动服务 + /// + /// + public async Task Start() + { + ip = IPAddress.Any.ToString(); + server = new NetServer(ip, port); + server.Started += OnNetServer_Started; + server.ExceptionCaught += OnNetServer_ExceptionCaught; + server.SessionStarted += OnNetServer_SessionStarted; + server.SessionClosed += OnNetServer_SessionClosed; + server.ReceivedData += OnNetServer_ReceivedData; + server.SendData += OnNetServer_SendData; + server.Start(); + } + private void OnNetServer_Started() + { + Console.WriteLine($"开始监听: {ip}:{port}"); + } + + private void OnNetServer_SendData(NetSession session, NetPackage netPackage, string rawText) + { + Console.WriteLine($"发送给: {session.RemoteIPEndPoint}: {rawText}"); + } + + private void OnNetServer_SessionClosed(TcpSession session) + { + Console.WriteLine("会话关闭: " + session.RemoteIPEndPoint + ", 当前会话总数: " + server.SessionCount); + } + + private static void OnNetServer_SessionStarted(TcpSession session) + { + Console.WriteLine("会话开始: " + session.RemoteIPEndPoint + ", 当前会话总数: " + server.SessionCount); + } + + private async void OnNetServer_ReceivedData(TcpSession session, NetPackage netPackage, string rawText) + { + Console.WriteLine("收到自: " + session.RemoteIPEndPoint + ": " + rawText); + HJ212_2017 hj = new HJ212_2017(); + if (hj.DecodeData(rawText)) + { + var body = JsonConvert.SerializeObject(hj.CP); + var entity = JsonConvert.DeserializeObject(body); + entity.deviceMN = hj.DATA_HEAD["MN"]; + //校验通过,开始入库 + await _hj212Service.Add(entity, session.RemoteIPEndPoint.ToString()); + } + } + private void OnNetServer_ExceptionCaught(Exception ex) + { + Console.WriteLine("Error in NetServer" + ex); + } + byte[] CallCRC(byte[] data) + { + string ccc = Convert.ToString(getCrc(data), 16).PadLeft(4, '0'); + return Encoding.ASCII.GetBytes(ccc.ToUpper()); + } + private int getCrc(byte[] data) + { + int high; + int flag; + + // 16位寄存器,所有数位均为1 + int wcrc = 0xffff; + for (int i = 0; i < data.Length; i++) + { + // 16 位寄存器的高位字节 + high = wcrc >> 8; + // 取被校验串的一个字节与 16 位寄存器的高位字节进行“异或”运算 + wcrc = high ^ data[i]; + + for (int j = 0; j < 8; j++) + { + flag = wcrc & 0x0001; + // 把这个 16 寄存器向右移一位 + wcrc = wcrc >> 1; + // 若向右(标记位)移出的数位是 1,则生成多项式 1010 0000 0000 0001 和这个寄存器进行“异或”运算 + if (flag == 1) + wcrc ^= 0xa001; + } + } + + return wcrc; + } + + } +} diff --git a/langguanApi/Service/Hj212Service.cs b/langguanApi/Service/Hj212Service.cs new file mode 100644 index 0000000..a813186 --- /dev/null +++ b/langguanApi/Service/Hj212Service.cs @@ -0,0 +1,110 @@ +using langguanApi.Extensions.AutoDI; +using langguanApi.Model; +using langguanApi.Model.Dto; +using System.Linq.Expressions; + +namespace langguanApi.Service +{ + [ServiceInjection(InjectionType.Transient)] + public class Hj212Service : BaseService + { + private DeviceService _deviceSerive; + public Hj212Service(IConfiguration config, DeviceService deviceSerive) : base(config, nameof(Model.HJ212)) + { + _deviceSerive = deviceSerive; + } + /// + /// 新加数据 + /// + /// + /// + /// + public async Task Add(Model.HJ212 hJ212, string deviceIp) + { + //先判断当前设备是否存在 + await _deviceSerive.Add(new DeviceDto() + { + deviceMN = hJ212.deviceMN, + Ip = deviceIp, + lat = hJ212.lat, + lng = hJ212.lng, + state = 1 + }); + await base.CreateAsync(hJ212); + } + /// + /// 最近10个小时的数据 + /// + /// + /// + public async Task> GetViewTop(int hours = -10) + { + var date = DateTime.Now.AddHours(-8).AddHours(hours); + Expression> exp = filter => filter.CreateDateTime >= date; + var result = (await base.GetListWithExp(exp)).ToList(); + + return result; + } + /// + /// 按设备号查询数据 + /// + /// + /// + public async TaskGetViewByDeviceMn(string deviceMn) + { + Expression> exp = filter => filter.deviceMN == deviceMn; + var result = (await base.GetListWithExp(exp)).OrderByDescending(s => s.CreateDateTime).Take(60).ToList(); + List list = new List(); + var temp = result.Select(s => new + { + s.a34001, + s.a34002, + s.a34004, + hour = s.CreateDateTime.AddHours(8).ToString("yyyy-MM-dd HH:mm") + }).ToList(); + + temp.GroupBy(g => new { g.hour }).ToList().ForEach(s => + { + var v1 = temp.Where(m => m.hour == s.Key.hour).Sum(t => t.a34001); + var v2 = temp.Where(m => m.hour == s.Key.hour).Sum(t => t.a34002); + var v3 = temp.Where(m => m.hour == s.Key.hour).Sum(t => t.a34004); + list.Add(new columnView() { hour = s.Key.hour, type = "a34001", value = v1 }); + list.Add(new columnView() { hour = s.Key.hour, type = "a34002", value = Math.Round(v2, 2) }); + list.Add(new columnView() { hour = s.Key.hour, type = "a34004", value = Math.Round(v3, 2) }); + }); + + return list; + } + + /// + /// 实时的数据 + /// + /// + public async Task> Realtime() + { + Expression> exp = filter => true; + var result = (await base.GetListWithExp(exp)).OrderByDescending(s => s.CreateDateTime) + .Take(60).OrderBy(s => s.CreateDateTime).ToList(); + List list = new List(); + var temp = result.Select(s => new + { + s.a34001, + s.a34002, + s.a34004, + hour = s.CreateDateTime.AddHours(8).ToString("yyyy-MM-dd HH:mm") + }).ToList(); + + temp.GroupBy(g => new { g.hour }).ToList().ForEach(s => + { + var v1 = temp.Where(m => m.hour == s.Key.hour).Sum(t => t.a34001); + var v2 = temp.Where(m => m.hour == s.Key.hour).Sum(t => t.a34002); + var v3 = temp.Where(m => m.hour == s.Key.hour).Sum(t => t.a34004); + list.Add(new columnView() { hour = s.Key.hour, type = "a34001", value = v1 }); + list.Add(new columnView() { hour = s.Key.hour, type = "a34002", value = Math.Round(v2, 2) }); + list.Add(new columnView() { hour = s.Key.hour, type = "a34004", value = Math.Round(v3, 2) }); + }); + + return list; + } + } +} diff --git a/langguanApi/Service/HomeService.cs b/langguanApi/Service/HomeService.cs new file mode 100644 index 0000000..e17773e --- /dev/null +++ b/langguanApi/Service/HomeService.cs @@ -0,0 +1,70 @@ +using langguanApi.Common.Redis; +using langguanApi.Common; +using langguanApi.Extensions.AutoDI; +using langguanApi.Model; +using System.Linq.Expressions; + +namespace langguanApi.Service +{ + /// + /// HomeService + /// + [ServiceInjection(InjectionType.Scoped)] + public class HomeService + { + private DeviceService _deviceService; + private Hj212Service _hj212Service; + private readonly IConfiguration _configuration; + private CacheManager _cacheManager; + private readonly WeatherService _weatherService; + /// + /// HomeService + /// + /// + /// + public HomeService(DeviceService device, Hj212Service hj212Service, + IConfiguration configuration, CacheManager cacheManager, WeatherService weatherService) + { + _deviceService = device; + _hj212Service = hj212Service; + _configuration = configuration; + _cacheManager = cacheManager; + _weatherService = weatherService; + } + /// + /// view + /// + /// + public async Task View() + { + var devices = await _deviceService.GetAsync(); + var ariQuality = ""; + Expression> filter = exp => true; + var Realtime = await _hj212Service.Realtime(); + var GetViewTop = await _hj212Service.GetViewTop(); + // 获取天气信息,缓存1小时,如果不存在,则调用WeatherService获取 + Func> getWeatherFunc = async () => await _weatherService.GetWeather(); + var weather = await _cacheManager.GetConvertVale(RedisKeylist.Weather, getWeatherFunc, 60 * 60); + return new ApiResult + { + code = 0, + data = new + { + home = new + { + center = new + { + lon = _configuration.GetValue("Home:Center:Lon"), + lat = _configuration.GetValue("Home:Center:Lat"), + }, + title = _configuration.GetValue("Home:Title"), + }, + devices, + ariQuality, + Realtime, + GetViewTop + } + }; + } + } +} diff --git a/langguanApi/Service/PingService.cs b/langguanApi/Service/PingService.cs new file mode 100644 index 0000000..20f9d0a --- /dev/null +++ b/langguanApi/Service/PingService.cs @@ -0,0 +1,84 @@ +using langguanApi.Extensions.AutoDI; +using System.Net.NetworkInformation; + +namespace langguanApi.Service +{ + + /// + /// ping service + /// + public class PingService + { + + private DeviceService _deviceSerive; + + /// + /// + /// + /// + public PingService(DeviceService deviceSerive) + { + _deviceSerive = deviceSerive; + + } + /// + /// + /// + public void CreatTask() + { + //5分钟执行一次 + + Timer myTimer = new Timer(new TimerCallback(Execute), null, 2000, 300000); + // Timer myTimer = new Timer(new TimerCallback(Execute), "ping service", 2000, 10000); + } + + /// + /// + /// + /// + public async void Execute(object b) + { + var deviceList = await _deviceSerive.GetAsync(); + if (deviceList.Any()) + { + foreach (var item in deviceList) + { + if (!string.IsNullOrEmpty(item.Ip)) + { + var ip = item.Ip.Split(":")[0]; + var res = await PingIp(ip); + item.state = res == true ? 1 : 0; + await _deviceSerive.UpdateAsync(item.Id, item); + } + } + } + + Console.WriteLine("{0} ping running.", (string)b); + + } + /// + /// ping + /// + /// + /// + public async Task PingIp(string ip) + { + try + { + //Ping 实例对象; + Ping pingSender = new Ping(); + PingReply pingReply = await pingSender.SendPingAsync(ip); + if (pingReply.Status == IPStatus.Success) + { + return true; + } + } + catch (Exception) + { + + return false; + } + return false; + } + } +} diff --git a/langguanApi/Service/WeatherService.cs b/langguanApi/Service/WeatherService.cs new file mode 100644 index 0000000..414deaa --- /dev/null +++ b/langguanApi/Service/WeatherService.cs @@ -0,0 +1,140 @@ +using Newtonsoft.Json; + +namespace langguanApi.Service +{ + public class WeatherService + { + private IHttpClientFactory _httpClientFactory; + private IConfiguration _configuration; + private ILogger _logger; + public WeatherService(IHttpClientFactory httpClientFactory, + IConfiguration configuration, ILogger logger) + { + _httpClientFactory = httpClientFactory; + _configuration = configuration; + _logger=logger; + } + /// + /// 爬气象局的天气数据% + /// + /// + public async Task GetWeather() + { + try + { + var client = _httpClientFactory.CreateClient(); + var resp = await client.GetAsync(_configuration.GetSection("Weather").Value); + if (resp.IsSuccessStatusCode) + { + var data = await resp.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(data); + return new + { + location = result?.data.location.name, + result?.data.now.precipitation, + result?.data.now.temperature, + result?.data.now.pressure, + result?.data.now.humidity, + result?.data.now.windDirection, + result?.data.now.windDirectionDegree, + result?.data.now.windSpeed, + result?.data.now.windScale, + }; + } + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + } + return null; + } + public class Location + { + /// + /// 54511 + /// + public string id { get; set; } + /// + /// 北京 + /// + public string name { get; set; } + /// + /// 中国, 北京, 北京 + /// + public string path { get; set; } + } + + public class Now + { + /// + /// Precipitation + /// + public double precipitation { get; set; } + /// + /// Temperature + /// + public double temperature { get; set; } + /// + /// Pressure + /// + public double pressure { get; set; } + /// + /// Humidity + /// + public double humidity { get; set; } + /// + /// 东北风 + /// + public string windDirection { get; set; } + /// + /// WindDirectionDegree + /// + public double windDirectionDegree { get; set; } + /// + /// WindSpeed + /// + public double windSpeed { get; set; } + /// + /// 微风 + /// + public string windScale { get; set; } + } + /// + /// + /// + public class Data + { + /// + /// Location + /// + public Location location { get; set; } + /// + /// Now + /// + public Now now { get; set; } + /// + /// Alarm + /// + public List alarm { get; set; } + /// + /// 2024/01/15 10:05 + /// + public DateTime lastUpdate { get; set; } + } + public class Root + { + /// + /// success + /// + public string msg { get; set; } + /// + /// Code + /// + public int code { get; set; } + /// + /// Data + /// + public Data data { get; set; } + } + } +} diff --git a/langguanApi/appsettings.Development.json b/langguanApi/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/langguanApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/langguanApi/appsettings.json b/langguanApi/appsettings.json new file mode 100644 index 0000000..26cc5df --- /dev/null +++ b/langguanApi/appsettings.json @@ -0,0 +1,26 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "MongoDBConn": "mongodb://admin:adminn@101.43.201.20:8017", + "DBName": "lg" + }, + "Redis": { + "Server": "101.43.201.20:6379,password=Aa123,abortConnect =false", + "Key": "tdss", + "Index": 5 + }, + "Weather": "https://weather.cma.cn/api/now/54511", //天气预报地址,中国气象 + "Home": { + "Title": "鄂托克旗新航焦化有限公司", + "Center": { + "lat": 39.4716613, + "lon": 107.1413332 + } + } +} diff --git a/langguanApi/langguanApi.csproj b/langguanApi/langguanApi.csproj new file mode 100644 index 0000000..a0c71bc --- /dev/null +++ b/langguanApi/langguanApi.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + enable + Linux + + + + true + + + D:\work\langguanApi\langguanApi.xml + + + + + + + + + + + + + +