From ead169605914edc112b5ad34c7710e7053a9fa06 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Wed, 12 Mar 2025 09:58:37 +0800 Subject: [PATCH 001/139] =?UTF-8?q?=E4=BC=98=E5=8C=96TCP=20ClientId?= =?UTF-8?q?=EF=BC=8C=E5=B0=86=E6=95=B0=E6=8D=AEClinetId=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E9=9B=86=E4=B8=AD=E5=99=A8=E7=BC=96=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FreeRedisProviderService.cs | 54 +++++++ .../IFreeRedisProviderService.cs | 29 ++++ .../Options/FreeRedisOptions.cs | 25 +++ .../FreeRedisProviderModule.cs | 26 ++++ .../JiShe.CollectBus.FreeRedisProvider.csproj | 12 ++ JiShe.CollectBus.sln | 7 + ...he.CollectBus.Application.Contracts.csproj | 4 + .../DTO/Energy/EnergyAmmeterInfoDto.cs | 146 ++++++++++++++++++ .../Workers/IWorkerScheduledService.cs | 45 ++++++ .../Workers/IWorkerSubscriberAppService.cs | 31 ++++ .../CollectBusAppService.cs | 28 +++- .../CollectBusApplicationModule.cs | 2 + .../JiShe.CollectBus.Application.csproj | 1 + .../Plugins/TcpMonitor.cs | 18 ++- .../Workers/EpiCollectWorker.cs | 2 +- .../Workers/ScheduledMeterReadingService.cs | 27 ++++ .../Workers/WorkerSubscriberAppService.cs | 103 ++++++++++++ .../Consts/FreeRedisConst.cs | 20 +++ .../Ammeters/AmmeterInfo.cs | 45 ++++++ src/JiShe.CollectBus.Domain/Devices/Device.cs | 18 +++ .../CollectBusHostModule.Configure.cs | 3 +- .../ProtocolConst.cs | 13 ++ 22 files changed, 653 insertions(+), 6 deletions(-) create mode 100644 JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/FreeRedisProviderService.cs create mode 100644 JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/IFreeRedisProviderService.cs create mode 100644 JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/Options/FreeRedisOptions.cs create mode 100644 JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs create mode 100644 JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj create mode 100644 src/JiShe.CollectBus.Application.Contracts/Workers/DTO/Energy/EnergyAmmeterInfoDto.cs create mode 100644 src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerScheduledService.cs create mode 100644 src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerSubscriberAppService.cs create mode 100644 src/JiShe.CollectBus.Application/Workers/ScheduledMeterReadingService.cs create mode 100644 src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs create mode 100644 src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs diff --git a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/FreeRedisProviderService.cs b/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/FreeRedisProviderService.cs new file mode 100644 index 0000000..2556803 --- /dev/null +++ b/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/FreeRedisProviderService.cs @@ -0,0 +1,54 @@ +using FreeRedis; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace JiShe.CollectBus.FreeRedisProvider; + +public class FreeRedisProviderService : IFreeRedisProviderService, ISingletonDependency +{ + private FreeRedisOptions freeRedisOptions; + + /// + /// FreeRedis + /// + public FreeRedisProviderService(IOptions options) + { + freeRedisOptions = options.Value; + } + + public IRedisClient FreeRedis { get => GetClient(); } + + /// + /// 获取 FreeRedis 客户端 + /// + /// + public IRedisClient GetClient() + { + + var redisClinet = new RedisClient(freeRedisOptions.ConnectionString); + redisClinet.Serialize = obj => JsonSerializer.Serialize(obj); + redisClinet.Deserialize = (json, type) => JsonSerializer.Deserialize(json, type); + redisClinet.Notice += (s, e) => Trace.WriteLine(e.Log); + + return redisClinet; + } + + /// + /// 切换Redis数据库 + /// + /// + /// + public IRedisClient GetDatabase(int index = 0) + { + var redisClinet = GetClient(); + redisClinet.GetDatabase(index); + return redisClinet; + } +} diff --git a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/IFreeRedisProviderService.cs b/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/IFreeRedisProviderService.cs new file mode 100644 index 0000000..6f0ff63 --- /dev/null +++ b/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/IFreeRedisProviderService.cs @@ -0,0 +1,29 @@ +using FreeRedis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.FreeRedisProvider; + +public interface IFreeRedisProviderService +{ + /// + /// 默认客户端 + /// + IRedisClient FreeRedis { get; } + + /// + /// 获取客户端 + /// + /// + IRedisClient GetClient(); + + /// + /// 切换Redis数据库 + /// + /// + /// + IRedisClient GetDatabase(int index = 0); +} diff --git a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/Options/FreeRedisOptions.cs b/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/Options/FreeRedisOptions.cs new file mode 100644 index 0000000..266c743 --- /dev/null +++ b/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/Options/FreeRedisOptions.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.FreeRedisProvider; + +public class FreeRedisOptions +{ + /// + /// 连接字符串 + /// + public string? ConnectionString { get; set; } + + /// + /// 默认数据库 + /// + public string? DefaultDB { get; set; } + + /// + /// HangfireDB + /// + public string? HangfireDB { get; set; } +} \ No newline at end of file diff --git a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs b/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs new file mode 100644 index 0000000..2bf7349 --- /dev/null +++ b/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Modularity; + +namespace JiShe.CollectBus.FreeRedisProvider; + +public class FreeRedisProviderModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + Configure(options => + { + configuration.GetSection("Redis").Bind(options); + }); + + + } +} + diff --git a/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj b/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj new file mode 100644 index 0000000..e4f3447 --- /dev/null +++ b/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + enable + enable + + + + + + diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index 5a41cab..822d02b 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.DbMigrator EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.FreeSql", "src\JiShe.CollectBus.FreeSql\JiShe.CollectBus.FreeSql.csproj", "{FE0457D9-4038-4A17-8808-DCAD06CFC0A0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.FreeRedisProvider", "JiShe.CollectBus.FreeRedisProvider\JiShe.CollectBus.FreeRedisProvider.csproj", "{920445D9-18D2-4886-9053-6A4CC3B4F3E2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -83,6 +85,10 @@ Global {FE0457D9-4038-4A17-8808-DCAD06CFC0A0}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE0457D9-4038-4A17-8808-DCAD06CFC0A0}.Release|Any CPU.ActiveCfg = Release|Any CPU {FE0457D9-4038-4A17-8808-DCAD06CFC0A0}.Release|Any CPU.Build.0 = Release|Any CPU + {920445D9-18D2-4886-9053-6A4CC3B4F3E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {920445D9-18D2-4886-9053-6A4CC3B4F3E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {920445D9-18D2-4886-9053-6A4CC3B4F3E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {920445D9-18D2-4886-9053-6A4CC3B4F3E2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -100,6 +106,7 @@ Global {38C1808B-009A-418B-B17B-AB3626341B5D} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {8BA01C3D-297D-42DF-BD63-EF07202A0A67} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {FE0457D9-4038-4A17-8808-DCAD06CFC0A0} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {920445D9-18D2-4886-9053-6A4CC3B4F3E2} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj index 0a28849..6841779 100644 --- a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj +++ b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj @@ -22,4 +22,8 @@ + + + + diff --git a/src/JiShe.CollectBus.Application.Contracts/Workers/DTO/Energy/EnergyAmmeterInfoDto.cs b/src/JiShe.CollectBus.Application.Contracts/Workers/DTO/Energy/EnergyAmmeterInfoDto.cs new file mode 100644 index 0000000..7836840 --- /dev/null +++ b/src/JiShe.CollectBus.Application.Contracts/Workers/DTO/Energy/EnergyAmmeterInfoDto.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Workers.DTO.Energy +{ + /// + /// 能耗电表信息数据模型 + /// + public class EnergyAmmeterInfoDto + { + /// + /// 电表ID + /// + public int ID { get; set; } + /// + /// 电表名称 + /// + public string Name { get; set; } + + /// + /// 集中器ID + /// + public int FocusID { get; set; } + + /// + /// 集中器地址 + /// + public string Address { get; set; } + + /// + /// 集中器区域代码 + /// + public string AreaCode { get; set; } + + /// + /// 电表类别 (1单相、2三相三线、3三相四线), + /// 07协议: 开合闸指令(1A开闸断电,1C单相表合闸,1B多相表合闸) 645 2007 表 + /// 97协议://true(合闸);false(跳闸) 545 1997 没有单相多相 之分 "true" ? "9966" : "3355" + /// + public int TypeName { get; set; } + /// + /// 跳合闸状态字段: 0 合闸,1 跳闸 + /// 电表:TripState (0 合闸-通电, 1 断开、跳闸); + /// + public int TripState { get; set; } + + /// + /// 规约 -电表default(30) 1:97协议,30:07协议 + /// + public int? Protocol { get; set; } + + /// + /// 一个集中器下的[MeteringCode]必须唯一。 PN + /// + public int MeteringCode { get; set; } + + /// + /// 电表通信地址 + /// + public string AmmerterAddress { get; set; } + + /// + /// 波特率 default(2400) + /// + public int Baudrate { get; set; } + + /// + /// MeteringPort 端口就几个可以枚举。 + /// + public int MeteringPort { get; set; } + + /// + /// 电表密码 + /// + public string Password { get; set; } + + /// + /// 采集时间间隔(分钟,如15) + /// + public int TimeDensity { get; set; } + + /// + /// 该电表方案下采集项,如:0D_80 + /// + public string ItemCodes { get; set; } + + /// + /// State表状态: + /// 0新装(未下发),1运行(档案下发成功时设置状态值1), 2暂停, 100销表(销表后是否重新启用) + /// 特定:State -1 已删除 + /// + public int State { get; set; } + + /// + /// 是否自动采集(0:主动采集,1:自动采集) + /// + public int AutomaticReport { get; set; } + + /// + /// 该电表方案下采集项编号 + /// + public string DataTypes { get; set; } + + /// + /// 品牌型号 + /// + public string BrandType { get; set; } + + /// + /// 采集器编号 + /// + public string GatherCode { get; set; } + + /// + /// 是否特殊表 + /// + public int Special { get; set; } + + /// + /// 费率类型,单、多 (SingleRate :单费率(单相表1),多费率(其他0) ,与TypeName字段无关) + /// SingleRate ? "单" : "复" + /// [SingleRate] --0 复费率 false , 1 单费率 true (与PayPlanID保持一致) + ///对应 TB_PayPlan.Type: 1复费率,2单费率 + /// + public bool SingleRate { get; set; } + + /// + /// 项目ID + /// + public int ProjectID { get; set; } + /// + /// 是否异常集中器 0:正常,1异常 + /// + public int AbnormalState { get; set; } + + public DateTime LastTime { get; set; } + + /// + /// 集中器地址 + /// + public string FocusAddress { get; set; } + } +} diff --git a/src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerScheduledService.cs b/src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerScheduledService.cs new file mode 100644 index 0000000..6ec0577 --- /dev/null +++ b/src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerScheduledService.cs @@ -0,0 +1,45 @@ +using JiShe.CollectBus.Ammeters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Workers +{ + /// + /// 定时任务基础约束 + /// + interface IWorkerScheduledService + { + /// + /// 获取电表信息 + /// + /// + Task> GetAmmeterInfoList(); + + /// + /// 初始化电表缓存数据 + /// + /// + Task InitAmmeterCacheData(); + + /// + /// 1分钟采集电表数据 + /// + /// + Task ScheduledMeterOneMinuteReading(); + + /// + /// 5分钟采集电表数据 + /// + /// + Task ScheduledMeterFiveMinuteReading(); + + /// + /// 15分钟采集电表数据 + /// + /// + Task ScheduledMeterFifteenMinuteReading(); + } +} diff --git a/src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerSubscriberAppService.cs new file mode 100644 index 0000000..c7442de --- /dev/null +++ b/src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerSubscriberAppService.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; +using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.MessageReceiveds; +using Volo.Abp.Application.Services; + +namespace JiShe.CollectBus.Subscribers +{ + /// + /// 定时抄读任务消息订阅 + /// + public interface IWorkerSubscriberAppService : IApplicationService + { + /// + /// 1分钟采集电表数据下行消息消费订阅 + /// + /// + Task ScheduledMeterOneMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + + /// + /// 5分钟采集电表数据下行消息消费订阅 + /// + /// + Task ScheduledMeterFiveMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + + /// + /// 15分钟采集电表数据下行消息消费订阅 + /// + /// + Task ScheduledMeterFifteenMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + } +} diff --git a/src/JiShe.CollectBus.Application/CollectBusAppService.cs b/src/JiShe.CollectBus.Application/CollectBusAppService.cs index 4a45327..c607ec4 100644 --- a/src/JiShe.CollectBus.Application/CollectBusAppService.cs +++ b/src/JiShe.CollectBus.Application/CollectBusAppService.cs @@ -1,6 +1,12 @@ -using JiShe.CollectBus.FreeSql; +using FreeRedis; +using FreeSql; +using JiShe.CollectBus.FreeRedisProvider; +using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.Localization; +using JiShe.CollectBus.Workers.DTO.Energy; using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; using Volo.Abp.Application.Services; namespace JiShe.CollectBus; @@ -9,10 +15,30 @@ namespace JiShe.CollectBus; public abstract class CollectBusAppService : ApplicationService { public IFreeSqlProvider SqlProvider => LazyServiceProvider.LazyGetRequiredService(); + protected IRedisClient? FreeRedis => LazyServiceProvider.LazyGetService()?.FreeRedis; protected CollectBusAppService() { LocalizationResource = typeof(CollectBusResource); ObjectMapperContext = typeof(CollectBusApplicationModule); } + + + #region 能耗相关 + /// + /// 查找当前采集器下所有电表 + /// + /// 采集端Code + /// + protected async Task> GetAmmetersByGatherCode(string gatherCode = "V4-Gather-8890") + { + string sql = $@"SELECT C.ID,C.Name,C.FocusID,C.SingleRate,C.MeteringCode,C.Code AS BrandType,C.Baudrate,C.Password,C.MeteringPort,C.[Address] AS AmmerterAddress,C.TypeName,C.Protocol,C.TripState,C.[State],B.[Address],B.AreaCode,B.AutomaticReport,D.DataTypes,B.TimeDensity,A.GatherCode,C.Special,C.[ProjectID],B.AbnormalState,B.LastTime + FROM TB_GatherInfo(NOLOCK) AS A + INNER JOIN TB_FocusInfo(NOLOCK) AS B ON A.ID = B.GatherInfoID AND B.RemoveState >= 0 AND B.State>=0 + INNER JOIN TB_AmmeterInfo(NOLOCK) AS C ON B.ID = C.FocusID AND C.State>= 0 AND C.State<100 + INNER JOIN TB_AmmeterGatherItem(NOLOCK) AS D ON C.ID = D.AmmeterID AND D.State>=0 + WHERE A.GatherCode = {gatherCode}"; + return await SqlProvider.Instance.Change(DbEnum.EnergyDB).Select(sql).ToListAsync(); + } + #endregion } diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index ab38baa..694f807 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -10,6 +10,7 @@ using System.Reflection; using JiShe.CollectBus.FreeSql; using System; using Volo.Abp.AspNetCore.Mvc.AntiForgery; +using JiShe.CollectBus.FreeRedisProvider; namespace JiShe.CollectBus; @@ -19,6 +20,7 @@ namespace JiShe.CollectBus; typeof(AbpDddApplicationModule), typeof(AbpAutoMapperModule), typeof(AbpBackgroundWorkersModule), + typeof(FreeRedisProviderModule), typeof(CollectBusFreeSqlModule) )] public class CollectBusApplicationModule : AbpModule diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index 8bd63d9..7160628 100644 --- a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -23,6 +23,7 @@ + diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index b40273a..1c5a6b6 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -53,14 +53,17 @@ namespace JiShe.CollectBus.Plugins var aFn = (int?)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN); var fn = (int?)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN); var aTuple = (Tuple)hexStringList.GetAnalyzeValue(CommandChunkEnum.A); - if (aFn.HasValue && fn.HasValue) + if (aFn.HasValue && fn.HasValue && aTuple != null && !string.IsNullOrWhiteSpace(aTuple.Item1)) { + string oldClinetId = client.Id; + await client.ResetIdAsync(aTuple.Item1); + if ((AFN)aFn == AFN.链路接口检测) { switch (fn) { case 1: - await OnTcpLoginReceived(client, messageHexString, aTuple.Item1); + await OnTcpLoginReceived(client, messageHexString, aTuple.Item1, oldClinetId); break; case 3: await OnTcpHeartbeatReceived(client, messageHexString, aTuple.Item1); @@ -113,7 +116,16 @@ namespace JiShe.CollectBus.Plugins await e.InvokeNext(); } - private async Task OnTcpLoginReceived(ITcpSessionClient client, string messageHexString, string deviceNo) + + /// + /// 登录帧处理 + /// + /// + /// + /// 集中器编号 + /// TCP首次连接时的Id + /// + private async Task OnTcpLoginReceived(ITcpSessionClient client, string messageHexString, string deviceNo,string oldClinetId) { var messageReceivedLoginEvent = new MessageReceivedLogin { diff --git a/src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs b/src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs index bbbee85..bc1badc 100644 --- a/src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs @@ -22,7 +22,7 @@ namespace JiShe.CollectBus.Workers public override Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { - _logger.LogInformation("Executed MyLogWorker..!"); + _logger.LogError("Executed MyLogWorker..!"); return Task.CompletedTask; } } diff --git a/src/JiShe.CollectBus.Application/Workers/ScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/Workers/ScheduledMeterReadingService.cs new file mode 100644 index 0000000..9673490 --- /dev/null +++ b/src/JiShe.CollectBus.Application/Workers/ScheduledMeterReadingService.cs @@ -0,0 +1,27 @@ +using FreeRedis; +using JiShe.CollectBus.FreeRedisProvider; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace JiShe.CollectBus.Workers +{ + /// + /// 定时采集服务 + /// + public class ScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService + { + /// + /// 初始化能耗电表数据 + /// + /// + public async Task InitEnergyAmmeterData() + { + var ammerterList = await GetAmmetersByGatherCode(); + } + } +} \ No newline at end of file diff --git a/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs new file mode 100644 index 0000000..1ddaeea --- /dev/null +++ b/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs @@ -0,0 +1,103 @@ +using System; +using System.Threading.Tasks; +using DeviceDetectorNET.Parser.Device; +using DotNetCore.CAP; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.Devices; +using JiShe.CollectBus.MessageReceiveds; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using TouchSocket.Sockets; +using Volo.Abp.Caching; +using Volo.Abp.Domain.Repositories; + +namespace JiShe.CollectBus.Subscribers +{ + /// + /// 定时抄读任务消息消费订阅 + /// + public class WorkerSubscriberAppService : CollectBusAppService, IWorkerSubscriberAppService,ICapSubscribe + { + private readonly ILogger _logger; + private readonly ITcpService _tcpService; + private readonly IServiceProvider _serviceProvider; + + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The TCP service. + /// The service provider. + public WorkerSubscriberAppService(ILogger logger, + ITcpService tcpService, IServiceProvider serviceProvider) + { + _logger = logger; + _tcpService = tcpService; + _serviceProvider = serviceProvider; + } + + /// + /// 一分钟定时抄读任务消息消费订阅 + /// + /// + /// + [CapSubscribe(ProtocolConst.SubscriberWorkerOneMinuteIssuedEventName)] + public async Task ScheduledMeterOneMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + { + _logger.LogInformation("1分钟采集电表数据下行消息消费队列开始处理"); + var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); + if (protocolPlugin == null) + { + _logger.LogError("【1分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); + } + else + { + await _tcpService.SendAsync(receivedMessage.ClientId, receivedMessage.Message); + } + } + + /// + /// 5分钟采集电表数据下行消息消费订阅 + /// + /// + /// + [CapSubscribe(ProtocolConst.SubscriberWorkerOneMinuteIssuedEventName)] + public async Task ScheduledMeterFiveMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + { + _logger.LogInformation("5分钟采集电表数据下行消息消费队列开始处理"); + var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); + if (protocolPlugin == null) + { + _logger.LogError("【5分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); + } + else + { + await _tcpService.SendAsync(receivedMessage.ClientId, receivedMessage.Message); + } + } + + /// + /// 15分钟采集电表数据下行消息消费订阅 + /// + /// + /// + [CapSubscribe(ProtocolConst.SubscriberWorkerOneMinuteIssuedEventName)] + public async Task ScheduledMeterFifteenMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + { + _logger.LogInformation("15分钟采集电表数据下行消息消费队列开始处理"); + var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); + if (protocolPlugin == null) + { + _logger.LogError("【15分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); + } + else + { + await _tcpService.SendAsync(receivedMessage.ClientId, receivedMessage.Message); + } + } + } +} diff --git a/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs b/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs new file mode 100644 index 0000000..96661d8 --- /dev/null +++ b/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Consts +{ + public class FreeRedisConst + { + /// + /// 缓存基础目录 + /// + public const string CacheBasicDirectoryKey = "CollectBus:"; + /// + /// 缓存电表信息 + /// + public const string CacheAmmeterInfoKey = $"{CacheBasicDirectoryKey}AmmeterInfo:"; + } +} diff --git a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs index e9bc4b5..aabbd32 100644 --- a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs +++ b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs @@ -8,80 +8,115 @@ namespace JiShe.CollectBus.Ammeters { public class AmmeterInfo { + /// + /// 电表ID + /// public int ID { get; set; } + /// + /// 电表名称 + /// public string Name { get; set; } + + /// + /// 集中器ID + /// public int FocusID { get; set; } + + /// + /// 集中器地址 + /// public string Address { get; set; } + + /// + /// 集中器区域代码 + /// public string AreaCode { get; set; } + /// /// 电表类别 (1单相、2三相三线、3三相四线), /// 07协议: 开合闸指令(1A开闸断电,1C单相表合闸,1B多相表合闸) 645 2007 表 /// 97协议://true(合闸);false(跳闸) 545 1997 没有单相多相 之分 "true" ? "9966" : "3355" /// public int TypeName { get; set; } + /// /// 跳合闸状态字段: 0 合闸,1 跳闸 /// 电表:TripState (0 合闸-通电, 1 断开、跳闸); /// public int TripState { get; set; } + /// /// 规约 -电表default(30) 1:97协议,30:07协议 /// public int? Protocol { get; set; } + /// /// 一个集中器下的[MeteringCode]必须唯一。 PN /// public int MeteringCode { get; set; } + /// /// 电表通信地址 /// public string AmmerterAddress { get; set; } + /// /// 波特率 default(2400) /// public int Baudrate { get; set; } + /// /// MeteringPort 端口就几个可以枚举。 /// public int MeteringPort { get; set; } + /// /// 电表密码 /// public string Password { get; set; } + /// /// 采集时间间隔(分钟,如15) /// public int TimeDensity { get; set; } + /// /// 该电表方案下采集项,如:0D_80 /// public string ItemCodes { get; set; } + /// /// State表状态: /// 0新装(未下发),1运行(档案下发成功时设置状态值1), 2暂停, 100销表(销表后是否重新启用) /// 特定:State -1 已删除 /// public int State { get; set; } + /// /// 是否自动采集(0:主动采集,1:自动采集) /// public int AutomaticReport { get; set; } + /// /// 该电表方案下采集项编号 /// public string DataTypes { get; set; } + /// /// 品牌型号 /// public string BrandType { get; set; } + /// /// 采集器编号 /// public string GatherCode { get; set; } + /// /// 是否特殊表 /// public int Special { get; set; } + /// /// 费率类型,单、多 (SingleRate :单费率(单相表1),多费率(其他0) ,与TypeName字段无关) /// SingleRate ? "单" : "复" @@ -89,11 +124,21 @@ namespace JiShe.CollectBus.Ammeters ///对应 TB_PayPlan.Type: 1复费率,2单费率 /// public bool SingleRate { get; set; } + + /// + /// 项目ID + /// public int ProjectID { get; set; } /// /// 是否异常集中器 0:正常,1异常 /// public int AbnormalState { get; set; } + public DateTime LastTime { get; set; } + + /// + /// 集中器地址 + /// + public string FocusAddress { get; set; } } } diff --git a/src/JiShe.CollectBus.Domain/Devices/Device.cs b/src/JiShe.CollectBus.Domain/Devices/Device.cs index 8dcf886..4fb015a 100644 --- a/src/JiShe.CollectBus.Domain/Devices/Device.cs +++ b/src/JiShe.CollectBus.Domain/Devices/Device.cs @@ -27,16 +27,34 @@ namespace JiShe.CollectBus.Devices Status = status; } + /// + /// 集中器编号,在集中器登录时解析获取,并会更新为当前TCP连接的最新ClientId + /// public string Number { get; set; } + /// + /// 首次上线时间 + /// public DateTime FirstOnlineTime { get; set; } + /// + /// 最后上线时间 + /// public DateTime LastOnlineTime { get; set; } + /// + /// TCP客户端首次连接ID,在登录解析成功以后会被Number集中器编号覆盖 + /// public string ClientId { get; set; } + /// + /// TCP客户端断线时间,用于计算是否断线 + /// public DateTime? LastOfflineTime { get; set; } + /// + /// 设备状态 + /// public DeviceStatus Status { get; set; } public void UpdateByLoginAndHeartbeat(string clientId) diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 1f306f8..699ba43 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -222,8 +222,9 @@ namespace JiShe.CollectBus.Host { context.Services.AddTcpService(config => { - config.SetListenIPHosts(int.Parse(configuration["TCP:ClientPort"] ?? "10500")) + config.SetListenIPHosts(int.Parse(configuration["TCP:ClientPort"] ?? "32580")) //.SetTcpDataHandlingAdapter(()=>new StandardFixedHeaderDataHandlingAdapter()) + .SetGetDefaultNewId(() => Guid.NewGuid().ToString())//定义ClinetId的生成策略 .ConfigurePlugins(a => { a.Add(); diff --git a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs index 25b160c..431fc4c 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs @@ -14,5 +14,18 @@ namespace JiShe.CollectBus.Protocol.Contracts public const string SubscriberReceivedHeartbeatEventName = "received.heartbeat.event"; public const string SubscriberReceivedLoginEventName = "received.login.event"; + /// + /// 1分钟采集电表数据下行消息主题 + /// + public const string SubscriberWorkerOneMinuteIssuedEventName = "issued.oneminute.event"; + /// + /// 5分钟采集电表数据下行消息主题 + /// + public const string SubscriberWorkerFiveMinuteIssuedEventName = "issued.fiveminute.event"; + /// + /// 15分钟采集电表数据下行消息主题 + /// + public const string SubscriberWorkerFifteenMinuteIssuedEventName = "issued.fifteenminute.event"; + } } -- 2.47.2 From 33a2901d7439f0b3afe6641c95b76a8c929aa690 Mon Sep 17 00:00:00 2001 From: Dai Mr <1822802785@qq.com> Date: Wed, 12 Mar 2025 13:39:26 +0800 Subject: [PATCH 002/139] =?UTF-8?q?=E6=A0=87=E5=87=86=E5=8D=8F=E8=AE=AE?= =?UTF-8?q?=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StandardProtocolPlugin.cs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs index 8233b99..a6918fd 100644 --- a/src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs @@ -27,19 +27,28 @@ namespace JiShe.CollectBus.Protocol var aTuple = (Tuple)hexStringList.GetAnalyzeValue(CommandChunkEnum.A); var afn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN); var fn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN); - if (afn == (int)AFN.请求实时数据) + switch ((AFN)afn) { - if (Enum.IsDefined(typeof(ATypeOfDataItems), fn)) //Enum.TryParse(afn.ToString(), out ATypeOfDataItems parseResult) - { - AnalyzeReadingDataAsync(messageReceived, sendAction); - } - } - else if(afn == (int)AFN.请求历史数据) - { - if (Enum.IsDefined(typeof(IIdataTypeItems), fn)) - { - AnalyzeReadingTdcDataAsync(messageReceived, sendAction); - } + case AFN.确认或否认: + AnalyzeAnswerDataAsync(messageReceived, sendAction); + break; + case AFN.设置参数: break; + case AFN.查询参数: break; + case AFN.请求实时数据: + if (Enum.IsDefined(typeof(ATypeOfDataItems), fn)) //Enum.TryParse(afn.ToString(), out ATypeOfDataItems parseResult) + { + AnalyzeReadingDataAsync(messageReceived, sendAction); + } + break; + case AFN.请求历史数据: + if (Enum.IsDefined(typeof(IIdataTypeItems), fn)) + { + AnalyzeReadingTdcDataAsync(messageReceived, sendAction); + } + break; + case AFN.数据转发: + AnalyzeTransparentForwardingAnswerAsync(messageReceived, sendAction); + break; } throw new NotImplementedException(); -- 2.47.2 From db96b72482a0008a10f6beb5b48ff007d5e550ef Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Wed, 12 Mar 2025 14:57:42 +0800 Subject: [PATCH 003/139] =?UTF-8?q?=E5=AE=9A=E6=97=B6=E9=87=87=E9=9B=86?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FreeRedisProviderService.cs | 14 +- .../Options/FreeRedisOptions.cs | 2 +- .../CollectBusRemoteServiceConsts.cs | 2 +- ...he.CollectBus.Application.Contracts.csproj | 4 - ...ce.cs => IScheduledMeterReadingService.cs} | 27 ++- .../Ammeters/AmmeterInfo.cs | 99 ----------- .../CollectBusAppService.cs | 24 +-- .../BasicScheduledMeterReadingService.cs | 137 +++++++++++++++ ...nergySystemScheduledMeterReadingService.cs | 103 ++++++++++++ .../Workers/ScheduledMeterReadingService.cs | 27 --- .../Workers/WorkerSubscriberAppService.cs | 8 + .../Consts/FreeRedisConst.cs | 8 +- .../Consts/SystemTypeConst.cs | 24 +++ .../Enums/MeterTypeEnum.cs | 53 ++++++ .../Ammeters/AmmeterInfo.cs | 14 +- .../Watermeter/WatermeterInfo.cs} | 158 ++++++++++-------- 16 files changed, 463 insertions(+), 241 deletions(-) rename src/JiShe.CollectBus.Application.Contracts/Workers/{IWorkerScheduledService.cs => IScheduledMeterReadingService.cs} (51%) delete mode 100644 src/JiShe.CollectBus.Application/Ammeters/AmmeterInfo.cs create mode 100644 src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs create mode 100644 src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs delete mode 100644 src/JiShe.CollectBus.Application/Workers/ScheduledMeterReadingService.cs create mode 100644 src/JiShe.CollectBus.Common/Consts/SystemTypeConst.cs create mode 100644 src/JiShe.CollectBus.Common/Enums/MeterTypeEnum.cs rename src/{JiShe.CollectBus.Application.Contracts/Workers/DTO/Energy/EnergyAmmeterInfoDto.cs => JiShe.CollectBus.Domain/Watermeter/WatermeterInfo.cs} (59%) diff --git a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/FreeRedisProviderService.cs b/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/FreeRedisProviderService.cs index 2556803..79e2037 100644 --- a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/FreeRedisProviderService.cs +++ b/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/FreeRedisProviderService.cs @@ -1,4 +1,5 @@ using FreeRedis; +using JetBrains.Annotations; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; @@ -23,7 +24,14 @@ public class FreeRedisProviderService : IFreeRedisProviderService, ISingletonDep freeRedisOptions = options.Value; } - public IRedisClient FreeRedis { get => GetClient(); } + [NotNull] + public IRedisClient FreeRedis + { + get + { + return GetClient(); + } + } /// /// 获取 FreeRedis 客户端 @@ -31,8 +39,8 @@ public class FreeRedisProviderService : IFreeRedisProviderService, ISingletonDep /// public IRedisClient GetClient() { - - var redisClinet = new RedisClient(freeRedisOptions.ConnectionString); + string connectionString = $"{freeRedisOptions.Configuration},defaultdatabase={freeRedisOptions.DefaultDB}"; + var redisClinet = new RedisClient(connectionString); redisClinet.Serialize = obj => JsonSerializer.Serialize(obj); redisClinet.Deserialize = (json, type) => JsonSerializer.Deserialize(json, type); redisClinet.Notice += (s, e) => Trace.WriteLine(e.Log); diff --git a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/Options/FreeRedisOptions.cs b/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/Options/FreeRedisOptions.cs index 266c743..9eb7bb9 100644 --- a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/Options/FreeRedisOptions.cs +++ b/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/Options/FreeRedisOptions.cs @@ -11,7 +11,7 @@ public class FreeRedisOptions /// /// 连接字符串 /// - public string? ConnectionString { get; set; } + public string? Configuration { get; set; } /// /// 默认数据库 diff --git a/src/JiShe.CollectBus.Application.Contracts/CollectBusRemoteServiceConsts.cs b/src/JiShe.CollectBus.Application.Contracts/CollectBusRemoteServiceConsts.cs index 396140d..2a7170e 100644 --- a/src/JiShe.CollectBus.Application.Contracts/CollectBusRemoteServiceConsts.cs +++ b/src/JiShe.CollectBus.Application.Contracts/CollectBusRemoteServiceConsts.cs @@ -4,5 +4,5 @@ public class CollectBusRemoteServiceConsts { public const string RemoteServiceName = "CollectBus"; - public const string ModuleName = "collectBus"; + public const string ModuleName = "collectBus"; } diff --git a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj index 6841779..0a28849 100644 --- a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj +++ b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj @@ -22,8 +22,4 @@ - - - - diff --git a/src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerScheduledService.cs b/src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs similarity index 51% rename from src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerScheduledService.cs rename to src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs index 6ec0577..7591f3e 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerScheduledService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs @@ -1,28 +1,47 @@ using JiShe.CollectBus.Ammeters; +using JiShe.CollectBus.Watermeter; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.DependencyInjection; namespace JiShe.CollectBus.Workers { /// /// 定时任务基础约束 /// - interface IWorkerScheduledService + public interface IScheduledMeterReadingService : IApplicationService { /// /// 获取电表信息 /// + /// 采集端Code /// - Task> GetAmmeterInfoList(); - + Task> GetAmmeterInfoList(string gatherCode = ""); + /// /// 初始化电表缓存数据 /// + /// 采集端Code /// - Task InitAmmeterCacheData(); + Task InitAmmeterCacheData(string gatherCode = ""); + + /// + /// 获取水表信息 + /// + /// 采集端Code + /// + Task> GetWatermeterInfoList(string gatherCode = ""); + + /// + /// 初始化水表缓存数据 + /// + /// 采集端Code + /// + Task InitWatermeterCacheData(string gatherCode = ""); /// /// 1分钟采集电表数据 diff --git a/src/JiShe.CollectBus.Application/Ammeters/AmmeterInfo.cs b/src/JiShe.CollectBus.Application/Ammeters/AmmeterInfo.cs deleted file mode 100644 index e9bc4b5..0000000 --- a/src/JiShe.CollectBus.Application/Ammeters/AmmeterInfo.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.Ammeters -{ - public class AmmeterInfo - { - public int ID { get; set; } - public string Name { get; set; } - public int FocusID { get; set; } - public string Address { get; set; } - public string AreaCode { get; set; } - /// - /// 电表类别 (1单相、2三相三线、3三相四线), - /// 07协议: 开合闸指令(1A开闸断电,1C单相表合闸,1B多相表合闸) 645 2007 表 - /// 97协议://true(合闸);false(跳闸) 545 1997 没有单相多相 之分 "true" ? "9966" : "3355" - /// - public int TypeName { get; set; } - /// - /// 跳合闸状态字段: 0 合闸,1 跳闸 - /// 电表:TripState (0 合闸-通电, 1 断开、跳闸); - /// - public int TripState { get; set; } - /// - /// 规约 -电表default(30) 1:97协议,30:07协议 - /// - public int? Protocol { get; set; } - /// - /// 一个集中器下的[MeteringCode]必须唯一。 PN - /// - public int MeteringCode { get; set; } - /// - /// 电表通信地址 - /// - public string AmmerterAddress { get; set; } - /// - /// 波特率 default(2400) - /// - public int Baudrate { get; set; } - /// - /// MeteringPort 端口就几个可以枚举。 - /// - public int MeteringPort { get; set; } - /// - /// 电表密码 - /// - public string Password { get; set; } - /// - /// 采集时间间隔(分钟,如15) - /// - public int TimeDensity { get; set; } - /// - /// 该电表方案下采集项,如:0D_80 - /// - public string ItemCodes { get; set; } - /// - /// State表状态: - /// 0新装(未下发),1运行(档案下发成功时设置状态值1), 2暂停, 100销表(销表后是否重新启用) - /// 特定:State -1 已删除 - /// - public int State { get; set; } - /// - /// 是否自动采集(0:主动采集,1:自动采集) - /// - public int AutomaticReport { get; set; } - /// - /// 该电表方案下采集项编号 - /// - public string DataTypes { get; set; } - /// - /// 品牌型号 - /// - public string BrandType { get; set; } - /// - /// 采集器编号 - /// - public string GatherCode { get; set; } - /// - /// 是否特殊表 - /// - public int Special { get; set; } - /// - /// 费率类型,单、多 (SingleRate :单费率(单相表1),多费率(其他0) ,与TypeName字段无关) - /// SingleRate ? "单" : "复" - /// [SingleRate] --0 复费率 false , 1 单费率 true (与PayPlanID保持一致) - ///对应 TB_PayPlan.Type: 1复费率,2单费率 - /// - public bool SingleRate { get; set; } - public int ProjectID { get; set; } - /// - /// 是否异常集中器 0:正常,1异常 - /// - public int AbnormalState { get; set; } - public DateTime LastTime { get; set; } - } -} diff --git a/src/JiShe.CollectBus.Application/CollectBusAppService.cs b/src/JiShe.CollectBus.Application/CollectBusAppService.cs index c607ec4..f45dda1 100644 --- a/src/JiShe.CollectBus.Application/CollectBusAppService.cs +++ b/src/JiShe.CollectBus.Application/CollectBusAppService.cs @@ -3,7 +3,6 @@ using FreeSql; using JiShe.CollectBus.FreeRedisProvider; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.Localization; -using JiShe.CollectBus.Workers.DTO.Energy; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Threading.Tasks; @@ -15,30 +14,11 @@ namespace JiShe.CollectBus; public abstract class CollectBusAppService : ApplicationService { public IFreeSqlProvider SqlProvider => LazyServiceProvider.LazyGetRequiredService(); - protected IRedisClient? FreeRedis => LazyServiceProvider.LazyGetService()?.FreeRedis; + protected IFreeRedisProviderService FreeRedisProvider => LazyServiceProvider.LazyGetService()!; protected CollectBusAppService() { LocalizationResource = typeof(CollectBusResource); ObjectMapperContext = typeof(CollectBusApplicationModule); - } - - - #region 能耗相关 - /// - /// 查找当前采集器下所有电表 - /// - /// 采集端Code - /// - protected async Task> GetAmmetersByGatherCode(string gatherCode = "V4-Gather-8890") - { - string sql = $@"SELECT C.ID,C.Name,C.FocusID,C.SingleRate,C.MeteringCode,C.Code AS BrandType,C.Baudrate,C.Password,C.MeteringPort,C.[Address] AS AmmerterAddress,C.TypeName,C.Protocol,C.TripState,C.[State],B.[Address],B.AreaCode,B.AutomaticReport,D.DataTypes,B.TimeDensity,A.GatherCode,C.Special,C.[ProjectID],B.AbnormalState,B.LastTime - FROM TB_GatherInfo(NOLOCK) AS A - INNER JOIN TB_FocusInfo(NOLOCK) AS B ON A.ID = B.GatherInfoID AND B.RemoveState >= 0 AND B.State>=0 - INNER JOIN TB_AmmeterInfo(NOLOCK) AS C ON B.ID = C.FocusID AND C.State>= 0 AND C.State<100 - INNER JOIN TB_AmmeterGatherItem(NOLOCK) AS D ON C.ID = D.AmmeterID AND D.State>=0 - WHERE A.GatherCode = {gatherCode}"; - return await SqlProvider.Instance.Change(DbEnum.EnergyDB).Select(sql).ToListAsync(); - } - #endregion + } } diff --git a/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs new file mode 100644 index 0000000..46dc901 --- /dev/null +++ b/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs @@ -0,0 +1,137 @@ +using FreeRedis; +using JiShe.CollectBus.Ammeters; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Watermeter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace JiShe.CollectBus.Workers +{ + /// + /// 定时采集服务 + /// + public abstract class BasicScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService + { + /// + /// 系统类型 + /// + public abstract string SystemType { get; } + + /// + /// 获取电表信息 + /// + /// 采集端Code + /// + public virtual Task> GetAmmeterInfoList(string gatherCode = "") + { + throw new NotImplementedException($"{nameof(GetAmmeterInfoList)}请根据不同系统类型进行实现"); + } + + /// + /// 初始化电表缓存数据 + /// + /// 采集端Code + /// + public virtual async Task InitAmmeterCacheData(string gatherCode = "") + { + var meterInfos = await GetAmmeterInfoList(gatherCode); + if (meterInfos == null || meterInfos.Count <= 0) + { + throw new NullReferenceException($"{nameof(InitWatermeterCacheData)} 初始化电表缓存数据时,电表数据为空"); + } + + //将表计信息根据集中器分组 + var meterInfoGroup = meterInfos.GroupBy(x => x.FocusAddress).ToList(); + foreach (var item in meterInfoGroup) + { + if (string.IsNullOrWhiteSpace(item.Key)) + { + continue; + } + + var redisCacheKey = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy)}{item.Key}"; + Dictionary keyValuePairs = new Dictionary(); + foreach (var subItem in item) + { + + keyValuePairs.TryAdd($"{subItem.ID}", subItem); + } + await FreeRedisProvider.FreeRedis.HSetAsync(redisCacheKey, keyValuePairs); + } + } + + /// + /// 获取水表信息 + /// + /// 采集端Code + /// + public virtual async Task> GetWatermeterInfoList(string gatherCode = "") + { + throw new NotImplementedException($"{nameof(GetWatermeterInfoList)}请根据不同系统类型进行实现"); + } + + /// + /// 初始化水表缓存数据 + /// + /// 采集端Code + /// + public virtual async Task InitWatermeterCacheData(string gatherCode = "") + { + var meterInfos = await GetWatermeterInfoList(gatherCode); + if (meterInfos == null || meterInfos.Count <= 0) + { + throw new NullReferenceException($"{nameof(InitWatermeterCacheData)} 初始化水表缓存数据时,水表数据为空"); + } + + //将表计信息根据集中器分组 + var meterInfoGroup = meterInfos.GroupBy(x => x.FocusAddress).ToList(); + foreach (var item in meterInfoGroup) + { + if (string.IsNullOrWhiteSpace(item.Key)) + { + continue; + } + + var redisCacheKey = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy)}{item.Key}"; + Dictionary keyValuePairs = new Dictionary(); + foreach (var subItem in item) + { + + keyValuePairs.TryAdd($"{subItem.ID}", subItem); + } + await FreeRedisProvider.FreeRedis.HSetAsync(redisCacheKey, keyValuePairs); + } + } + + /// + /// 1分钟采集电表数据 + /// + /// + public virtual Task ScheduledMeterOneMinuteReading() + { + throw new NotImplementedException($"{nameof(ScheduledMeterOneMinuteReading)}请根据不同系统类型进行实现"); + } + + /// + /// 5分钟采集电表数据 + /// + /// + public virtual Task ScheduledMeterFiveMinuteReading() + { + throw new NotImplementedException($"{nameof(ScheduledMeterFiveMinuteReading)}请根据不同系统类型进行实现"); + } + + /// + /// 15分钟采集电表数据 + /// + /// + public virtual Task ScheduledMeterFifteenMinuteReading() + { + throw new NotImplementedException($"{nameof(ScheduledMeterFifteenMinuteReading)}请根据不同系统类型进行实现"); + } + } +} diff --git a/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs new file mode 100644 index 0000000..92a9861 --- /dev/null +++ b/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs @@ -0,0 +1,103 @@ +using FreeRedis; +using JiShe.CollectBus.Ammeters; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.FreeRedisProvider; +using JiShe.CollectBus.FreeSql; +using JiShe.CollectBus.Watermeter; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace JiShe.CollectBus.Workers +{ + /// + /// 能耗系统定时采集服务 + /// + [AllowAnonymous] + //[Route($"/energy/app/scheduled")] + public class EnergySystemScheduledMeterReadingService : BasicScheduledMeterReadingService + { + public sealed override string SystemType => SystemTypeConst.Energy; + + /// + /// 获取电表信息 + /// + /// 采集端Code + /// + //[HttpGet] + //[Route($"ammeter/list")] + public override async Task> GetAmmeterInfoList(string gatherCode = "V4-Gather-8890") + { + string sql = $@"SELECT C.ID,C.Name,C.FocusID,C.SingleRate,C.MeteringCode,C.Code AS BrandType,C.Baudrate,C.Password,C.MeteringPort,C.[Address] AS AmmerterAddress,C.TypeName,C.Protocol,C.TripState,C.[State],B.[Address],B.AreaCode,B.AutomaticReport,D.DataTypes,B.TimeDensity,A.GatherCode,C.Special,C.[ProjectID],B.AbnormalState,B.LastTime,CONCAT(B.AreaCode, B.[Address]) AS FocusAddress + FROM TB_GatherInfo(NOLOCK) AS A + INNER JOIN TB_FocusInfo(NOLOCK) AS B ON A.ID = B.GatherInfoID AND B.RemoveState >= 0 AND B.State>=0 + INNER JOIN TB_AmmeterInfo(NOLOCK) AS C ON B.ID = C.FocusID AND C.State>= 0 AND C.State<100 + INNER JOIN TB_AmmeterGatherItem(NOLOCK) AS D ON C.ID = D.AmmeterID AND D.State>=0 + WHERE 1=1 "; + + if (!string.IsNullOrWhiteSpace(gatherCode)) + { + sql = $@"{sql} A.GatherCode = '{gatherCode}'"; + } + return await SqlProvider.Instance.Change(DbEnum.EnergyDB) + .Ado + .QueryAsync(sql); + } + + /// + /// 获取水表信息 + /// + /// 采集端Code + /// + //[HttpGet] + //[Route($"ammeter/list")] + public override async Task> GetWatermeterInfoList(string gatherCode = "V4-Gather-8890") + { + string sql = $@"SELECT + A.ID, + A.Name, + A.FocusID, + A.MeteringCode, + A.Baudrate, + A.MeteringPort, + A.[Address] AS MeterAddress, + A.[Password], + A.TypeName, + A.Protocol, + A.Code, + A.LinkType, + A.HaveValve, + A.MeterType AS MeterTypeName, + A.MeterBrand, + A.TimesRate, + A.TimeDensity, + A.TripState, + B.[Address], + B.AreaCode, + B.AutomaticReport, + A.[State], + C.GatherCode, + A.[ProjectID], + B.AbnormalState, + B.LastTime + FROM [dbo].[TB_WatermeterInfo](NOLOCK) AS A + INNER JOIN [dbo].[TB_FocusInfo](NOLOCK) AS B ON A.FocusID=B.ID AND B.RemoveState >= 0 AND B.State>=0 + INNER JOIN [dbo].[TB_GatherInfo](NOLOCK) AS C ON B.GatherInfoID=C.ID + WHERE A.State>=0 AND A.State<100 "; + + if (!string.IsNullOrWhiteSpace(gatherCode)) + { + sql = $@"{sql} AND C.GatherCode= '{gatherCode}'"; + } + return await SqlProvider.Instance.Change(DbEnum.EnergyDB) + .Ado + .QueryAsync(sql); + } + + } +} \ No newline at end of file diff --git a/src/JiShe.CollectBus.Application/Workers/ScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/Workers/ScheduledMeterReadingService.cs deleted file mode 100644 index 9673490..0000000 --- a/src/JiShe.CollectBus.Application/Workers/ScheduledMeterReadingService.cs +++ /dev/null @@ -1,27 +0,0 @@ -using FreeRedis; -using JiShe.CollectBus.FreeRedisProvider; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; - -namespace JiShe.CollectBus.Workers -{ - /// - /// 定时采集服务 - /// - public class ScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService - { - /// - /// 初始化能耗电表数据 - /// - /// - public async Task InitEnergyAmmeterData() - { - var ammerterList = await GetAmmetersByGatherCode(); - } - } -} \ No newline at end of file diff --git a/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs index 1ddaeea..6c7bfab 100644 --- a/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs @@ -8,6 +8,7 @@ using JiShe.CollectBus.Devices; using JiShe.CollectBus.MessageReceiveds; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using TouchSocket.Sockets; @@ -19,6 +20,7 @@ namespace JiShe.CollectBus.Subscribers /// /// 定时抄读任务消息消费订阅 /// + [Route($"/worker/app/subscriber")] public class WorkerSubscriberAppService : CollectBusAppService, IWorkerSubscriberAppService,ICapSubscribe { private readonly ILogger _logger; @@ -45,6 +47,8 @@ namespace JiShe.CollectBus.Subscribers /// /// /// + [HttpPost] + [Route("oneminute/issued-event")] [CapSubscribe(ProtocolConst.SubscriberWorkerOneMinuteIssuedEventName)] public async Task ScheduledMeterOneMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) { @@ -65,6 +69,8 @@ namespace JiShe.CollectBus.Subscribers /// /// /// + [HttpPost] + [Route("fiveminute/issued-event")] [CapSubscribe(ProtocolConst.SubscriberWorkerOneMinuteIssuedEventName)] public async Task ScheduledMeterFiveMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) { @@ -85,6 +91,8 @@ namespace JiShe.CollectBus.Subscribers /// /// /// + [HttpPost] + [Route("fifteenminute/issued-event")] [CapSubscribe(ProtocolConst.SubscriberWorkerOneMinuteIssuedEventName)] public async Task ScheduledMeterFifteenMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) { diff --git a/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs b/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs index 96661d8..ae37f9d 100644 --- a/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs @@ -12,9 +12,15 @@ namespace JiShe.CollectBus.Common.Consts /// 缓存基础目录 /// public const string CacheBasicDirectoryKey = "CollectBus:"; + /// /// 缓存电表信息 /// - public const string CacheAmmeterInfoKey = $"{CacheBasicDirectoryKey}AmmeterInfo:"; + public const string CacheAmmeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:AmmeterInfo:"; + + /// + /// 缓存水表信息 + /// + public const string CacheWatermeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:WatermeterInfo:"; } } diff --git a/src/JiShe.CollectBus.Common/Consts/SystemTypeConst.cs b/src/JiShe.CollectBus.Common/Consts/SystemTypeConst.cs new file mode 100644 index 0000000..fd37fd9 --- /dev/null +++ b/src/JiShe.CollectBus.Common/Consts/SystemTypeConst.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Consts +{ + /// + /// 系统类型常量 + /// + public class SystemTypeConst + { + /// + /// 预付费系统 + /// + public const string Prepay = "Prepay"; + + /// + /// 能耗系统 + /// + public const string Energy = "Energy"; + } +} diff --git a/src/JiShe.CollectBus.Common/Enums/MeterTypeEnum.cs b/src/JiShe.CollectBus.Common/Enums/MeterTypeEnum.cs new file mode 100644 index 0000000..43cfaae --- /dev/null +++ b/src/JiShe.CollectBus.Common/Enums/MeterTypeEnum.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Enums +{ + /// + /// 表计类型 + /// 电表= 1,水表= 2,燃气表= 3,热能表= 4,水表流量计=5,燃气表流量计=6 + /// + public enum MeterTypeEnum + { + /// + /// 电表 + /// + Ammeter = 1, + /// + /// 水表 + /// + WaterMeter = 2, + /// + /// 燃气表 + /// + Gasmeter = 3, + /// + /// 热能表 + /// + HeatMeter = 4, + /// + /// 水表流量计 + /// + WaterMeterFlowmeter = 5, + /// + /// 燃气表流量计 + /// + GasmeterFlowmeter = 6, + /// + /// 特殊电表 + /// + SpecialAmmeter = 7, + /// + /// 传感器 + /// + Sensor = 8, + + /// + /// 采集器 + /// + Collector = 9, + } +} diff --git a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs index aabbd32..7dce8ac 100644 --- a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs +++ b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs @@ -12,6 +12,7 @@ namespace JiShe.CollectBus.Ammeters /// 电表ID /// public int ID { get; set; } + /// /// 电表名称 /// @@ -22,6 +23,11 @@ namespace JiShe.CollectBus.Ammeters /// public int FocusID { get; set; } + /// + /// 集中器地址 + /// + public string FocusAddress { get; set; } + /// /// 集中器地址 /// @@ -129,16 +135,12 @@ namespace JiShe.CollectBus.Ammeters /// 项目ID /// public int ProjectID { get; set; } + /// /// 是否异常集中器 0:正常,1异常 /// public int AbnormalState { get; set; } - public DateTime LastTime { get; set; } - - /// - /// 集中器地址 - /// - public string FocusAddress { get; set; } + public DateTime LastTime { get; set; } } } diff --git a/src/JiShe.CollectBus.Application.Contracts/Workers/DTO/Energy/EnergyAmmeterInfoDto.cs b/src/JiShe.CollectBus.Domain/Watermeter/WatermeterInfo.cs similarity index 59% rename from src/JiShe.CollectBus.Application.Contracts/Workers/DTO/Energy/EnergyAmmeterInfoDto.cs rename to src/JiShe.CollectBus.Domain/Watermeter/WatermeterInfo.cs index 7836840..dd7e125 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Workers/DTO/Energy/EnergyAmmeterInfoDto.cs +++ b/src/JiShe.CollectBus.Domain/Watermeter/WatermeterInfo.cs @@ -1,67 +1,46 @@ -using System; +using JiShe.CollectBus.Common.Enums; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.Workers.DTO.Energy +namespace JiShe.CollectBus.Watermeter { /// - /// 能耗电表信息数据模型 + /// 水表信息 /// - public class EnergyAmmeterInfoDto + public class WatermeterInfo { /// - /// 电表ID + /// 水表ID /// public int ID { get; set; } + /// - /// 电表名称 + /// 水表名称 /// public string Name { get; set; } + /// + /// 表密码 + /// + public string Password { get; set; } /// /// 集中器ID /// public int FocusID { get; set; } - + /// /// 集中器地址 /// - public string Address { get; set; } - - /// - /// 集中器区域代码 - /// - public string AreaCode { get; set; } - - /// - /// 电表类别 (1单相、2三相三线、3三相四线), - /// 07协议: 开合闸指令(1A开闸断电,1C单相表合闸,1B多相表合闸) 645 2007 表 - /// 97协议://true(合闸);false(跳闸) 545 1997 没有单相多相 之分 "true" ? "9966" : "3355" - /// - public int TypeName { get; set; } - /// - /// 跳合闸状态字段: 0 合闸,1 跳闸 - /// 电表:TripState (0 合闸-通电, 1 断开、跳闸); - /// - public int TripState { get; set; } - - /// - /// 规约 -电表default(30) 1:97协议,30:07协议 - /// - public int? Protocol { get; set; } + public string FocusAddress { get; set; } /// /// 一个集中器下的[MeteringCode]必须唯一。 PN /// public int MeteringCode { get; set; } - /// - /// 电表通信地址 - /// - public string AmmerterAddress { get; set; } - /// /// 波特率 default(2400) /// @@ -73,74 +52,107 @@ namespace JiShe.CollectBus.Workers.DTO.Energy public int MeteringPort { get; set; } /// - /// 电表密码 + /// 水表通信地址 (当protocol=32时,Address为14位字符;否则12位字符) /// - public string Password { get; set; } + public string MeterAddress { get; set; } /// - /// 采集时间间隔(分钟,如15) + /// 水表类别 (水表类型改成“公称口径”) /// - public int TimeDensity { get; set; } + public string TypeName { get; set; } /// - /// 该电表方案下采集项,如:0D_80 + /// 规约 -电表default(30) 1:97协议,30:07协议,32:CJ/T 188—2018协议 /// - public string ItemCodes { get; set; } + public int? Protocol { get; set; } + + public string Code { get; set; } + /// + /// 通讯方案: + /// NB-IOT常德水表、NB-IOT泽联电表、GPRS华立水表、 + /// RS-485、无线、载波 + /// + public string LinkType { get; set; } + + /// + /// HaveValve: 是否带阀 (0 不带阀, 1 带阀) + /// 注意:NULL表示未设置 + /// + public int? HaveValve { get; set; } + + /// + /// 设备类型: 水表\气表、流量计 + /// + public string MeterTypeName { get; set; } + + /// + /// 表计类型 + //// 电表= 1,水表= 2,燃气表= 3,热能表= 4,水表流量计=5,燃气表流量计=6,特殊电表=7 + /// + public MeterTypeEnum MeterType { get; set; } + /// + /// 设备品牌; + /// (当 MeterType = 水表, 如 威铭、捷先 等) + /// (当 MeterType = 流量计, 如 西恩超声波流量计、西恩电磁流量计、涡街流量计 等) + /// + public string MeterBrand { get; set; } + + /// + /// 倍率 + /// + public decimal TimesRate { get; set; } + + /// + /// 集中器地址 + /// + public string Address { get; set; } + + /// + /// 网关地址 + /// + public string GateAddress { get; set; } + /// + /// 集中器区域 + /// + public string AreaCode { get; set; } + /// + /// 通讯状态 + /// 水表:TripState (0 合闸-开阀, 1 关阀);开阀关阀 + /// + public int TripState { get; set; } + /// + /// 是否自动采集 + /// + public int AutomaticReport { get; set; } /// /// State表状态: /// 0新装(未下发),1运行(档案下发成功时设置状态值1), 2暂停, 100销表(销表后是否重新启用) /// 特定:State -1 已删除 /// public int State { get; set; } - /// - /// 是否自动采集(0:主动采集,1:自动采集) + /// 采集时间间隔(分钟,如15) /// - public int AutomaticReport { get; set; } - - /// - /// 该电表方案下采集项编号 - /// - public string DataTypes { get; set; } - - /// - /// 品牌型号 - /// - public string BrandType { get; set; } - + public int TimeDensity { get; set; } /// /// 采集器编号 /// public string GatherCode { get; set; } - - /// - /// 是否特殊表 - /// - public int Special { get; set; } - - /// - /// 费率类型,单、多 (SingleRate :单费率(单相表1),多费率(其他0) ,与TypeName字段无关) - /// SingleRate ? "单" : "复" - /// [SingleRate] --0 复费率 false , 1 单费率 true (与PayPlanID保持一致) - ///对应 TB_PayPlan.Type: 1复费率,2单费率 - /// - public bool SingleRate { get; set; } - + /// /// 项目ID /// public int ProjectID { get; set; } + /// /// 是否异常集中器 0:正常,1异常 /// public int AbnormalState { get; set; } - public DateTime LastTime { get; set; } - /// - /// 集中器地址 + /// 集中器最后在线时间 /// - public string FocusAddress { get; set; } + public DateTime LastTime { get; set; } } } -- 2.47.2 From 171a39eabb6082a469d881956b792cb1d9fd2047 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Wed, 12 Mar 2025 16:29:38 +0800 Subject: [PATCH 004/139] =?UTF-8?q?=E5=AE=8C=E5=96=84Device=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs | 6 +++--- .../Workers/BasicScheduledMeterReadingService.cs | 2 ++ .../Workers/EnergySystemScheduledMeterReadingService.cs | 9 ++++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index 1c5a6b6..b4b6079 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -129,7 +129,7 @@ namespace JiShe.CollectBus.Plugins { var messageReceivedLoginEvent = new MessageReceivedLogin { - ClientId = client.Id, + ClientId = oldClinetId, ClientIp = client.IP, ClientPort = client.Port, MessageHexString = messageHexString, @@ -140,11 +140,11 @@ namespace JiShe.CollectBus.Plugins var entity = await _deviceRepository.FindAsync(a => a.Number == deviceNo); if (entity == null) { - await _deviceRepository.InsertAsync(new Device(deviceNo, client.Id, DateTime.Now, DateTime.Now, DeviceStatus.Online)); + await _deviceRepository.InsertAsync(new Device(deviceNo, oldClinetId, DateTime.Now, DateTime.Now, DeviceStatus.Online)); } else { - entity.UpdateByLoginAndHeartbeat(client.Id); + entity.UpdateByLoginAndHeartbeat(oldClinetId); await _deviceRepository.UpdateAsync(entity); } } diff --git a/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs index 46dc901..51b6652 100644 --- a/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Volo.Abp.Application.Services; +using static FreeSql.Internal.GlobalFilter; namespace JiShe.CollectBus.Workers { @@ -113,6 +114,7 @@ namespace JiShe.CollectBus.Workers /// public virtual Task ScheduledMeterOneMinuteReading() { + throw new NotImplementedException($"{nameof(ScheduledMeterOneMinuteReading)}请根据不同系统类型进行实现"); } diff --git a/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs index 92a9861..1db9e4a 100644 --- a/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs @@ -1,6 +1,7 @@ using FreeRedis; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Devices; using JiShe.CollectBus.FreeRedisProvider; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.Watermeter; @@ -12,6 +13,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; namespace JiShe.CollectBus.Workers { @@ -22,6 +24,12 @@ namespace JiShe.CollectBus.Workers //[Route($"/energy/app/scheduled")] public class EnergySystemScheduledMeterReadingService : BasicScheduledMeterReadingService { + private readonly IRepository _deviceRepository; + public EnergySystemScheduledMeterReadingService(IRepository deviceRepository) + { + this._deviceRepository = deviceRepository; + } + public sealed override string SystemType => SystemTypeConst.Energy; /// @@ -98,6 +106,5 @@ namespace JiShe.CollectBus.Workers .Ado .QueryAsync(sql); } - } } \ No newline at end of file -- 2.47.2 From 9a5cb565e95a44db38f68208d3c7097bf62b50b3 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Wed, 12 Mar 2025 17:10:00 +0800 Subject: [PATCH 005/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Plugins/TcpMonitor.cs | 14 +++++--------- .../CollectBusHostModule.Configure.cs | 4 ++-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index b4b6079..d5c95c6 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -55,15 +55,12 @@ namespace JiShe.CollectBus.Plugins var aTuple = (Tuple)hexStringList.GetAnalyzeValue(CommandChunkEnum.A); if (aFn.HasValue && fn.HasValue && aTuple != null && !string.IsNullOrWhiteSpace(aTuple.Item1)) { - string oldClinetId = client.Id; - await client.ResetIdAsync(aTuple.Item1); - if ((AFN)aFn == AFN.链路接口检测) { switch (fn) { case 1: - await OnTcpLoginReceived(client, messageHexString, aTuple.Item1, oldClinetId); + await OnTcpLoginReceived(client, messageHexString, aTuple.Item1); break; case 3: await OnTcpHeartbeatReceived(client, messageHexString, aTuple.Item1); @@ -123,13 +120,12 @@ namespace JiShe.CollectBus.Plugins /// /// /// 集中器编号 - /// TCP首次连接时的Id /// - private async Task OnTcpLoginReceived(ITcpSessionClient client, string messageHexString, string deviceNo,string oldClinetId) + private async Task OnTcpLoginReceived(ITcpSessionClient client, string messageHexString, string deviceNo) { var messageReceivedLoginEvent = new MessageReceivedLogin { - ClientId = oldClinetId, + ClientId = client.Id, ClientIp = client.IP, ClientPort = client.Port, MessageHexString = messageHexString, @@ -140,11 +136,11 @@ namespace JiShe.CollectBus.Plugins var entity = await _deviceRepository.FindAsync(a => a.Number == deviceNo); if (entity == null) { - await _deviceRepository.InsertAsync(new Device(deviceNo, oldClinetId, DateTime.Now, DateTime.Now, DeviceStatus.Online)); + await _deviceRepository.InsertAsync(new Device(deviceNo, client.Id,DateTime.Now, DateTime.Now, DeviceStatus.Online)); } else { - entity.UpdateByLoginAndHeartbeat(oldClinetId); + entity.UpdateByLoginAndHeartbeat(client.Id); await _deviceRepository.UpdateAsync(entity); } } diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 699ba43..3a0390a 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -222,9 +222,9 @@ namespace JiShe.CollectBus.Host { context.Services.AddTcpService(config => { - config.SetListenIPHosts(int.Parse(configuration["TCP:ClientPort"] ?? "32580")) + config.SetListenIPHosts(int.Parse(configuration["TCP:ClientPort"] ?? "10500")) //.SetTcpDataHandlingAdapter(()=>new StandardFixedHeaderDataHandlingAdapter()) - .SetGetDefaultNewId(() => Guid.NewGuid().ToString())//定义ClinetId的生成策略 + //.SetGetDefaultNewId(() => Guid.NewGuid().ToString())//定义ClinetId的生成策略 .ConfigurePlugins(a => { a.Add(); -- 2.47.2 From bb0099bc9833226426a72dde97c85977b57fb394 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 13 Mar 2025 10:51:16 +0800 Subject: [PATCH 006/139] =?UTF-8?q?1=E5=88=86=E9=92=9F=E9=87=87=E9=9B=86?= =?UTF-8?q?=E7=94=B5=E8=A1=A8=E6=95=B0=E6=8D=AE=E6=95=B0=E6=8D=AE=E8=8E=B7?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Workers/IScheduledMeterReadingService.cs | 41 ++++- .../Workers/IWorkerSubscriberAppService.cs | 29 ++- .../BasicScheduledMeterReadingService.cs | 173 ++++++++++++++---- ...nergySystemScheduledMeterReadingService.cs | 6 +- .../Workers/WorkerSubscriberAppService.cs | 132 +++++++++++-- .../Consts/FreeRedisConst.cs | 19 +- .../CollectBusHostModule.Configure.cs | 2 +- .../ProtocolConst.cs | 19 +- 8 files changed, 355 insertions(+), 66 deletions(-) diff --git a/src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs index 7591f3e..bd1a090 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs @@ -15,13 +15,15 @@ namespace JiShe.CollectBus.Workers /// public interface IScheduledMeterReadingService : IApplicationService { + + #region 电表采集处理 /// /// 获取电表信息 /// /// 采集端Code /// Task> GetAmmeterInfoList(string gatherCode = ""); - + /// /// 初始化电表缓存数据 /// @@ -29,6 +31,28 @@ namespace JiShe.CollectBus.Workers /// Task InitAmmeterCacheData(string gatherCode = ""); + /// + /// 1分钟采集电表数据 + /// + /// + Task AmmeterScheduledMeterOneMinuteReading(); + + /// + /// 5分钟采集电表数据 + /// + /// + Task AmmeterScheduledMeterFiveMinuteReading(); + + /// + /// 15分钟采集电表数据 + /// + /// + Task AmmeterScheduledMeterFifteenMinuteReading(); + + #endregion + + + #region 水表采集处理 /// /// 获取水表信息 /// @@ -44,21 +68,24 @@ namespace JiShe.CollectBus.Workers Task InitWatermeterCacheData(string gatherCode = ""); /// - /// 1分钟采集电表数据 + /// 1分钟采集水表数据 /// /// - Task ScheduledMeterOneMinuteReading(); + Task WatermeterScheduledMeterOneMinuteReading(); /// - /// 5分钟采集电表数据 + /// 5分钟采集水表数据 /// /// - Task ScheduledMeterFiveMinuteReading(); + Task WatermeterScheduledMeterFiveMinuteReading(); /// - /// 15分钟采集电表数据 + /// 15分钟采集水表数据 /// /// - Task ScheduledMeterFifteenMinuteReading(); + Task WatermeterScheduledMeterFifteenMinuteReading(); + #endregion + + } } diff --git a/src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerSubscriberAppService.cs index c7442de..dede8c4 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerSubscriberAppService.cs @@ -10,22 +10,45 @@ namespace JiShe.CollectBus.Subscribers /// public interface IWorkerSubscriberAppService : IApplicationService { + + #region 电表消息采集 /// /// 1分钟采集电表数据下行消息消费订阅 /// /// - Task ScheduledMeterOneMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); /// /// 5分钟采集电表数据下行消息消费订阅 /// /// - Task ScheduledMeterFiveMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); /// /// 15分钟采集电表数据下行消息消费订阅 /// /// - Task ScheduledMeterFifteenMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + #endregion + + #region 水表消息采集 + /// + /// 1分钟采集水表数据下行消息消费订阅 + /// + /// + Task WatermeterScheduledMeterOneMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + + /// + /// 5分钟采集水表数据下行消息消费订阅 + /// + /// + Task WatermeterScheduledMeterFiveMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + + /// + /// 15分钟采集水表数据下行消息消费订阅 + /// + /// + Task WatermeterScheduledMeterFifteenMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + #endregion } } diff --git a/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs index 51b6652..e5494a1 100644 --- a/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs @@ -2,6 +2,7 @@ using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Watermeter; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -17,11 +18,19 @@ namespace JiShe.CollectBus.Workers /// public abstract class BasicScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService { + private readonly ILogger _logger; + public BasicScheduledMeterReadingService(ILogger logger) + { + _logger = logger; + } + /// /// 系统类型 /// public abstract string SystemType { get; } + #region 电表采集处理 + /// /// 获取电表信息 /// @@ -45,32 +54,115 @@ namespace JiShe.CollectBus.Workers throw new NullReferenceException($"{nameof(InitWatermeterCacheData)} 初始化电表缓存数据时,电表数据为空"); } - //将表计信息根据集中器分组 - var meterInfoGroup = meterInfos.GroupBy(x => x.FocusAddress).ToList(); - foreach (var item in meterInfoGroup) + //根据采集频率分组 + var meterInfoGroupByTimeDensity = meterInfos.GroupBy(d => d.TimeDensity); + foreach (var itemTimeDensity in meterInfoGroupByTimeDensity) { - if (string.IsNullOrWhiteSpace(item.Key)) + //将表计信息根据集中器分组 + var meterInfoGroup = itemTimeDensity.GroupBy(x => x.FocusAddress).ToList(); + foreach (var item in meterInfoGroup) { - continue; - } + if (string.IsNullOrWhiteSpace(item.Key)) + { + continue; + } - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy)}{item.Key}"; - Dictionary keyValuePairs = new Dictionary(); - foreach (var subItem in item) - { + var redisCacheKey = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, itemTimeDensity.Key)}{item.Key}"; + Dictionary keyValuePairs = new Dictionary(); + foreach (var subItem in item) + { - keyValuePairs.TryAdd($"{subItem.ID}", subItem); + keyValuePairs.TryAdd($"{subItem.ID}", subItem); + } + await FreeRedisProvider.FreeRedis.HSetAsync(redisCacheKey, keyValuePairs); } - await FreeRedisProvider.FreeRedis.HSetAsync(redisCacheKey, keyValuePairs); } + + _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据完成"); } + /// + /// 1分钟采集电表数据 + /// + /// + public virtual async Task AmmeterScheduledMeterOneMinuteReading() + { + //获取缓存中的电表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 1)}*"; + var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <=0) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); + return; + } + + //通过lua脚本一次性获取所有缓存内容 + var luaScript = @" + local results = {} + for i, key in ipairs(KEYS) do + local data = redis.call('HGETALL', key) + results[i] = {key, data} + end + return results"; + var oneMinuteAmmerterResult = FreeRedisProvider.FreeRedis.Eval(luaScript, oneMinutekeyList); // 传递 KEYS + if (oneMinuteAmmerterResult == null) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); + return; + } + + // 解析结果(结果为嵌套数组) + var parsedResults = new Dictionary>(); + if (oneMinuteAmmerterResult is object[] arr) + { + foreach (object[] item in arr) + { + string key = (string)item[0]; + object[] fieldsAndValues = (object[])item[1]; + + var dict = new Dictionary(); + for (int i = 0; i < fieldsAndValues.Length; i += 2) + { + string field = (string)fieldsAndValues[i]; + string value = (string)fieldsAndValues[i + 1]; + dict[field] = value; + } + parsedResults[key] = dict; + } + } + + _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理完成"); + + } + + /// + /// 5分钟采集电表数据 + /// + /// + public virtual Task AmmeterScheduledMeterFiveMinuteReading() + { + throw new NotImplementedException($"{nameof(AmmeterScheduledMeterFiveMinuteReading)}请根据不同系统类型进行实现"); + } + + /// + /// 15分钟采集电表数据 + /// + /// + public virtual Task AmmeterScheduledMeterFifteenMinuteReading() + { + throw new NotImplementedException($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)}请根据不同系统类型进行实现"); + } + #endregion + + + #region 水表采集处理 + /// /// 获取水表信息 /// /// 采集端Code /// - public virtual async Task> GetWatermeterInfoList(string gatherCode = "") + public virtual Task> GetWatermeterInfoList(string gatherCode = "") { throw new NotImplementedException($"{nameof(GetWatermeterInfoList)}请根据不同系统类型进行实现"); } @@ -85,55 +177,66 @@ namespace JiShe.CollectBus.Workers var meterInfos = await GetWatermeterInfoList(gatherCode); if (meterInfos == null || meterInfos.Count <= 0) { - throw new NullReferenceException($"{nameof(InitWatermeterCacheData)} 初始化水表缓存数据时,水表数据为空"); + throw new NullReferenceException($"{nameof(InitWatermeterCacheData)} 初始化水表缓存数据时,水表数据为空"); } - - //将表计信息根据集中器分组 - var meterInfoGroup = meterInfos.GroupBy(x => x.FocusAddress).ToList(); - foreach (var item in meterInfoGroup) + //根据采集频率分组 + var meterInfoGroupByTimeDensity = meterInfos.GroupBy(d => d.TimeDensity); + foreach (var itemTimeDensity in meterInfoGroupByTimeDensity) { - if (string.IsNullOrWhiteSpace(item.Key)) + //将表计信息根据集中器分组 + var meterInfoGroup = itemTimeDensity.GroupBy(x => x.FocusAddress).ToList(); + foreach (var item in meterInfoGroup) { - continue; - } + if (string.IsNullOrWhiteSpace(item.Key)) + { + continue; + } - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy)}{item.Key}"; - Dictionary keyValuePairs = new Dictionary(); - foreach (var subItem in item) - { + var redisCacheKey = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, itemTimeDensity.Key)}{item.Key}"; + Dictionary keyValuePairs = new Dictionary(); + foreach (var subItem in item) + { - keyValuePairs.TryAdd($"{subItem.ID}", subItem); + keyValuePairs.TryAdd($"{subItem.ID}", subItem); + } + await FreeRedisProvider.FreeRedis.HSetAsync(redisCacheKey, keyValuePairs); } - await FreeRedisProvider.FreeRedis.HSetAsync(redisCacheKey, keyValuePairs); } + _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化水表缓存数据完成"); } /// - /// 1分钟采集电表数据 + /// 1分钟采集水表数据 /// /// - public virtual Task ScheduledMeterOneMinuteReading() + public virtual async Task WatermeterScheduledMeterOneMinuteReading() { - - throw new NotImplementedException($"{nameof(ScheduledMeterOneMinuteReading)}请根据不同系统类型进行实现"); + //获取缓存中的电表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 1)}*"; + var oneMinuteList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + + _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表数据处理完成"); } /// /// 5分钟采集电表数据 /// /// - public virtual Task ScheduledMeterFiveMinuteReading() + public virtual Task WatermeterScheduledMeterFiveMinuteReading() { - throw new NotImplementedException($"{nameof(ScheduledMeterFiveMinuteReading)}请根据不同系统类型进行实现"); + + throw new NotImplementedException($"{nameof(WatermeterScheduledMeterFiveMinuteReading)}请根据不同系统类型进行实现"); } /// /// 15分钟采集电表数据 /// /// - public virtual Task ScheduledMeterFifteenMinuteReading() + public virtual Task WatermeterScheduledMeterFifteenMinuteReading() { - throw new NotImplementedException($"{nameof(ScheduledMeterFifteenMinuteReading)}请根据不同系统类型进行实现"); + throw new NotImplementedException($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)}请根据不同系统类型进行实现"); } + #endregion + } } diff --git a/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs index 1db9e4a..a5d5940 100644 --- a/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs @@ -7,6 +7,7 @@ using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.Watermeter; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -25,9 +26,12 @@ namespace JiShe.CollectBus.Workers public class EnergySystemScheduledMeterReadingService : BasicScheduledMeterReadingService { private readonly IRepository _deviceRepository; - public EnergySystemScheduledMeterReadingService(IRepository deviceRepository) + private readonly ILogger _logger; + + public EnergySystemScheduledMeterReadingService(IRepository deviceRepository, ILogger logger):base(logger) { this._deviceRepository = deviceRepository; + this._logger = logger; } public sealed override string SystemType => SystemTypeConst.Energy; diff --git a/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs index 6c7bfab..552f964 100644 --- a/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs @@ -26,6 +26,7 @@ namespace JiShe.CollectBus.Subscribers private readonly ILogger _logger; private readonly ITcpService _tcpService; private readonly IServiceProvider _serviceProvider; + private readonly IRepository _deviceRepository; /// @@ -35,22 +36,27 @@ namespace JiShe.CollectBus.Subscribers /// The TCP service. /// The service provider. public WorkerSubscriberAppService(ILogger logger, - ITcpService tcpService, IServiceProvider serviceProvider) + ITcpService tcpService, + IRepository deviceRepository, + IServiceProvider serviceProvider) { _logger = logger; _tcpService = tcpService; - _serviceProvider = serviceProvider; + _serviceProvider = serviceProvider; + _deviceRepository = deviceRepository; } + + #region 电表消息采集 /// /// 一分钟定时抄读任务消息消费订阅 /// /// /// [HttpPost] - [Route("oneminute/issued-event")] - [CapSubscribe(ProtocolConst.SubscriberWorkerOneMinuteIssuedEventName)] - public async Task ScheduledMeterOneMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + [Route("ammeter/oneminute/issued-event")] + [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] + public async Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) { _logger.LogInformation("1分钟采集电表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); @@ -60,7 +66,12 @@ namespace JiShe.CollectBus.Subscribers } else { - await _tcpService.SendAsync(receivedMessage.ClientId, receivedMessage.Message); + var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); + if (device != null) + { + await _tcpService.SendAsync(device.ClientId, receivedMessage.Message); + + } } } @@ -70,9 +81,9 @@ namespace JiShe.CollectBus.Subscribers /// /// [HttpPost] - [Route("fiveminute/issued-event")] - [CapSubscribe(ProtocolConst.SubscriberWorkerOneMinuteIssuedEventName)] - public async Task ScheduledMeterFiveMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + [Route("ammeter/fiveminute/issued-event")] + [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] + public async Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) { _logger.LogInformation("5分钟采集电表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); @@ -82,7 +93,12 @@ namespace JiShe.CollectBus.Subscribers } else { - await _tcpService.SendAsync(receivedMessage.ClientId, receivedMessage.Message); + var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); + if (device != null) + { + await _tcpService.SendAsync(device.ClientId, receivedMessage.Message); + + } } } @@ -92,9 +108,9 @@ namespace JiShe.CollectBus.Subscribers /// /// [HttpPost] - [Route("fifteenminute/issued-event")] - [CapSubscribe(ProtocolConst.SubscriberWorkerOneMinuteIssuedEventName)] - public async Task ScheduledMeterFifteenMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + [Route("ammeter/fifteenminute/issued-event")] + [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] + public async Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) { _logger.LogInformation("15分钟采集电表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); @@ -104,8 +120,96 @@ namespace JiShe.CollectBus.Subscribers } else { - await _tcpService.SendAsync(receivedMessage.ClientId, receivedMessage.Message); + var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); + if (device != null) + { + await _tcpService.SendAsync(device.ClientId, receivedMessage.Message); + + } } } + #endregion + + #region 水表消息采集 + /// + /// 一分钟定时抄读任务消息消费订阅 + /// + /// + /// + [HttpPost] + [Route("watermeter/oneminute/issued-event")] + [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerOneMinuteIssuedEventName)] + public async Task WatermeterScheduledMeterOneMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + { + _logger.LogInformation("1分钟采集电表数据下行消息消费队列开始处理"); + var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); + if (protocolPlugin == null) + { + _logger.LogError("【1分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); + } + else + { + var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); + if (device != null) + { + await _tcpService.SendAsync(device.ClientId, receivedMessage.Message); + + } + } + } + + /// + /// 5分钟采集电表数据下行消息消费订阅 + /// + /// + /// + [HttpPost] + [Route("watermeter/fiveminute/issued-event")] + [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerOneMinuteIssuedEventName)] + public async Task WatermeterScheduledMeterFiveMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + { + _logger.LogInformation("5分钟采集电表数据下行消息消费队列开始处理"); + var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); + if (protocolPlugin == null) + { + _logger.LogError("【5分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); + } + else + { + var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); + if (device != null) + { + await _tcpService.SendAsync(device.ClientId, receivedMessage.Message); + + } + } + } + + /// + /// 15分钟采集电表数据下行消息消费订阅 + /// + /// + /// + [HttpPost] + [Route("watermeter/fifteenminute/issued-event")] + [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerOneMinuteIssuedEventName)] + public async Task WatermeterScheduledMeterFifteenMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + { + _logger.LogInformation("15分钟采集电表数据下行消息消费队列开始处理"); + var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); + if (protocolPlugin == null) + { + _logger.LogError("【15分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); + } + else + { + var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); + if (device != null) + { + await _tcpService.SendAsync(device.ClientId, receivedMessage.Message); + } + } + } + #endregion } } diff --git a/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs b/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs index ae37f9d..859e86e 100644 --- a/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs @@ -13,14 +13,29 @@ namespace JiShe.CollectBus.Common.Consts /// public const string CacheBasicDirectoryKey = "CollectBus:"; + /// + /// 1分钟采集间隔 + /// + public const string OneMinuteAcquisitionTimeInterval = $"one"; + + /// + /// 5分钟采集间隔 + /// + public const string FiveMinuteAcquisitionTimeInterval = $"Five"; + + /// + /// 15分钟采集间隔 + /// + public const string FifteenMinuteAcquisitionTimeInterval = $"Fifteen"; + /// /// 缓存电表信息 /// - public const string CacheAmmeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:AmmeterInfo:"; + public const string CacheAmmeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:{"{1}"}:AmmeterInfo:"; /// /// 缓存水表信息 /// - public const string CacheWatermeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:WatermeterInfo:"; + public const string CacheWatermeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:{"{1}"}:WatermeterInfo:"; } } diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 699ba43..afca265 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -222,7 +222,7 @@ namespace JiShe.CollectBus.Host { context.Services.AddTcpService(config => { - config.SetListenIPHosts(int.Parse(configuration["TCP:ClientPort"] ?? "32580")) + config.SetListenIPHosts(int.Parse(configuration["TCP:ClientPort"] ?? "10500")) //.SetTcpDataHandlingAdapter(()=>new StandardFixedHeaderDataHandlingAdapter()) .SetGetDefaultNewId(() => Guid.NewGuid().ToString())//定义ClinetId的生成策略 .ConfigurePlugins(a => diff --git a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs index 431fc4c..ee2cddf 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs @@ -17,15 +17,28 @@ namespace JiShe.CollectBus.Protocol.Contracts /// /// 1分钟采集电表数据下行消息主题 /// - public const string SubscriberWorkerOneMinuteIssuedEventName = "issued.oneminute.event"; + public const string AmmeterSubscriberWorkerOneMinuteIssuedEventName = "issued.one.ammeter.event"; /// /// 5分钟采集电表数据下行消息主题 /// - public const string SubscriberWorkerFiveMinuteIssuedEventName = "issued.fiveminute.event"; + public const string AmmeterSubscriberWorkerFiveMinuteIssuedEventName = "issued.five.ammeter.event"; /// /// 15分钟采集电表数据下行消息主题 /// - public const string SubscriberWorkerFifteenMinuteIssuedEventName = "issued.fifteenminute.event"; + public const string AmmeterSubscriberWorkerFifteenMinuteIssuedEventName = "issued.fifteen.ammeter.event"; + + /// + /// 1分钟采集水表数据下行消息主题 + /// + public const string WatermeterSubscriberWorkerOneMinuteIssuedEventName = "issued.one.watermeter.event"; + /// + /// 5分钟采集水表数据下行消息主题 + /// + public const string WatermeterSubscriberWorkerFiveMinuteIssuedEventName = "issued.five.watermeter.event"; + /// + /// 15分钟采集水表数据下行消息主题 + /// + public const string WatermeterSubscriberWorkerFifteenMinuteIssuedEventName = "issued.fifteen.watermeter.event"; } } -- 2.47.2 From 0b0eaa6ad0b2d96d0253e78f952a1bf874b1107b Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Thu, 13 Mar 2025 10:58:49 +0800 Subject: [PATCH 007/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 12 ++++++------ .../JiShe.CollectBus.Application.csproj | 2 +- .../FreeRedisProviderModule.cs | 3 ++- .../FreeRedisProviderService.cs | 7 ++++--- .../IFreeRedisProviderService.cs | 0 .../JiShe.CollectBus.FreeRedisProvider.csproj | 0 .../Options/FreeRedisOptions.cs | 2 +- 7 files changed, 14 insertions(+), 12 deletions(-) rename {JiShe.CollectBus.FreeRedisProvider => src/JiShe.CollectBus.FreeRedisProvider}/FreeRedisProviderModule.cs (85%) rename {JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider => src/JiShe.CollectBus.FreeRedisProvider}/FreeRedisProviderService.cs (84%) rename {JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider => src/JiShe.CollectBus.FreeRedisProvider}/IFreeRedisProviderService.cs (100%) rename {JiShe.CollectBus.FreeRedisProvider => src/JiShe.CollectBus.FreeRedisProvider}/JiShe.CollectBus.FreeRedisProvider.csproj (100%) rename {JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider => src/JiShe.CollectBus.FreeRedisProvider}/Options/FreeRedisOptions.cs (89%) diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index 822d02b..674eca4 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -29,7 +29,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.DbMigrator EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.FreeSql", "src\JiShe.CollectBus.FreeSql\JiShe.CollectBus.FreeSql.csproj", "{FE0457D9-4038-4A17-8808-DCAD06CFC0A0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.FreeRedisProvider", "JiShe.CollectBus.FreeRedisProvider\JiShe.CollectBus.FreeRedisProvider.csproj", "{920445D9-18D2-4886-9053-6A4CC3B4F3E2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.FreeRedisProvider", "src\JiShe.CollectBus.FreeRedisProvider\JiShe.CollectBus.FreeRedisProvider.csproj", "{C06C4082-638F-2996-5FED-7784475766C1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -85,10 +85,10 @@ Global {FE0457D9-4038-4A17-8808-DCAD06CFC0A0}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE0457D9-4038-4A17-8808-DCAD06CFC0A0}.Release|Any CPU.ActiveCfg = Release|Any CPU {FE0457D9-4038-4A17-8808-DCAD06CFC0A0}.Release|Any CPU.Build.0 = Release|Any CPU - {920445D9-18D2-4886-9053-6A4CC3B4F3E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {920445D9-18D2-4886-9053-6A4CC3B4F3E2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {920445D9-18D2-4886-9053-6A4CC3B4F3E2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {920445D9-18D2-4886-9053-6A4CC3B4F3E2}.Release|Any CPU.Build.0 = Release|Any CPU + {C06C4082-638F-2996-5FED-7784475766C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C06C4082-638F-2996-5FED-7784475766C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C06C4082-638F-2996-5FED-7784475766C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C06C4082-638F-2996-5FED-7784475766C1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -106,7 +106,7 @@ Global {38C1808B-009A-418B-B17B-AB3626341B5D} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {8BA01C3D-297D-42DF-BD63-EF07202A0A67} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {FE0457D9-4038-4A17-8808-DCAD06CFC0A0} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {920445D9-18D2-4886-9053-6A4CC3B4F3E2} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {C06C4082-638F-2996-5FED-7784475766C1} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index 7160628..0102b4f 100644 --- a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -23,11 +23,11 @@ - + diff --git a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs similarity index 85% rename from JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs rename to src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs index 2bf7349..881cab9 100644 --- a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Configuration; +using JiShe.CollectBus.FreeRedisProvider.Options; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; diff --git a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/FreeRedisProviderService.cs b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderService.cs similarity index 84% rename from JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/FreeRedisProviderService.cs rename to src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderService.cs index 79e2037..c4bf3ff 100644 --- a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/FreeRedisProviderService.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderService.cs @@ -1,5 +1,6 @@ using FreeRedis; using JetBrains.Annotations; +using JiShe.CollectBus.FreeRedisProvider.Options; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; @@ -14,14 +15,14 @@ namespace JiShe.CollectBus.FreeRedisProvider; public class FreeRedisProviderService : IFreeRedisProviderService, ISingletonDependency { - private FreeRedisOptions freeRedisOptions; + private FreeRedisOptions _freeRedisOptions; /// /// FreeRedis /// public FreeRedisProviderService(IOptions options) { - freeRedisOptions = options.Value; + _freeRedisOptions = options.Value; } [NotNull] @@ -39,7 +40,7 @@ public class FreeRedisProviderService : IFreeRedisProviderService, ISingletonDep /// public IRedisClient GetClient() { - string connectionString = $"{freeRedisOptions.Configuration},defaultdatabase={freeRedisOptions.DefaultDB}"; + string connectionString = $"{_freeRedisOptions.Configuration},defaultdatabase={_freeRedisOptions.DefaultDB}"; var redisClinet = new RedisClient(connectionString); redisClinet.Serialize = obj => JsonSerializer.Serialize(obj); redisClinet.Deserialize = (json, type) => JsonSerializer.Deserialize(json, type); diff --git a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/IFreeRedisProviderService.cs b/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProviderService.cs similarity index 100% rename from JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/IFreeRedisProviderService.cs rename to src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProviderService.cs diff --git a/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj b/src/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj similarity index 100% rename from JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj rename to src/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj diff --git a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/Options/FreeRedisOptions.cs b/src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs similarity index 89% rename from JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/Options/FreeRedisOptions.cs rename to src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs index 9eb7bb9..bddfa28 100644 --- a/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider/Options/FreeRedisOptions.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.FreeRedisProvider; +namespace JiShe.CollectBus.FreeRedisProvider.Options; public class FreeRedisOptions { -- 2.47.2 From addec01dc82437b4161710740947289d5594f770 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 13 Mar 2025 11:55:51 +0800 Subject: [PATCH 008/139] =?UTF-8?q?=E5=B0=81=E8=A3=85=E6=B0=B4=E7=94=B5?= =?UTF-8?q?=E8=A1=A8redis=E7=BC=93=E5=AD=98=E6=89=B9=E9=87=8F=E8=8E=B7?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 212 ++++++++++++++---- ...nergySystemScheduledMeterReadingService.cs | 10 +- .../Helpers/JsonHelper.cs | 126 +++++++++++ .../FreeRedisProviderModule.cs | 21 +- .../FreeRedisProviderService.cs | 88 ++++---- .../IFreeRedisProviderService.cs | 39 ++-- .../Options/FreeRedisOptions.cs | 34 +-- 7 files changed, 389 insertions(+), 141 deletions(-) create mode 100644 src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs diff --git a/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs index e5494a1..680354f 100644 --- a/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs @@ -1,6 +1,8 @@ -using FreeRedis; +using DotNetCore.CAP; +using FreeRedis; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Watermeter; using Microsoft.Extensions.Logging; using System; @@ -19,8 +21,10 @@ namespace JiShe.CollectBus.Workers public abstract class BasicScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService { private readonly ILogger _logger; - public BasicScheduledMeterReadingService(ILogger logger) + private readonly ICapPublisher _capBus; + public BasicScheduledMeterReadingService(ILogger logger, ICapPublisher capBus) { + _capBus = capBus; _logger = logger; } @@ -90,48 +94,21 @@ namespace JiShe.CollectBus.Workers //获取缓存中的电表信息 var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 1)}*"; var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <=0) + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { - _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-101"); return; } - - //通过lua脚本一次性获取所有缓存内容 - var luaScript = @" - local results = {} - for i, key in ipairs(KEYS) do - local data = redis.call('HGETALL', key) - results[i] = {key, data} - end - return results"; - var oneMinuteAmmerterResult = FreeRedisProvider.FreeRedis.Eval(luaScript, oneMinutekeyList); // 传递 KEYS - if (oneMinuteAmmerterResult == null) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); - return; - } - + // 解析结果(结果为嵌套数组) - var parsedResults = new Dictionary>(); - if (oneMinuteAmmerterResult is object[] arr) + List meterInfos = await GetMeterCacheData(oneMinutekeyList); + if (meterInfos == null || meterInfos.Count <= 0) { - foreach (object[] item in arr) - { - string key = (string)item[0]; - object[] fieldsAndValues = (object[])item[1]; - - var dict = new Dictionary(); - for (int i = 0; i < fieldsAndValues.Length; i += 2) - { - string field = (string)fieldsAndValues[i]; - string value = (string)fieldsAndValues[i + 1]; - dict[field] = value; - } - parsedResults[key] = dict; - } + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); + return; } - _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理完成"); + _logger.LogInformation($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理完成"); } @@ -139,18 +116,52 @@ namespace JiShe.CollectBus.Workers /// 5分钟采集电表数据 /// /// - public virtual Task AmmeterScheduledMeterFiveMinuteReading() + public virtual async Task AmmeterScheduledMeterFiveMinuteReading() { - throw new NotImplementedException($"{nameof(AmmeterScheduledMeterFiveMinuteReading)}请根据不同系统类型进行实现"); + //获取缓存中的电表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 5)}*"; + var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理时没有获取到缓存信息,-101"); + return; + } + + // 解析结果(结果为嵌套数组) + List meterInfos = await GetMeterCacheData(oneMinutekeyList); + if (meterInfos == null || meterInfos.Count <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理时没有获取到缓存信息,-102"); + return; + } + + _logger.LogInformation($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理完成"); } /// /// 15分钟采集电表数据 /// /// - public virtual Task AmmeterScheduledMeterFifteenMinuteReading() + public virtual async Task AmmeterScheduledMeterFifteenMinuteReading() { - throw new NotImplementedException($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)}请根据不同系统类型进行实现"); + //获取缓存中的电表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 15)}*"; + var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理时没有获取到缓存信息,-101"); + return; + } + + // 解析结果(结果为嵌套数组) + List meterInfos = await GetMeterCacheData(oneMinutekeyList); + if (meterInfos == null || meterInfos.Count <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理时没有获取到缓存信息,-102"); + return; + } + + _logger.LogInformation($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理完成"); } #endregion @@ -211,9 +222,22 @@ namespace JiShe.CollectBus.Workers /// public virtual async Task WatermeterScheduledMeterOneMinuteReading() { - //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 1)}*"; - var oneMinuteList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + //获取缓存中的水表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 1)}*"; + var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表据处理时没有获取到缓存信息,-101"); + return; + } + + // 解析结果(结果为嵌套数组) + List meterInfos = await GetMeterCacheData(oneMinutekeyList); + if (meterInfos == null || meterInfos.Count <= 0) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表数据处理时没有获取到缓存信息,-102"); + return; + } _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表数据处理完成"); } @@ -222,21 +246,113 @@ namespace JiShe.CollectBus.Workers /// 5分钟采集电表数据 /// /// - public virtual Task WatermeterScheduledMeterFiveMinuteReading() + public virtual async Task WatermeterScheduledMeterFiveMinuteReading() { - throw new NotImplementedException($"{nameof(WatermeterScheduledMeterFiveMinuteReading)}请根据不同系统类型进行实现"); + //获取缓存中的水表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 5)}*"; + var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表据处理时没有获取到缓存信息,-101"); + return; + } + + // 解析结果(结果为嵌套数组) + List meterInfos = await GetMeterCacheData(oneMinutekeyList); + if (meterInfos == null || meterInfos.Count <= 0) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表数据处理时没有获取到缓存信息,-102"); + return; + } + + _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表数据处理完成"); } /// /// 15分钟采集电表数据 /// /// - public virtual Task WatermeterScheduledMeterFifteenMinuteReading() + public virtual async Task WatermeterScheduledMeterFifteenMinuteReading() { - throw new NotImplementedException($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)}请根据不同系统类型进行实现"); + //获取缓存中的水表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 15)}*"; + var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表据处理时没有获取到缓存信息,-101"); + return; + } + + // 解析结果(结果为嵌套数组) + List meterInfos = await GetMeterCacheData(oneMinutekeyList); + if (meterInfos == null || meterInfos.Count <= 0) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表数据处理时没有获取到缓存信息,-102"); + return; + } + + _logger.LogInformation($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表数据处理完成"); } #endregion + + #region 公共处理方法 + /// + /// 批量获取缓存的表计信息 + /// + /// + /// + /// + private async Task> GetMeterCacheData(string[] redisKeys) + { + //通过lua脚本一次性获取所有缓存内容 + var luaScript = @" + local results = {} + for i, key in ipairs(KEYS) do + local data = redis.call('HGETALL', key) + results[i] = {key, data} + end + return results"; + var oneMinuteAmmerterResult = await FreeRedisProvider.FreeRedis.EvalAsync(luaScript, redisKeys); //传递 KEYS + if (oneMinuteAmmerterResult == null) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); + return null; + } + + // 解析结果(结果为嵌套数组) + List meterInfos = new List(); + if (oneMinuteAmmerterResult is object[] arr) + { + foreach (object[] item in arr) + { + string key = (string)item[0]; + object[] fieldsAndValues = (object[])item[1]; + + for (int i = 0; i < fieldsAndValues.Length; i += 2) + { + string field = (string)fieldsAndValues[i]; + string valueStr = (string)fieldsAndValues[i + 1]; + T value = default; + if (!string.IsNullOrWhiteSpace(valueStr)) + { + value = valueStr.Deserialize()!; + } + if (value != null) + { + meterInfos.Add(value); + } + else + { + _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表{key}数据{field}处理异常"); + } + } + } + } + + return meterInfos; + } + #endregion } } diff --git a/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs index a5d5940..07e0264 100644 --- a/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs @@ -1,4 +1,5 @@ -using FreeRedis; +using DotNetCore.CAP; +using FreeRedis; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Devices; @@ -25,13 +26,10 @@ namespace JiShe.CollectBus.Workers //[Route($"/energy/app/scheduled")] public class EnergySystemScheduledMeterReadingService : BasicScheduledMeterReadingService { - private readonly IRepository _deviceRepository; - private readonly ILogger _logger; - public EnergySystemScheduledMeterReadingService(IRepository deviceRepository, ILogger logger):base(logger) + public EnergySystemScheduledMeterReadingService(ILogger logger, ICapPublisher capBus) :base(logger, capBus) { - this._deviceRepository = deviceRepository; - this._logger = logger; + } public sealed override string SystemType => SystemTypeConst.Energy; diff --git a/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs b/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs new file mode 100644 index 0000000..029ae1f --- /dev/null +++ b/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Helpers +{ + /// + /// json帮助类 + /// + public static class JsonHelper + { + /// + /// json对象转换成字符串 + /// + /// 需要序列化的对象 + /// 是否忽略实体中实体,不再序列化里面包含的实体 + /// 配置 + /// + public static string Serialize(this object obj, bool IsIgnore = false, JsonSerializerOptions jsonSerializerOptions = null) + { + if (jsonSerializerOptions == null) + { + jsonSerializerOptions = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + WriteIndented = false, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + } + + if (jsonSerializerOptions.Converters != null) + { + jsonSerializerOptions.Converters.Add(new DateTimeJsonConverter()); + } + + if (IsIgnore == true) + { + jsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; // 忽略循环引用 + + return JsonSerializer.Serialize(obj, jsonSerializerOptions); + } + else + { + return JsonSerializer.Serialize(obj, jsonSerializerOptions); + } + } + + /// + /// json字符串转换成json对象 + /// + /// + /// + /// 是否忽略实体中实体,不再序列化里面包含的实体 + /// 配置 + /// + public static T? Deserialize(this string json, bool IsIgnore = false, JsonSerializerOptions jsonSerializerOptions = null) + { + if (jsonSerializerOptions == null) + { + jsonSerializerOptions = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + WriteIndented = false, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + } + + if (jsonSerializerOptions.Converters != null) + { + jsonSerializerOptions.Converters.Add(new DateTimeJsonConverter()); + } + + if (IsIgnore == true) + { + jsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; // 忽略循环引用 + + return json == null ? default(T) : JsonSerializer.Deserialize(json, jsonSerializerOptions); + } + else + { + return json == null ? default(T) : JsonSerializer.Deserialize(json, jsonSerializerOptions); + } + } + + /// + /// list json字符串转换成list + /// + /// + /// + /// + public static List? DeserializeToList(this string json) + { + return json == null ? default(List) : Deserialize>(json); + } + } + + + public class DateTimeJsonConverter : JsonConverter + { + private readonly string _dateFormatString; + public DateTimeJsonConverter() + { + _dateFormatString = "yyyy-MM-dd HH:mm:ss"; + } + + public DateTimeJsonConverter(string dateFormatString) + { + _dateFormatString = dateFormatString; + } + + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(_dateFormatString)); + } + } +} diff --git a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs index 881cab9..768332f 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs @@ -8,20 +8,23 @@ using System.Text; using System.Threading.Tasks; using Volo.Abp.Modularity; -namespace JiShe.CollectBus.FreeRedisProvider; - -public class FreeRedisProviderModule : AbpModule +namespace JiShe.CollectBus.FreeRedisProvider { - public override void ConfigureServices(ServiceConfigurationContext context) + public class FreeRedisProviderModule : AbpModule { - var configuration = context.Services.GetConfiguration(); - - Configure(options => + public override void ConfigureServices(ServiceConfigurationContext context) { - configuration.GetSection("Redis").Bind(options); - }); + var configuration = context.Services.GetConfiguration(); + + Configure(options => + { + configuration.GetSection("Redis").Bind(options); + }); + } } } + + diff --git a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderService.cs b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderService.cs index c4bf3ff..b36001d 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderService.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderService.cs @@ -11,53 +11,55 @@ using System.Text.Json; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; -namespace JiShe.CollectBus.FreeRedisProvider; - -public class FreeRedisProviderService : IFreeRedisProviderService, ISingletonDependency +namespace JiShe.CollectBus.FreeRedisProvider { - private FreeRedisOptions _freeRedisOptions; - /// - /// FreeRedis - /// - public FreeRedisProviderService(IOptions options) + public class FreeRedisProviderService : IFreeRedisProviderService, ISingletonDependency { - _freeRedisOptions = options.Value; - } + private FreeRedisOptions _freeRedisOptions; - [NotNull] - public IRedisClient FreeRedis - { - get + /// + /// FreeRedis + /// + public FreeRedisProviderService(IOptions options) { - return GetClient(); + _freeRedisOptions = options.Value; + } + + [NotNull] + public IRedisClient FreeRedis + { + get + { + return GetClient(); + } + } + + /// + /// 获取 FreeRedis 客户端 + /// + /// + public IRedisClient GetClient() + { + string connectionString = $"{_freeRedisOptions.Configuration},defaultdatabase={_freeRedisOptions.DefaultDB}"; + var redisClient = new RedisClient(connectionString); + redisClient.Serialize = obj => JsonSerializer.Serialize(obj); + redisClient.Deserialize = (json, type) => JsonSerializer.Deserialize(json, type); + redisClient.Notice += (s, e) => Trace.WriteLine(e.Log); + + return redisClient; + } + + /// + /// 切换Redis数据库 + /// + /// + /// + public IRedisClient GetDatabase(int index = 0) + { + var redisClient = GetClient(); + redisClient.GetDatabase(index); + return redisClient; } } - - /// - /// 获取 FreeRedis 客户端 - /// - /// - public IRedisClient GetClient() - { - string connectionString = $"{_freeRedisOptions.Configuration},defaultdatabase={_freeRedisOptions.DefaultDB}"; - var redisClinet = new RedisClient(connectionString); - redisClinet.Serialize = obj => JsonSerializer.Serialize(obj); - redisClinet.Deserialize = (json, type) => JsonSerializer.Deserialize(json, type); - redisClinet.Notice += (s, e) => Trace.WriteLine(e.Log); - - return redisClinet; - } - - /// - /// 切换Redis数据库 - /// - /// - /// - public IRedisClient GetDatabase(int index = 0) - { - var redisClinet = GetClient(); - redisClinet.GetDatabase(index); - return redisClinet; - } -} +} \ No newline at end of file diff --git a/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProviderService.cs b/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProviderService.cs index 6f0ff63..2e244dc 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProviderService.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProviderService.cs @@ -5,25 +5,26 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.FreeRedisProvider; - -public interface IFreeRedisProviderService +namespace JiShe.CollectBus.FreeRedisProvider { - /// - /// 默认客户端 - /// - IRedisClient FreeRedis { get; } + public interface IFreeRedisProviderService + { + /// + /// 默认客户端 + /// + IRedisClient FreeRedis { get; } - /// - /// 获取客户端 - /// - /// - IRedisClient GetClient(); + /// + /// 获取客户端 + /// + /// + IRedisClient GetClient(); - /// - /// 切换Redis数据库 - /// - /// - /// - IRedisClient GetDatabase(int index = 0); -} + /// + /// 切换Redis数据库 + /// + /// + /// + IRedisClient GetDatabase(int index = 0); + } +} diff --git a/src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs b/src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs index bddfa28..75d92a2 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs @@ -4,22 +4,24 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.FreeRedisProvider.Options; - -public class FreeRedisOptions +namespace JiShe.CollectBus.FreeRedisProvider.Options { - /// - /// 连接字符串 - /// - public string? Configuration { get; set; } + public class FreeRedisOptions + { + /// + /// 连接字符串 + /// + public string? Configuration { get; set; } - /// - /// 默认数据库 - /// - public string? DefaultDB { get; set; } + /// + /// 默认数据库 + /// + public string? DefaultDB { get; set; } + + /// + /// HangfireDB + /// + public string? HangfireDB { get; set; } + } +} - /// - /// HangfireDB - /// - public string? HangfireDB { get; set; } -} \ No newline at end of file -- 2.47.2 From c174738095de02f7b6516377291730a660d8a7db Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Fri, 14 Mar 2025 13:45:29 +0800 Subject: [PATCH 009/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9BackgroundWorker=20BU?= =?UTF-8?q?G?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusApplicationModule.cs | 8 +++-- .../Workers/EpiCollectWorker.cs | 11 ++++-- .../JiShe.CollectBus.Common.csproj | 7 ++-- .../JiShe.CollectBus.Domain.Shared.csproj | 7 ++-- .../Loggers/LoggerExtensions.cs | 34 +++++++++++++++++++ .../JiShe.CollectBus.Domain.csproj | 4 +++ src/JiShe.CollectBus.Host/Startup.cs | 1 + 7 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 src/JiShe.CollectBus.Domain.Shared/Loggers/LoggerExtensions.cs diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 694f807..8078e48 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -11,6 +11,8 @@ using JiShe.CollectBus.FreeSql; using System; using Volo.Abp.AspNetCore.Mvc.AntiForgery; using JiShe.CollectBus.FreeRedisProvider; +using JiShe.CollectBus.Workers; +using Volo.Abp.BackgroundWorkers.Hangfire; namespace JiShe.CollectBus; @@ -19,7 +21,7 @@ namespace JiShe.CollectBus; typeof(CollectBusApplicationContractsModule), typeof(AbpDddApplicationModule), typeof(AbpAutoMapperModule), - typeof(AbpBackgroundWorkersModule), + typeof(AbpBackgroundWorkersHangfireModule), typeof(FreeRedisProviderModule), typeof(CollectBusFreeSqlModule) )] @@ -40,7 +42,7 @@ public class CollectBusApplicationModule : AbpModule }); } - public override async Task OnApplicationInitializationAsync( + public override void OnApplicationInitialization( ApplicationInitializationContext context) { var assembly = Assembly.GetExecutingAssembly(); @@ -48,7 +50,7 @@ public class CollectBusApplicationModule : AbpModule foreach (var type in types) { - await context.AddBackgroundWorkerAsync(type); + context.AddBackgroundWorkerAsync(type); } } diff --git a/src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs b/src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs index bc1badc..e1c5510 100644 --- a/src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs @@ -1,8 +1,10 @@ using System.Threading; using System.Threading.Tasks; +using Hangfire; using Microsoft.Extensions.Logging; using Volo.Abp.BackgroundWorkers.Hangfire; using Volo.Abp.DependencyInjection; +using Volo.Abp.Uow; namespace JiShe.CollectBus.Workers { @@ -17,13 +19,18 @@ namespace JiShe.CollectBus.Workers public EpiCollectWorker(ILogger logger) { _logger = logger; + RecurringJobId = nameof(EpiCollectWorker); + CronExpression = Cron.Daily(); } public override Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { - _logger.LogError("Executed MyLogWorker..!"); - return Task.CompletedTask; + using (var uow = LazyServiceProvider.LazyGetRequiredService().Begin()) + { + Logger.LogInformation("Executed MyLogWorker..!"); + return Task.CompletedTask; + } } } } diff --git a/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj b/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj index 8b0b7af..687269f 100644 --- a/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj +++ b/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj @@ -8,8 +8,11 @@ + + + @@ -24,8 +27,4 @@ - - - - diff --git a/src/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.csproj b/src/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.csproj index 0552858..a2747f4 100644 --- a/src/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.csproj +++ b/src/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.csproj @@ -20,7 +20,10 @@ + + + @@ -28,8 +31,4 @@ - - - - diff --git a/src/JiShe.CollectBus.Domain.Shared/Loggers/LoggerExtensions.cs b/src/JiShe.CollectBus.Domain.Shared/Loggers/LoggerExtensions.cs new file mode 100644 index 0000000..7fbbb41 --- /dev/null +++ b/src/JiShe.CollectBus.Domain.Shared/Loggers/LoggerExtensions.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Loggers +{ + public static class LoggerExtensions + { + public static void LogInfoToMongo( + this ILogger logger, + EventId eventId, + Exception? exception, + string? message, + params object?[] args) + { + + } + + public static void LogInfoToMongo(this ILogger logger) + { + + } + + private static void Log( + this ILogger logger, + LogLevel logLevel, + EventId eventId, + string? message, + params object?[] args) + { + + } + } +} + \ No newline at end of file diff --git a/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj b/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj index ae1b120..7d4299b 100644 --- a/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj +++ b/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj @@ -26,4 +26,8 @@ + + + + diff --git a/src/JiShe.CollectBus.Host/Startup.cs b/src/JiShe.CollectBus.Host/Startup.cs index d5a0e4e..1f43bc8 100644 --- a/src/JiShe.CollectBus.Host/Startup.cs +++ b/src/JiShe.CollectBus.Host/Startup.cs @@ -39,6 +39,7 @@ namespace JiShe.CollectBus.Host public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime) { app.InitializeApplication(); + //await app.InitializeApplicationAsync(); } } } -- 2.47.2 From 3b2e1c6e9c44ad3035d682ee9727d39ede50bbf4 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Fri, 14 Mar 2025 14:04:49 +0800 Subject: [PATCH 010/139] =?UTF-8?q?=E9=9B=86=E6=88=90=E7=BB=B4=E6=8A=A4?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusHostConst.cs | 15 ++ .../Controllers/HomeController.cs | 13 ++ .../Controllers/WeatherForecastController.cs | 33 ---- .../Pages/Monitor.cshtml | 171 ++++++++++++++++++ .../Pages/Monitor.cshtml.cs | 12 ++ .../Properties/launchSettings.json | 35 +--- src/JiShe.CollectBus.Host/WeatherForecast.cs | 13 -- .../wwwroot/images/cap.png | Bin 0 -> 11113 bytes .../wwwroot/images/hangfire.png | Bin 0 -> 36278 bytes .../wwwroot/images/miniprofiler.png | Bin 0 -> 22147 bytes .../wwwroot/images/more.png | Bin 0 -> 20558 bytes .../wwwroot/images/swagger.png | Bin 0 -> 38571 bytes .../libs/bootstrap/css/bootstrap.min.css | 6 + 13 files changed, 220 insertions(+), 78 deletions(-) create mode 100644 src/JiShe.CollectBus.Host/Controllers/HomeController.cs delete mode 100644 src/JiShe.CollectBus.Host/Controllers/WeatherForecastController.cs create mode 100644 src/JiShe.CollectBus.Host/Pages/Monitor.cshtml create mode 100644 src/JiShe.CollectBus.Host/Pages/Monitor.cshtml.cs delete mode 100644 src/JiShe.CollectBus.Host/WeatherForecast.cs create mode 100644 src/JiShe.CollectBus.Host/wwwroot/images/cap.png create mode 100644 src/JiShe.CollectBus.Host/wwwroot/images/hangfire.png create mode 100644 src/JiShe.CollectBus.Host/wwwroot/images/miniprofiler.png create mode 100644 src/JiShe.CollectBus.Host/wwwroot/images/more.png create mode 100644 src/JiShe.CollectBus.Host/wwwroot/images/swagger.png create mode 100644 src/JiShe.CollectBus.Host/wwwroot/libs/bootstrap/css/bootstrap.min.css diff --git a/src/JiShe.CollectBus.Host/CollectBusHostConst.cs b/src/JiShe.CollectBus.Host/CollectBusHostConst.cs index 4df4b8d..319dfed 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostConst.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostConst.cs @@ -12,5 +12,20 @@ /// public const string DefaultCookieName = "JiShe.CollectBus.Host"; + /// + /// SwaggerUi 端点 + /// + public const string SwaggerUiEndPoint = "/swagger"; + + /// + /// Hangfire 端点 + /// + public const string HangfireDashboardEndPoint = "/hangfire"; + + /// + /// CAP 端点 + /// + public const string CapDashboardEndPoint = "/cap"; + } } diff --git a/src/JiShe.CollectBus.Host/Controllers/HomeController.cs b/src/JiShe.CollectBus.Host/Controllers/HomeController.cs new file mode 100644 index 0000000..0b4c594 --- /dev/null +++ b/src/JiShe.CollectBus.Host/Controllers/HomeController.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; + +namespace JiShe.CollectBus.Host.Controllers +{ + public class HomeController : AbpController + { + public ActionResult Index() + { + return Redirect("/Monitor"); + } + } +} diff --git a/src/JiShe.CollectBus.Host/Controllers/WeatherForecastController.cs b/src/JiShe.CollectBus.Host/Controllers/WeatherForecastController.cs deleted file mode 100644 index 986c33b..0000000 --- a/src/JiShe.CollectBus.Host/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace JiShe.CollectBus.Host.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} diff --git a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml new file mode 100644 index 0000000..d742dbc --- /dev/null +++ b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml @@ -0,0 +1,171 @@ +@page +@using JiShe.CollectBus.Host +@model JiShe.CollectBus.Host.Pages.Monitor + + +@{ + Layout = null; +} + + + + + + + + + + 后端服务 + + + +
+ +
+
+
+ + + +
+

+ SwaggerUI +

+
+
+
+ +
+
+ + + +
+

+ Hangfire面板 +

+
+
+
+
+
+ + + +
+

+ CAP +

+
+
+
+ @*
+
+ + + +
+

+ 了解更多... +

+
+
+
*@ +
+
+ + + \ No newline at end of file diff --git a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml.cs b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml.cs new file mode 100644 index 0000000..6019db9 --- /dev/null +++ b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace JiShe.CollectBus.Host.Pages +{ + public class Monitor : PageModel + { + public void OnGet() + { + + } + } +} \ No newline at end of file diff --git a/src/JiShe.CollectBus.Host/Properties/launchSettings.json b/src/JiShe.CollectBus.Host/Properties/launchSettings.json index cf8c7cb..ab22661 100644 --- a/src/JiShe.CollectBus.Host/Properties/launchSettings.json +++ b/src/JiShe.CollectBus.Host/Properties/launchSettings.json @@ -1,41 +1,12 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:38293", - "sslPort": 44329 - } - }, "profiles": { - "http": { + "JiShe.CollectBus.Host": { "commandName": "Project", - "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "http://localhost:5063", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:7232;http://localhost:5063", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", + "applicationUrl": "http://localhost:44315", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } -} +} \ No newline at end of file diff --git a/src/JiShe.CollectBus.Host/WeatherForecast.cs b/src/JiShe.CollectBus.Host/WeatherForecast.cs deleted file mode 100644 index 137fed8..0000000 --- a/src/JiShe.CollectBus.Host/WeatherForecast.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace JiShe.CollectBus.Host -{ - public class WeatherForecast - { - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } - } -} diff --git a/src/JiShe.CollectBus.Host/wwwroot/images/cap.png b/src/JiShe.CollectBus.Host/wwwroot/images/cap.png new file mode 100644 index 0000000000000000000000000000000000000000..76c667e0525b3368071af5abc103685afcb1894d GIT binary patch literal 11113 zcmdUV=R2HV)UJde`slq3gQ!uWmxR$GTJ+u`dM7#&Z4iVg(PK!2=)Lz&FnaWEM2$A0 zo@ajV^`3v=d^#T(<9eQ5*1Ffd?zQ%g)>Ky_#G}Q-z`!6>QHH+8z<9_D{5^n+4g6-i zM>qf<4?JHh$zha@((eLauxwwby}-bzOu)afd<1-d?51q!iGe}XjsAJi@A~5n1_sxi z3iO4Jui3#;l#kAAEB`U-z9rMc)7T|oZ@J`pnKz!nM4w(WPO{5qSY)%FO&F8(rj=BiXIUfi}|(CB*4H269s z6b6G0U{M>PKgjJIJX@y5J9Ss)Cy5UbZ<7uAT z$S?+F#Br>(Q{K1kdOa@j$?04Yf=kE2INuTQLb2MHFl}c^uDItB?z_9`Dk4lM{&qa4zE^YzUjO)`&qnr_QeLWR@UnwJQBsW09y?`nS}eX~7wbEYizp$hL- z#eAqP6Z1GY)*qELHW3RE1k-J6J|8+C16Eyi~6TJ$4IPu&j>S7QEgZGci z35SM%U}+42^4%uaba=Ef#&O_nImK|j>Z$ILN&Eg;VV(m5j$uR($=nqqORtw@rMG3q zN*lkdgt#HM@ueDfKcTWgxFrFGy@GC1U-DS>eC_~AmBPron)~*NgN-h*!b(>r`Awqgba9Y4+t+tJT2ib+eadu38ng>3jQsz^Y+?}4YJBHq6rj%tf?<}~~Ao&Hiycm0ia zLrNbjLIp{|NM^q}8At+FImzr|rQ)mxnADMbW$8@j!*6PuvDA!k23 z74C@hLm>j$z0lTP>@*u{#>`sZV-`SzaP5S3pPMZ@|4+-uQCOEWqfCTO<34YW1r5qKkz5AKSA z(ChRJLO|cCU&A;P1!_ooqh06X;#)X;&+O|2m3#7`nnkx4O(s{M>(|(hO(bVHHGuz; z=Kz?kKX-aXp~}DE9rei|rg&NReVr$*U^5?hsT!0>1^5}t-jHO`67H|iy53T0PKQ*eSTT?x9(R0YDN96y8paB`!hargd{)O=+;5hH zmJ41qa&!hw6In1}YyCXr6cy;~Y9Gn10Cb`hB7c-S)C)XtOG>Tu#Md>vq)*M~KPVX4z zJgWt=Jm@iT+7*trYmsJxJ184d5vn>hM~j7sH9y5Wo*nst0k)L57pRGFE%squeJ)5{d21ZWvbp6UtVV`DWKMg;zRZ*-G z8gJiGHAov(m`z@xd7Wy=^wsrRk)SY#04M$WSVV+bOpIBcv>PAPiU(l$qr|D>@$u(V zz#LHN--!tzkU^jCE0niX)a`4Lt9LF6sP*5-j0=&b@9M836TZBZ(v}LilB@{->Do`( z5Oc(etZ>W?hbhMa;e*}ALIvcYkA^>k-Q}wrSR^)?njtJZrH;t^ZB+w(J}9cizG5Ws zUg3x4qj&+%;Stn$s~fhHu}r7(49%s)Lal}(^sFDf$#L&mqOG6(+py}nsq34-M}YvJ zh-_eGt2eyFL$te!mT*&c2-*rZeedJ{@ncAE!_kvw)xck47e&P4waECJ6KxfSd%3ObT<_l+##TL@BEm@fLyVRlYmq6T>?hzn^~R#h+C~ zK=4?1?J1;dxZ|#TwIzToST&8n6nb_AB%lcvCgC3y;e!b4go$+PL z%+�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 literal 0 HcmV?d00001 diff --git a/src/JiShe.CollectBus.Host/wwwroot/images/hangfire.png b/src/JiShe.CollectBus.Host/wwwroot/images/hangfire.png new file mode 100644 index 0000000000000000000000000000000000000000..5cdeb40f0157e93361de3ca004454f7e840639f8 GIT binary patch literal 36278 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 literal 0 HcmV?d00001 diff --git a/src/JiShe.CollectBus.Host/wwwroot/images/miniprofiler.png b/src/JiShe.CollectBus.Host/wwwroot/images/miniprofiler.png new file mode 100644 index 0000000000000000000000000000000000000000..244c7029ac13d24d9f11101d0d4ff550cf233dfd GIT binary patch literal 22147 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 P1AKI~4YbPcScm-=eWKC$ literal 0 HcmV?d00001 diff --git a/src/JiShe.CollectBus.Host/wwwroot/libs/bootstrap/css/bootstrap.min.css b/src/JiShe.CollectBus.Host/wwwroot/libs/bootstrap/css/bootstrap.min.css new file mode 100644 index 0000000..ed3905e --- /dev/null +++ b/src/JiShe.CollectBus.Host/wwwroot/libs/bootstrap/css/bootstrap.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file -- 2.47.2 From e564d8d7e675b081f95f8a7efdf5b0360dc336a9 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Fri, 14 Mar 2025 14:24:38 +0800 Subject: [PATCH 011/139] =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E4=BC=98=E5=8C=96202503141424?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...he.CollectBus.Application.Contracts.csproj | 4 + .../IScheduledMeterReadingService.cs | 7 + .../IWorkerSubscriberAppService.cs | 0 .../Consumers/IssuedConsumer.cs | 4 +- .../Consumers/WorkerConsumer.cs | 40 ++ .../BasicScheduledMeterReadingService.cs | 672 ++++++++++++++++++ ...nergySystemScheduledMeterReadingService.cs | 21 + .../WorkerSubscriberAppService.cs | 0 .../BasicScheduledMeterReadingService.cs | 358 ---------- .../Enums/MeterLinkProtocolEnum.cs | 54 ++ .../Ammeters/AmmeterInfo.cs | 2 +- .../GatherItem/GatherItemInfo.cs | 21 + .../CollectBusHostModule.Configure.cs | 2 +- .../ProtocolConst.cs | 26 + 14 files changed, 849 insertions(+), 362 deletions(-) rename src/JiShe.CollectBus.Application.Contracts/{Workers => ScheduledMeterReading}/IScheduledMeterReadingService.cs (92%) rename src/JiShe.CollectBus.Application.Contracts/{Workers => Subscribers}/IWorkerSubscriberAppService.cs (100%) create mode 100644 src/JiShe.CollectBus.Application/Consumers/WorkerConsumer.cs create mode 100644 src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs rename src/JiShe.CollectBus.Application/{Workers => ScheduledMeterReading}/EnergySystemScheduledMeterReadingService.cs (88%) rename src/JiShe.CollectBus.Application/{Workers => Subscribers}/WorkerSubscriberAppService.cs (100%) delete mode 100644 src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs create mode 100644 src/JiShe.CollectBus.Common/Enums/MeterLinkProtocolEnum.cs create mode 100644 src/JiShe.CollectBus.Domain/GatherItem/GatherItemInfo.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj index 0a28849..0b1f26d 100644 --- a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj +++ b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj @@ -22,4 +22,8 @@ + + + + diff --git a/src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs similarity index 92% rename from src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs rename to src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs index bd1a090..4f41051 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs @@ -1,4 +1,5 @@ using JiShe.CollectBus.Ammeters; +using JiShe.CollectBus.GatherItem; using JiShe.CollectBus.Watermeter; using System; using System.Collections.Generic; @@ -16,6 +17,12 @@ namespace JiShe.CollectBus.Workers public interface IScheduledMeterReadingService : IApplicationService { + /// + /// 获取采集项列表 + /// + /// + Task> GetGatherItemByDataTypes(); + #region 电表采集处理 /// /// 获取电表信息 diff --git a/src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/Workers/IWorkerSubscriberAppService.cs rename to src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs diff --git a/src/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs b/src/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs index 710a268..52da062 100644 --- a/src/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs +++ b/src/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs @@ -12,7 +12,7 @@ namespace JiShe.CollectBus.Consumers { public class IssuedConsumer: IConsumer { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ITcpService _tcpService; private readonly IRepository _messageReceivedLoginEventRepository; private readonly IRepository _messageReceivedHeartbeatEventRepository; @@ -24,7 +24,7 @@ namespace JiShe.CollectBus.Consumers /// /// /// - public IssuedConsumer(ILogger logger, + public IssuedConsumer(ILogger logger, ITcpService tcpService, IRepository messageReceivedLoginEventRepository, IRepository messageReceivedHeartbeatEventRepository) diff --git a/src/JiShe.CollectBus.Application/Consumers/WorkerConsumer.cs b/src/JiShe.CollectBus.Application/Consumers/WorkerConsumer.cs new file mode 100644 index 0000000..5a5652d --- /dev/null +++ b/src/JiShe.CollectBus.Application/Consumers/WorkerConsumer.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.MessageIssueds; +using JiShe.CollectBus.MessageReceiveds; +using MassTransit; +using Microsoft.Extensions.Logging; +using TouchSocket.Sockets; +using Volo.Abp.Domain.Repositories; + +namespace JiShe.CollectBus.Consumers +{ + /// + /// 定时抄读任务消费者 + /// + public class WorkerConsumer : IConsumer + { + private readonly ILogger _logger; + private readonly ITcpService _tcpService; + + /// + /// WorkerConsumer + /// + /// + /// + public WorkerConsumer(ILogger logger, + ITcpService tcpService) + { + _logger = logger; + _tcpService = tcpService; + } + + + public async Task Consume(ConsumeContext context) + { + await _tcpService.SendAsync(context.Message.ClientId, context.Message.Message); + } + } +} diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs new file mode 100644 index 0000000..c573ab5 --- /dev/null +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -0,0 +1,672 @@ +using DotNetCore.CAP; +using FreeRedis; +using JiShe.CollectBus.Ammeters; +using JiShe.CollectBus.Common.BuildSendDatas; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.GatherItem; +using JiShe.CollectBus.MessageReceiveds; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Watermeter; +using MassTransit; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using TouchSocket.Sockets; +using Volo.Abp.Application.Services; +using static FreeSql.Internal.GlobalFilter; + +namespace JiShe.CollectBus.Workers +{ + /// + /// 定时采集服务 + /// + public abstract class BasicScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService + { + private readonly ILogger _logger; + private readonly ICapPublisher _capBus; + public BasicScheduledMeterReadingService(ILogger logger, ICapPublisher capBus) + { + _capBus = capBus; + _logger = logger; + } + + /// + /// 系统类型 + /// + public abstract string SystemType { get; } + + /// + ///电表日冻结采集项 + /// + protected List DayFreezeCodes = new List() { "0D_3", "0D_4", "0D_161", "0D_162", "0D_163", "0D_164", "0D_165", "0D_166", "0D_167", "0D_168", "0C_149", }; + + /// + /// 电表月冻结采集项 + /// + protected List MonthFreezeCodes = new List() { "0D_177", "0D_178", "0D_179", "0D_180", "0D_181", "0D_182", "0D_183", "0D_184", "0D_193", "0D_195", }; + + /// + /// 获取采集项列表 + /// + /// + public virtual Task> GetGatherItemByDataTypes() + { + throw new NotImplementedException($"{nameof(GetGatherItemByDataTypes)}请根据不同系统类型进行实现"); + } + + #region 电表采集处理 + + /// + /// 获取电表信息 + /// + /// 采集端Code + /// + public virtual Task> GetAmmeterInfoList(string gatherCode = "") + { + throw new NotImplementedException($"{nameof(GetAmmeterInfoList)}请根据不同系统类型进行实现"); + } + + /// + /// 初始化电表缓存数据 + /// + /// 采集端Code + /// + public virtual async Task InitAmmeterCacheData(string gatherCode = "") + { + var meterInfos = await GetAmmeterInfoList(gatherCode); + if (meterInfos == null || meterInfos.Count <= 0) + { + throw new NullReferenceException($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据时,电表数据为空"); + } + + //获取采集项类型数据 + var gatherItemInfos = await GetGatherItemByDataTypes(); + if (gatherItemInfos == null || gatherItemInfos.Count <= 0) + { + throw new NullReferenceException($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据时,采集项类型数据为空"); + } + + //根据采集频率分组,获得采集频率分组 + var meterInfoGroupByTimeDensity = meterInfos.GroupBy(d => d.TimeDensity); + foreach (var itemTimeDensity in meterInfoGroupByTimeDensity) + { + //将表计信息根据集中器分组,获得集中器号 + var meterInfoGroup = itemTimeDensity.GroupBy(x => x.FocusAddress).ToList(); + foreach (var item in meterInfoGroup) + { + if (string.IsNullOrWhiteSpace(item.Key)) + { + continue; + } + + var redisCacheKey = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, itemTimeDensity.Key)}{item.Key}"; + Dictionary keyValuePairs = new Dictionary(); + foreach (var ammeter in item) + { + //处理ItemCode + if (string.IsNullOrWhiteSpace(ammeter.ItemCodes)) + { + var itemArr = ammeter.DataTypes.Split(',').ToList(); + + #region 拼接采集项 + List itemCodeList = new List(); + foreach (var dataType in itemArr) + { + var excludeItemCode = "10_98,10_94";//排除透明转发:尖峰平谷时段、跳合闸 + var gatherItem = gatherItemInfos.FirstOrDefault(f => f.DataType.Equals(dataType)); + if (gatherItem != null) + { + if (!excludeItemCode.Contains(gatherItem.ItemCode)) + { + itemCodeList.Add(gatherItem.ItemCode); + } + } + } + #endregion + + #region 特殊电表采集项编号处理 + if (itemArr.Exists(e => e.Equals("95"))) //德力西DTS + { + itemCodeList.Add("10_95"); + } + //if (itemArr.Exists(e => e.Equals("109")))//WAVE_109 + // ammeter.ItemCodes += "10_109,"; + #endregion + + ammeter.ItemCodes = itemCodeList.Serialize();//转换成JSON字符串 + + if (!string.IsNullOrWhiteSpace(ammeter.ItemCodes)) + { + ammeter.ItemCodes = ammeter.ItemCodes.Replace("WAVE_109", "10_109"); + } + } + + keyValuePairs.TryAdd($"{ammeter.ID}", ammeter); + } + await FreeRedisProvider.FreeRedis.HSetAsync(redisCacheKey, keyValuePairs); + } + } + + _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据完成"); + } + + /// + /// 1分钟采集电表数据 + /// + /// + public virtual async Task AmmeterScheduledMeterOneMinuteReading() + { + //获取缓存中的电表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 1)}*"; + var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-101"); + return; + } + + // 解析结果(结果为嵌套数组) + Dictionary> meterInfos = await GetMeterCacheData(oneMinutekeyList, 1); + if (meterInfos == null || meterInfos.Count <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); + return; + } + + await AmmerterScheduledMeterReadingIssued(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, meterInfos); + + _logger.LogInformation($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理完成"); + + } + + /// + /// 5分钟采集电表数据 + /// + /// + public virtual async Task AmmeterScheduledMeterFiveMinuteReading() + { + //获取缓存中的电表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 5)}*"; + var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理时没有获取到缓存信息,-101"); + return; + } + + // 解析结果(结果为嵌套数组) + Dictionary> meterInfos = await GetMeterCacheData(oneMinutekeyList, 5); + if (meterInfos == null || meterInfos.Count <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理时没有获取到缓存信息,-102"); + return; + } + await AmmerterScheduledMeterReadingIssued(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, meterInfos); + + _logger.LogInformation($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理完成"); + } + + /// + /// 15分钟采集电表数据 + /// + /// + public virtual async Task AmmeterScheduledMeterFifteenMinuteReading() + { + //获取缓存中的电表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 15)}*"; + var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理时没有获取到缓存信息,-101"); + return; + } + + // 解析结果(结果为嵌套数组) + Dictionary> meterInfos = await GetMeterCacheData(oneMinutekeyList, 15); + if (meterInfos == null || meterInfos.Count <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理时没有获取到缓存信息,-102"); + return; + } + + await AmmerterScheduledMeterReadingIssued(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, meterInfos); + _logger.LogInformation($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理完成"); + } + #endregion + + + #region 水表采集处理 + + /// + /// 获取水表信息 + /// + /// 采集端Code + /// + public virtual Task> GetWatermeterInfoList(string gatherCode = "") + { + throw new NotImplementedException($"{nameof(GetWatermeterInfoList)}请根据不同系统类型进行实现"); + } + + /// + /// 初始化水表缓存数据 + /// + /// 采集端Code + /// + public virtual async Task InitWatermeterCacheData(string gatherCode = "") + { + var meterInfos = await GetWatermeterInfoList(gatherCode); + if (meterInfos == null || meterInfos.Count <= 0) + { + throw new NullReferenceException($"{nameof(InitWatermeterCacheData)} 初始化水表缓存数据时,水表数据为空"); + } + + //获取采集项类型数据 + var gatherItemInfos = await GetGatherItemByDataTypes(); + if (gatherItemInfos == null || gatherItemInfos.Count <= 0) + { + throw new NullReferenceException($"{nameof(InitAmmeterCacheData)} 初始化水表缓存数据时,采集项类型数据为空"); + } + + //根据采集频率分组,获得采集频率分组 + var meterInfoGroupByTimeDensity = meterInfos.GroupBy(d => d.TimeDensity); + foreach (var itemTimeDensity in meterInfoGroupByTimeDensity) + { + //将表计信息根据集中器分组,获得集中器号 + var meterInfoGroup = itemTimeDensity.GroupBy(x => x.FocusAddress).ToList(); + foreach (var item in meterInfoGroup) + { + if (string.IsNullOrWhiteSpace(item.Key)) + { + continue; + } + + var redisCacheKey = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, itemTimeDensity.Key)}{item.Key}"; + Dictionary keyValuePairs = new Dictionary(); + foreach (var subItem in item) + { + + keyValuePairs.TryAdd($"{subItem.ID}", subItem); + } + await FreeRedisProvider.FreeRedis.HSetAsync(redisCacheKey, keyValuePairs); + } + } + _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化水表缓存数据完成"); + } + + /// + /// 1分钟采集水表数据 + /// + /// + public virtual async Task WatermeterScheduledMeterOneMinuteReading() + { + //获取缓存中的水表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 1)}*"; + var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表据处理时没有获取到缓存信息,-101"); + return; + } + + // 解析结果(结果为嵌套数组) + Dictionary> meterInfos = await GetMeterCacheData(oneMinutekeyList, 1); + if (meterInfos == null || meterInfos.Count <= 0) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表数据处理时没有获取到缓存信息,-102"); + return; + } + + _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表数据处理完成"); + } + + /// + /// 5分钟采集电表数据 + /// + /// + public virtual async Task WatermeterScheduledMeterFiveMinuteReading() + { + + //获取缓存中的水表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 5)}*"; + var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表据处理时没有获取到缓存信息,-101"); + return; + } + + // 解析结果(结果为嵌套数组) + Dictionary> meterInfos = await GetMeterCacheData(oneMinutekeyList, 5); + if (meterInfos == null || meterInfos.Count <= 0) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表数据处理时没有获取到缓存信息,-102"); + return; + } + + _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表数据处理完成"); + } + + /// + /// 15分钟采集电表数据 + /// + /// + public virtual async Task WatermeterScheduledMeterFifteenMinuteReading() + { + //获取缓存中的水表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 15)}*"; + var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表据处理时没有获取到缓存信息,-101"); + return; + } + + // 解析结果(结果为嵌套数组) + Dictionary> meterInfos = await GetMeterCacheData(oneMinutekeyList, 15); + if (meterInfos == null || meterInfos.Count <= 0) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表数据处理时没有获取到缓存信息,-102"); + return; + } + + _logger.LogInformation($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表数据处理完成"); + } + #endregion + + + #region 公共处理方法 + /// + /// 批量获取缓存的表计信息 + /// + /// 表信息数据对象 + /// 采集频率对应的缓存Key集合 + /// 采集频率,1分钟、5分钟、15分钟 + /// + private async Task>> GetMeterCacheData(string[] redisKeys, int minute) + { + //通过lua脚本一次性获取所有缓存内容 + var luaScript = @" + local results = {} + for i, key in ipairs(KEYS) do + local data = redis.call('HGETALL', key) + results[i] = {key, data} + end + return results"; + var oneMinuteAmmerterResult = await FreeRedisProvider.FreeRedis.EvalAsync(luaScript, redisKeys); //传递 KEYS + if (oneMinuteAmmerterResult == null) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 定时任务采集表数据处理时没有获取到缓存信息,-102"); + return null; + } + + // 解析结果(结果为嵌套数组) + var meterInfos = new Dictionary>(); ; + if (oneMinuteAmmerterResult is object[] arr) + { + foreach (object[] item in arr) + { + string key = (string)item[0];//集中器地址对应的Redis缓存Key + object[] fieldsAndValues = (object[])item[1];//缓存Key对应的Hash表数据集合 + var redisCacheKey = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, minute)}"; + string focusAddress = key.Replace(redisCacheKey, "");//集中器地址 + + var meterHashs = new Dictionary(); + for (int i = 0; i < fieldsAndValues.Length; i += 2) + { + string meterld = (string)fieldsAndValues[i];//表ID + string meterStr = (string)fieldsAndValues[i + 1];//表详情数据 + + T meterInfo = default; + if (!string.IsNullOrWhiteSpace(meterStr)) + { + meterInfo = meterStr.Deserialize()!; + } + if (meterInfo != null) + { + meterHashs[meterld] = meterInfo; + } + else + { + _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 定时任务采集表数据处理时集中器缓存{key}数据的{meterld}处理异常"); + } + } + meterInfos[focusAddress] = meterHashs; + } + } + + return meterInfos; + } + + /// + /// 电表采集任务指令创建 + /// + /// 采集频率订阅主题 + /// 集中器数据分组 + /// + private async Task AmmerterScheduledMeterReadingIssued(string eventName, Dictionary> focusGroup) + { + if (string.IsNullOrWhiteSpace(eventName) || focusGroup == null || focusGroup.Count <= 0) + { + _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 电表数据采集指令生成失败,参数异常,-101"); + return; + } + try + { + //将采集器编号的hash值取模分组 + const int TotalShards = 20; + var focusHashGroups = new Dictionary>>(); + + foreach (var (collectorId, ammetersDictionary) in focusGroup) + { + if (string.IsNullOrWhiteSpace(collectorId)) + { + _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 集中器信息分组取模失败,无效Key -102"); + continue; + } + + // 计算哈希分组ID + int hashGroupId = Math.Abs(collectorId.GetHashCode() % TotalShards); + + // 获取或创建分组(避免重复查找) + if (!focusHashGroups.TryGetValue(hashGroupId, out var group)) + { + group = new Dictionary>(); + focusHashGroups[hashGroupId] = group; + } + + // 将当前集中器数据加入分组 + group[collectorId] = ammetersDictionary; + } + + if (focusHashGroups == null) + { + _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 集中器信息分组取模失败 -103"); + return; + } + + //根据分组创建线程批处理集中器 + foreach (var group in focusHashGroups) + { + _= Task.Run(async () => { await CreatePublishTask(eventName,group.Value); }); + } + + await Task.CompletedTask; + } + catch (Exception) + { + + throw; + } + } + + /// + /// 创建发布任务 + /// + /// + /// + /// + private async Task CreatePublishTask(string eventName, Dictionary> focusGroup) + { + foreach (var focusInfo in focusGroup) + { + foreach (var ammeterInfo in focusInfo.Value) + { + var meter = ammeterInfo.Value; + + if (string.IsNullOrWhiteSpace(meter.ItemCodes)) + { + _logger.LogError($"{nameof(CreatePublishTask)} 集中器{meter.FocusAddress}的电表{meter.Name}数据采集指令生成失败,采集项为空,-101"); + continue; + } + + //载波的不处理 + if (meter.MeteringPort == (int)MeterLinkProtocolEnum.Carrierwave) + { + _logger.LogError($"{nameof(CreatePublishTask)} 集中器{meter.FocusAddress}的电表{meter.Name}数据采集指令生成失败,载波不处理,-102"); + continue; + } + + if (meter.State.Equals(2)) + { + _logger.LogWarning($"{nameof(CreatePublishTask)} {meter.Name} 集中器{meter.FocusAddress}的电表{meter.Name}状态为禁用,不处理"); + continue; + } + + //排除1天未在线的集中器生成指令 或 排除集中器配置为自动上报的集中器 + if (!IsGennerateCmd(meter.LastTime, -1)) + { + _logger.LogInformation($"{nameof(CreatePublishTask)} 集中器{meter.FocusAddress}的电表{meter.Name},采集时间:{meter.LastTime},已超过1天未在线,不生成指令"); + continue; + } + + if (string.IsNullOrWhiteSpace(meter.AreaCode)) + { + _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{meter.ID},集中器通信区号为空"); + continue; + } + if (string.IsNullOrWhiteSpace(meter.Address)) + { + _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{meter.ID},集中器通信地址为空"); + continue; + } + if (Convert.ToInt32(meter.Address) > 65535) + { + _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{meter.ID},集中器通信地址无效,确保大于65535"); + continue; + } + if (meter.MeteringCode <= 0 || meter.MeteringCode > 2033) + { + _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{meter.ID},非有效测量点号({meter.MeteringCode})"); + continue; + } + + List tempCodes = meter.ItemCodes.Deserialize>()!; + + //TODO:自动上报数据只主动采集1类数据。 + if (meter.AutomaticReport.Equals(1)) + { + var tempSubCodes = new List(); + var tempItemCodes = string.Empty; + if (meter.ItemCodes.Contains("0C_49")) + tempItemCodes += "0C_49,"; + if (meter.ItemCodes.Contains("0C_149")) + tempItemCodes += "0C_149,"; + if (meter.ItemCodes.Contains("10_97")) + tempItemCodes += "10_97"; + + if (string.IsNullOrWhiteSpace(tempItemCodes)) + { + continue; + } + else + { + meter.ItemCodes = tempItemCodes; + } + } + + foreach (var tempItem in tempCodes) + { + //排除已发送日冻结和月冻结采集项配置 + if(DayFreezeCodes.Contains(tempItem)) + { + continue; + } + + if (MonthFreezeCodes.Contains(tempItem)) + { + continue; + } + + } + //排除已发送日冻结和月冻结采集项配置 + //if (!isSendDayFreeze) + meter.ItemCodes = meter.ItemCodes.Replace("0D_3", "").Replace("0D_4", "") + .Replace("0D_161", "").Replace("0D_162", "").Replace("0D_163", "").Replace("0D_164", "") + .Replace("0D_165", "").Replace("0D_166", "").Replace("0D_167", "").Replace("0D_168", "").Replace("0C_149", ""); + + //if (!isSendMonthFreeze) + meter.ItemCodes = meter.ItemCodes.Replace("0D_177", "").Replace("0D_178", "").Replace("0D_179", "").Replace("0D_180", "") + .Replace("0D_181", "").Replace("0D_181", "").Replace("0D_182", "").Replace("0D_183", "").Replace("0D_184", "") + .Replace("0D_193", "").Replace("0D_195", ""); + + + + //TODO:特殊表 + + + //var itemCodeArr = itemCode.Split('_'); + //var aFN = (AFN)itemCodeArr[0].HexToDec(); + //var fn = int.Parse(itemCodeArr[1]); + //if (aFN == AFN.请求实时数据) + //{ + // var bytes = Build3761SendData.BuildAmmeterReadRealTimeDataSendCmd(address, ammeter.MeterCode.Value, (ATypeOfDataItems)fn); + // bytesList.Add(bytes); + //} + //else if (aFN == AFN.请求历史数据) + //{ + // var density = (FreezeDensity)input.Density; + // var bytes = Build3761SendData.BuildAmmeterReadingIIdataTypeItemsSendCmd(address, ammeter.MeterCode.Value, (IIdataTypeItems)fn, density, 0); + // bytesList.Add(bytes); + //} + + } + } + + string deviceNo = ""; + string messageHexString = ""; + + var messageReceivedHeartbeatEvent = new MessageReceivedHeartbeat + { + //ClientId = client.Id, + //ClientIp = client.IP, + //ClientPort = client.Port, + MessageHexString = messageHexString, + DeviceNo = deviceNo, + MessageId = NewId.NextGuid().ToString() + }; + await _capBus.PublishAsync(eventName, messageReceivedHeartbeatEvent); + } + + /// + /// 指定时间对比当前时间 + /// + /// + /// + /// + private bool IsGennerateCmd(DateTime lastTime, int subtrahend = 0) + { + if (DateTime.Now.AddDays(subtrahend) >= lastTime)//当前时间减去一天,大于等于最后在线时间,不再生成该集中器下表生成采集指令 + return false; + return true; + } + + #endregion + } +} diff --git a/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs similarity index 88% rename from src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs rename to src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 07e0264..2d0fd19 100644 --- a/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -1,10 +1,12 @@ using DotNetCore.CAP; using FreeRedis; +using FreeSql; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Devices; using JiShe.CollectBus.FreeRedisProvider; using JiShe.CollectBus.FreeSql; +using JiShe.CollectBus.GatherItem; using JiShe.CollectBus.Watermeter; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -34,6 +36,25 @@ namespace JiShe.CollectBus.Workers public sealed override string SystemType => SystemTypeConst.Energy; + /// + /// 获取采集项列表 + /// + /// + public override async Task> GetGatherItemByDataTypes() + { + try + { + string sql = $"SELECT DataType,ItemCode FROM TB_GatherItem(NOLOCK) WHERE [State]=0"; + return await SqlProvider.Instance.Change(DbEnum.EnergyDB) + .Ado + .QueryAsync(sql, null); + } + catch + { + return null; + } + } + /// /// 获取电表信息 /// diff --git a/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs rename to src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs diff --git a/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs deleted file mode 100644 index 680354f..0000000 --- a/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs +++ /dev/null @@ -1,358 +0,0 @@ -using DotNetCore.CAP; -using FreeRedis; -using JiShe.CollectBus.Ammeters; -using JiShe.CollectBus.Common.Consts; -using JiShe.CollectBus.Common.Helpers; -using JiShe.CollectBus.Watermeter; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; -using static FreeSql.Internal.GlobalFilter; - -namespace JiShe.CollectBus.Workers -{ - /// - /// 定时采集服务 - /// - public abstract class BasicScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService - { - private readonly ILogger _logger; - private readonly ICapPublisher _capBus; - public BasicScheduledMeterReadingService(ILogger logger, ICapPublisher capBus) - { - _capBus = capBus; - _logger = logger; - } - - /// - /// 系统类型 - /// - public abstract string SystemType { get; } - - #region 电表采集处理 - - /// - /// 获取电表信息 - /// - /// 采集端Code - /// - public virtual Task> GetAmmeterInfoList(string gatherCode = "") - { - throw new NotImplementedException($"{nameof(GetAmmeterInfoList)}请根据不同系统类型进行实现"); - } - - /// - /// 初始化电表缓存数据 - /// - /// 采集端Code - /// - public virtual async Task InitAmmeterCacheData(string gatherCode = "") - { - var meterInfos = await GetAmmeterInfoList(gatherCode); - if (meterInfos == null || meterInfos.Count <= 0) - { - throw new NullReferenceException($"{nameof(InitWatermeterCacheData)} 初始化电表缓存数据时,电表数据为空"); - } - - //根据采集频率分组 - var meterInfoGroupByTimeDensity = meterInfos.GroupBy(d => d.TimeDensity); - foreach (var itemTimeDensity in meterInfoGroupByTimeDensity) - { - //将表计信息根据集中器分组 - var meterInfoGroup = itemTimeDensity.GroupBy(x => x.FocusAddress).ToList(); - foreach (var item in meterInfoGroup) - { - if (string.IsNullOrWhiteSpace(item.Key)) - { - continue; - } - - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, itemTimeDensity.Key)}{item.Key}"; - Dictionary keyValuePairs = new Dictionary(); - foreach (var subItem in item) - { - - keyValuePairs.TryAdd($"{subItem.ID}", subItem); - } - await FreeRedisProvider.FreeRedis.HSetAsync(redisCacheKey, keyValuePairs); - } - } - - _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据完成"); - } - - /// - /// 1分钟采集电表数据 - /// - /// - public virtual async Task AmmeterScheduledMeterOneMinuteReading() - { - //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 1)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-101"); - return; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = await GetMeterCacheData(oneMinutekeyList); - if (meterInfos == null || meterInfos.Count <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); - return; - } - - _logger.LogInformation($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理完成"); - - } - - /// - /// 5分钟采集电表数据 - /// - /// - public virtual async Task AmmeterScheduledMeterFiveMinuteReading() - { - //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 5)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理时没有获取到缓存信息,-101"); - return; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = await GetMeterCacheData(oneMinutekeyList); - if (meterInfos == null || meterInfos.Count <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理时没有获取到缓存信息,-102"); - return; - } - - _logger.LogInformation($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理完成"); - } - - /// - /// 15分钟采集电表数据 - /// - /// - public virtual async Task AmmeterScheduledMeterFifteenMinuteReading() - { - //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 15)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理时没有获取到缓存信息,-101"); - return; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = await GetMeterCacheData(oneMinutekeyList); - if (meterInfos == null || meterInfos.Count <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理时没有获取到缓存信息,-102"); - return; - } - - _logger.LogInformation($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理完成"); - } - #endregion - - - #region 水表采集处理 - - /// - /// 获取水表信息 - /// - /// 采集端Code - /// - public virtual Task> GetWatermeterInfoList(string gatherCode = "") - { - throw new NotImplementedException($"{nameof(GetWatermeterInfoList)}请根据不同系统类型进行实现"); - } - - /// - /// 初始化水表缓存数据 - /// - /// 采集端Code - /// - public virtual async Task InitWatermeterCacheData(string gatherCode = "") - { - var meterInfos = await GetWatermeterInfoList(gatherCode); - if (meterInfos == null || meterInfos.Count <= 0) - { - throw new NullReferenceException($"{nameof(InitWatermeterCacheData)} 初始化水表缓存数据时,水表数据为空"); - } - //根据采集频率分组 - var meterInfoGroupByTimeDensity = meterInfos.GroupBy(d => d.TimeDensity); - foreach (var itemTimeDensity in meterInfoGroupByTimeDensity) - { - //将表计信息根据集中器分组 - var meterInfoGroup = itemTimeDensity.GroupBy(x => x.FocusAddress).ToList(); - foreach (var item in meterInfoGroup) - { - if (string.IsNullOrWhiteSpace(item.Key)) - { - continue; - } - - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, itemTimeDensity.Key)}{item.Key}"; - Dictionary keyValuePairs = new Dictionary(); - foreach (var subItem in item) - { - - keyValuePairs.TryAdd($"{subItem.ID}", subItem); - } - await FreeRedisProvider.FreeRedis.HSetAsync(redisCacheKey, keyValuePairs); - } - } - _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化水表缓存数据完成"); - } - - /// - /// 1分钟采集水表数据 - /// - /// - public virtual async Task WatermeterScheduledMeterOneMinuteReading() - { - //获取缓存中的水表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 1)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表据处理时没有获取到缓存信息,-101"); - return; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = await GetMeterCacheData(oneMinutekeyList); - if (meterInfos == null || meterInfos.Count <= 0) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表数据处理时没有获取到缓存信息,-102"); - return; - } - - _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表数据处理完成"); - } - - /// - /// 5分钟采集电表数据 - /// - /// - public virtual async Task WatermeterScheduledMeterFiveMinuteReading() - { - - //获取缓存中的水表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 5)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表据处理时没有获取到缓存信息,-101"); - return; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = await GetMeterCacheData(oneMinutekeyList); - if (meterInfos == null || meterInfos.Count <= 0) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表数据处理时没有获取到缓存信息,-102"); - return; - } - - _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表数据处理完成"); - } - - /// - /// 15分钟采集电表数据 - /// - /// - public virtual async Task WatermeterScheduledMeterFifteenMinuteReading() - { - //获取缓存中的水表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 15)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表据处理时没有获取到缓存信息,-101"); - return; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = await GetMeterCacheData(oneMinutekeyList); - if (meterInfos == null || meterInfos.Count <= 0) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表数据处理时没有获取到缓存信息,-102"); - return; - } - - _logger.LogInformation($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表数据处理完成"); - } - #endregion - - - #region 公共处理方法 - /// - /// 批量获取缓存的表计信息 - /// - /// - /// - /// - private async Task> GetMeterCacheData(string[] redisKeys) - { - //通过lua脚本一次性获取所有缓存内容 - var luaScript = @" - local results = {} - for i, key in ipairs(KEYS) do - local data = redis.call('HGETALL', key) - results[i] = {key, data} - end - return results"; - var oneMinuteAmmerterResult = await FreeRedisProvider.FreeRedis.EvalAsync(luaScript, redisKeys); //传递 KEYS - if (oneMinuteAmmerterResult == null) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); - return null; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = new List(); - if (oneMinuteAmmerterResult is object[] arr) - { - foreach (object[] item in arr) - { - string key = (string)item[0]; - object[] fieldsAndValues = (object[])item[1]; - - for (int i = 0; i < fieldsAndValues.Length; i += 2) - { - string field = (string)fieldsAndValues[i]; - string valueStr = (string)fieldsAndValues[i + 1]; - T value = default; - if (!string.IsNullOrWhiteSpace(valueStr)) - { - value = valueStr.Deserialize()!; - } - if (value != null) - { - meterInfos.Add(value); - } - else - { - _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表{key}数据{field}处理异常"); - } - } - } - } - - return meterInfos; - } - #endregion - } -} diff --git a/src/JiShe.CollectBus.Common/Enums/MeterLinkProtocolEnum.cs b/src/JiShe.CollectBus.Common/Enums/MeterLinkProtocolEnum.cs new file mode 100644 index 0000000..637acf9 --- /dev/null +++ b/src/JiShe.CollectBus.Common/Enums/MeterLinkProtocolEnum.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Enums +{ + /// + /// 表计连接通讯协议--表计与集中器的通讯协议 + /// + public enum MeterLinkProtocolEnum + { + /// + /// 无 + /// + None = 0, + + /// + /// DL/T 645—1997 + /// + DLT_645_1997 = 1, + + /// + /// 交流采样装置通信协议(电表) + /// + ACSamplingDevice = 2, + + /// + /// DL/T 645—2007 + /// + DLT_645_2007 = 30, + + /// + /// 载波通信 + /// + Carrierwave = 31, + + /// + /// CJ/T 188—2018协议(水表) + /// + CJT_188_2018 = 32, + + /// + /// CJ/T 188—2004协议 + /// + CJT_188_2004 = 33, + + /// + /// MODBUS-RTU + /// + MODBUS_RTU = 34, + } +} diff --git a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs index 7dce8ac..f62ba92 100644 --- a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs +++ b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs @@ -87,7 +87,7 @@ namespace JiShe.CollectBus.Ammeters public int TimeDensity { get; set; } /// - /// 该电表方案下采集项,如:0D_80 + /// 该电表方案下采集项,JSON格式,如:["0D_80","0D_80"] /// public string ItemCodes { get; set; } diff --git a/src/JiShe.CollectBus.Domain/GatherItem/GatherItemInfo.cs b/src/JiShe.CollectBus.Domain/GatherItem/GatherItemInfo.cs new file mode 100644 index 0000000..da16012 --- /dev/null +++ b/src/JiShe.CollectBus.Domain/GatherItem/GatherItemInfo.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.GatherItem +{ + public class GatherItemInfo + { + /// + /// 数据类型 + /// + public string DataType { get; set; } + + /// + /// 采集项编码 + /// + public string ItemCode { get; set; } + } +} diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 3a0390a..56d3fca 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -224,7 +224,7 @@ namespace JiShe.CollectBus.Host { config.SetListenIPHosts(int.Parse(configuration["TCP:ClientPort"] ?? "10500")) //.SetTcpDataHandlingAdapter(()=>new StandardFixedHeaderDataHandlingAdapter()) - //.SetGetDefaultNewId(() => Guid.NewGuid().ToString())//定义ClinetId的生成策略 + //.SetGetDefaultNewId(() => Guid.NewGuid().ToString())//定义ClientId的生成策略 .ConfigurePlugins(a => { a.Add(); diff --git a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs index ee2cddf..844a50b 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs @@ -14,6 +14,7 @@ namespace JiShe.CollectBus.Protocol.Contracts public const string SubscriberReceivedHeartbeatEventName = "received.heartbeat.event"; public const string SubscriberReceivedLoginEventName = "received.login.event"; + #region 电表消息主题 /// /// 1分钟采集电表数据下行消息主题 /// @@ -27,6 +28,18 @@ namespace JiShe.CollectBus.Protocol.Contracts /// public const string AmmeterSubscriberWorkerFifteenMinuteIssuedEventName = "issued.fifteen.ammeter.event"; + /// + /// 其他采集数据下行消息主题,日冻结,月冻结、集中器版本号、定时阀控等 + /// + public const string AmmeterSubscriberWorkerOtherIssuedEventName = "issued.other.ammeter.event"; + + /// + /// 电表手动阀控 + /// + public const string AmmeterSubscriberWorkerManualValveControlIssuedEventName = "issued.control.ammeter.event"; + #endregion + + #region 水表消息主题 /// /// 1分钟采集水表数据下行消息主题 /// @@ -40,5 +53,18 @@ namespace JiShe.CollectBus.Protocol.Contracts /// public const string WatermeterSubscriberWorkerFifteenMinuteIssuedEventName = "issued.fifteen.watermeter.event"; + /// + /// 其他采集数据下行消息主题,日冻结,月冻结、集中器版本号、定时阀控等 + /// + public const string WatermeterSubscriberWorkerOtherIssuedEventName = "issued.other.watermeter.event"; + + /// + /// 水表手动阀控 + /// + public const string WatermeterSubscriberWorkerManualValveControlIssuedEventName = "issued.control.watermeter.event"; + #endregion + + + } } -- 2.47.2 From e0b055c612a66a9b0f86540b75663cc4d25362f0 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Fri, 14 Mar 2025 14:28:04 +0800 Subject: [PATCH 012/139] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Subscribers/ISubscriberAppService.cs | 2 +- .../Workers/IScheduledMeterReadingService.cs | 43 +--- .../Consumers/IssuedConsumer.cs | 4 +- .../Consumers/IssuedFaultConsumer.cs | 2 +- .../Consumers/ReceivedConsumer.cs | 2 +- .../Consumers/ReceivedFaultConsumer.cs | 2 +- .../Consumers/ReceivedHeartbeatConsumer.cs | 2 +- .../Consumers/ReceivedLoginConsumer.cs | 2 +- .../EnergySystem/EnergySystemAppService.cs | 4 +- .../Plugins/TcpMonitor.cs | 4 +- .../Samples/SampleAppService.cs | 2 +- .../Subscribers/SubscriberAppService.cs | 4 +- .../BasicScheduledMeterReadingService.cs | 220 +++++------------- ...nergySystemScheduledMeterReadingService.cs | 3 +- .../Workers/WorkerSubscriberAppService.cs | 2 +- .../TableViews/V_FocusAmmeter.cs | 13 ++ .../{ => IotSystems}/Devices/Device.cs | 2 +- .../MessageIssueds/MessageIssued.cs | 2 +- .../MessageReceiveds/IReceived.cs | 2 +- .../MessageReceiveds/MessageReceived.cs | 2 +- .../PrepayModel/Vi_BaseAmmeterInfo.cs | 129 ++++++++++ .../Protocols/ProtocolInfo.cs | 2 +- .../Records/ConrOnlineRecord.cs | 2 +- .../{ => IotSystems}/Records/CsqRecord.cs | 2 +- .../{ => IotSystems}/Records/FocusRecord.cs | 2 +- .../Watermeter/WatermeterInfo.cs | 2 +- .../JiShe.CollectBus.Domain.csproj | 10 +- .../PrepayModel/Vi_BaseAmmeterInfo.cs | 129 ---------- .../CollectBusHostModule.Configure.cs | 2 +- .../MongoDB/CollectBusMongoDbContext.cs | 6 +- .../Abstracts/BaseProtocolPlugin.cs | 4 +- .../Interfaces/IProtocolPlugin.cs | 4 +- .../StandardProtocolPlugin.cs | 4 +- 33 files changed, 247 insertions(+), 370 deletions(-) create mode 100644 src/JiShe.CollectBus.Domain/EnergySystems/TableViews/V_FocusAmmeter.cs rename src/JiShe.CollectBus.Domain/{ => IotSystems}/Devices/Device.cs (97%) rename src/JiShe.CollectBus.Domain/{ => IotSystems}/MessageIssueds/MessageIssued.cs (89%) rename src/JiShe.CollectBus.Domain/{ => IotSystems}/MessageReceiveds/IReceived.cs (60%) rename src/JiShe.CollectBus.Domain/{ => IotSystems}/MessageReceiveds/MessageReceived.cs (96%) create mode 100644 src/JiShe.CollectBus.Domain/IotSystems/PrepayModel/Vi_BaseAmmeterInfo.cs rename src/JiShe.CollectBus.Domain/{ => IotSystems}/Protocols/ProtocolInfo.cs (96%) rename src/JiShe.CollectBus.Domain/{ => IotSystems}/Records/ConrOnlineRecord.cs (95%) rename src/JiShe.CollectBus.Domain/{ => IotSystems}/Records/CsqRecord.cs (95%) rename src/JiShe.CollectBus.Domain/{ => IotSystems}/Records/FocusRecord.cs (97%) rename src/JiShe.CollectBus.Domain/{ => IotSystems}/Watermeter/WatermeterInfo.cs (98%) delete mode 100644 src/JiShe.CollectBus.Domain/PrepayModel/Vi_BaseAmmeterInfo.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs b/src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs index bf7dec1..22c79e6 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MessageReceiveds; using Volo.Abp.Application.Services; namespace JiShe.CollectBus.Subscribers diff --git a/src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs index bd1a090..fa857d3 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Workers/IScheduledMeterReadingService.cs @@ -1,5 +1,5 @@ using JiShe.CollectBus.Ammeters; -using JiShe.CollectBus.Watermeter; +using JiShe.CollectBus.IotSystems.Watermeter; using System; using System.Collections.Generic; using System.Linq; @@ -15,15 +15,13 @@ namespace JiShe.CollectBus.Workers /// public interface IScheduledMeterReadingService : IApplicationService { - - #region 电表采集处理 /// /// 获取电表信息 /// /// 采集端Code /// Task> GetAmmeterInfoList(string gatherCode = ""); - + /// /// 初始化电表缓存数据 /// @@ -31,28 +29,6 @@ namespace JiShe.CollectBus.Workers /// Task InitAmmeterCacheData(string gatherCode = ""); - /// - /// 1分钟采集电表数据 - /// - /// - Task AmmeterScheduledMeterOneMinuteReading(); - - /// - /// 5分钟采集电表数据 - /// - /// - Task AmmeterScheduledMeterFiveMinuteReading(); - - /// - /// 15分钟采集电表数据 - /// - /// - Task AmmeterScheduledMeterFifteenMinuteReading(); - - #endregion - - - #region 水表采集处理 /// /// 获取水表信息 /// @@ -68,24 +44,21 @@ namespace JiShe.CollectBus.Workers Task InitWatermeterCacheData(string gatherCode = ""); /// - /// 1分钟采集水表数据 + /// 1分钟采集电表数据 /// /// - Task WatermeterScheduledMeterOneMinuteReading(); + Task ScheduledMeterOneMinuteReading(); /// - /// 5分钟采集水表数据 + /// 5分钟采集电表数据 /// /// - Task WatermeterScheduledMeterFiveMinuteReading(); + Task ScheduledMeterFiveMinuteReading(); /// - /// 15分钟采集水表数据 + /// 15分钟采集电表数据 /// /// - Task WatermeterScheduledMeterFifteenMinuteReading(); - #endregion - - + Task ScheduledMeterFifteenMinuteReading(); } } diff --git a/src/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs b/src/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs index 710a268..11c27dd 100644 --- a/src/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs +++ b/src/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs @@ -1,8 +1,8 @@ using System; using System.Threading.Tasks; using JiShe.CollectBus.Common.Enums; -using JiShe.CollectBus.MessageIssueds; -using JiShe.CollectBus.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MessageIssueds; +using JiShe.CollectBus.IotSystems.MessageReceiveds; using MassTransit; using Microsoft.Extensions.Logging; using TouchSocket.Sockets; diff --git a/src/JiShe.CollectBus.Application/Consumers/IssuedFaultConsumer.cs b/src/JiShe.CollectBus.Application/Consumers/IssuedFaultConsumer.cs index 903beac..9bc9983 100644 --- a/src/JiShe.CollectBus.Application/Consumers/IssuedFaultConsumer.cs +++ b/src/JiShe.CollectBus.Application/Consumers/IssuedFaultConsumer.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; -using JiShe.CollectBus.MessageIssueds; +using JiShe.CollectBus.IotSystems.MessageIssueds; using MassTransit; namespace JiShe.CollectBus.Consumers diff --git a/src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs b/src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs index 3dc22e3..4e00864 100644 --- a/src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs +++ b/src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using JiShe.CollectBus.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using MassTransit; using Microsoft.Extensions.DependencyInjection; diff --git a/src/JiShe.CollectBus.Application/Consumers/ReceivedFaultConsumer.cs b/src/JiShe.CollectBus.Application/Consumers/ReceivedFaultConsumer.cs index e569769..60bbdfc 100644 --- a/src/JiShe.CollectBus.Application/Consumers/ReceivedFaultConsumer.cs +++ b/src/JiShe.CollectBus.Application/Consumers/ReceivedFaultConsumer.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; -using JiShe.CollectBus.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MessageReceiveds; using MassTransit; namespace JiShe.CollectBus.Consumers diff --git a/src/JiShe.CollectBus.Application/Consumers/ReceivedHeartbeatConsumer.cs b/src/JiShe.CollectBus.Application/Consumers/ReceivedHeartbeatConsumer.cs index ce5233c..c2317c6 100644 --- a/src/JiShe.CollectBus.Application/Consumers/ReceivedHeartbeatConsumer.cs +++ b/src/JiShe.CollectBus.Application/Consumers/ReceivedHeartbeatConsumer.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using MassTransit; using Microsoft.Extensions.DependencyInjection; diff --git a/src/JiShe.CollectBus.Application/Consumers/ReceivedLoginConsumer.cs b/src/JiShe.CollectBus.Application/Consumers/ReceivedLoginConsumer.cs index f17fe8e..ce67886 100644 --- a/src/JiShe.CollectBus.Application/Consumers/ReceivedLoginConsumer.cs +++ b/src/JiShe.CollectBus.Application/Consumers/ReceivedLoginConsumer.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using MassTransit; using Microsoft.Extensions.DependencyInjection; diff --git a/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs b/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs index 9c4b653..07db264 100644 --- a/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs +++ b/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs @@ -12,9 +12,9 @@ using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.EnergySystem.Dto; using JiShe.CollectBus.FreeSql; -using JiShe.CollectBus.PrepayModel; +using JiShe.CollectBus.IotSystems.PrepayModel; +using JiShe.CollectBus.IotSystems.Records; using JiShe.CollectBus.Protocol.Contracts; -using JiShe.CollectBus.Records; using MassTransit; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index d5c95c6..8ff138a 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -4,10 +4,10 @@ using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; -using JiShe.CollectBus.Devices; using JiShe.CollectBus.Enums; using JiShe.CollectBus.Interceptors; -using JiShe.CollectBus.MessageReceiveds; +using JiShe.CollectBus.IotSystems.Devices; +using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.Protocol.Contracts; using MassTransit; using Microsoft.Extensions.Logging; diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 5c0dae8..6235a55 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using JiShe.CollectBus.FreeSql; -using JiShe.CollectBus.PrepayModel; +using JiShe.CollectBus.IotSystems.PrepayModel; using Microsoft.AspNetCore.Authorization; namespace JiShe.CollectBus.Samples; diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index d5cc55e..3c47b83 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -3,8 +3,8 @@ using System.Threading.Tasks; using DotNetCore.CAP; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.Devices; -using JiShe.CollectBus.MessageReceiveds; +using JiShe.CollectBus.IotSystems.Devices; +using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using Microsoft.Extensions.DependencyInjection; diff --git a/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs index 680354f..cbd5d9e 100644 --- a/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/Workers/BasicScheduledMeterReadingService.cs @@ -1,9 +1,7 @@ -using DotNetCore.CAP; -using FreeRedis; +using FreeRedis; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; -using JiShe.CollectBus.Common.Helpers; -using JiShe.CollectBus.Watermeter; +using JiShe.CollectBus.IotSystems.Watermeter; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -21,10 +19,8 @@ namespace JiShe.CollectBus.Workers public abstract class BasicScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService { private readonly ILogger _logger; - private readonly ICapPublisher _capBus; - public BasicScheduledMeterReadingService(ILogger logger, ICapPublisher capBus) + public BasicScheduledMeterReadingService(ILogger logger) { - _capBus = capBus; _logger = logger; } @@ -94,21 +90,48 @@ namespace JiShe.CollectBus.Workers //获取缓存中的电表信息 var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 1)}*"; var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + if (oneMinutekeyList == null || oneMinutekeyList.Length <=0) { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-101"); - return; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = await GetMeterCacheData(oneMinutekeyList); - if (meterInfos == null || meterInfos.Count <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); + _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); return; } - _logger.LogInformation($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理完成"); + //通过lua脚本一次性获取所有缓存内容 + var luaScript = @" + local results = {} + for i, key in ipairs(KEYS) do + local data = redis.call('HGETALL', key) + results[i] = {key, data} + end + return results"; + var oneMinuteAmmerterResult = FreeRedisProvider.FreeRedis.Eval(luaScript, oneMinutekeyList); // 传递 KEYS + if (oneMinuteAmmerterResult == null) + { + _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); + return; + } + + // 解析结果(结果为嵌套数组) + var parsedResults = new Dictionary>(); + if (oneMinuteAmmerterResult is object[] arr) + { + foreach (object[] item in arr) + { + string key = (string)item[0]; + object[] fieldsAndValues = (object[])item[1]; + + var dict = new Dictionary(); + for (int i = 0; i < fieldsAndValues.Length; i += 2) + { + string field = (string)fieldsAndValues[i]; + string value = (string)fieldsAndValues[i + 1]; + dict[field] = value; + } + parsedResults[key] = dict; + } + } + + _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理完成"); } @@ -116,52 +139,18 @@ namespace JiShe.CollectBus.Workers /// 5分钟采集电表数据 /// /// - public virtual async Task AmmeterScheduledMeterFiveMinuteReading() + public virtual Task AmmeterScheduledMeterFiveMinuteReading() { - //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 5)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理时没有获取到缓存信息,-101"); - return; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = await GetMeterCacheData(oneMinutekeyList); - if (meterInfos == null || meterInfos.Count <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理时没有获取到缓存信息,-102"); - return; - } - - _logger.LogInformation($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理完成"); + throw new NotImplementedException($"{nameof(AmmeterScheduledMeterFiveMinuteReading)}请根据不同系统类型进行实现"); } /// /// 15分钟采集电表数据 /// /// - public virtual async Task AmmeterScheduledMeterFifteenMinuteReading() + public virtual Task AmmeterScheduledMeterFifteenMinuteReading() { - //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 15)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理时没有获取到缓存信息,-101"); - return; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = await GetMeterCacheData(oneMinutekeyList); - if (meterInfos == null || meterInfos.Count <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理时没有获取到缓存信息,-102"); - return; - } - - _logger.LogInformation($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理完成"); + throw new NotImplementedException($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)}请根据不同系统类型进行实现"); } #endregion @@ -222,22 +211,9 @@ namespace JiShe.CollectBus.Workers /// public virtual async Task WatermeterScheduledMeterOneMinuteReading() { - //获取缓存中的水表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 1)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表据处理时没有获取到缓存信息,-101"); - return; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = await GetMeterCacheData(oneMinutekeyList); - if (meterInfos == null || meterInfos.Count <= 0) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表数据处理时没有获取到缓存信息,-102"); - return; - } + //获取缓存中的电表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 1)}*"; + var oneMinuteList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表数据处理完成"); } @@ -246,113 +222,21 @@ namespace JiShe.CollectBus.Workers /// 5分钟采集电表数据 /// /// - public virtual async Task WatermeterScheduledMeterFiveMinuteReading() + public virtual Task WatermeterScheduledMeterFiveMinuteReading() { - //获取缓存中的水表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 5)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表据处理时没有获取到缓存信息,-101"); - return; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = await GetMeterCacheData(oneMinutekeyList); - if (meterInfos == null || meterInfos.Count <= 0) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表数据处理时没有获取到缓存信息,-102"); - return; - } - - _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表数据处理完成"); + throw new NotImplementedException($"{nameof(WatermeterScheduledMeterFiveMinuteReading)}请根据不同系统类型进行实现"); } /// /// 15分钟采集电表数据 /// /// - public virtual async Task WatermeterScheduledMeterFifteenMinuteReading() + public virtual Task WatermeterScheduledMeterFifteenMinuteReading() { - //获取缓存中的水表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 15)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表据处理时没有获取到缓存信息,-101"); - return; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = await GetMeterCacheData(oneMinutekeyList); - if (meterInfos == null || meterInfos.Count <= 0) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表数据处理时没有获取到缓存信息,-102"); - return; - } - - _logger.LogInformation($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表数据处理完成"); + throw new NotImplementedException($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)}请根据不同系统类型进行实现"); } #endregion - - #region 公共处理方法 - /// - /// 批量获取缓存的表计信息 - /// - /// - /// - /// - private async Task> GetMeterCacheData(string[] redisKeys) - { - //通过lua脚本一次性获取所有缓存内容 - var luaScript = @" - local results = {} - for i, key in ipairs(KEYS) do - local data = redis.call('HGETALL', key) - results[i] = {key, data} - end - return results"; - var oneMinuteAmmerterResult = await FreeRedisProvider.FreeRedis.EvalAsync(luaScript, redisKeys); //传递 KEYS - if (oneMinuteAmmerterResult == null) - { - _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); - return null; - } - - // 解析结果(结果为嵌套数组) - List meterInfos = new List(); - if (oneMinuteAmmerterResult is object[] arr) - { - foreach (object[] item in arr) - { - string key = (string)item[0]; - object[] fieldsAndValues = (object[])item[1]; - - for (int i = 0; i < fieldsAndValues.Length; i += 2) - { - string field = (string)fieldsAndValues[i]; - string valueStr = (string)fieldsAndValues[i + 1]; - T value = default; - if (!string.IsNullOrWhiteSpace(valueStr)) - { - value = valueStr.Deserialize()!; - } - if (value != null) - { - meterInfos.Add(value); - } - else - { - _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集电表{key}数据{field}处理异常"); - } - } - } - } - - return meterInfos; - } - #endregion } } diff --git a/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs index 07e0264..09ea16e 100644 --- a/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/Workers/EnergySystemScheduledMeterReadingService.cs @@ -2,10 +2,9 @@ using FreeRedis; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; -using JiShe.CollectBus.Devices; using JiShe.CollectBus.FreeRedisProvider; using JiShe.CollectBus.FreeSql; -using JiShe.CollectBus.Watermeter; +using JiShe.CollectBus.IotSystems.Watermeter; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; diff --git a/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs index 552f964..275886e 100644 --- a/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Workers/WorkerSubscriberAppService.cs @@ -4,7 +4,7 @@ using DeviceDetectorNET.Parser.Device; using DotNetCore.CAP; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.Devices; +using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.MessageReceiveds; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Protocol.Contracts.Interfaces; diff --git a/src/JiShe.CollectBus.Domain/EnergySystems/TableViews/V_FocusAmmeter.cs b/src/JiShe.CollectBus.Domain/EnergySystems/TableViews/V_FocusAmmeter.cs new file mode 100644 index 0000000..5ff30d6 --- /dev/null +++ b/src/JiShe.CollectBus.Domain/EnergySystems/TableViews/V_FocusAmmeter.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.EnergySystems.TableViews +{ + public class V_FocusAmmeter + { + + } +} diff --git a/src/JiShe.CollectBus.Domain/Devices/Device.cs b/src/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs similarity index 97% rename from src/JiShe.CollectBus.Domain/Devices/Device.cs rename to src/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs index 4fb015a..c31dbc9 100644 --- a/src/JiShe.CollectBus.Domain/Devices/Device.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using JiShe.CollectBus.Enums; using Volo.Abp.Domain.Entities; -namespace JiShe.CollectBus.Devices +namespace JiShe.CollectBus.IotSystems.Devices { public class Device : AggregateRoot { diff --git a/src/JiShe.CollectBus.Domain/MessageIssueds/MessageIssued.cs b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs similarity index 89% rename from src/JiShe.CollectBus.Domain/MessageIssueds/MessageIssued.cs rename to src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs index 45f53b1..42d740c 100644 --- a/src/JiShe.CollectBus.Domain/MessageIssueds/MessageIssued.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using JiShe.CollectBus.Common.Enums; -namespace JiShe.CollectBus.MessageIssueds +namespace JiShe.CollectBus.IotSystems.MessageIssueds { public class MessageIssued { diff --git a/src/JiShe.CollectBus.Domain/MessageReceiveds/IReceived.cs b/src/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/IReceived.cs similarity index 60% rename from src/JiShe.CollectBus.Domain/MessageReceiveds/IReceived.cs rename to src/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/IReceived.cs index 40c6a68..1f4c97f 100644 --- a/src/JiShe.CollectBus.Domain/MessageReceiveds/IReceived.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/IReceived.cs @@ -1,4 +1,4 @@ -namespace JiShe.CollectBus.MessageReceiveds +namespace JiShe.CollectBus.IotSystems.MessageReceiveds { public interface IReceived { diff --git a/src/JiShe.CollectBus.Domain/MessageReceiveds/MessageReceived.cs b/src/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/MessageReceived.cs similarity index 96% rename from src/JiShe.CollectBus.Domain/MessageReceiveds/MessageReceived.cs rename to src/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/MessageReceived.cs index 4503873..4fad589 100644 --- a/src/JiShe.CollectBus.Domain/MessageReceiveds/MessageReceived.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/MessageReceived.cs @@ -1,7 +1,7 @@ using System; using Volo.Abp.Domain.Entities; -namespace JiShe.CollectBus.MessageReceiveds +namespace JiShe.CollectBus.IotSystems.MessageReceiveds { public class MessageReceived: AggregateRoot,IReceived { diff --git a/src/JiShe.CollectBus.Domain/IotSystems/PrepayModel/Vi_BaseAmmeterInfo.cs b/src/JiShe.CollectBus.Domain/IotSystems/PrepayModel/Vi_BaseAmmeterInfo.cs new file mode 100644 index 0000000..b3ff165 --- /dev/null +++ b/src/JiShe.CollectBus.Domain/IotSystems/PrepayModel/Vi_BaseAmmeterInfo.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IotSystems.PrepayModel +{ + /// + /// 预付费电表视图模型 + /// + public partial class Vi_BaseAmmeterInfo + { + public int ID { get; set; } + public string BarCode { get; set; } + public string Address { get; set; } + public string BaudRate { get; set; } + public string Password { get; set; } + public string Explan { get; set; } + public DateTime AddTime { get; set; } + public bool State { get; set; } + public int TB_EquipmentTypeID { get; set; } + public int BaseID { get; set; } + public string Code { get; set; } + public int? MeterCode { get; set; } + public int? PortNumber { get; set; } + public int CT { get; set; } + public bool SoftTripsLock { get; set; } + public bool HardwareTripsLock { get; set; } + public bool ValveState { get; set; } + public bool ArchivesState { get; set; } + public int? SortNumber { get; set; } + public decimal Balance { get; set; } + public bool IsAlarm { get; set; } + public string sysExplan { get; set; } + public DateTime sysAddTime { get; set; } + public bool sysState { get; set; } + public int? TB_sysConcentratorID { get; set; } + public int? TB_sysChargingSchemeID { get; set; } + public int? TB_sysChargingSchemeID1 { get; set; } + public int TB_CustomerID { get; set; } + public int? TB_sysAlarmPlanID { get; set; } + public int? TB_sysCollectorID { get; set; } + public int? TB_sysRoomID { get; set; } + public string SpecialNoCode { get; set; } + public decimal? RatedCurrent { get; set; } + public int? TripDelayTime { get; set; } + public int? ClosingTime { get; set; } + public int? Remainder { get; set; } + public decimal? RatedPower { get; set; } + public bool? ParentAmmState { get; set; } + public int? ParentAmmID { get; set; } + public DateTime? LastUpdateTime { get; set; } + public bool? IsAuthentication { get; set; } + public bool? IsShowBalance { get; set; } + public string Number { get; set; } + public DateTime? LastAuthTime { get; set; } + public bool? IsAuthenticationFunction { get; set; } + public bool? IsShowBalanceFunction { get; set; } + public int TB_ProtocolID { get; set; } + public string Operator { get; set; } + public bool? IsTimingPowerSetting { get; set; } + public string CommSchemeCode { get; set; } + public string RoomAllName { get; set; } + public bool? IsLadderPrice { get; set; } + public int? LadderNum { get; set; } + public decimal? RoomBalance { get; set; } + public decimal? OtherBalance { get; set; } + public int? LastValveEventType { get; set; } + public decimal? ReadKwh4 { get; set; } + public decimal? ReadKwh3 { get; set; } + public decimal? ReadKwh2 { get; set; } + public decimal? ReadKwh1 { get; set; } + public decimal? ReadKwh { get; set; } + public DateTime? LastEventTime { get; set; } + public DateTime? LastReadTime { get; set; } + public string RoomNumber { get; set; } + public string EquipmentName { get; set; } + public int? EquipmentId { get; set; } + public bool? IsMalignantLoad { get; set; } + public string BrandName { get; set; } + public decimal? MalignantPower { get; set; } + public bool? MalignantPowerState { get; set; } + public bool? RatedPowerState { get; set; } + public string SolveStatus { get; set; } + public int? PowerDownStatus { get; set; } + public DateTime? SolveStatusTime { get; set; } + public decimal DisableKwh { get; set; } + public int? IsDeliver { get; set; } + public int? HandleUser { get; set; } + public string HandleUserName { get; set; } + public string Solutions { get; set; } + public decimal DisableKwh1 { get; set; } + public decimal DisableKwh2 { get; set; } + public decimal DisableKwh3 { get; set; } + public decimal DisableKwh4 { get; set; } + public bool DelaySwitchOff { get; set; } + public string MeterSolveStatus { get; set; } + public DateTime? DeliverTime { get; set; } + public bool EnableState { get; set; } + public string DxNbiotDeviceId { get; set; } + public int MeterStatus { get; set; } + public string AllowTripTime { get; set; } + public int? LastCostMilliSecond { get; set; } + public string DxNbiotIMEI { get; set; } + public decimal? ReadKwh5 { get; set; } + public decimal? ReadKwh6 { get; set; } + public decimal? ReadKwh7 { get; set; } + public decimal? ReadKwh8 { get; set; } + public decimal? DisableKwh5 { get; set; } + public decimal? DisableKwh6 { get; set; } + public decimal? DisableKwh7 { get; set; } + public decimal? DisableKwh8 { get; set; } + public bool? IsLadder { get; set; } + public int? TimeSpanSetNum { get; set; } + public string ExecutePeriod { get; set; } + public string FreqInterval { get; set; } + public int? Tb_sysUseValueAlarmID { get; set; } + public bool? IsMaxDemandKwh { get; set; } + public DateTime? OnLineTime { get; set; } + public int? OperatorId { get; set; } + public int PowerCT { get; set; } + public string CommunicationsModule { get; set; } + public bool? CanBlueTooth { get; set; } + public bool? CanRemote { get; set; } + public bool? IsDownPrice { get; set; } + public int? BuyCount { get; set; } + } +} diff --git a/src/JiShe.CollectBus.Domain/Protocols/ProtocolInfo.cs b/src/JiShe.CollectBus.Domain/IotSystems/Protocols/ProtocolInfo.cs similarity index 96% rename from src/JiShe.CollectBus.Domain/Protocols/ProtocolInfo.cs rename to src/JiShe.CollectBus.Domain/IotSystems/Protocols/ProtocolInfo.cs index 4ae8572..c193535 100644 --- a/src/JiShe.CollectBus.Domain/Protocols/ProtocolInfo.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/Protocols/ProtocolInfo.cs @@ -2,7 +2,7 @@ using JiShe.CollectBus.Interfaces; using Volo.Abp.Domain.Entities; -namespace JiShe.CollectBus.Protocols +namespace JiShe.CollectBus.IotSystems.Protocols { public class ProtocolInfo : AggregateRoot, IProtocolInfo { diff --git a/src/JiShe.CollectBus.Domain/Records/ConrOnlineRecord.cs b/src/JiShe.CollectBus.Domain/IotSystems/Records/ConrOnlineRecord.cs similarity index 95% rename from src/JiShe.CollectBus.Domain/Records/ConrOnlineRecord.cs rename to src/JiShe.CollectBus.Domain/IotSystems/Records/ConrOnlineRecord.cs index e62525f..1c07b01 100644 --- a/src/JiShe.CollectBus.Domain/Records/ConrOnlineRecord.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/Records/ConrOnlineRecord.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using Volo.Abp.Domain.Entities; -namespace JiShe.CollectBus.Records +namespace JiShe.CollectBus.IotSystems.Records { /// /// 集中器在线记录 diff --git a/src/JiShe.CollectBus.Domain/Records/CsqRecord.cs b/src/JiShe.CollectBus.Domain/IotSystems/Records/CsqRecord.cs similarity index 95% rename from src/JiShe.CollectBus.Domain/Records/CsqRecord.cs rename to src/JiShe.CollectBus.Domain/IotSystems/Records/CsqRecord.cs index e99235a..72b8638 100644 --- a/src/JiShe.CollectBus.Domain/Records/CsqRecord.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/Records/CsqRecord.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using Volo.Abp.Domain.Entities; -namespace JiShe.CollectBus.Records +namespace JiShe.CollectBus.IotSystems.Records { /// /// 信号强度 diff --git a/src/JiShe.CollectBus.Domain/Records/FocusRecord.cs b/src/JiShe.CollectBus.Domain/IotSystems/Records/FocusRecord.cs similarity index 97% rename from src/JiShe.CollectBus.Domain/Records/FocusRecord.cs rename to src/JiShe.CollectBus.Domain/IotSystems/Records/FocusRecord.cs index 6436e33..d94e3e0 100644 --- a/src/JiShe.CollectBus.Domain/Records/FocusRecord.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/Records/FocusRecord.cs @@ -6,7 +6,7 @@ using System.Text; using System.Threading.Tasks; using Volo.Abp.Domain.Entities; -namespace JiShe.CollectBus.Records +namespace JiShe.CollectBus.IotSystems.Records { /// /// 集中器上下线、心跳记录 diff --git a/src/JiShe.CollectBus.Domain/Watermeter/WatermeterInfo.cs b/src/JiShe.CollectBus.Domain/IotSystems/Watermeter/WatermeterInfo.cs similarity index 98% rename from src/JiShe.CollectBus.Domain/Watermeter/WatermeterInfo.cs rename to src/JiShe.CollectBus.Domain/IotSystems/Watermeter/WatermeterInfo.cs index dd7e125..c735a9a 100644 --- a/src/JiShe.CollectBus.Domain/Watermeter/WatermeterInfo.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/Watermeter/WatermeterInfo.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.Watermeter +namespace JiShe.CollectBus.IotSystems.Watermeter { /// /// 水表信息 diff --git a/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj b/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj index 7d4299b..035298c 100644 --- a/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj +++ b/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj @@ -8,6 +8,12 @@ JiShe.CollectBus + + + + + + @@ -27,7 +33,9 @@ - + + + diff --git a/src/JiShe.CollectBus.Domain/PrepayModel/Vi_BaseAmmeterInfo.cs b/src/JiShe.CollectBus.Domain/PrepayModel/Vi_BaseAmmeterInfo.cs deleted file mode 100644 index 2990b1e..0000000 --- a/src/JiShe.CollectBus.Domain/PrepayModel/Vi_BaseAmmeterInfo.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.PrepayModel -{ - /// - /// 预付费电表视图模型 - /// - public partial class Vi_BaseAmmeterInfo - { - public int ID { get; set; } - public string BarCode { get; set; } - public string Address { get; set; } - public string BaudRate { get; set; } - public string Password { get; set; } - public string Explan { get; set; } - public System.DateTime AddTime { get; set; } - public bool State { get; set; } - public int TB_EquipmentTypeID { get; set; } - public int BaseID { get; set; } - public string Code { get; set; } - public Nullable MeterCode { get; set; } - public Nullable PortNumber { get; set; } - public int CT { get; set; } - public bool SoftTripsLock { get; set; } - public bool HardwareTripsLock { get; set; } - public bool ValveState { get; set; } - public bool ArchivesState { get; set; } - public Nullable SortNumber { get; set; } - public decimal Balance { get; set; } - public bool IsAlarm { get; set; } - public string sysExplan { get; set; } - public System.DateTime sysAddTime { get; set; } - public bool sysState { get; set; } - public Nullable TB_sysConcentratorID { get; set; } - public Nullable TB_sysChargingSchemeID { get; set; } - public Nullable TB_sysChargingSchemeID1 { get; set; } - public int TB_CustomerID { get; set; } - public Nullable TB_sysAlarmPlanID { get; set; } - public Nullable TB_sysCollectorID { get; set; } - public Nullable TB_sysRoomID { get; set; } - public string SpecialNoCode { get; set; } - public Nullable RatedCurrent { get; set; } - public Nullable TripDelayTime { get; set; } - public Nullable ClosingTime { get; set; } - public Nullable Remainder { get; set; } - public Nullable RatedPower { get; set; } - public Nullable ParentAmmState { get; set; } - public Nullable ParentAmmID { get; set; } - public Nullable LastUpdateTime { get; set; } - public Nullable IsAuthentication { get; set; } - public Nullable IsShowBalance { get; set; } - public string Number { get; set; } - public Nullable LastAuthTime { get; set; } - public Nullable IsAuthenticationFunction { get; set; } - public Nullable IsShowBalanceFunction { get; set; } - public int TB_ProtocolID { get; set; } - public string Operator { get; set; } - public Nullable IsTimingPowerSetting { get; set; } - public string CommSchemeCode { get; set; } - public string RoomAllName { get; set; } - public Nullable IsLadderPrice { get; set; } - public Nullable LadderNum { get; set; } - public Nullable RoomBalance { get; set; } - public Nullable OtherBalance { get; set; } - public Nullable LastValveEventType { get; set; } - public Nullable ReadKwh4 { get; set; } - public Nullable ReadKwh3 { get; set; } - public Nullable ReadKwh2 { get; set; } - public Nullable ReadKwh1 { get; set; } - public Nullable ReadKwh { get; set; } - public Nullable LastEventTime { get; set; } - public Nullable LastReadTime { get; set; } - public string RoomNumber { get; set; } - public string EquipmentName { get; set; } - public Nullable EquipmentId { get; set; } - public Nullable IsMalignantLoad { get; set; } - public string BrandName { get; set; } - public Nullable MalignantPower { get; set; } - public Nullable MalignantPowerState { get; set; } - public Nullable RatedPowerState { get; set; } - public string SolveStatus { get; set; } - public Nullable PowerDownStatus { get; set; } - public Nullable SolveStatusTime { get; set; } - public decimal DisableKwh { get; set; } - public Nullable IsDeliver { get; set; } - public Nullable HandleUser { get; set; } - public string HandleUserName { get; set; } - public string Solutions { get; set; } - public decimal DisableKwh1 { get; set; } - public decimal DisableKwh2 { get; set; } - public decimal DisableKwh3 { get; set; } - public decimal DisableKwh4 { get; set; } - public bool DelaySwitchOff { get; set; } - public string MeterSolveStatus { get; set; } - public Nullable DeliverTime { get; set; } - public bool EnableState { get; set; } - public string DxNbiotDeviceId { get; set; } - public int MeterStatus { get; set; } - public string AllowTripTime { get; set; } - public Nullable LastCostMilliSecond { get; set; } - public string DxNbiotIMEI { get; set; } - public Nullable ReadKwh5 { get; set; } - public Nullable ReadKwh6 { get; set; } - public Nullable ReadKwh7 { get; set; } - public Nullable ReadKwh8 { get; set; } - public Nullable DisableKwh5 { get; set; } - public Nullable DisableKwh6 { get; set; } - public Nullable DisableKwh7 { get; set; } - public Nullable DisableKwh8 { get; set; } - public Nullable IsLadder { get; set; } - public Nullable TimeSpanSetNum { get; set; } - public string ExecutePeriod { get; set; } - public string FreqInterval { get; set; } - public Nullable Tb_sysUseValueAlarmID { get; set; } - public Nullable IsMaxDemandKwh { get; set; } - public Nullable OnLineTime { get; set; } - public Nullable OperatorId { get; set; } - public int PowerCT { get; set; } - public string CommunicationsModule { get; set; } - public Nullable CanBlueTooth { get; set; } - public Nullable CanRemote { get; set; } - public Nullable IsDownPrice { get; set; } - public Nullable BuyCount { get; set; } - } -} diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 3a0390a..567df93 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -18,8 +18,8 @@ using TouchSocket.Core; using TouchSocket.Sockets; using JiShe.CollectBus.Plugins; using JiShe.CollectBus.Consumers; -using JiShe.CollectBus.MessageReceiveds; using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.IotSystems.MessageReceiveds; namespace JiShe.CollectBus.Host diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs index 890a29f..a42a250 100644 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs @@ -1,6 +1,6 @@ -using JiShe.CollectBus.Devices; -using JiShe.CollectBus.MessageReceiveds; -using JiShe.CollectBus.Protocols; +using JiShe.CollectBus.IotSystems.Devices; +using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.Protocols; using MongoDB.Driver; using Volo.Abp.Data; using Volo.Abp.MongoDB; diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index 6f504f5..15fa60f 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -3,15 +3,15 @@ using DotNetCore.CAP; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.MessageReceiveds; using JiShe.CollectBus.Protocol.Contracts.Interfaces; -using JiShe.CollectBus.Protocols; using Microsoft.Extensions.Logging; using JiShe.CollectBus.Protocol.Contracts.Models; using Volo.Abp.Domain.Repositories; using JiShe.CollectBus.Common.BuildSendDatas; using JiShe.CollectBus.Protocol.Contracts.AnalysisData; using Microsoft.Extensions.DependencyInjection; +using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.Protocols; namespace JiShe.CollectBus.Protocol.Contracts.Abstracts { diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs index 1ccc45b..bd08d60 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs @@ -1,9 +1,9 @@ using System; using System.Threading.Tasks; using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.Protocols; using JiShe.CollectBus.Protocol.Contracts.Models; -using JiShe.CollectBus.Protocols; using TouchSocket.Sockets; namespace JiShe.CollectBus.Protocol.Contracts.Interfaces diff --git a/src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs index a6918fd..79a92dd 100644 --- a/src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs @@ -1,10 +1,10 @@ using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.Protocols; using JiShe.CollectBus.Protocol.Contracts.Abstracts; using JiShe.CollectBus.Protocol.Contracts.Models; -using JiShe.CollectBus.Protocols; using Newtonsoft.Json.Linq; namespace JiShe.CollectBus.Protocol -- 2.47.2 From 5915f5961b4b0a1a1cd4514c9e229c751dc26d9f Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Fri, 14 Mar 2025 14:38:08 +0800 Subject: [PATCH 013/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...he.CollectBus.Application.Contracts.csproj | 10 ++-- .../IScheduledMeterReadingService.cs | 54 +++++++++++++------ .../IWorkerSubscriberAppService.cs | 1 - .../Consumers/WorkerConsumer.cs | 2 - .../BasicScheduledMeterReadingService.cs | 25 ++++----- ...nergySystemScheduledMeterReadingService.cs | 19 ++----- .../Subscribers/WorkerSubscriberAppService.cs | 1 - 7 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj index 0b1f26d..de972d1 100644 --- a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj +++ b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj @@ -9,6 +9,12 @@ True + + + + + + @@ -22,8 +28,4 @@ - - - - diff --git a/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs index df8f4f1..a89d071 100644 --- a/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs @@ -1,16 +1,11 @@ -using JiShe.CollectBus.Ammeters; -using JiShe.CollectBus.IotSystems.Watermeter; -using JiShe.CollectBus.GatherItem; - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Collections.Generic; using System.Threading.Tasks; +using JiShe.CollectBus.Ammeters; +using JiShe.CollectBus.GatherItem; +using JiShe.CollectBus.IotSystems.Watermeter; using Volo.Abp.Application.Services; -using Volo.Abp.DependencyInjection; -namespace JiShe.CollectBus.Workers +namespace JiShe.CollectBus.ScheduledMeterReading { /// /// 定时任务基础约束 @@ -31,7 +26,7 @@ namespace JiShe.CollectBus.Workers /// 采集端Code /// Task> GetAmmeterInfoList(string gatherCode = ""); - + /// /// 初始化电表缓存数据 /// @@ -39,6 +34,28 @@ namespace JiShe.CollectBus.Workers /// Task InitAmmeterCacheData(string gatherCode = ""); + /// + /// 1分钟采集电表数据 + /// + /// + Task AmmeterScheduledMeterOneMinuteReading(); + + /// + /// 5分钟采集电表数据 + /// + /// + Task AmmeterScheduledMeterFiveMinuteReading(); + + /// + /// 15分钟采集电表数据 + /// + /// + Task AmmeterScheduledMeterFifteenMinuteReading(); + + #endregion + + + #region 水表采集处理 /// /// 获取水表信息 /// @@ -54,21 +71,24 @@ namespace JiShe.CollectBus.Workers Task InitWatermeterCacheData(string gatherCode = ""); /// - /// 1分钟采集电表数据 + /// 1分钟采集水表数据 /// /// - Task ScheduledMeterOneMinuteReading(); + Task WatermeterScheduledMeterOneMinuteReading(); /// - /// 5分钟采集电表数据 + /// 5分钟采集水表数据 /// /// - Task ScheduledMeterFiveMinuteReading(); + Task WatermeterScheduledMeterFiveMinuteReading(); /// - /// 15分钟采集电表数据 + /// 15分钟采集水表数据 /// /// - Task ScheduledMeterFifteenMinuteReading(); + Task WatermeterScheduledMeterFifteenMinuteReading(); + #endregion + + } } diff --git a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs index dede8c4..12a8e19 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs @@ -1,6 +1,5 @@ using System.Threading.Tasks; using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.MessageReceiveds; using Volo.Abp.Application.Services; namespace JiShe.CollectBus.Subscribers diff --git a/src/JiShe.CollectBus.Application/Consumers/WorkerConsumer.cs b/src/JiShe.CollectBus.Application/Consumers/WorkerConsumer.cs index 5a5652d..53ad836 100644 --- a/src/JiShe.CollectBus.Application/Consumers/WorkerConsumer.cs +++ b/src/JiShe.CollectBus.Application/Consumers/WorkerConsumer.cs @@ -2,8 +2,6 @@ using System.Threading.Tasks; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.MessageIssueds; -using JiShe.CollectBus.MessageReceiveds; using MassTransit; using Microsoft.Extensions.Logging; using TouchSocket.Sockets; diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index c573ab5..82a11ab 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -1,28 +1,21 @@ -using DotNetCore.CAP; -using FreeRedis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; -using JiShe.CollectBus.Common.BuildSendDatas; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.GatherItem; -using JiShe.CollectBus.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.Watermeter; using JiShe.CollectBus.Protocol.Contracts; -using JiShe.CollectBus.Watermeter; +using JiShe.CollectBus.Workers; using MassTransit; using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Diagnostics.Metrics; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Sockets; -using Volo.Abp.Application.Services; -using static FreeSql.Internal.GlobalFilter; -namespace JiShe.CollectBus.Workers +namespace JiShe.CollectBus.ScheduledMeterReading { /// /// 定时采集服务 diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 3aa355e..16359f2 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -1,24 +1,15 @@ -using DotNetCore.CAP; -using FreeRedis; -using FreeSql; +using System.Collections.Generic; +using System.Threading.Tasks; +using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; -using JiShe.CollectBus.FreeRedisProvider; using JiShe.CollectBus.FreeSql; -using JiShe.CollectBus.IotSystems.Watermeter; using JiShe.CollectBus.GatherItem; +using JiShe.CollectBus.IotSystems.Watermeter; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; -namespace JiShe.CollectBus.Workers +namespace JiShe.CollectBus.ScheduledMeterReading { /// /// 能耗系统定时采集服务 diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index 275886e..d5c20b0 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -5,7 +5,6 @@ using DotNetCore.CAP; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IotSystems.Devices; -using JiShe.CollectBus.MessageReceiveds; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using Microsoft.AspNetCore.Mvc; -- 2.47.2 From 0a3b37d80d673ab9f893ad440b18eeb58efe5473 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Fri, 14 Mar 2025 17:28:58 +0800 Subject: [PATCH 014/139] =?UTF-8?q?=E5=B0=81=E8=A3=85=E9=80=9A=E7=94=A8AFN?= =?UTF-8?q?=E6=8A=A5=E6=96=87=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IWorkerSubscriberAppService.cs | 12 +- .../BasicScheduledMeterReadingService.cs | 163 +++++++++--------- .../Subscribers/WorkerSubscriberAppService.cs | 12 +- .../BuildSendDatas/TelemetryPacketBuilder.cs | 144 ++++++++++++++++ ...ScheduledMeterReadingIssuedEventMessage.cs | 35 ++++ 5 files changed, 277 insertions(+), 89 deletions(-) create mode 100644 src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs create mode 100644 src/JiShe.CollectBus.Common/Models/ScheduledMeterReadingIssuedEventMessage.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs index 12a8e19..05c9e6a 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs @@ -15,19 +15,19 @@ namespace JiShe.CollectBus.Subscribers /// 1分钟采集电表数据下行消息消费订阅 /// /// - Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); /// /// 5分钟采集电表数据下行消息消费订阅 /// /// - Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); /// /// 15分钟采集电表数据下行消息消费订阅 /// /// - Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); #endregion #region 水表消息采集 @@ -35,19 +35,19 @@ namespace JiShe.CollectBus.Subscribers /// 1分钟采集水表数据下行消息消费订阅 /// /// - Task WatermeterScheduledMeterOneMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + Task WatermeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); /// /// 5分钟采集水表数据下行消息消费订阅 /// /// - Task WatermeterScheduledMeterFiveMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + Task WatermeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); /// /// 15分钟采集水表数据下行消息消费订阅 /// /// - Task WatermeterScheduledMeterFifteenMinuteReadingIssuedEvent(IssuedEventMessage issuedEventMessage); + Task WatermeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); #endregion } } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 82a11ab..7de35f2 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -1,18 +1,24 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; +using JiShe.CollectBus.Common; +using JiShe.CollectBus.Common.BuildSendDatas; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.GatherItem; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.Watermeter; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Workers; using MassTransit; +using MassTransit.Internals.GraphValidation; using Microsoft.Extensions.Logging; namespace JiShe.CollectBus.ScheduledMeterReading @@ -487,7 +493,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading //根据分组创建线程批处理集中器 foreach (var group in focusHashGroups) { - _= Task.Run(async () => { await CreatePublishTask(eventName,group.Value); }); + _ = Task.Run(async () => { await CreatePublishTask(eventName, group.Value); }); } await Task.CompletedTask; @@ -496,7 +502,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { throw; - } + } } /// @@ -510,84 +516,92 @@ namespace JiShe.CollectBus.ScheduledMeterReading foreach (var focusInfo in focusGroup) { foreach (var ammeterInfo in focusInfo.Value) - { - var meter = ammeterInfo.Value; + { + var ammeter = ammeterInfo.Value; - if (string.IsNullOrWhiteSpace(meter.ItemCodes)) + if (string.IsNullOrWhiteSpace(ammeter.ItemCodes)) { - _logger.LogError($"{nameof(CreatePublishTask)} 集中器{meter.FocusAddress}的电表{meter.Name}数据采集指令生成失败,采集项为空,-101"); + _logger.LogError($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}数据采集指令生成失败,采集项为空,-101"); continue; } //载波的不处理 - if (meter.MeteringPort == (int)MeterLinkProtocolEnum.Carrierwave) + if (ammeter.MeteringPort == (int)MeterLinkProtocolEnum.Carrierwave) { - _logger.LogError($"{nameof(CreatePublishTask)} 集中器{meter.FocusAddress}的电表{meter.Name}数据采集指令生成失败,载波不处理,-102"); + _logger.LogError($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}数据采集指令生成失败,载波不处理,-102"); continue; } - if (meter.State.Equals(2)) + if (ammeter.State.Equals(2)) { - _logger.LogWarning($"{nameof(CreatePublishTask)} {meter.Name} 集中器{meter.FocusAddress}的电表{meter.Name}状态为禁用,不处理"); + _logger.LogWarning($"{nameof(CreatePublishTask)} {ammeter.Name} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}状态为禁用,不处理"); continue; } //排除1天未在线的集中器生成指令 或 排除集中器配置为自动上报的集中器 - if (!IsGennerateCmd(meter.LastTime, -1)) + if (!IsGennerateCmd(ammeter.LastTime, -1)) { - _logger.LogInformation($"{nameof(CreatePublishTask)} 集中器{meter.FocusAddress}的电表{meter.Name},采集时间:{meter.LastTime},已超过1天未在线,不生成指令"); + _logger.LogInformation($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name},采集时间:{ammeter.LastTime},已超过1天未在线,不生成指令"); continue; } - if (string.IsNullOrWhiteSpace(meter.AreaCode)) + if (string.IsNullOrWhiteSpace(ammeter.AreaCode)) { - _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{meter.ID},集中器通信区号为空"); + _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{ammeter.ID},集中器通信区号为空"); continue; } - if (string.IsNullOrWhiteSpace(meter.Address)) + if (string.IsNullOrWhiteSpace(ammeter.Address)) { - _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{meter.ID},集中器通信地址为空"); + _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{ammeter.ID},集中器通信地址为空"); continue; } - if (Convert.ToInt32(meter.Address) > 65535) + if (Convert.ToInt32(ammeter.Address) > 65535) { - _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{meter.ID},集中器通信地址无效,确保大于65535"); + _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{ammeter.ID},集中器通信地址无效,确保大于65535"); continue; } - if (meter.MeteringCode <= 0 || meter.MeteringCode > 2033) + if (ammeter.MeteringCode <= 0 || ammeter.MeteringCode > 2033) { - _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{meter.ID},非有效测量点号({meter.MeteringCode})"); + _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{ammeter.ID},非有效测量点号({ammeter.MeteringCode})"); continue; } - List tempCodes = meter.ItemCodes.Deserialize>()!; + List tempCodes = ammeter.ItemCodes.Deserialize>()!; //TODO:自动上报数据只主动采集1类数据。 - if (meter.AutomaticReport.Equals(1)) + if (ammeter.AutomaticReport.Equals(1)) { var tempSubCodes = new List(); - var tempItemCodes = string.Empty; - if (meter.ItemCodes.Contains("0C_49")) - tempItemCodes += "0C_49,"; - if (meter.ItemCodes.Contains("0C_149")) - tempItemCodes += "0C_149,"; - if (meter.ItemCodes.Contains("10_97")) - tempItemCodes += "10_97"; - - if (string.IsNullOrWhiteSpace(tempItemCodes)) + if (tempCodes.Contains("0C_49")) { + tempSubCodes.Add("0C_49"); + } + + if (tempSubCodes.Contains("0C_149")) + { + tempSubCodes.Add("0C_149"); + } + + if (ammeter.ItemCodes.Contains("10_97")) + { + tempSubCodes.Add("10_97"); + } + + if (tempSubCodes == null || tempSubCodes.Count <= 0) + { + _logger.LogInformation($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}自动上报数据主动采集1类数据时数据类型为空"); continue; } else { - meter.ItemCodes = tempItemCodes; + tempCodes = tempSubCodes; } } - + foreach (var tempItem in tempCodes) { //排除已发送日冻结和月冻结采集项配置 - if(DayFreezeCodes.Contains(tempItem)) + if (DayFreezeCodes.Contains(tempItem)) { continue; } @@ -597,54 +611,49 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - } - //排除已发送日冻结和月冻结采集项配置 - //if (!isSendDayFreeze) - meter.ItemCodes = meter.ItemCodes.Replace("0D_3", "").Replace("0D_4", "") - .Replace("0D_161", "").Replace("0D_162", "").Replace("0D_163", "").Replace("0D_164", "") - .Replace("0D_165", "").Replace("0D_166", "").Replace("0D_167", "").Replace("0D_168", "").Replace("0C_149", ""); + var itemCodeArr = tempItem.Split('_'); + var aFN = (AFN)itemCodeArr[0].HexToDec(); + var fn = int.Parse(itemCodeArr[1]); + byte[] dataInfos = null; + if(ammeter.AutomaticReport.Equals(1) && aFN == AFN.请求实时数据) + { + //实时数据 + dataInfos = Build3761SendData.BuildAmmeterReadRealTimeDataSendCmd(ammeter.FocusAddress, ammeter.MeteringCode, (ATypeOfDataItems)fn); + } + else + { + if (TelemetryPacketBuilder.AFNHandlers.TryGetValue(tempItem, out var handler)) + { + handler(ammeter.FocusAddress, fn, ammeter.MeteringCode); + } + else + { + throw new InvalidOperationException($"无效编码: {tempItem}"); + } + } + //TODO:特殊表 - //if (!isSendMonthFreeze) - meter.ItemCodes = meter.ItemCodes.Replace("0D_177", "").Replace("0D_178", "").Replace("0D_179", "").Replace("0D_180", "") - .Replace("0D_181", "").Replace("0D_181", "").Replace("0D_182", "").Replace("0D_183", "").Replace("0D_184", "") - .Replace("0D_193", "").Replace("0D_195", ""); + + + if (dataInfos == null || dataInfos.Length <= 0) + { + _logger.LogWarning($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}采集项{tempItem}未能正确获取报文。"); + continue; + } + + var evenMessageInfo = new ScheduledMeterReadingIssuedEventMessage + { + Message = dataInfos!, + DeviceNo = ammeter.FocusAddress, + MessageId = NewId.NextGuid().ToString() + }; + await _capBus.PublishAsync(eventName, evenMessageInfo); + } - //TODO:特殊表 - - - //var itemCodeArr = itemCode.Split('_'); - //var aFN = (AFN)itemCodeArr[0].HexToDec(); - //var fn = int.Parse(itemCodeArr[1]); - //if (aFN == AFN.请求实时数据) - //{ - // var bytes = Build3761SendData.BuildAmmeterReadRealTimeDataSendCmd(address, ammeter.MeterCode.Value, (ATypeOfDataItems)fn); - // bytesList.Add(bytes); - //} - //else if (aFN == AFN.请求历史数据) - //{ - // var density = (FreezeDensity)input.Density; - // var bytes = Build3761SendData.BuildAmmeterReadingIIdataTypeItemsSendCmd(address, ammeter.MeterCode.Value, (IIdataTypeItems)fn, density, 0); - // bytesList.Add(bytes); - //} - } - } - - string deviceNo = ""; - string messageHexString = ""; - - var messageReceivedHeartbeatEvent = new MessageReceivedHeartbeat - { - //ClientId = client.Id, - //ClientIp = client.IP, - //ClientPort = client.Port, - MessageHexString = messageHexString, - DeviceNo = deviceNo, - MessageId = NewId.NextGuid().ToString() - }; - await _capBus.PublishAsync(eventName, messageReceivedHeartbeatEvent); + } } /// diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index d5c20b0..693abda 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -55,7 +55,7 @@ namespace JiShe.CollectBus.Subscribers [HttpPost] [Route("ammeter/oneminute/issued-event")] [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] - public async Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + public async Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("1分钟采集电表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); @@ -82,7 +82,7 @@ namespace JiShe.CollectBus.Subscribers [HttpPost] [Route("ammeter/fiveminute/issued-event")] [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] - public async Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + public async Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("5分钟采集电表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); @@ -109,7 +109,7 @@ namespace JiShe.CollectBus.Subscribers [HttpPost] [Route("ammeter/fifteenminute/issued-event")] [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] - public async Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + public async Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("15分钟采集电表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); @@ -138,7 +138,7 @@ namespace JiShe.CollectBus.Subscribers [HttpPost] [Route("watermeter/oneminute/issued-event")] [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerOneMinuteIssuedEventName)] - public async Task WatermeterScheduledMeterOneMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + public async Task WatermeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("1分钟采集电表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); @@ -165,7 +165,7 @@ namespace JiShe.CollectBus.Subscribers [HttpPost] [Route("watermeter/fiveminute/issued-event")] [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerOneMinuteIssuedEventName)] - public async Task WatermeterScheduledMeterFiveMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + public async Task WatermeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("5分钟采集电表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); @@ -192,7 +192,7 @@ namespace JiShe.CollectBus.Subscribers [HttpPost] [Route("watermeter/fifteenminute/issued-event")] [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerOneMinuteIssuedEventName)] - public async Task WatermeterScheduledMeterFifteenMinuteReadingIssuedEvent(IssuedEventMessage receivedMessage) + public async Task WatermeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("15分钟采集电表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs b/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs new file mode 100644 index 0000000..6772ef1 --- /dev/null +++ b/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs @@ -0,0 +1,144 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Models; +using System; +using System.Collections.Generic; +using System.Data.SqlTypes; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.BuildSendDatas +{ + /// + /// 构建下发报文 + /// + public static class TelemetryPacketBuilder + { + /// + /// 构建报文的委托 + /// + /// + /// + /// + public delegate void AFNDelegate(string address, int fn, int pn = 0); + + /// + /// 编码与方法的映射表 + /// + public static readonly Dictionary AFNHandlers = new(); + + static TelemetryPacketBuilder() + { + // 初始化时自动注册所有符合命名规则的方法 + var methods = typeof(TelemetryPacketBuilder).GetMethods(BindingFlags.Static | BindingFlags.Public); + foreach (var method in methods) + { + if (method.Name.StartsWith("AFN") && method.Name.EndsWith("_Send")) + { + // 提取编码部分(例如 "AFN0D_F184_Send" -> "0D_184") + string code = method.Name[3..^5].Replace("F", "_"); // 移除前缀和后缀,替换F为_ + var delegateInstance = (AFNDelegate)Delegate.CreateDelegate(typeof(AFNDelegate), method); + AFNHandlers[code] = delegateInstance; + } + } + } + + #region AFN_00H + public static byte[] AFN00_F1_Send(string address,int fn,int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.确认或否认, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN00_F3_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.确认或否认, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + #endregion + + + #region AFN_01H + public static byte[] AFN01_F1_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.复位, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + #endregion + + + #region AFN_02H + public static byte[] AFN02_F2_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + #endregion + } +} diff --git a/src/JiShe.CollectBus.Common/Models/ScheduledMeterReadingIssuedEventMessage.cs b/src/JiShe.CollectBus.Common/Models/ScheduledMeterReadingIssuedEventMessage.cs new file mode 100644 index 0000000..b62c7db --- /dev/null +++ b/src/JiShe.CollectBus.Common/Models/ScheduledMeterReadingIssuedEventMessage.cs @@ -0,0 +1,35 @@ +using JiShe.CollectBus.Common.Enums; + +namespace JiShe.CollectBus.Common.Models +{ + /// + /// 定时抄读Kafka消息实体,1分钟、5分钟、15分钟 + /// + public class ScheduledMeterReadingIssuedEventMessage + { + /// + /// 消息接收客户端Id + /// + public string ClientId { get; set; } + + /// + /// 消息内容 + /// + public byte[] Message { get; set; } + + /// + /// 集中器编号 + /// + public string DeviceNo { get; set; } + + ///// + ///// 采集时间间隔(分钟,如15) + ///// + //public int TimeDensity { get; set; } + + /// + /// 消息Id + /// + public string MessageId { get; set; } + } +} -- 2.47.2 From d35882bf7231c648d7703fa722877cb0ed0a17d1 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Sat, 15 Mar 2025 23:06:27 +0800 Subject: [PATCH 015/139] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=8C=87=E4=BB=A4?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E5=A7=94=E6=89=98=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 4 +--- .../BuildSendDatas/TelemetryPacketBuilder.cs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 7de35f2..3cb1334 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -624,7 +624,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { if (TelemetryPacketBuilder.AFNHandlers.TryGetValue(tempItem, out var handler)) { - handler(ammeter.FocusAddress, fn, ammeter.MeteringCode); + dataInfos = handler(ammeter.FocusAddress, fn, ammeter.MeteringCode); } else { @@ -633,8 +633,6 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //TODO:特殊表 - - if (dataInfos == null || dataInfos.Length <= 0) { _logger.LogWarning($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}采集项{tempItem}未能正确获取报文。"); diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs b/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs index 6772ef1..96bb97f 100644 --- a/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs +++ b/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs @@ -22,7 +22,7 @@ namespace JiShe.CollectBus.Common.BuildSendDatas /// /// /// - public delegate void AFNDelegate(string address, int fn, int pn = 0); + public delegate byte[] AFNDelegate(string address, int fn, int pn = 0); /// /// 编码与方法的映射表 -- 2.47.2 From 5571369af74927743905dfb6eedc1786afc6fab5 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Mon, 17 Mar 2025 08:35:19 +0800 Subject: [PATCH 016/139] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 4 +- .../EnergySystem/ICacheAppService.cs | 13 + .../CollectBusAppService.cs | 2 +- .../CollectBusApplicationModule.cs | 2 +- .../EnergySystem/CacheAppService.cs | 15 + .../BasicScheduledMeterReadingService.cs | 18 +- .../EnergySystems/Entities/TB_AmmeterInfo.cs | 259 ++++++++++++++++++ .../TableViews/V_FocusAmmeter.cs | 12 +- .../JiShe.CollectBus.Domain.csproj | 3 +- ...Module.cs => CollectBusFreeRedisModule.cs} | 7 +- .../FreeRedisProvider.cs | 47 ++++ .../FreeRedisProviderService.cs | 65 ----- .../IFreeRedisProvider.cs | 14 + .../IFreeRedisProviderService.cs | 30 -- 14 files changed, 373 insertions(+), 118 deletions(-) create mode 100644 src/JiShe.CollectBus.Application.Contracts/EnergySystem/ICacheAppService.cs create mode 100644 src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs create mode 100644 src/JiShe.CollectBus.Domain/EnergySystems/Entities/TB_AmmeterInfo.cs rename src/JiShe.CollectBus.FreeRedisProvider/{FreeRedisProviderModule.cs => CollectBusFreeRedisModule.cs} (76%) create mode 100644 src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs delete mode 100644 src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderService.cs create mode 100644 src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs delete mode 100644 src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProviderService.cs diff --git a/readme.md b/readme.md index 28a0678..57cce20 100644 --- a/readme.md +++ b/readme.md @@ -17,7 +17,7 @@ - V4 → V5核心升级: - 微服务化架构改造 - 统一配置管理中心 - - 支持Kafka/RabbitMQ双引擎 + - 支持Kafka引擎 - 新增边缘计算能力 - 资源利用率提升40% @@ -82,7 +82,7 @@ Body: |----------------|------------|--------------| | 最大连接数 | 10,000 | 线性扩展 | | 数据处理延迟 | <50ms(p99) | - | -| 吞吐量 | 20,000 TPS | 百万级TPS | +| 吞吐量 | 20,000 TPS | 十万级TPS | | CPU利用率 | ≤70%@峰值 | 自动负载均衡 | ## 6. 高可用设计 diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/ICacheAppService.cs b/src/JiShe.CollectBus.Application.Contracts/EnergySystem/ICacheAppService.cs new file mode 100644 index 0000000..629733d --- /dev/null +++ b/src/JiShe.CollectBus.Application.Contracts/EnergySystem/ICacheAppService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace JiShe.CollectBus.EnergySystem +{ + public interface ICacheAppService : IApplicationService + { + } +} diff --git a/src/JiShe.CollectBus.Application/CollectBusAppService.cs b/src/JiShe.CollectBus.Application/CollectBusAppService.cs index f45dda1..c634859 100644 --- a/src/JiShe.CollectBus.Application/CollectBusAppService.cs +++ b/src/JiShe.CollectBus.Application/CollectBusAppService.cs @@ -14,7 +14,7 @@ namespace JiShe.CollectBus; public abstract class CollectBusAppService : ApplicationService { public IFreeSqlProvider SqlProvider => LazyServiceProvider.LazyGetRequiredService(); - protected IFreeRedisProviderService FreeRedisProvider => LazyServiceProvider.LazyGetService()!; + protected IFreeRedisProvider FreeRedisProvider => LazyServiceProvider.LazyGetService()!; protected CollectBusAppService() { diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 8078e48..4efe690 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -22,7 +22,7 @@ namespace JiShe.CollectBus; typeof(AbpDddApplicationModule), typeof(AbpAutoMapperModule), typeof(AbpBackgroundWorkersHangfireModule), - typeof(FreeRedisProviderModule), + typeof(CollectBusFreeRedisModule), typeof(CollectBusFreeSqlModule) )] public class CollectBusApplicationModule : AbpModule diff --git a/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs b/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs new file mode 100644 index 0000000..2a038a6 --- /dev/null +++ b/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs @@ -0,0 +1,15 @@ +using JiShe.CollectBus.IotSystems.Records; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace JiShe.CollectBus.EnergySystem +{ + public class CacheAppService: CollectBusAppService,ICacheAppService + { + + } +} diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 82a11ab..5ea6c6d 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -143,7 +143,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading keyValuePairs.TryAdd($"{ammeter.ID}", ammeter); } - await FreeRedisProvider.FreeRedis.HSetAsync(redisCacheKey, keyValuePairs); + await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); } } @@ -158,7 +158,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的电表信息 var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 1)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-101"); @@ -187,7 +187,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的电表信息 var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 5)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理时没有获取到缓存信息,-101"); @@ -214,7 +214,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的电表信息 var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 15)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理时没有获取到缓存信息,-101"); @@ -287,7 +287,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading keyValuePairs.TryAdd($"{subItem.ID}", subItem); } - await FreeRedisProvider.FreeRedis.HSetAsync(redisCacheKey, keyValuePairs); + await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); } } _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化水表缓存数据完成"); @@ -301,7 +301,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的水表信息 var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 1)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表据处理时没有获取到缓存信息,-101"); @@ -328,7 +328,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading //获取缓存中的水表信息 var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 5)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { _logger.LogError($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表据处理时没有获取到缓存信息,-101"); @@ -354,7 +354,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的水表信息 var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 15)}*"; - var oneMinutekeyList = await FreeRedisProvider.FreeRedis.KeysAsync(redisKeyList); + var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { _logger.LogError($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表据处理时没有获取到缓存信息,-101"); @@ -392,7 +392,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading results[i] = {key, data} end return results"; - var oneMinuteAmmerterResult = await FreeRedisProvider.FreeRedis.EvalAsync(luaScript, redisKeys); //传递 KEYS + var oneMinuteAmmerterResult = await FreeRedisProvider.Instance.EvalAsync(luaScript, redisKeys); //传递 KEYS if (oneMinuteAmmerterResult == null) { _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 定时任务采集表数据处理时没有获取到缓存信息,-102"); diff --git a/src/JiShe.CollectBus.Domain/EnergySystems/Entities/TB_AmmeterInfo.cs b/src/JiShe.CollectBus.Domain/EnergySystems/Entities/TB_AmmeterInfo.cs new file mode 100644 index 0000000..a194d38 --- /dev/null +++ b/src/JiShe.CollectBus.Domain/EnergySystems/Entities/TB_AmmeterInfo.cs @@ -0,0 +1,259 @@ +using System; + +namespace JiShe.CollectBus.EnergySystems.Entities +{ + public class TB_AmmeterInfo + { + + /// + /// 电表信息 + /// + public int ID { get; set; } + + /// + /// 电表编号、电表型号 + /// + public string Code { get; set; } + + /// + /// 区域信息外键 + /// + public int AreaID { get; set; } + /// + /// 区域名 + /// + public string AreaName { get; set; } + + /// + /// 电表别名 + /// + public string Name { get; set; } + + /// + /// 电表类别 (1单相、2三相三线、3三相四线) + /// + public int TypeName { get; set; } + + /// + /// 电表安装地址 + /// + public string Location { get; set; } + /// + /// 电表安装时间 + /// + public DateTime? InstallTime { get; set; } + + /// + /// 电表密码 + /// + public string Password { get; set; } + + /// + /// 电表通信地址 + /// + public string Address { get; set; } + + /// + /// 采集器地址 + /// + public string CollectorAddress { get; set; } + + /// + /// 电压变比 + /// 电压互感器(PT) + /// + public double TimesV { get; set; } + + /// + /// 电流变比 + /// 电流互感器(CT) + /// + public double TimesA { get; set; } + + /// + /// 是否总表 + /// + public int IsSum { get; set; } + + /// + /// 总表ID + /// + public int ParentID { get; set; } + + /// + /// Explain + /// + public string Explain { get; set; } + + /// + /// AddDate + /// + public DateTime AddDate { get; set; } + + /// + /// State表状态: (对应枚举 MeterStateEnum) + /// 0新装(未下发),1运行(档案下发成功时设置状态值1), 2暂停, 100销表(销表后是否重新启用); + /// 特定State: -1 已删除 + /// + public int State { get; set; } + + + /// + /// 费率类型,单、多 (SingleRate :单费率(单相表1),多费率(其他0) ,与TypeName字段无关) + /// SingleRate ? "单" : "复" + /// [SingleRate] --0 复费率 false , 1 单费率 true (与PayPlanID保持一致) + ///对应 TB_PayPlan.Type: 1复费率,2单费率 + /// + public bool SingleRate { get; set; } + + /// + /// 0 未下发 (false), 1 已下发 (true) + /// + public bool IsSend { get; set; } + + /// + /// 创建人ID + /// + public int CreateUserID { get; set; } + + /// + /// 波特率 default(2400) + /// + public int Baudrate { get; set; } + /// + /// 规约 -电表default(30) + /// + public int? Protocol { get; set; } + /// + /// 一个集中器下的[MeteringCode]必须唯一。 + /// + public int MeteringCode { get; set; } + /// + /// MeteringPort 端口就几个可以枚举。 + /// + public int MeteringPort { get; set; } + + /// + /// 对应[TB_PayPlan] + /// + public int PayPlanID { get; set; } + + + public int ProjectID { get; set; } + + public int FocusID { get; set; } + + /// + /// 集中器名称(扩展字段) + /// + public string FocusName { get; set; } + + /// + /// 跳合闸状态字段: 0 合闸,1 跳闸 + /// 电表:TripState (0 合闸-通电, 1 断开、跳闸); + /// + public int TripState { get; set; } + /// + /// 最近阀控时间 + /// + public DateTime? TripTime { get; set; } + + /// + /// 排序字段 + /// + public int Sort { get; set; } + + /// + /// 电子表0 , + /// 机械表1(德力西机械电表-Code) + /// (原有数据都默认:电子电表) + /// + public int MeterKind { get; set; } + + /// + /// 采集方案ID + /// + public int GatherPlanID { get; set; } + + /// + /// 采集项 + /// + public string ReadClass { get; set; } + + /// + /// 修改日期 + /// + public DateTime? EditDate { get; set; } + + /// + /// 修改用ID + /// + public int? EditUserID { get; set; } + + /// + /// 删除时间 + /// + public DateTime? RemoveDate { get; set; } + + /// + /// 删除用户ID + /// + public int? RemoveUserID { get; set; } + + /// + /// 掉电状态 (未上电=1,上电掉电中=2) + /// + public int? PowerDownStatus { get; set; } + + /// + /// 电流规格 + /// + public string CurrentSpec { get; set; } + + /// + /// 电压规格 + /// + public string VoltageSpec { get; set; } + + /// + /// 通讯状态 1:在线 0:离线 + /// + public int LineState { get; set; } + + /// + /// 特殊表 1:是 0:否 + /// + public int Special { get; set; } + + /* + /// + /// 采集项总数 + /// + public int GatherTotal { get; set; } + + /// + /// 采集项 + /// + public string GatherDataTypes { get; set; } + */ + + /// + /// 复费率类型(四费率=4,八费率=8) + /// + public int? MultipleRateType { get; set; } + } + + public class VMAmmeterInfo : TB_AmmeterInfo + { + public decimal? Rate { get; set; } + public decimal? Rate1 { get; set; } + public decimal? Rate2 { get; set; } + public decimal? Rate3 { get; set; } + + public decimal? Rate4 { get; set; } + + public decimal? Rate5 { get; set; } + + public decimal? Rate6 { get; set; } + } +} diff --git a/src/JiShe.CollectBus.Domain/EnergySystems/TableViews/V_FocusAmmeter.cs b/src/JiShe.CollectBus.Domain/EnergySystems/TableViews/V_FocusAmmeter.cs index 5ff30d6..b217972 100644 --- a/src/JiShe.CollectBus.Domain/EnergySystems/TableViews/V_FocusAmmeter.cs +++ b/src/JiShe.CollectBus.Domain/EnergySystems/TableViews/V_FocusAmmeter.cs @@ -1,4 +1,5 @@ -using System; +using JiShe.CollectBus.EnergySystems.Entities; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,8 +7,15 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.EnergySystems.TableViews { - public class V_FocusAmmeter + public class V_FocusAmmeter: TB_AmmeterInfo { + public string FocusAddress { get; set; } + + public string FocusAreaCode { get; set; } + + public string FocusCode { get; set; } + + public string FocusState { get; set; } } } diff --git a/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj b/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj index 035298c..09edef1 100644 --- a/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj +++ b/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj @@ -33,8 +33,7 @@ - - + diff --git a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs b/src/JiShe.CollectBus.FreeRedisProvider/CollectBusFreeRedisModule.cs similarity index 76% rename from src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs rename to src/JiShe.CollectBus.FreeRedisProvider/CollectBusFreeRedisModule.cs index 768332f..6485e92 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderModule.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/CollectBusFreeRedisModule.cs @@ -1,16 +1,11 @@ using JiShe.CollectBus.FreeRedisProvider.Options; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Volo.Abp.Modularity; namespace JiShe.CollectBus.FreeRedisProvider { - public class FreeRedisProviderModule : AbpModule + public class CollectBusFreeRedisModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { diff --git a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs new file mode 100644 index 0000000..b7f6a03 --- /dev/null +++ b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs @@ -0,0 +1,47 @@ +using FreeRedis; +using JetBrains.Annotations; +using JiShe.CollectBus.FreeRedisProvider.Options; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace JiShe.CollectBus.FreeRedisProvider +{ + + public class FreeRedisProvider : IFreeRedisProvider, ISingletonDependency + { + + private readonly FreeRedisOptions _option; + + /// + /// FreeRedis + /// + public FreeRedisProvider(IOptions options) + { + _option = options.Value; + GetInstance(); + } + + public RedisClient Instance { get; set; } = new (string.Empty); + + /// + /// 获取 FreeRedis 客户端 + /// + /// + public IRedisClient GetInstance() + { + var connectionString = $"{_option.Configuration},defaultdatabase={_option.DefaultDB}"; + Instance = new RedisClient(connectionString); + Instance.Serialize = obj => JsonSerializer.Serialize(obj); + Instance.Deserialize = (json, type) => JsonSerializer.Deserialize(json, type); + Instance.Notice += (s, e) => Trace.WriteLine(e.Log); + return Instance; + } + } +} \ No newline at end of file diff --git a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderService.cs b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderService.cs deleted file mode 100644 index b36001d..0000000 --- a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProviderService.cs +++ /dev/null @@ -1,65 +0,0 @@ -using FreeRedis; -using JetBrains.Annotations; -using JiShe.CollectBus.FreeRedisProvider.Options; -using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; - -namespace JiShe.CollectBus.FreeRedisProvider -{ - - public class FreeRedisProviderService : IFreeRedisProviderService, ISingletonDependency - { - private FreeRedisOptions _freeRedisOptions; - - /// - /// FreeRedis - /// - public FreeRedisProviderService(IOptions options) - { - _freeRedisOptions = options.Value; - } - - [NotNull] - public IRedisClient FreeRedis - { - get - { - return GetClient(); - } - } - - /// - /// 获取 FreeRedis 客户端 - /// - /// - public IRedisClient GetClient() - { - string connectionString = $"{_freeRedisOptions.Configuration},defaultdatabase={_freeRedisOptions.DefaultDB}"; - var redisClient = new RedisClient(connectionString); - redisClient.Serialize = obj => JsonSerializer.Serialize(obj); - redisClient.Deserialize = (json, type) => JsonSerializer.Deserialize(json, type); - redisClient.Notice += (s, e) => Trace.WriteLine(e.Log); - - return redisClient; - } - - /// - /// 切换Redis数据库 - /// - /// - /// - public IRedisClient GetDatabase(int index = 0) - { - var redisClient = GetClient(); - redisClient.GetDatabase(index); - return redisClient; - } - } -} \ No newline at end of file diff --git a/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs b/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs new file mode 100644 index 0000000..cc3ff02 --- /dev/null +++ b/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs @@ -0,0 +1,14 @@ +using FreeRedis; + +namespace JiShe.CollectBus.FreeRedisProvider +{ + public interface IFreeRedisProvider + { + /// + /// 获取客户端 + /// + /// + RedisClient Instance { get; set; } + } +} + diff --git a/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProviderService.cs b/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProviderService.cs deleted file mode 100644 index 2e244dc..0000000 --- a/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProviderService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using FreeRedis; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.FreeRedisProvider -{ - public interface IFreeRedisProviderService - { - /// - /// 默认客户端 - /// - IRedisClient FreeRedis { get; } - - /// - /// 获取客户端 - /// - /// - IRedisClient GetClient(); - - /// - /// 切换Redis数据库 - /// - /// - /// - IRedisClient GetDatabase(int index = 0); - } -} -- 2.47.2 From 29560ad4ef88c01ad75ffbf6b1db71f230071736 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Mon, 17 Mar 2025 09:57:58 +0800 Subject: [PATCH 017/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Subscribers/SubscriberAppService.cs | 10 +- .../Abstracts/BaseProtocolPlugin.cs | 211 +++++++++--------- 2 files changed, 107 insertions(+), 114 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index 3c47b83..9df8c39 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -77,7 +77,6 @@ namespace JiShe.CollectBus.Subscribers if (device!=null) { await _tcpService.SendAsync(device.ClientId, issuedEventMessage.Message); - } } @@ -99,35 +98,30 @@ namespace JiShe.CollectBus.Subscribers [CapSubscribe(ProtocolConst.SubscriberReceivedHeartbeatEventName)] public async Task ReceivedHeartbeatEvent(MessageReceivedHeartbeat receivedHeartbeatMessage) { - _logger.LogInformation("心跳消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); if (protocolPlugin == null) { - _logger.LogError("【心跳消费队列开始处理】协议不存在!"); + _logger.LogError("协议不存在!"); } else { await protocolPlugin.HeartbeatAsync(receivedHeartbeatMessage); await _messageReceivedHeartbeatEventRepository.InsertAsync(receivedHeartbeatMessage); - - _logger.LogInformation($"心跳消费队列完成处理:{receivedHeartbeatMessage.MessageId}"); } } [CapSubscribe(ProtocolConst.SubscriberReceivedLoginEventName)] public async Task ReceivedLoginEvent(MessageReceivedLogin receivedLoginMessage) { - _logger.LogInformation("登录消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); if (protocolPlugin == null) { - _logger.LogError("【登录消费队列开始处理】协议不存在!"); + _logger.LogError("协议不存在!"); } else { await protocolPlugin.LoginAsync(receivedLoginMessage); await _messageReceivedLoginEventRepository.InsertAsync(receivedLoginMessage); - _logger.LogInformation("登录消费队列完成处理"); } } } diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index 15fa60f..b69c541 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -1,5 +1,4 @@ -using System.Globalization; -using DotNetCore.CAP; +using DotNetCore.CAP; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Models; @@ -153,55 +152,55 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts /// public virtual List AnalyzeAmmeterParameterReadingDataAsync(MessageReceived messageReceived, Action? sendAction = null) { - var hexDatas = GetHexDatas(messageReceived.MessageHexString); + var hexData = GetHexData(messageReceived.MessageHexString); var meterList = new List(); - var count = (hexDatas[1] + hexDatas[0]).HexToDec(); + var count = (hexData[1] + hexData[0]).HexToDec(); //if (2 + count * 27 != hexDatas.Count - pWLen - tPLen - 2) // return; var index = 2;//数量 for (int i = 1; i <= count; i++) { - var meterNumber = $"{hexDatas[index + 1]}{hexDatas[index]}".HexToDec(); + var meterNumber = $"{hexData[index + 1]}{hexData[index]}".HexToDec(); index += 2; - var pn = $"{hexDatas[index + 1]}{hexDatas[index]}".HexToDec(); + var pn = $"{hexData[index + 1]}{hexData[index]}".HexToDec(); index += 2; - var baudRateAndPortBin = hexDatas[index].HexToBin().PadLeft(8, '0'); + var baudRateAndPortBin = hexData[index].HexToBin().PadLeft(8, '0'); var baudRate = baudRateAndPortBin.Substring(0, 3).BinToDec(); var port = baudRateAndPortBin.Substring(3, 5).BinToDec(); index += 1; - var protocolType = (CommunicationProtocolType)hexDatas[index].HexToDec(); + var protocolType = (CommunicationProtocolType)hexData[index].HexToDec(); index += 1; - var addressHexList = hexDatas.Skip(index).Take(6).ToList(); + var addressHexList = hexData.Skip(index).Take(6).ToList(); addressHexList.Reverse(); var address = string.Join("", addressHexList); index += 6; - var pwdHexList = hexDatas.Skip(index).Take(6).ToList(); + var pwdHexList = hexData.Skip(index).Take(6).ToList(); pwdHexList.Reverse(); var password = string.Join("", pwdHexList.Take(3).ToList()); index += 6; - var rateNumberBin = hexDatas[index].HexToBin().PadLeft(8, '0'); + var rateNumberBin = hexData[index].HexToBin().PadLeft(8, '0'); var rateNumber = rateNumberBin.Substring(4).BinToDec(); index += 1; - var intBitAndDecBitNumberBin = hexDatas[index].HexToBin().PadLeft(8, '0'); + var intBitAndDecBitNumberBin = hexData[index].HexToBin().PadLeft(8, '0'); var intBitNumber = intBitAndDecBitNumberBin.Substring(4, 2).BinToDec() + 4; var decBitNumber = intBitAndDecBitNumberBin.Substring(6, 2).BinToDec() + 1; index += 1; // hexDatas.GetRange() - var collectorAddressHexList = hexDatas.Skip(index).Take(6).ToList(); + var collectorAddressHexList = hexData.Skip(index).Take(6).ToList(); collectorAddressHexList.Reverse(); var collectorAddress = string.Join("", collectorAddressHexList); index += 6; - var userClassNumberBin = hexDatas[index].HexToBin().PadLeft(8, '0'); + var userClassNumberBin = hexData[index].HexToBin().PadLeft(8, '0'); var userClass = userClassNumberBin.Substring(0, 4).BinToDec(); var userSubClass = userClassNumberBin.Substring(4, 4).BinToDec(); index += 1; @@ -234,24 +233,24 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts /// public virtual CurrentPositiveActiveEnergyAnalyze AnalyzeActivePowerIndicationReadingDataAsync(MessageReceived messageReceived, Action? sendAction = null) { - var hexDatas = GetHexDatas(messageReceived.MessageHexString); + var hexData = GetHexData(messageReceived.MessageHexString); - var minute = Convert.ToInt32(hexDatas[0]); // 获取当前分钟数 - var hour = Convert.ToInt32(hexDatas[1]); // 获取当前小时数 - var day = Convert.ToInt32(hexDatas[2]); // 获取当前日期的日数 - var month = Convert.ToInt32(hexDatas[3]); // 获取当前月份 - var year = Convert.ToInt32(hexDatas[4]); // 获取当前日期的年份 + var minute = Convert.ToInt32(hexData[0]); // 获取当前分钟数 + var hour = Convert.ToInt32(hexData[1]); // 获取当前小时数 + var day = Convert.ToInt32(hexData[2]); // 获取当前日期的日数 + var month = Convert.ToInt32(hexData[3]); // 获取当前月份 + var year = Convert.ToInt32(hexData[4]); // 获取当前日期的年份 var dateTime = new DateTime(year, month, day, hour, minute, 0); // 转换为本地时间 var localDateTime = dateTime.ToLocalTime(); - var rateNumber = Convert.ToInt32(hexDatas[5]); - var kwhTotal = hexDatas.Skip(5).Take(5).ToList(); + var rateNumber = Convert.ToInt32(hexData[5]); + var kwhTotal = hexData.Skip(5).Take(5).ToList(); var kwhList = new List(); var index = 11; for (int i = 0; i < rateNumber; i++) { - var kwhHexList = hexDatas.Skip(index).Take(5).ToList(); + var kwhHexList = hexData.Skip(index).Take(5).ToList(); kwhHexList.Reverse(); var integerStr = $"{kwhHexList.Take(0)}{kwhHexList.Take(1)}{kwhHexList.Take(2)}"; var decimalValStr = $"{kwhHexList[3]}{kwhHexList[4]}"; @@ -280,19 +279,19 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts /// public virtual void AnalyzeDailyFrozenReadingDataAsync(MessageReceived messageReceived, Action? sendAction = null) { - var hexDatas = GetHexDatas(messageReceived.MessageHexString); + var hexData = GetHexData(messageReceived.MessageHexString); //附录A.20 日月年 - var td_dHex = hexDatas.Take(3).ToList(); + var td_dHex = hexData.Take(3).ToList(); //附录A.15 分时日月年 - var readingTimeHex = hexDatas.Skip(3).Take(5).ToList(); - var rateNumberHex = hexDatas.Skip(8).Take(1).FirstOrDefault().HexToDec(); + var readingTimeHex = hexData.Skip(3).Take(5).ToList(); + var rateNumberHex = hexData.Skip(8).Take(1).FirstOrDefault().HexToDec(); var datas = new List(); //附录A.14 kWh 5字节 for (int i = 0; i < rateNumberHex; i++) { var skipCount = 9 + i * 5; - var dataHexs = hexDatas.Skip(skipCount).Take(5).ToList(); + var dataHexs = hexData.Skip(skipCount).Take(5).ToList(); var data = AnalyzeDataAccordingToA14(dataHexs[0], dataHexs[1], dataHexs[2], dataHexs[3], dataHexs[4]); datas.Add(data); } @@ -344,83 +343,83 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts /// //F25ReadingAnalyze public virtual Analyze3761Data AnalyzeF25ReadingDataAsync(MessageReceived messageReceived, Action? sendAction = null) { - var hexDatas = GetHexDatas(messageReceived.MessageHexString); + var hexData = GetHexData(messageReceived.MessageHexString); //A.15 分时日月年 - var readingTimeHex = hexDatas.Take(5).ToList(); + var readingTimeHex = hexData.Take(5).ToList(); var readingTime = AnalyzeDataAccordingToA15(readingTimeHex[0], readingTimeHex[1], readingTimeHex[2], readingTimeHex[3], readingTimeHex[4]); //A.9 kW - var crntTotalActivePowerHexs = hexDatas.Skip((int)F25DataItemEnum.CrntTotalActivePower).Take(3).ToList(); + var crntTotalActivePowerHexs = hexData.Skip((int)F25DataItemEnum.CrntTotalActivePower).Take(3).ToList(); var crntTotalActivePower = AnalyzeDataAccordingToA09(crntTotalActivePowerHexs[0], crntTotalActivePowerHexs[1], crntTotalActivePowerHexs[2]); - var crntActivePowerOfAHexs = hexDatas.Skip((int)F25DataItemEnum.CrntActivePowerOfA).Take(3).ToList(); + var crntActivePowerOfAHexs = hexData.Skip((int)F25DataItemEnum.CrntActivePowerOfA).Take(3).ToList(); var crntActivePowerOfA = AnalyzeDataAccordingToA09(crntActivePowerOfAHexs[0], crntActivePowerOfAHexs[1], crntActivePowerOfAHexs[2]); - var crntActivePowerOfBHexs = hexDatas.Skip((int)F25DataItemEnum.CrntActivePowerOfB).Take(3).ToList(); + var crntActivePowerOfBHexs = hexData.Skip((int)F25DataItemEnum.CrntActivePowerOfB).Take(3).ToList(); var crntActivePowerOfB = AnalyzeDataAccordingToA09(crntActivePowerOfBHexs[0], crntActivePowerOfBHexs[1], crntActivePowerOfBHexs[2]); - var crntActivePowerOfCHexs = hexDatas.Skip((int)F25DataItemEnum.CrntActivePowerOfC).Take(3).ToList(); + var crntActivePowerOfCHexs = hexData.Skip((int)F25DataItemEnum.CrntActivePowerOfC).Take(3).ToList(); var crntActivePowerOfC = AnalyzeDataAccordingToA09(crntActivePowerOfCHexs[0], crntActivePowerOfCHexs[1], crntActivePowerOfCHexs[2]); - var crntTotalReactivePowerHexs = hexDatas.Skip((int)F25DataItemEnum.CrntTotalReactivePower).Take(3).ToList(); + var crntTotalReactivePowerHexs = hexData.Skip((int)F25DataItemEnum.CrntTotalReactivePower).Take(3).ToList(); var crntTotalReactivePower = AnalyzeDataAccordingToA09(crntTotalReactivePowerHexs[0], crntTotalReactivePowerHexs[1], crntTotalReactivePowerHexs[2]); - var crntReactivePowerOfAHexs = hexDatas.Skip((int)F25DataItemEnum.CrntReactivePowerOfA).Take(3).ToList(); + var crntReactivePowerOfAHexs = hexData.Skip((int)F25DataItemEnum.CrntReactivePowerOfA).Take(3).ToList(); var crntReactivePowerOfA = AnalyzeDataAccordingToA09(crntReactivePowerOfAHexs[0], crntReactivePowerOfAHexs[1], crntReactivePowerOfAHexs[2]); - var crntReactivePowerOfBHexs = hexDatas.Skip((int)F25DataItemEnum.CrntReactivePowerOfB).Take(3).ToList(); + var crntReactivePowerOfBHexs = hexData.Skip((int)F25DataItemEnum.CrntReactivePowerOfB).Take(3).ToList(); var crntReactivePowerOfB = AnalyzeDataAccordingToA09(crntReactivePowerOfBHexs[0], crntReactivePowerOfBHexs[1], crntReactivePowerOfBHexs[2]); - var crntReactivePowerOfCHexs = hexDatas.Skip((int)F25DataItemEnum.CrntReactivePowerOfC).Take(2).ToList(); + var crntReactivePowerOfCHexs = hexData.Skip((int)F25DataItemEnum.CrntReactivePowerOfC).Take(2).ToList(); var crntReactivePowerOfC = AnalyzeDataAccordingToA09(crntReactivePowerOfCHexs[0], crntReactivePowerOfCHexs[1], crntReactivePowerOfCHexs[2]); //A.5 % - var crntTotalPowerFactorHexs = hexDatas.Skip((int)F25DataItemEnum.CrntTotalPowerFactor).Take(2).ToList(); + var crntTotalPowerFactorHexs = hexData.Skip((int)F25DataItemEnum.CrntTotalPowerFactor).Take(2).ToList(); var crntTotalPowerFactor = AnalyzeDataAccordingToA05(crntTotalPowerFactorHexs[0], crntTotalPowerFactorHexs[1]); - var crntPowerFactorOfAHexs = hexDatas.Skip((int)F25DataItemEnum.CrntPowerFactorOfA).Take(2).ToList(); + var crntPowerFactorOfAHexs = hexData.Skip((int)F25DataItemEnum.CrntPowerFactorOfA).Take(2).ToList(); var crntPowerFactorOfA = AnalyzeDataAccordingToA05(crntPowerFactorOfAHexs[0], crntPowerFactorOfAHexs[1]); - var crntPowerFactorOfBHexs = hexDatas.Skip((int)F25DataItemEnum.CrntPowerFactorOfB).Take(2).ToList(); + var crntPowerFactorOfBHexs = hexData.Skip((int)F25DataItemEnum.CrntPowerFactorOfB).Take(2).ToList(); var crntPowerFactorOfB = AnalyzeDataAccordingToA05(crntPowerFactorOfBHexs[0], crntPowerFactorOfBHexs[1]); - var crntPowerFactorOfCHexs = hexDatas.Skip((int)F25DataItemEnum.CrntPowerFactorOfC).Take(2).ToList(); + var crntPowerFactorOfCHexs = hexData.Skip((int)F25DataItemEnum.CrntPowerFactorOfC).Take(2).ToList(); var crntPowerFactorOfC = AnalyzeDataAccordingToA05(crntPowerFactorOfCHexs[0], crntPowerFactorOfCHexs[1]); //A.7 V - var crntVoltageOfAHexs = hexDatas.Skip((int)F25DataItemEnum.CrntVoltageOfA).Take(2).ToList(); + var crntVoltageOfAHexs = hexData.Skip((int)F25DataItemEnum.CrntVoltageOfA).Take(2).ToList(); var crntVoltageOfA = AnalyzeDataAccordingToA07(crntVoltageOfAHexs[0], crntVoltageOfAHexs[1]); - var crntVoltageOfBHexs = hexDatas.Skip((int)F25DataItemEnum.CrntVoltageOfB).Take(2).ToList(); + var crntVoltageOfBHexs = hexData.Skip((int)F25DataItemEnum.CrntVoltageOfB).Take(2).ToList(); var crntVoltageOfB = AnalyzeDataAccordingToA07(crntVoltageOfBHexs[0], crntVoltageOfBHexs[1]); - var crntVoltageOfCHexs = hexDatas.Skip((int)F25DataItemEnum.CrntVoltageOfC).Take(2).ToList(); + var crntVoltageOfCHexs = hexData.Skip((int)F25DataItemEnum.CrntVoltageOfC).Take(2).ToList(); var crntVoltageOfC = AnalyzeDataAccordingToA07(crntVoltageOfCHexs[0], crntVoltageOfCHexs[1]); //A.25 A - var crntCurrentOfAHexs = hexDatas.Skip((int)F25DataItemEnum.CrntCurrentOfA).Take(3).ToList(); + var crntCurrentOfAHexs = hexData.Skip((int)F25DataItemEnum.CrntCurrentOfA).Take(3).ToList(); var crntCurrentOfA = AnalyzeDataAccordingToA25(crntCurrentOfAHexs[0], crntCurrentOfAHexs[1], crntCurrentOfAHexs[2]); - var crntCurrentOfBHexs = hexDatas.Skip((int)F25DataItemEnum.CrntCurrentOfB).Take(3).ToList(); + var crntCurrentOfBHexs = hexData.Skip((int)F25DataItemEnum.CrntCurrentOfB).Take(3).ToList(); var crntCurrentOfB = AnalyzeDataAccordingToA25(crntCurrentOfBHexs[0], crntCurrentOfBHexs[1], crntCurrentOfBHexs[2]); - var crntCurrentOfCHexs = hexDatas.Skip((int)F25DataItemEnum.CrntCurrentOfC).Take(3).ToList(); + var crntCurrentOfCHexs = hexData.Skip((int)F25DataItemEnum.CrntCurrentOfC).Take(3).ToList(); var crntCurrentOfC = AnalyzeDataAccordingToA25(crntCurrentOfCHexs[0], crntCurrentOfCHexs[1], crntCurrentOfCHexs[2]); - var crntZeroSequenceCurrentHexs = hexDatas.Skip((int)F25DataItemEnum.CrntZeroSequenceCurrent).Take(3).ToList(); + var crntZeroSequenceCurrentHexs = hexData.Skip((int)F25DataItemEnum.CrntZeroSequenceCurrent).Take(3).ToList(); var crntZeroSequenceCurrent = AnalyzeDataAccordingToA25(crntZeroSequenceCurrentHexs[0], crntZeroSequenceCurrentHexs[1], crntZeroSequenceCurrentHexs[2]); //A.9 kVA - var crntTotalApparentPowerHexs = hexDatas.Skip((int)F25DataItemEnum.CrntTotalApparentPower).Take(3).ToList(); + var crntTotalApparentPowerHexs = hexData.Skip((int)F25DataItemEnum.CrntTotalApparentPower).Take(3).ToList(); var crntTotalApparentPower = AnalyzeDataAccordingToA09(crntTotalApparentPowerHexs[0], crntTotalApparentPowerHexs[1], crntTotalApparentPowerHexs[2]); - var crntApparentPowerOfAHexs = hexDatas.Skip((int)F25DataItemEnum.CrntApparentPowerOfA).Take(3).ToList(); + var crntApparentPowerOfAHexs = hexData.Skip((int)F25DataItemEnum.CrntApparentPowerOfA).Take(3).ToList(); var crntApparentPowerOfA = AnalyzeDataAccordingToA09(crntApparentPowerOfAHexs[0], crntApparentPowerOfAHexs[1], crntApparentPowerOfAHexs[2]); - var crntApparentPowerOfBHexs = hexDatas.Skip((int)F25DataItemEnum.CrntApparentPowerOfB).Take(3).ToList(); + var crntApparentPowerOfBHexs = hexData.Skip((int)F25DataItemEnum.CrntApparentPowerOfB).Take(3).ToList(); var crntApparentPowerOfB = AnalyzeDataAccordingToA09(crntApparentPowerOfBHexs[0], crntApparentPowerOfBHexs[1], crntApparentPowerOfBHexs[2]); - var crntApparentPowerOfCHexs = hexDatas.Skip((int)F25DataItemEnum.CrntApparentPowerOfC).Take(3).ToList(); + var crntApparentPowerOfCHexs = hexData.Skip((int)F25DataItemEnum.CrntApparentPowerOfC).Take(3).ToList(); var crntApparentPowerOfC = AnalyzeDataAccordingToA09(crntApparentPowerOfCHexs[0], crntApparentPowerOfCHexs[1], crntApparentPowerOfCHexs[2]); return new Analyze3761Data() @@ -497,17 +496,17 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts /// //TerminalVersionInfoAnalyze public virtual Analyze3761Data AnalyzeTerminalVersionInfoReadingDataAsync(MessageReceived messageReceived, Action? sendAction = null) { - var hexDatas = GetHexDatas(messageReceived.MessageHexString); + var hexData = GetHexData(messageReceived.MessageHexString); - var makerNo = string.Join("",hexDatas.Take(4).Select(s => (char)s.HexToDec()));//厂商代码 - var deviceNo = string.Join("", hexDatas.Skip((int)TerminalVersionInfoEnum.DeviceNo).Take(8).Select(s => (char)s.HexToDec()));//设备编号 - var softwareVersionNo = string.Join("", hexDatas.Skip((int)TerminalVersionInfoEnum.SoftwareVersionNo).Take(4).Select(s => (char)s.HexToDec()));//软件版本号 - var softwareReleaseDateList = hexDatas.Skip((int)TerminalVersionInfoEnum.SoftwareReleaseDate).Take(3).ToList(); + var makerNo = string.Join("",hexData.Take(4).Select(s => (char)s.HexToDec()));//厂商代码 + var deviceNo = string.Join("", hexData.Skip((int)TerminalVersionInfoEnum.DeviceNo).Take(8).Select(s => (char)s.HexToDec()));//设备编号 + var softwareVersionNo = string.Join("", hexData.Skip((int)TerminalVersionInfoEnum.SoftwareVersionNo).Take(4).Select(s => (char)s.HexToDec()));//软件版本号 + var softwareReleaseDateList = hexData.Skip((int)TerminalVersionInfoEnum.SoftwareReleaseDate).Take(3).ToList(); var softwareReleaseDate = $"20{AnalyzeDataAccordingToA20(softwareReleaseDateList[0], softwareReleaseDateList[1], softwareReleaseDateList[2])}";//软件发布日期 - var capacityInformationCode = string.Join("", hexDatas.Skip((int)TerminalVersionInfoEnum.CapacityInformationCode).Take(11).Select(s => (char)s.HexToDec()));//容量信息码 - var protocolVersionNo = string.Join("", hexDatas.Skip((int)TerminalVersionInfoEnum.ProtocolVersionNo).Take(4).Select(s => (char)s.HexToDec()));//通信协议编号 - var hardwareVersionNo = string.Join("", hexDatas.Skip((int)TerminalVersionInfoEnum.HardwareVersionNo).Take(4).Select(s => (char)s.HexToDec()));//硬件版本号 - var hardwareReleaseDateList = hexDatas.Skip((int)TerminalVersionInfoEnum.HardwareReleaseDate).Take(3).ToList(); + var capacityInformationCode = string.Join("", hexData.Skip((int)TerminalVersionInfoEnum.CapacityInformationCode).Take(11).Select(s => (char)s.HexToDec()));//容量信息码 + var protocolVersionNo = string.Join("", hexData.Skip((int)TerminalVersionInfoEnum.ProtocolVersionNo).Take(4).Select(s => (char)s.HexToDec()));//通信协议编号 + var hardwareVersionNo = string.Join("", hexData.Skip((int)TerminalVersionInfoEnum.HardwareVersionNo).Take(4).Select(s => (char)s.HexToDec()));//硬件版本号 + var hardwareReleaseDateList = hexData.Skip((int)TerminalVersionInfoEnum.HardwareReleaseDate).Take(3).ToList(); var hardwareReleaseDate = $"20{AnalyzeDataAccordingToA20(hardwareReleaseDateList[0], hardwareReleaseDateList[1], hardwareReleaseDateList[2])}";//软件发布日期 return new Analyze3761Data() @@ -548,24 +547,24 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts /// public virtual Analyze3761Data AnalyzeATypeOfDataItems49ReadingDataAsync(MessageReceived messageReceived, Action? sendAction = null) { - var hexDatas = GetHexDatas(messageReceived.MessageHexString); + var hexData = GetHexData(messageReceived.MessageHexString); - var uabUaList = hexDatas.Take(2).ToList(); + var uabUaList = hexData.Take(2).ToList(); var uabUa = AnalyzeDataAccordingToA05(uabUaList[0], uabUaList[1]); //单位 度 - var ubList = hexDatas.Skip((int)ATypeOfDataItems49.Ub).Take(2).ToList(); + var ubList = hexData.Skip((int)ATypeOfDataItems49.Ub).Take(2).ToList(); var ub = AnalyzeDataAccordingToA05(ubList[0], ubList[1]); - var ucbUcList = hexDatas.Skip((int)ATypeOfDataItems49.UcbUc).Take(2).ToList(); + var ucbUcList = hexData.Skip((int)ATypeOfDataItems49.UcbUc).Take(2).ToList(); var ucbUc = AnalyzeDataAccordingToA05(ucbUcList[0], ucbUcList[1]); - var iaList = hexDatas.Skip((int)ATypeOfDataItems49.Ia).Take(2).ToList(); + var iaList = hexData.Skip((int)ATypeOfDataItems49.Ia).Take(2).ToList(); var ia = AnalyzeDataAccordingToA05(iaList[0], iaList[1]); - var ibList = hexDatas.Skip((int)ATypeOfDataItems49.Ib).Take(2).ToList(); + var ibList = hexData.Skip((int)ATypeOfDataItems49.Ib).Take(2).ToList(); var ib = AnalyzeDataAccordingToA05(ibList[0], ibList[1]); - var icList = hexDatas.Skip((int)ATypeOfDataItems49.Ic).Take(2).ToList(); + var icList = hexData.Skip((int)ATypeOfDataItems49.Ic).Take(2).ToList(); var ic = AnalyzeDataAccordingToA05(icList[0], icList[1]); return new Analyze3761Data() @@ -573,15 +572,15 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts AFN = 12, FN = 49, Text = "相位角", - DataUpChilds = new List() - { - new Analyze3761DataUpChild(1,"UabUa相位角",uabUa.ToString(),1), - new Analyze3761DataUpChild(2,"Ub相位角",ub.ToString(),2), - new Analyze3761DataUpChild(3,"UcbUc相位角",ucbUc.ToString(),3), - new Analyze3761DataUpChild(4,"Ia相位角",ia.ToString(),4), - new Analyze3761DataUpChild(5,"Ib相位角",ib.ToString(),5), - new Analyze3761DataUpChild(6,"Ic相位角",ic.ToString(),6), - } + DataUpChilds = + [ + new Analyze3761DataUpChild(1, "UabUa相位角", uabUa.ToString(), 1), + new Analyze3761DataUpChild(2, "Ub相位角", ub.ToString(), 2), + new Analyze3761DataUpChild(3, "UcbUc相位角", ucbUc.ToString(), 3), + new Analyze3761DataUpChild(4, "Ia相位角", ia.ToString(), 4), + new Analyze3761DataUpChild(5, "Ib相位角", ib.ToString(), 5), + new Analyze3761DataUpChild(6, "Ic相位角", ic.ToString(), 6) + ] }; } @@ -593,7 +592,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts public virtual void AnalyzeTerminalTimeReadingDataAsync(MessageReceived messageReceived, Action? sendAction = null) { - var hexDatas = GetHexDatas(messageReceived.MessageHexString); + var hexDatas = GetHexData(messageReceived.MessageHexString); var time = Appendix.Appendix_A1(hexDatas.Take(6).ToList()); } @@ -616,14 +615,14 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts var tb3761Fn = tb3761.FnList.FirstOrDefault(it => it.Fn == fn); if (tb3761Fn == null) return null; - var hexDatas = (List)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data); + var analyzeValue = (List)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data); var m = 0; var rateNumberUpSort = -1; var rateNumberUp = tb3761Fn.UpList.FirstOrDefault(it => it.Name.Contains("费率数M")); if (rateNumberUp != null) { - var rateNumber = hexDatas.Skip(rateNumberUp.DataIndex).Take(rateNumberUp.DataCount).FirstOrDefault(); + var rateNumber = analyzeValue.Skip(rateNumberUp.DataIndex).Take(rateNumberUp.DataCount).FirstOrDefault(); m = Convert.ToInt32(rateNumber); rateNumberUpSort = rateNumberUp.Sort; } @@ -640,7 +639,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts dataIndex = sum1 + sum2; } - var value = AnalyzeDataAccordingDataType(hexDatas, dataIndex, up.DataCount, up.DataType); + var value = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, up.DataCount, up.DataType); if (value != null) { up.Value = value.ToString(); @@ -652,7 +651,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts { for (var j = 0; j < repeatCount; j++) { - var val = AnalyzeDataAccordingDataType(hexDatas, dataIndex, upChild.DataCount, upChild.DataType); + var val = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, upChild.DataCount, upChild.DataType); if (val != null) { upChild.Name = string.Format(upChild.Name, j + 1); @@ -688,11 +687,11 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts var tb3761Fn = tb3761.FnList.FirstOrDefault(it => it.Fn == fn); if (tb3761Fn == null) return null; - var hexDatas = (List)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data); + var analyzeValue = (List)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data); foreach (var up in tb3761Fn.UpList) { - var value = AnalyzeDataAccordingDataType(hexDatas, up.DataIndex, up.DataCount, up.DataType); + var value = AnalyzeDataAccordingDataType(analyzeValue, up.DataIndex, up.DataCount, up.DataType); if (value != null) { up.Value = value.ToString(); @@ -705,7 +704,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts { for (var j = 0; j < repeatCount; j++) { - var val = AnalyzeDataAccordingDataType(hexDatas, dataIndex, upChild.DataCount, upChild.DataType); + var val = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, upChild.DataCount, upChild.DataType); if (val != null) { upChild.Value = val.ToString(); @@ -741,9 +740,9 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts // } } - private object? AnalyzeDataAccordingDataType(List hexDatas, int dataIndex,int dataCount,string dataType) + private object? AnalyzeDataAccordingDataType(List analyzeValue, int dataIndex,int dataCount,string dataType) { - var valueList = hexDatas.Skip(dataIndex).Take(dataCount).ToList(); + var valueList = analyzeValue.Skip(dataIndex).Take(dataCount).ToList(); object? value = null; switch (dataType) { @@ -771,11 +770,11 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts case "A15": if (valueList.Count == 5) { - //var minutes = Convert.ToInt32(hexDatas[0]); // 获取当前分钟数 - //var hours = Convert.ToInt32(hexDatas[1]); // 获取当前小时数 - //var day = Convert.ToInt32(hexDatas[2]); // 获取当前日期的日数 - //var month = Convert.ToInt32(hexDatas[3]); // 获取当前月份 - //var year = Convert.ToInt32(hexDatas[4]); // 获取当前日期的年份 + //var minutes = Convert.ToInt32(analyzeValue[0]); // 获取当前分钟数 + //var hours = Convert.ToInt32(analyzeValue[1]); // 获取当前小时数 + //var day = Convert.ToInt32(analyzeValue[2]); // 获取当前日期的日数 + //var month = Convert.ToInt32(analyzeValue[3]); // 获取当前月份 + //var year = Convert.ToInt32(analyzeValue[4]); // 获取当前日期的年份 value = AnalyzeDataAccordingToA15(valueList[0], valueList[1], valueList[2], valueList[3], valueList[4]); } break; @@ -801,7 +800,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts /// public virtual async Task AnalyzeTransparentForwardingAnswerAsync(MessageReceived messageReceivedEvent, Action? sendAction = null) { - var hexDatas = GetHexDatas(messageReceivedEvent.MessageHexString); + var hexDatas = GetHexData(messageReceivedEvent.MessageHexString); var port = hexDatas[0].HexToDec(); @@ -828,7 +827,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts /// public virtual async Task AnalyzeTransparentForwardingAnswerResultAsync(MessageReceived messageReceived, Action? sendAction = null) { - var hexDatas = GetHexDatas(messageReceived.MessageHexString); + var hexDatas = GetHexData(messageReceived.MessageHexString); var port = hexDatas[0].HexToDec(); @@ -844,25 +843,25 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts /// /// /// - public static List GetHexDatas(string messageHexString) + public static List GetHexData(string messageHexString) { var hexStringList = messageHexString.StringToPairs(); - var hexDatas = (List)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data); - return hexDatas; + var analyzeValue = (List)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data); + return analyzeValue; } /// /// 解析时间标签 /// - /// - public void AnalysisTp(List hexDatas) + /// + public void AnalysisTp(List hexData) { - var pFC = hexDatas[0].HexToDec();//启动帧帧序号计数器 - var seconds = Convert.ToInt32(hexDatas[1]); // 获取当前秒数 - var minutes = Convert.ToInt32(hexDatas[2]); // 获取当前分钟数 - var hours = Convert.ToInt32(hexDatas[3]); // 获取当前小时数 - var day = Convert.ToInt32(hexDatas[4]); // 获取当前日期的日数 - var delayTime = hexDatas[5].HexToDec();//延迟时间 min + var pFC = hexData[0].HexToDec();//启动帧帧序号计数器 + var seconds = Convert.ToInt32(hexData[1]); // 获取当前秒数 + var minutes = Convert.ToInt32(hexData[2]); // 获取当前分钟数 + var hours = Convert.ToInt32(hexData[3]); // 获取当前小时数 + var day = Convert.ToInt32(hexData[4]); // 获取当前日期的日数 + var delayTime = hexData[5].HexToDec();//延迟时间 min } #region 报文指定的数据格式 -- 2.47.2 From 5b177759bd4fe171cf24f3d53bc2db5892e759d4 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 17 Mar 2025 11:34:30 +0800 Subject: [PATCH 018/139] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=B8=8B=E5=8F=91?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=9A=84=E5=A4=84=E7=90=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 62 +- ...nergySystemScheduledMeterReadingService.cs | 5 +- .../Subscribers/WorkerSubscriberAppService.cs | 8 +- .../BuildSendDatas/Build3761SendData.cs | 2 +- .../BuildSendDatas/TelemetryPacketBuilder.cs | 1936 ++++++++++++++++- .../Ammeters/AmmeterInfo.cs | 2 +- .../Pages/Monitor.cshtml | 24 +- .../appsettings.Development.json | 2 +- src/JiShe.CollectBus.Host/appsettings.json | 2 +- 9 files changed, 1993 insertions(+), 50 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index c7b7b4e..7f4d6ce 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Net; using System.Threading.Tasks; @@ -118,7 +119,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading List itemCodeList = new List(); foreach (var dataType in itemArr) { - var excludeItemCode = "10_98,10_94";//排除透明转发:尖峰平谷时段、跳合闸 + var excludeItemCode = "10_98,10_94";//TODO 排除透明转发:尖峰平谷时段、跳合闸,特殊电表 var gatherItem = gatherItemInfos.FirstOrDefault(f => f.DataType.Equals(dataType)); if (gatherItem != null) { @@ -127,17 +128,21 @@ namespace JiShe.CollectBus.ScheduledMeterReading itemCodeList.Add(gatherItem.ItemCode); } } + + #region 特殊电表采集项编号处理 + if (itemArr.Exists(e => e.Equals("95"))) //德力西DTS + { + itemCodeList.Add("10_95"); + } + if (itemArr.Exists(e => e.Equals("109")))//WAVE_109 + { + itemCodeList.Add("10_109"); + } + #endregion } #endregion - #region 特殊电表采集项编号处理 - if (itemArr.Exists(e => e.Equals("95"))) //德力西DTS - { - itemCodeList.Add("10_95"); - } - //if (itemArr.Exists(e => e.Equals("109")))//WAVE_109 - // ammeter.ItemCodes += "10_109,"; - #endregion + ammeter.ItemCodes = itemCodeList.Serialize();//转换成JSON字符串 @@ -207,7 +212,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading _logger.LogError($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理时没有获取到缓存信息,-102"); return; } - await AmmerterScheduledMeterReadingIssued(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, meterInfos); + await AmmerterScheduledMeterReadingIssued(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, meterInfos); _logger.LogInformation($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理完成"); } @@ -235,8 +240,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading return; } - await AmmerterScheduledMeterReadingIssued(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, meterInfos); - _logger.LogInformation($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理完成"); + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + + await AmmerterScheduledMeterReadingIssued(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, meterInfos); + + stopwatch.Stop(); + + _logger.LogInformation($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理完成,共消耗{stopwatch.ElapsedMilliseconds}毫秒。"); } #endregion @@ -494,6 +505,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading foreach (var group in focusHashGroups) { _ = Task.Run(async () => { await CreatePublishTask(eventName, group.Value); }); + //await CreatePublishTask(eventName, group.Value); } await Task.CompletedTask; @@ -538,12 +550,12 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - //排除1天未在线的集中器生成指令 或 排除集中器配置为自动上报的集中器 - if (!IsGennerateCmd(ammeter.LastTime, -1)) - { - _logger.LogInformation($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name},采集时间:{ammeter.LastTime},已超过1天未在线,不生成指令"); - continue; - } + ////排除1天未在线的集中器生成指令 或 排除集中器配置为自动上报的集中器 + //if (!IsGennerateCmd(ammeter.LastTime, -1)) + //{ + // _logger.LogInformation($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name},采集时间:{ammeter.LastTime},已超过1天未在线,不生成指令"); + // continue; + //} if (string.IsNullOrWhiteSpace(ammeter.AreaCode)) { @@ -597,7 +609,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading tempCodes = tempSubCodes; } } - + foreach (var tempItem in tempCodes) { //排除已发送日冻结和月冻结采集项配置 @@ -615,20 +627,22 @@ namespace JiShe.CollectBus.ScheduledMeterReading var aFN = (AFN)itemCodeArr[0].HexToDec(); var fn = int.Parse(itemCodeArr[1]); byte[] dataInfos = null; - if(ammeter.AutomaticReport.Equals(1) && aFN == AFN.请求实时数据) + if (ammeter.AutomaticReport.Equals(1) && aFN == AFN.请求实时数据) { //实时数据 dataInfos = Build3761SendData.BuildAmmeterReadRealTimeDataSendCmd(ammeter.FocusAddress, ammeter.MeteringCode, (ATypeOfDataItems)fn); } else { + //特殊表暂不处理 if (TelemetryPacketBuilder.AFNHandlers.TryGetValue(tempItem, out var handler)) { - dataInfos = handler(ammeter.FocusAddress, fn, ammeter.MeteringCode); + dataInfos = handler(ammeter.FocusAddress, fn, ammeter.MeteringCode); } else { - throw new InvalidOperationException($"无效编码: {tempItem}"); + _logger.LogWarning($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}采集项{tempItem}无效编码。"); + continue; } } //TODO:特殊表 @@ -648,10 +662,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading await _capBus.PublishAsync(eventName, evenMessageInfo); } - + } - } + } } /// diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 16359f2..dd49e74 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -59,11 +59,12 @@ namespace JiShe.CollectBus.ScheduledMeterReading INNER JOIN TB_FocusInfo(NOLOCK) AS B ON A.ID = B.GatherInfoID AND B.RemoveState >= 0 AND B.State>=0 INNER JOIN TB_AmmeterInfo(NOLOCK) AS C ON B.ID = C.FocusID AND C.State>= 0 AND C.State<100 INNER JOIN TB_AmmeterGatherItem(NOLOCK) AS D ON C.ID = D.AmmeterID AND D.State>=0 - WHERE 1=1 "; + WHERE 1=1 and C.Special = 0 "; + //TODO 记得移除特殊表过滤 if (!string.IsNullOrWhiteSpace(gatherCode)) { - sql = $@"{sql} A.GatherCode = '{gatherCode}'"; + sql = $@"{sql} AND A.GatherCode = '{gatherCode}'"; } return await SqlProvider.Instance.Change(DbEnum.EnergyDB) .Ado diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index 693abda..b1cc03b 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -81,7 +81,7 @@ namespace JiShe.CollectBus.Subscribers /// [HttpPost] [Route("ammeter/fiveminute/issued-event")] - [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] + [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName)] public async Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("5分钟采集电表数据下行消息消费队列开始处理"); @@ -108,7 +108,7 @@ namespace JiShe.CollectBus.Subscribers /// [HttpPost] [Route("ammeter/fifteenminute/issued-event")] - [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] + [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName)] public async Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("15分钟采集电表数据下行消息消费队列开始处理"); @@ -164,7 +164,7 @@ namespace JiShe.CollectBus.Subscribers /// [HttpPost] [Route("watermeter/fiveminute/issued-event")] - [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerOneMinuteIssuedEventName)] + [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName)] public async Task WatermeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("5分钟采集电表数据下行消息消费队列开始处理"); @@ -191,7 +191,7 @@ namespace JiShe.CollectBus.Subscribers /// [HttpPost] [Route("watermeter/fifteenminute/issued-event")] - [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerOneMinuteIssuedEventName)] + [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName)] public async Task WatermeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("15分钟采集电表数据下行消息消费队列开始处理"); diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs b/src/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs index 9bed5b8..d7a9ece 100644 --- a/src/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs +++ b/src/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs @@ -1401,7 +1401,7 @@ namespace JiShe.CollectBus.Common.BuildSendDatas cmdStrList.AddRange(userDatas); cmdStrList.Add(cs); cmdStrList.Add(endStr); - Console.WriteLine(string.Join(" ", cmdStrList)); + //Console.WriteLine(string.Join(" ", cmdStrList)); var bytes = cmdStrList.Select(x => Convert.ToByte(x, 16)).ToArray(); return bytes; } diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs b/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs index 96bb97f..9f5c3ec 100644 --- a/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs +++ b/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs @@ -38,14 +38,14 @@ namespace JiShe.CollectBus.Common.BuildSendDatas if (method.Name.StartsWith("AFN") && method.Name.EndsWith("_Send")) { // 提取编码部分(例如 "AFN0D_F184_Send" -> "0D_184") - string code = method.Name[3..^5].Replace("F", "_"); // 移除前缀和后缀,替换F为_ + string code = method.Name[3..^5].Replace("F", ""); // 移除前缀和后缀,替换F为_ var delegateInstance = (AFNDelegate)Delegate.CreateDelegate(typeof(AFNDelegate), method); AFNHandlers[code] = delegateInstance; } } } - #region AFN_00H + #region AFN_00H 确认∕否认 public static byte[] AFN00_F1_Send(string address,int fn,int pn = 0) { var reqParameter = new ReqParameter2() @@ -92,7 +92,7 @@ namespace JiShe.CollectBus.Common.BuildSendDatas #endregion - #region AFN_01H + #region AFN_01H 复位命令 public static byte[] AFN01_F1_Send(string address, int fn, int pn = 0) { var reqParameter = new ReqParameter2() @@ -117,7 +117,7 @@ namespace JiShe.CollectBus.Common.BuildSendDatas #endregion - #region AFN_02H + #region AFN_02H 链路接口检测 public static byte[] AFN02_F2_Send(string address, int fn, int pn = 0) { var reqParameter = new ReqParameter2() @@ -140,5 +140,1933 @@ namespace JiShe.CollectBus.Common.BuildSendDatas return bytes; } #endregion + + #region AFN_04H 设置参数 + public static byte[] AFN04_F3_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN04_F10_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN04_F66_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN04_F88_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + #endregion + + #region AFN_05H 控制命令 + public static byte[] AFN05_F31_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + #endregion + + #region AFN_09H 请求终端配置及信息 + public static byte[] AFN09_F1_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN09_F9_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + #endregion + + #region AFN_0AH 查询参数 + public static byte[] AFN0A_F10_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0A_F66_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0A_F88_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + #endregion + + #region AFN_0CH 请求一类数据 + public static byte[] AFN0C_F2_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0C_F25_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0C_F33_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0C_F49_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0C_F129_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0C_F130_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0C_F131_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0C_F132_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0C_F145_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0C_F149_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0C_F188_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + #endregion + + #region AFN_0DH 请求二类数据 + public static byte[] AFN0D_F3_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F4_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F11_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F19_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F81_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F82_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F83_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F84_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F85_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F86_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F87_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + public static byte[] AFN0D_F88_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F89_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F90_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F91_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F92_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F93_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F94_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F95_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + public static byte[] AFN0D_F96_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + public static byte[] AFN0D_F97_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F98_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F99_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F100_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F101_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F102_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F103_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F104_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F105_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + public static byte[] AFN0D_F106_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F107_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F108_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F145_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F146_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F147_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F148_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F161_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F162_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + public static byte[] AFN0D_F163_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F164_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F165_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F166_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F167_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F1618_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F177_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F178_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F179_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + public static byte[] AFN0D_F180_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F181_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F182_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F183_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F184_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F190_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F193_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN0D_F195_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + #endregion + + #region AFN10H 数据转发 + public static byte[] AFN10_F4_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN10_F94_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN10_F97_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + public static byte[] AFN10_F101_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN10_F102_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN10_F103_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN10_F104_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN10_F105_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN10_F106_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN10_F107_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + public static byte[] AFN10_F249_Send(string address, int fn, int pn = 0) + { + var reqParameter = new ReqParameter2() + { + AFN = AFN.链路接口检测, + FunCode = (int)CMasterStationFunCode.请求2级数据, + A = address, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = 0, + }, + MSA = Build3761SendData.GetMSA(address), + Pn = pn, + Fn = fn + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + return bytes; + } + + #region SpecialAmmeter 特殊电表转发 + //TODO 特殊电表处理 + + #endregion + + #endregion } } diff --git a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs index f62ba92..eb191ca 100644 --- a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs +++ b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs @@ -119,7 +119,7 @@ namespace JiShe.CollectBus.Ammeters public string GatherCode { get; set; } /// - /// 是否特殊表 + /// 是否特殊表,1是特殊电表 /// public int Special { get; set; } diff --git a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml index d742dbc..f5c12d6 100644 --- a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml +++ b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml @@ -86,9 +86,9 @@ box-sizing: border-box; } .container { - width: 1170 px; - padding-right: 15 px; - padding-left: 15 px; + width: 1170px; + padding-right: 15px; + padding-left: 15px; margin-right: auto; margin-left: auto; } @@ -97,31 +97,31 @@ text-align: center; font-weight: 200; display: block; - margin: 60 px auto 40 px !important; + margin: 60px auto 40px !important; } .page-header { padding-bottom: 9px; margin: 40px auto; - border-bottom: 1 px solid #eee; + border-bottom: 1px solid #eee; } .projects-header h2 { font-size: 42px; letter-spacing: -1px; } h2 { - margin-top: 20 px; - margin-bottom: 10 px; + margin-top: 20px; + margin-bottom: 10px; font-weight: 500; line-height: 1.1; color: inherit; /* text-align: center; */ } p { - margin: 0 0 10 px; + margin: 0 0 10px; } .row { - margin-right: -15 px; - margin-left: -15 px; + margin-right: -15px; + margin-left: -15px; } .col-lg-3 { width: 25%; @@ -131,7 +131,7 @@ margin-right: auto; margin-left: auto; text-align: center; - margin-bottom: 30 px; + margin-bottom: 30px; border-radius: 0; } .thumbnail { @@ -139,7 +139,7 @@ padding: 4px; line-height: 1.42857143; background-color: #fff; - border: 1 px solid #ddd; + border: 1px solid #ddd; .transition(border 0.2s ease-in-out); } a { diff --git a/src/JiShe.CollectBus.Host/appsettings.Development.json b/src/JiShe.CollectBus.Host/appsettings.Development.json index 0c208ae..bcdd3fc 100644 --- a/src/JiShe.CollectBus.Host/appsettings.Development.json +++ b/src/JiShe.CollectBus.Host/appsettings.Development.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Information", + "Default": "Warning", "Microsoft.AspNetCore": "Warning" } } diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 4a166da..ab593ab 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -5,7 +5,7 @@ "Serilog.Sinks.File" ], "MinimumLevel": { - "Default": "Information", + "Default": "Warning", "Override": { "Microsoft": "Information", "Volo.Abp": "Warning", -- 2.47.2 From ddb6d527552821834cab55b584c545551e3d933d Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 17 Mar 2025 14:23:48 +0800 Subject: [PATCH 019/139] =?UTF-8?q?=E5=AE=9A=E6=97=B6=E6=8A=84=E8=AF=BB?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=AD=98=E5=85=A5mangodb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IWorkerSubscriberAppService.cs | 5 +-- .../BasicScheduledMeterReadingService.cs | 32 +++++++++++++------ ...nergySystemScheduledMeterReadingService.cs | 7 ++-- .../Subscribers/WorkerSubscriberAppService.cs | 3 +- ...ScheduledMeterReadingIssuedEventMessage.cs | 24 +++++++------- .../MongoDB/CollectBusMongoDbContext.cs | 3 ++ 6 files changed, 49 insertions(+), 25 deletions(-) rename src/{JiShe.CollectBus.Common/Models => JiShe.CollectBus.Domain/IotSystems/MessageIssueds}/ScheduledMeterReadingIssuedEventMessage.cs (56%) diff --git a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs index 05c9e6a..a7c4032 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs @@ -1,5 +1,6 @@ -using System.Threading.Tasks; -using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.IotSystems.MessageIssueds; +using JiShe.CollectBus.IotSystems.MessageReceiveds; +using System.Threading.Tasks; using Volo.Abp.Application.Services; namespace JiShe.CollectBus.Subscribers diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 7f4d6ce..39be20d 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -14,6 +14,8 @@ using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.GatherItem; +using JiShe.CollectBus.IotSystems.Devices; +using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.Watermeter; using JiShe.CollectBus.Protocol.Contracts; @@ -21,6 +23,7 @@ using JiShe.CollectBus.Workers; using MassTransit; using MassTransit.Internals.GraphValidation; using Microsoft.Extensions.Logging; +using Volo.Abp.Domain.Repositories; namespace JiShe.CollectBus.ScheduledMeterReading { @@ -31,10 +34,17 @@ namespace JiShe.CollectBus.ScheduledMeterReading { private readonly ILogger _logger; private readonly ICapPublisher _capBus; - public BasicScheduledMeterReadingService(ILogger logger, ICapPublisher capBus) + private readonly IRepository _meterReadingIssuedRepository; + + + public BasicScheduledMeterReadingService( + ILogger logger, + ICapPublisher capBus, + IRepository meterReadingIssuedRepository) { _capBus = capBus; _logger = logger; + _meterReadingIssuedRepository = meterReadingIssuedRepository; } /// @@ -247,7 +257,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading stopwatch.Stop(); - _logger.LogInformation($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理完成,共消耗{stopwatch.ElapsedMilliseconds}毫秒。"); + _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理完成,共消耗{stopwatch.ElapsedMilliseconds}毫秒。"); } #endregion @@ -470,7 +480,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading try { //将采集器编号的hash值取模分组 - const int TotalShards = 20; + const int TotalShards = 1024; var focusHashGroups = new Dictionary>>(); foreach (var (collectorId, ammetersDictionary) in focusGroup) @@ -504,11 +514,12 @@ namespace JiShe.CollectBus.ScheduledMeterReading //根据分组创建线程批处理集中器 foreach (var group in focusHashGroups) { - _ = Task.Run(async () => { await CreatePublishTask(eventName, group.Value); }); - //await CreatePublishTask(eventName, group.Value); + //TODO _meterReadingIssuedRepository 需要优化 + //_ = Task.Run(async () => { await CreatePublishTask(eventName, group.Value); }); + await CreatePublishTask(eventName, group.Value); } - await Task.CompletedTask; + //await Task.CompletedTask; } catch (Exception) { @@ -610,6 +621,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } } + List evenMessageInfoList = new List(); foreach (var tempItem in tempCodes) { //排除已发送日冻结和月冻结采集项配置 @@ -657,13 +669,15 @@ namespace JiShe.CollectBus.ScheduledMeterReading { Message = dataInfos!, DeviceNo = ammeter.FocusAddress, - MessageId = NewId.NextGuid().ToString() + MessageId = NewId.NextGuid().ToString(), + TimeDensity = eventName, + WasSuccessful = false, }; await _capBus.PublishAsync(eventName, evenMessageInfo); + evenMessageInfoList.Add(evenMessageInfo); } - - + await _meterReadingIssuedRepository.InsertManyAsync(evenMessageInfoList); } } } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index dd49e74..a95312b 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -1,13 +1,16 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.GatherItem; +using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.Watermeter; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; +using Volo.Abp.Domain.Repositories; namespace JiShe.CollectBus.ScheduledMeterReading { @@ -19,7 +22,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading public class EnergySystemScheduledMeterReadingService : BasicScheduledMeterReadingService { - public EnergySystemScheduledMeterReadingService(ILogger logger, ICapPublisher capBus) :base(logger, capBus) + public EnergySystemScheduledMeterReadingService(ILogger logger, ICapPublisher capBus, IRepository meterReadingIssuedRepository) :base(logger, capBus, meterReadingIssuedRepository) { } diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index b1cc03b..8d0d154 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -3,8 +3,9 @@ using System.Threading.Tasks; using DeviceDetectorNET.Parser.Device; using DotNetCore.CAP; using JiShe.CollectBus.Common.Enums; -using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IotSystems.Devices; +using JiShe.CollectBus.IotSystems.MessageIssueds; +using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using Microsoft.AspNetCore.Mvc; diff --git a/src/JiShe.CollectBus.Common/Models/ScheduledMeterReadingIssuedEventMessage.cs b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs similarity index 56% rename from src/JiShe.CollectBus.Common/Models/ScheduledMeterReadingIssuedEventMessage.cs rename to src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs index b62c7db..cdab61a 100644 --- a/src/JiShe.CollectBus.Common/Models/ScheduledMeterReadingIssuedEventMessage.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs @@ -1,17 +1,14 @@ using JiShe.CollectBus.Common.Enums; +using System; +using Volo.Abp.Domain.Entities; -namespace JiShe.CollectBus.Common.Models +namespace JiShe.CollectBus.IotSystems.MessageIssueds { /// /// 定时抄读Kafka消息实体,1分钟、5分钟、15分钟 /// - public class ScheduledMeterReadingIssuedEventMessage + public class ScheduledMeterReadingIssuedEventMessage : AggregateRoot { - /// - /// 消息接收客户端Id - /// - public string ClientId { get; set; } - /// /// 消息内容 /// @@ -22,14 +19,19 @@ namespace JiShe.CollectBus.Common.Models /// public string DeviceNo { get; set; } - ///// - ///// 采集时间间隔(分钟,如15) - ///// - //public int TimeDensity { get; set; } + /// + /// 采集时间间隔,通过Kafka主题区分(分钟,如15) + /// + public string TimeDensity { get; set; } /// /// 消息Id /// public string MessageId { get; set; } + + /// + /// 是否下发成功 + /// + public bool WasSuccessful { get; set; } } } diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs index a42a250..06dfd1a 100644 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs @@ -1,4 +1,5 @@ using JiShe.CollectBus.IotSystems.Devices; +using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.Protocols; using MongoDB.Driver; @@ -22,6 +23,8 @@ public class CollectBusMongoDbContext : AbpMongoDbContext, ICollectBusMongoDbCon public IMongoCollection Devices => Collection(); public IMongoCollection ProtocolInfos => Collection(); + public IMongoCollection MeterReadingIssued => Collection(); + protected override void CreateModel(IMongoModelBuilder modelBuilder) { base.CreateModel(modelBuilder); -- 2.47.2 From 0b2b3bc3d23f6f434ff6293105fe97e36bd18fab Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 17 Mar 2025 15:13:54 +0800 Subject: [PATCH 020/139] =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Workers/SubscriberFifteenMinuteWorker.cs | 44 +++++++++++++++++++ .../Workers/SubscriberFiveMinuteWorker.cs | 39 ++++++++++++++++ .../Workers/SubscriberOneMinuteWorker.cs | 39 ++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs create mode 100644 src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs create mode 100644 src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs b/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs new file mode 100644 index 0000000..90c032f --- /dev/null +++ b/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs @@ -0,0 +1,44 @@ +using System.Threading; +using System.Threading.Tasks; +using Hangfire; +using JiShe.CollectBus.ScheduledMeterReading; +using Microsoft.Extensions.Logging; +using Volo.Abp.BackgroundWorkers.Hangfire; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Uow; + +namespace JiShe.CollectBus.Workers +{ + /// + /// 15分钟采集数据 + /// + public class SubscriberFifteenMinuteWorker : HangfireBackgroundWorkerBase, ITransientDependency, ICollectWorker + { + private readonly ILogger _logger; + private readonly IScheduledMeterReadingService _scheduledMeterReadingService; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// 定时任务 + public SubscriberFifteenMinuteWorker(ILogger logger, IScheduledMeterReadingService scheduledMeterReadingService) + { + _logger = logger; + RecurringJobId = nameof(SubscriberFifteenMinuteWorker); + CronExpression = Cron.Hourly(15); + this._scheduledMeterReadingService = scheduledMeterReadingService; + } + + + public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) + { + await _scheduledMeterReadingService.AmmeterScheduledMeterFifteenMinuteReading(); + //using (var uow = LazyServiceProvider.LazyGetRequiredService().Begin()) + //{ + // Logger.LogInformation("Executed MyLogWorker..!"); + // return Task.CompletedTask; + //} + } + } +} diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs b/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs new file mode 100644 index 0000000..f20cf86 --- /dev/null +++ b/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs @@ -0,0 +1,39 @@ +using System.Threading; +using System.Threading.Tasks; +using Hangfire; +using JiShe.CollectBus.ScheduledMeterReading; +using Microsoft.Extensions.Logging; +using Volo.Abp.BackgroundWorkers.Hangfire; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Uow; + +namespace JiShe.CollectBus.Workers +{ + /// + /// 5分钟采集数据 + /// + public class SubscriberFiveMinuteWorker : HangfireBackgroundWorkerBase, ITransientDependency,ICollectWorker + { + private readonly ILogger _logger; + private readonly IScheduledMeterReadingService _scheduledMeterReadingService; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// 定时任务 + public SubscriberFiveMinuteWorker(ILogger logger, IScheduledMeterReadingService scheduledMeterReadingService) + { + _logger = logger; + RecurringJobId = nameof(SubscriberFiveMinuteWorker); + CronExpression = Cron.Hourly(15); + this._scheduledMeterReadingService = scheduledMeterReadingService; + } + + + public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) + { + await _scheduledMeterReadingService.AmmeterScheduledMeterFifteenMinuteReading(); + } + } +} diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs b/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs new file mode 100644 index 0000000..2132cdf --- /dev/null +++ b/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs @@ -0,0 +1,39 @@ +using System.Threading; +using System.Threading.Tasks; +using Hangfire; +using JiShe.CollectBus.ScheduledMeterReading; +using Microsoft.Extensions.Logging; +using Volo.Abp.BackgroundWorkers.Hangfire; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Uow; + +namespace JiShe.CollectBus.Workers +{ + /// + /// 1分钟采集数据 + /// + public class SubscriberOneMinuteWorker : HangfireBackgroundWorkerBase, ITransientDependency,ICollectWorker + { + private readonly ILogger _logger; + private readonly IScheduledMeterReadingService _scheduledMeterReadingService; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// 定时任务 + public SubscriberOneMinuteWorker(ILogger logger, IScheduledMeterReadingService scheduledMeterReadingService) + { + _logger = logger; + RecurringJobId = nameof(SubscriberOneMinuteWorker); + CronExpression = Cron.Hourly(15); + this._scheduledMeterReadingService = scheduledMeterReadingService; + } + + + public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) + { + await _scheduledMeterReadingService.AmmeterScheduledMeterFifteenMinuteReading(); + } + } +} -- 2.47.2 From e95d356780637d44d0e9f5392a1ed376329ece6a Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Mon, 17 Mar 2025 15:51:40 +0800 Subject: [PATCH 021/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EnergySystem/CacheAppService.cs | 22 ++++++++++++++++++- .../EnergySystems/Entities/TB_AmmeterInfo.cs | 5 +++++ .../JiShe.CollectBus.Domain.csproj | 1 + src/JiShe.CollectBus.Host/appsettings.json | 2 +- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs b/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs index 2a038a6..314c267 100644 --- a/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs +++ b/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs @@ -4,12 +4,32 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using JiShe.CollectBus.EnergySystems.TableViews; +using JiShe.CollectBus.FreeSql; using Volo.Abp.Domain.Repositories; +using JiShe.CollectBus.IotSystems.PrepayModel; namespace JiShe.CollectBus.EnergySystem { - public class CacheAppService: CollectBusAppService,ICacheAppService + public class CacheAppService : CollectBusAppService, ICacheAppService { + //public async Task GetHashByKey(string key) + //{ + // await FreeRedisProvider.Instance.HGetAsync(key,); + //} + public async Task SetHashByKey(string key) + { + var data = await SqlProvider.Instance.Change(DbEnum.EnergyDB).Select().ToListAsync(); + + var groupData = data.GroupBy(a => $"{a.FocusAreaCode}{a.FocusAddress}").ToList(); + + foreach (var group in groupData) + { + var aa = data.ToDictionary(a => a.Address, b => b); + await FreeRedisProvider.Instance.HSetAsync(group.Key, aa); + } + + } } } diff --git a/src/JiShe.CollectBus.Domain/EnergySystems/Entities/TB_AmmeterInfo.cs b/src/JiShe.CollectBus.Domain/EnergySystems/Entities/TB_AmmeterInfo.cs index a194d38..e324535 100644 --- a/src/JiShe.CollectBus.Domain/EnergySystems/Entities/TB_AmmeterInfo.cs +++ b/src/JiShe.CollectBus.Domain/EnergySystems/Entities/TB_AmmeterInfo.cs @@ -1,4 +1,5 @@ using System; +using FreeSql.DataAnnotations; namespace JiShe.CollectBus.EnergySystems.Entities { @@ -22,6 +23,7 @@ namespace JiShe.CollectBus.EnergySystems.Entities /// /// 区域名 /// + [Column(IsIgnore = true)] public string AreaName { get; set; } /// @@ -146,6 +148,7 @@ namespace JiShe.CollectBus.EnergySystems.Entities /// /// 集中器名称(扩展字段) /// + [Column(IsIgnore = true)] public string FocusName { get; set; } /// @@ -168,11 +171,13 @@ namespace JiShe.CollectBus.EnergySystems.Entities /// 机械表1(德力西机械电表-Code) /// (原有数据都默认:电子电表) /// + [Column(IsIgnore = true)] public int MeterKind { get; set; } /// /// 采集方案ID /// + [Column(IsIgnore = true)] public int GatherPlanID { get; set; } /// diff --git a/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj b/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj index 09edef1..20068a5 100644 --- a/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj +++ b/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj @@ -23,6 +23,7 @@ + diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 4a166da..5894f33 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -40,7 +40,7 @@ "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" }, "Redis": { - "Configuration": "118.190.144.92:6379,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + "Configuration": "192.168.111.248:6379,password=123456abcD,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", "DefaultDB": "14", "HangfireDB": "15" }, -- 2.47.2 From 9cda1af5fcb63f6612fac5656d5d0b3f4fc9044b Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Tue, 18 Mar 2025 13:56:38 +0800 Subject: [PATCH 022/139] update --- .../EnergySystem/CacheAppService.cs | 13 +++++-- .../BasicScheduledMeterReadingService.cs | 35 +++++++++++++------ ...nergySystemScheduledMeterReadingService.cs | 6 +++- .../{FreeRedisConst.cs => RedisConst.cs} | 19 ++++++---- 4 files changed, 52 insertions(+), 21 deletions(-) rename src/JiShe.CollectBus.Common/Consts/{FreeRedisConst.cs => RedisConst.cs} (71%) diff --git a/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs b/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs index 314c267..71b2595 100644 --- a/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs +++ b/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.EnergySystems.TableViews; using JiShe.CollectBus.FreeSql; using Volo.Abp.Domain.Repositories; @@ -26,8 +27,16 @@ namespace JiShe.CollectBus.EnergySystem foreach (var group in groupData) { - var aa = data.ToDictionary(a => a.Address, b => b); - await FreeRedisProvider.Instance.HSetAsync(group.Key, aa); + try + { + var aa = group.ToDictionary(a => $"{a.ID}_{a.Address}" , b => b); + await FreeRedisProvider.Instance.HSetAsync($"{RedisConst.CacheAmmeterFocusKey}:{group.Key}", aa); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } } } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 39be20d..92b937a 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; using DotNetCore.CAP; +using FreeSql; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common; using JiShe.CollectBus.Common.BuildSendDatas; @@ -24,6 +25,7 @@ using MassTransit; using MassTransit.Internals.GraphValidation; using Microsoft.Extensions.Logging; using Volo.Abp.Domain.Repositories; +using Volo.Abp.Uow; namespace JiShe.CollectBus.ScheduledMeterReading { @@ -35,16 +37,18 @@ namespace JiShe.CollectBus.ScheduledMeterReading private readonly ILogger _logger; private readonly ICapPublisher _capBus; private readonly IRepository _meterReadingIssuedRepository; + private readonly IUnitOfWorkManager _unitOfWorkManager; public BasicScheduledMeterReadingService( ILogger logger, ICapPublisher capBus, - IRepository meterReadingIssuedRepository) + IRepository meterReadingIssuedRepository, IUnitOfWorkManager unitOfWorkManager) { _capBus = capBus; _logger = logger; _meterReadingIssuedRepository = meterReadingIssuedRepository; + _unitOfWorkManager = unitOfWorkManager; } /// @@ -116,7 +120,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, itemTimeDensity.Key)}{item.Key}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, itemTimeDensity.Key)}{item.Key}"; Dictionary keyValuePairs = new Dictionary(); foreach (var ammeter in item) { @@ -178,7 +182,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading public virtual async Task AmmeterScheduledMeterOneMinuteReading() { //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 1)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 1)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -207,7 +211,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading public virtual async Task AmmeterScheduledMeterFiveMinuteReading() { //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 5)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 5)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -234,7 +238,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading public virtual async Task AmmeterScheduledMeterFifteenMinuteReading() { //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 15)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 15)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -307,7 +311,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, itemTimeDensity.Key)}{item.Key}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, itemTimeDensity.Key)}{item.Key}"; Dictionary keyValuePairs = new Dictionary(); foreach (var subItem in item) { @@ -327,7 +331,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading public virtual async Task WatermeterScheduledMeterOneMinuteReading() { //获取缓存中的水表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 1)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 1)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -354,7 +358,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的水表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 5)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 5)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -380,7 +384,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading public virtual async Task WatermeterScheduledMeterFifteenMinuteReading() { //获取缓存中的水表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 15)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 15)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -434,7 +438,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { string key = (string)item[0];//集中器地址对应的Redis缓存Key object[] fieldsAndValues = (object[])item[1];//缓存Key对应的Hash表数据集合 - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, minute)}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, minute)}"; string focusAddress = key.Replace(redisCacheKey, "");//集中器地址 var meterHashs = new Dictionary(); @@ -515,7 +519,16 @@ namespace JiShe.CollectBus.ScheduledMeterReading foreach (var group in focusHashGroups) { //TODO _meterReadingIssuedRepository 需要优化 - //_ = Task.Run(async () => { await CreatePublishTask(eventName, group.Value); }); + + await Task.Factory.StartNew(async () => + { + using var uow = _unitOfWorkManager.Begin(); + await CreatePublishTask(eventName, group.Value); + await uow.SaveChangesAsync(); + }); + + + //await Task.Run(async () => { await CreatePublishTask(eventName, group.Value); }); await CreatePublishTask(eventName, group.Value); } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index a95312b..4662ca8 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -11,6 +11,7 @@ using JiShe.CollectBus.IotSystems.Watermeter; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; using Volo.Abp.Domain.Repositories; +using Volo.Abp.Uow; namespace JiShe.CollectBus.ScheduledMeterReading { @@ -22,7 +23,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading public class EnergySystemScheduledMeterReadingService : BasicScheduledMeterReadingService { - public EnergySystemScheduledMeterReadingService(ILogger logger, ICapPublisher capBus, IRepository meterReadingIssuedRepository) :base(logger, capBus, meterReadingIssuedRepository) + public EnergySystemScheduledMeterReadingService(ILogger logger, + ICapPublisher capBus, + IRepository meterReadingIssuedRepository, + IUnitOfWorkManager unitOfWorkManager) :base(logger, capBus, meterReadingIssuedRepository, unitOfWorkManager) { } diff --git a/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs similarity index 71% rename from src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs rename to src/JiShe.CollectBus.Common/Consts/RedisConst.cs index 859e86e..0dcfd93 100644 --- a/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs @@ -6,36 +6,41 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.Common.Consts { - public class FreeRedisConst + public class RedisConst { /// /// 缓存基础目录 /// - public const string CacheBasicDirectoryKey = "CollectBus:"; + public const string CacheBasicDirectoryKey = "CollectBus"; /// /// 1分钟采集间隔 /// - public const string OneMinuteAcquisitionTimeInterval = $"one"; + public const string OneMinuteAcquisitionTimeInterval = "One"; /// /// 5分钟采集间隔 /// - public const string FiveMinuteAcquisitionTimeInterval = $"Five"; + public const string FiveMinuteAcquisitionTimeInterval = "Five"; /// /// 15分钟采集间隔 /// - public const string FifteenMinuteAcquisitionTimeInterval = $"Fifteen"; + public const string FifteenMinuteAcquisitionTimeInterval = "Fifteen"; /// /// 缓存电表信息 /// - public const string CacheAmmeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:{"{1}"}:AmmeterInfo:"; + public const string CacheAmmeterInfoKey = $"{CacheBasicDirectoryKey}:{"{0}"}:{"{1}"}:AmmeterInfo:"; /// /// 缓存水表信息 /// - public const string CacheWatermeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:{"{1}"}:WatermeterInfo:"; + public const string CacheAmmeterFocusKey = $"{CacheBasicDirectoryKey}:AmmeterFocus"; + + /// + /// 缓存水表信息 + /// + public const string CacheWatermeterInfoKey = $"{CacheBasicDirectoryKey}:{"{0}"}:{"{1}"}:WatermeterInfo:"; } } -- 2.47.2 From 0efb87482d2764ff68ec7a2520256d839d3a0243 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Tue, 18 Mar 2025 15:58:37 +0800 Subject: [PATCH 023/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=8B=E5=8F=91?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=E6=9E=84=E5=BB=BA=E6=9C=BA=E5=88=B6=EF=BC=8C?= =?UTF-8?q?=E5=B0=86=E6=9E=84=E5=BB=BA=E4=B8=8E=E4=B8=8B=E5=8F=91=E6=8B=86?= =?UTF-8?q?=E5=88=86=EF=BC=8C=E8=B5=B0=E5=8D=95=E7=8B=AC=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=A4=84=E7=90=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IScheduledMeterReadingService.cs | 18 +- .../BasicScheduledMeterReadingService.cs | 854 +++++--- .../Subscribers/WorkerSubscriberAppService.cs | 13 +- .../Workers/CreateToBeIssueTaskWorker.cs | 42 + .../Workers/SubscriberFifteenMinuteWorker.cs | 8 +- .../Workers/SubscriberFiveMinuteWorker.cs | 10 +- .../Workers/SubscriberOneMinuteWorker.cs | 11 +- .../BuildSendDatas/TasksToBeIssueModel.cs | 24 + .../BuildSendDatas/TelemetryPacketBuilder.cs | 1933 +---------------- .../BuildSendDatas/TelemetryPacketRequest.cs | 34 + .../Consts/FreeRedisConst.cs | 13 +- ...ScheduledMeterReadingIssuedEventMessage.cs | 7 +- 12 files changed, 796 insertions(+), 2171 deletions(-) create mode 100644 src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs create mode 100644 src/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs create mode 100644 src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketRequest.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs index a89d071..15d60d9 100644 --- a/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs @@ -19,6 +19,12 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// Task> GetGatherItemByDataTypes(); + /// + /// 构建待处理的下发指令任务处理 + /// + /// + Task CreateToBeIssueTasks(); + #region 电表采集处理 /// /// 获取电表信息 @@ -35,19 +41,19 @@ namespace JiShe.CollectBus.ScheduledMeterReading Task InitAmmeterCacheData(string gatherCode = ""); /// - /// 1分钟采集电表数据 + /// 1分钟采集电表数据,只获取任务数据下发,不构建任务 /// /// Task AmmeterScheduledMeterOneMinuteReading(); /// - /// 5分钟采集电表数据 + /// 5分钟采集电表数据,只获取任务数据下发,不构建任务 /// /// Task AmmeterScheduledMeterFiveMinuteReading(); /// - /// 15分钟采集电表数据 + /// 15分钟采集电表数据,只获取任务数据下发,不构建任务 /// /// Task AmmeterScheduledMeterFifteenMinuteReading(); @@ -64,7 +70,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading Task> GetWatermeterInfoList(string gatherCode = ""); /// - /// 初始化水表缓存数据 + /// 初始化水表缓存数据,只获取任务数据下发,不构建任务 /// /// 采集端Code /// @@ -77,13 +83,13 @@ namespace JiShe.CollectBus.ScheduledMeterReading Task WatermeterScheduledMeterOneMinuteReading(); /// - /// 5分钟采集水表数据 + /// 5分钟采集水表数据,只获取任务数据下发,不构建任务 /// /// Task WatermeterScheduledMeterFiveMinuteReading(); /// - /// 15分钟采集水表数据 + /// 15分钟采集水表数据,只获取任务数据下发,不构建任务 /// /// Task WatermeterScheduledMeterFifteenMinuteReading(); diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 39be20d..4e8c7d0 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -24,6 +24,7 @@ using MassTransit; using MassTransit.Internals.GraphValidation; using Microsoft.Extensions.Logging; using Volo.Abp.Domain.Repositories; +using static FreeSql.Internal.GlobalFilter; namespace JiShe.CollectBus.ScheduledMeterReading { @@ -71,6 +72,71 @@ namespace JiShe.CollectBus.ScheduledMeterReading throw new NotImplementedException($"{nameof(GetGatherItemByDataTypes)}请根据不同系统类型进行实现"); } + /// + /// 构建待处理的下发指令任务处理 + /// + /// + public virtual async Task CreateToBeIssueTasks() + { + var redisCacheKey = $"{FreeRedisConst.CacheBasicDirectoryKey}{SystemType}:TaskInfo:*"; + var taskInfos = await FreeRedisProvider.Instance.KeysAsync(redisCacheKey); + if (taskInfos == null || taskInfos.Length <= 0) + { + _logger.LogWarning($"{nameof(CreateToBeIssueTasks)} 构建待处理的下发指令任务处理时没有缓存数据,-101"); + return; + } + + foreach (var item in taskInfos) + { + var tasksToBeIssueModel = await FreeRedisProvider.Instance.GetAsync(item); + if (tasksToBeIssueModel == null) + { + _logger.LogWarning($"{nameof(CreateToBeIssueTasks)} 构建待处理的下发指令任务处理时Key=>{item}没有缓存数据,102"); + continue; + } + + //item 为 CacheTasksToBeIssuedKey 对应的缓存待下发的指令生产任务数据Redis Key tempArryay[0]=>CollectBus,tempArryay[1]=>SystemTypeConst,tempArryay[2]=>TaskInfo,tempArryay[3]=>表计类别,tempArryay[4]=>采集频率 + var tempArryay = item.Split(":"); + string meteryType = tempArryay[3];//表计类别 + string timeDensity = tempArryay[4];//采集频率 + + //获取缓存中的电表信息 + var redisKeyList = $"{string.Format(FreeRedisConst.CacheMeterInfoKey, SystemType, meteryType, timeDensity)}*"; + var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-103"); + return; + } + + if (meteryType == MeterTypeEnum.Ammeter.ToString()) + { + // 解析结果(结果为嵌套数组) + var meterInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity, meteryType); + if (meterInfos == null || meterInfos.Count <= 0) + { + _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-104"); + return; + } + await AmmerterScheduledMeterReadingIssued(timeDensity, meterInfos); + } + else if (meteryType == MeterTypeEnum.WaterMeter.ToString()) + { + //todo 水表任务创建待处理 + //await WatermeterScheduledMeterReadingIssued(timeDensity, meterInfos); + } + else + { + _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-105"); + } + + _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建完成"); + + //删除已经处理过的缓存数据 + await FreeRedisProvider.Instance.DelAsync(item); + } + } + #region 电表采集处理 /// @@ -111,12 +177,21 @@ namespace JiShe.CollectBus.ScheduledMeterReading var meterInfoGroup = itemTimeDensity.GroupBy(x => x.FocusAddress).ToList(); foreach (var item in meterInfoGroup) { - if (string.IsNullOrWhiteSpace(item.Key)) + if (string.IsNullOrWhiteSpace(item.Key))//集中器号为空,跳过 { continue; } - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, itemTimeDensity.Key)}{item.Key}"; + var redisCacheKey = $"{string.Format(FreeRedisConst.CacheMeterInfoKey, SystemType, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}{item.Key}"; + +#if DEBUG + //每次缓存时,删除缓存,避免缓存数据错误 + await FreeRedisProvider.Instance.DelAsync(redisCacheKey); +#else + //每次缓存时,删除缓存,避免缓存数据错误 + await FreeRedisProvider.Instance.DelAsync(redisCacheKey); +#endif + Dictionary keyValuePairs = new Dictionary(); foreach (var ammeter in item) { @@ -166,37 +241,72 @@ namespace JiShe.CollectBus.ScheduledMeterReading } await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); } + + //在缓存表信息数据的时候,新增下一个时间的自动处理任务,1分钟后执行 + TasksToBeIssueModel nextTask = new TasksToBeIssueModel() + { + TimeDensity = itemTimeDensity.Key, + NextTask = DateTime.Now.AddMinutes(1) + }; + + var taskRedisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, itemTimeDensity.Key); + await FreeRedisProvider.Instance.SetAsync(taskRedisCacheKey, nextTask); } _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据完成"); } /// - /// 1分钟采集电表数据 + /// 1分钟采集电表数据,只获取任务数据下发,不构建任务 /// /// public virtual async Task AmmeterScheduledMeterOneMinuteReading() { //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 1)}*"; + int timeDensity = 5; + var redisKeyList = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-101"); + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-101"); return; } - // 解析结果(结果为嵌套数组) - Dictionary> meterInfos = await GetMeterCacheData(oneMinutekeyList, 1); - if (meterInfos == null || meterInfos.Count <= 0) + //获取下发任务缓存数据 + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.Ammeter).ToString()); + if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理时没有获取到缓存信息,-102"); + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); return; } - await AmmerterScheduledMeterReadingIssued(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, meterInfos); + List meterTaskInfosList = new List(); - _logger.LogInformation($"{nameof(AmmeterScheduledMeterOneMinuteReading)} 1分钟采集电表数据处理完成"); + //将取出的缓存任务数据发送到Kafka消息队列中 + foreach (var focusItem in meterTaskInfos) + { + foreach (var ammerterItem in focusItem.Value) + { + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, ammerterItem.Value); + meterTaskInfosList.Add(ammerterItem.Value); + } + } + if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) + { + await _meterReadingIssuedRepository.InsertManyAsync(meterTaskInfosList); + } + + //缓存下一个时间的任务 + TasksToBeIssueModel nextTask = new TasksToBeIssueModel() + { + TimeDensity = timeDensity, + NextTask = DateTime.Now.AddMinutes(timeDensity) + }; + + var redisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, timeDensity); + await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); + + _logger.LogInformation($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理完成"); } @@ -207,24 +317,50 @@ namespace JiShe.CollectBus.ScheduledMeterReading public virtual async Task AmmeterScheduledMeterFiveMinuteReading() { //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 5)}*"; + int timeDensity = 5; + var redisKeyList = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { - _logger.LogError($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理时没有获取到缓存信息,-101"); + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-101"); return; } - // 解析结果(结果为嵌套数组) - Dictionary> meterInfos = await GetMeterCacheData(oneMinutekeyList, 5); - if (meterInfos == null || meterInfos.Count <= 0) + //获取下发任务缓存数据 + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.Ammeter).ToString()); + if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { - _logger.LogError($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理时没有获取到缓存信息,-102"); + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); return; } - await AmmerterScheduledMeterReadingIssued(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, meterInfos); - _logger.LogInformation($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} 5分钟采集电表数据处理完成"); + List meterTaskInfosList = new List(); + + //将取出的缓存任务数据发送到Kafka消息队列中 + foreach (var focusItem in meterTaskInfos) + { + foreach (var ammerterItem in focusItem.Value) + { + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, ammerterItem.Value); + meterTaskInfosList.Add(ammerterItem.Value); + } + } + if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) + { + await _meterReadingIssuedRepository.InsertManyAsync(meterTaskInfosList); + } + + //缓存下一个时间的任务 + TasksToBeIssueModel nextTask = new TasksToBeIssueModel() + { + TimeDensity = timeDensity, + NextTask = DateTime.Now.AddMinutes(timeDensity) + }; + + var redisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, timeDensity); + await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); + + _logger.LogInformation($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集电表数据处理完成"); } /// @@ -233,31 +369,288 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// public virtual async Task AmmeterScheduledMeterFifteenMinuteReading() { - //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, 15)}*"; - var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理时没有获取到缓存信息,-101"); - return; - } - - // 解析结果(结果为嵌套数组) - Dictionary> meterInfos = await GetMeterCacheData(oneMinutekeyList, 15); - if (meterInfos == null || meterInfos.Count <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理时没有获取到缓存信息,-102"); - return; - } - Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); - await AmmerterScheduledMeterReadingIssued(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, meterInfos); + //获取缓存中的电表信息 + int timeDensity = 15; + var redisKeyList = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; + var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-101"); + return; + } + + //获取下发任务缓存数据 + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), MeterTypeEnum.Ammeter.ToString()); + if (meterTaskInfos == null || meterTaskInfos.Count <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); + return; + } + List meterTaskInfosList = new List(); + + //将取出的缓存任务数据发送到Kafka消息队列中 + foreach (var focusItem in meterTaskInfos) + { + foreach (var ammerterItem in focusItem.Value) + { + _= _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, ammerterItem.Value); + meterTaskInfosList.Add(ammerterItem.Value); + } + } + if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) + { + await _meterReadingIssuedRepository.InsertManyAsync(meterTaskInfosList); + } + + //删除任务数据 + await FreeRedisProvider.Instance.DelAsync(redisKeyList); + + //缓存下一个时间的任务 + TasksToBeIssueModel nextTask = new TasksToBeIssueModel() + { + TimeDensity = timeDensity, + NextTask = DateTime.Now.AddMinutes(timeDensity) + }; + + var redisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, timeDensity); + await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); + stopwatch.Stop(); - _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} 15分钟采集电表数据处理完成,共消耗{stopwatch.ElapsedMilliseconds}毫秒。"); + _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} {timeDensity}分钟采集电表数据处理完成,共消耗{stopwatch.ElapsedMilliseconds}毫秒。"); + } + + /// + /// 电表采集任务指令创建 + /// + /// 采集频率1分钟、5分钟、15分钟 + /// 集中器数据分组 + /// + private async Task AmmerterScheduledMeterReadingIssued(string timeDensity, Dictionary> focusGroup) + { + if (string.IsNullOrWhiteSpace(timeDensity) || focusGroup == null || focusGroup.Count <= 0) + { + _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 电表数据采集指令生成失败,参数异常,-101"); + return; + } + try + { + //将采集器编号的hash值取模分组 + const int TotalShards = 1024; + var focusHashGroups = new Dictionary>>(); + + foreach (var (collectorId, ammetersDictionary) in focusGroup) + { + if (string.IsNullOrWhiteSpace(collectorId)) + { + _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 集中器信息分组取模失败,无效Key -102"); + continue; + } + + // 计算哈希分组ID + int hashGroupId = Math.Abs(collectorId.GetHashCode() % TotalShards); + + // 获取或创建分组(避免重复查找) + if (!focusHashGroups.TryGetValue(hashGroupId, out var group)) + { + group = new Dictionary>(); + focusHashGroups[hashGroupId] = group; + } + + // 将当前集中器数据加入分组 + group[collectorId] = ammetersDictionary; + } + + if (focusHashGroups == null) + { + _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 集中器信息分组取模失败 -103"); + return; + } + + //根据分组创建线程批处理集中器 + foreach (var group in focusHashGroups) + { + await AmmerterCreatePublishTask(timeDensity, group.Value); + } + } + catch (Exception) + { + + throw; + } + } + + /// + /// 电表创建发布任务 + /// + /// 采集频率 + /// 集中器号hash分组的集中器集合数据 + /// + private async Task AmmerterCreatePublishTask(string timeDensity + , Dictionary> focusGroup) + { + var HandlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; + + var currentTime = DateTime.Now; + foreach (var focusInfo in focusGroup) + { + //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 + var redisCacheKey = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}{focusInfo.Key}"; + + foreach (var ammeterInfo in focusInfo.Value) + { + var ammeter = ammeterInfo.Value; + + if (string.IsNullOrWhiteSpace(ammeter.ItemCodes)) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}数据采集指令生成失败,采集项为空,-101"); + continue; + } + + //载波的不处理 + if (ammeter.MeteringPort == (int)MeterLinkProtocolEnum.Carrierwave) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}数据采集指令生成失败,载波不处理,-102"); + continue; + } + + if (ammeter.State.Equals(2)) + { + _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} {ammeter.Name} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}状态为禁用,不处理"); + continue; + } + + ////排除1天未在线的集中器生成指令 或 排除集中器配置为自动上报的集中器 + //if (!IsGennerateCmd(ammeter.LastTime, -1)) + //{ + // _logger.LogInformation($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name},采集时间:{ammeter.LastTime},已超过1天未在线,不生成指令"); + // continue; + //} + + if (string.IsNullOrWhiteSpace(ammeter.AreaCode)) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeter.ID},集中器通信区号为空"); + continue; + } + if (string.IsNullOrWhiteSpace(ammeter.Address)) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeter.ID},集中器通信地址为空"); + continue; + } + if (Convert.ToInt32(ammeter.Address) > 65535) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeter.ID},集中器通信地址无效,确保大于65535"); + continue; + } + if (ammeter.MeteringCode <= 0 || ammeter.MeteringCode > 2033) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeter.ID},非有效测量点号({ammeter.MeteringCode})"); + continue; + } + + List tempCodes = ammeter.ItemCodes.Deserialize>()!; + + //TODO:自动上报数据只主动采集1类数据。 + if (ammeter.AutomaticReport.Equals(1)) + { + var tempSubCodes = new List(); + if (tempCodes.Contains("0C_49")) + { + tempSubCodes.Add("0C_49"); + } + + if (tempSubCodes.Contains("0C_149")) + { + tempSubCodes.Add("0C_149"); + } + + if (ammeter.ItemCodes.Contains("10_97")) + { + tempSubCodes.Add("10_97"); + } + + if (tempSubCodes == null || tempSubCodes.Count <= 0) + { + _logger.LogInformation($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}自动上报数据主动采集1类数据时数据类型为空"); + continue; + } + else + { + tempCodes = tempSubCodes; + } + } + + Dictionary keyValuePairs = new Dictionary(); + + foreach (var tempItem in tempCodes) + { + //排除已发送日冻结和月冻结采集项配置 + if (DayFreezeCodes.Contains(tempItem)) + { + continue; + } + + if (MonthFreezeCodes.Contains(tempItem)) + { + continue; + } + + var itemCodeArr = tempItem.Split('_'); + var aFNStr = itemCodeArr[0]; + var aFN = (AFN)aFNStr.HexToDec(); + var fn = int.Parse(itemCodeArr[1]); + byte[] dataInfos = null; + if (ammeter.AutomaticReport.Equals(1) && aFN == AFN.请求实时数据) + { + //实时数据 + dataInfos = Build3761SendData.BuildAmmeterReadRealTimeDataSendCmd(ammeter.FocusAddress, ammeter.MeteringCode, (ATypeOfDataItems)fn); + } + else + { + string methonCode = $"AFN{aFNStr}_Fn_Send"; + //特殊表暂不处理 + if (HandlerPacketBuilder != null && HandlerPacketBuilder.TryGetValue(methonCode + , out var handler)) + { + dataInfos = handler(new TelemetryPacketRequest() + { + FocusAddress = ammeter.FocusAddress, + Fn = fn, + Pn = ammeter.MeteringCode + }); + } + else + { + _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}采集项{tempItem}无效编码。"); + continue; + } + } + //TODO:特殊表 + + if (dataInfos == null || dataInfos.Length <= 0) + { + _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}采集项{tempItem}未能正确获取报文。"); + continue; + } + + var evenMessageInfo = new ScheduledMeterReadingIssuedEventMessage + { + MessageHexString = Convert.ToHexString(dataInfos), + DeviceNo = ammeter.FocusAddress, + MessageId = NewId.NextGuid().ToString(), + TimeDensity = timeDensity, + WasSuccessful = false, + CreationTime = currentTime, + }; + keyValuePairs.TryAdd($"{ammeter.ID}_{tempItem}", evenMessageInfo); + } + await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); + } + } } #endregion @@ -307,7 +700,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, itemTimeDensity.Key)}{item.Key}"; + var redisCacheKey = $"{string.Format(FreeRedisConst.CacheMeterInfoKey, SystemType, MeterTypeEnum.WaterMeter, itemTimeDensity.Key)}{item.Key}"; Dictionary keyValuePairs = new Dictionary(); foreach (var subItem in item) { @@ -316,6 +709,16 @@ namespace JiShe.CollectBus.ScheduledMeterReading } await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); } + + //在缓存表信息数据的时候,新增下一个时间的自动处理任务,1分钟后执行 + TasksToBeIssueModel nextTask = new TasksToBeIssueModel() + { + TimeDensity = itemTimeDensity.Key, + NextTask = DateTime.Now.AddMinutes(1) + }; + + var taskRedisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, itemTimeDensity.Key); + await FreeRedisProvider.Instance.SetAsync(taskRedisCacheKey, nextTask); } _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化水表缓存数据完成"); } @@ -327,76 +730,160 @@ namespace JiShe.CollectBus.ScheduledMeterReading public virtual async Task WatermeterScheduledMeterOneMinuteReading() { //获取缓存中的水表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 1)}*"; + int timeDensity = 5; + var redisKeyList = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { - _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表据处理时没有获取到缓存信息,-101"); + _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-101"); return; } - // 解析结果(结果为嵌套数组) - Dictionary> meterInfos = await GetMeterCacheData(oneMinutekeyList, 1); - if (meterInfos == null || meterInfos.Count <= 0) + //获取下发任务缓存数据 + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); + if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { - _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表数据处理时没有获取到缓存信息,-102"); + _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); return; } - _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 1分钟采集水表数据处理完成"); + List meterTaskInfosList = new List(); + + //将取出的缓存任务数据发送到Kafka消息队列中 + foreach (var focusItem in meterTaskInfos) + { + foreach (var ammerterItem in focusItem.Value) + { + await _capBus.PublishAsync(ProtocolConst.WatermeterSubscriberWorkerOneMinuteIssuedEventName, ammerterItem.Value); + meterTaskInfosList.Add(ammerterItem.Value); + } + } + if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) + { + await _meterReadingIssuedRepository.InsertManyAsync(meterTaskInfosList); + } + + //缓存下一个时间的任务 + TasksToBeIssueModel nextTask = new TasksToBeIssueModel() + { + TimeDensity = timeDensity, + NextTask = DateTime.Now.AddMinutes(timeDensity) + }; + + var redisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity); + await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); + + + _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理完成"); } /// - /// 5分钟采集电表数据 + /// 5分钟采集水表数据 /// /// public virtual async Task WatermeterScheduledMeterFiveMinuteReading() { - //获取缓存中的水表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 5)}*"; + //获取缓存中的电表信息 + int timeDensity = 5; + var redisKeyList = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { - _logger.LogError($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表据处理时没有获取到缓存信息,-101"); + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-101"); return; } - // 解析结果(结果为嵌套数组) - Dictionary> meterInfos = await GetMeterCacheData(oneMinutekeyList, 5); - if (meterInfos == null || meterInfos.Count <= 0) + //获取下发任务缓存数据 + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); + if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { - _logger.LogError($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表数据处理时没有获取到缓存信息,-102"); + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); return; } - _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} 5分钟采集水表数据处理完成"); + List meterTaskInfosList = new List(); + + //将取出的缓存任务数据发送到Kafka消息队列中 + foreach (var focusItem in meterTaskInfos) + { + foreach (var ammerterItem in focusItem.Value) + { + await _capBus.PublishAsync(ProtocolConst.WatermeterSubscriberWorkerFiveMinuteIssuedEventName, ammerterItem.Value); + meterTaskInfosList.Add(ammerterItem.Value); + } + } + if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) + { + await _meterReadingIssuedRepository.InsertManyAsync(meterTaskInfosList); + } + + //缓存下一个时间的任务 + TasksToBeIssueModel nextTask = new TasksToBeIssueModel() + { + TimeDensity = timeDensity, + NextTask = DateTime.Now.AddMinutes(timeDensity) + }; + + var redisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity); + await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); + + + _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集水表数据处理完成"); } /// - /// 15分钟采集电表数据 + /// 15分钟采集水表数据 /// /// public virtual async Task WatermeterScheduledMeterFifteenMinuteReading() { - //获取缓存中的水表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheWatermeterInfoKey, SystemTypeConst.Energy, 15)}*"; + //获取缓存中的电表信息 + int timeDensity = 15; + var redisKeyList = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { - _logger.LogError($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表据处理时没有获取到缓存信息,-101"); + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-101"); return; } - // 解析结果(结果为嵌套数组) - Dictionary> meterInfos = await GetMeterCacheData(oneMinutekeyList, 15); - if (meterInfos == null || meterInfos.Count <= 0) + //获取下发任务缓存数据 + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); + if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { - _logger.LogError($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表数据处理时没有获取到缓存信息,-102"); + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); return; } - _logger.LogInformation($"{nameof(WatermeterScheduledMeterFifteenMinuteReading)} 15分钟采集水表数据处理完成"); + List meterTaskInfosList = new List(); + + //将取出的缓存任务数据发送到Kafka消息队列中 + foreach (var focusItem in meterTaskInfos) + { + foreach (var ammerterItem in focusItem.Value) + { + await _capBus.PublishAsync(ProtocolConst.WatermeterSubscriberWorkerFifteenMinuteIssuedEventName, ammerterItem.Value); + meterTaskInfosList.Add(ammerterItem.Value); + } + } + if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) + { + await _meterReadingIssuedRepository.InsertManyAsync(meterTaskInfosList); + } + + //缓存下一个时间的任务 + TasksToBeIssueModel nextTask = new TasksToBeIssueModel() + { + TimeDensity = timeDensity, + NextTask = DateTime.Now.AddMinutes(timeDensity) + }; + + var redisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity); + await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); + + + _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集水表数据处理完成"); } #endregion @@ -407,9 +894,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// /// 表信息数据对象 /// 采集频率对应的缓存Key集合 - /// 采集频率,1分钟、5分钟、15分钟 + /// 采集频率,1分钟、5分钟、15分钟 + /// 表计类型 /// - private async Task>> GetMeterCacheData(string[] redisKeys, int minute) + private async Task>> GetMeterRedisCacheData(string[] redisKeys, string timeDensity, string meterType) where T : class { //通过lua脚本一次性获取所有缓存内容 var luaScript = @" @@ -419,22 +907,22 @@ namespace JiShe.CollectBus.ScheduledMeterReading results[i] = {key, data} end return results"; - var oneMinuteAmmerterResult = await FreeRedisProvider.Instance.EvalAsync(luaScript, redisKeys); //传递 KEYS - if (oneMinuteAmmerterResult == null) + var merterResult = await FreeRedisProvider.Instance.EvalAsync(luaScript, redisKeys); //传递 KEYS + if (merterResult == null) { - _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 定时任务采集表数据处理时没有获取到缓存信息,-102"); + _logger.LogError($"{nameof(GetMeterRedisCacheData)} 定时任务采集表数据处理时没有获取到缓存信息,-102"); return null; } // 解析结果(结果为嵌套数组) var meterInfos = new Dictionary>(); ; - if (oneMinuteAmmerterResult is object[] arr) + if (merterResult is object[] arr) { foreach (object[] item in arr) { string key = (string)item[0];//集中器地址对应的Redis缓存Key object[] fieldsAndValues = (object[])item[1];//缓存Key对应的Hash表数据集合 - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheAmmeterInfoKey, SystemTypeConst.Energy, minute)}"; + var redisCacheKey = $"{string.Format(FreeRedisConst.CacheMeterInfoKey, SystemType, meterType, timeDensity)}"; string focusAddress = key.Replace(redisCacheKey, "");//集中器地址 var meterHashs = new Dictionary(); @@ -443,7 +931,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading string meterld = (string)fieldsAndValues[i];//表ID string meterStr = (string)fieldsAndValues[i + 1];//表详情数据 - T meterInfo = default; + T meterInfo = default!; if (!string.IsNullOrWhiteSpace(meterStr)) { meterInfo = meterStr.Deserialize()!; @@ -454,7 +942,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } else { - _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} 定时任务采集表数据处理时集中器缓存{key}数据的{meterld}处理异常"); + _logger.LogInformation($"{nameof(GetMeterRedisCacheData)} 定时任务采集表数据处理时集中器缓存{key}数据的{meterld}处理异常"); } } meterInfos[focusAddress] = meterHashs; @@ -463,225 +951,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading return meterInfos; } - - /// - /// 电表采集任务指令创建 - /// - /// 采集频率订阅主题 - /// 集中器数据分组 - /// - private async Task AmmerterScheduledMeterReadingIssued(string eventName, Dictionary> focusGroup) - { - if (string.IsNullOrWhiteSpace(eventName) || focusGroup == null || focusGroup.Count <= 0) - { - _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 电表数据采集指令生成失败,参数异常,-101"); - return; - } - try - { - //将采集器编号的hash值取模分组 - const int TotalShards = 1024; - var focusHashGroups = new Dictionary>>(); - - foreach (var (collectorId, ammetersDictionary) in focusGroup) - { - if (string.IsNullOrWhiteSpace(collectorId)) - { - _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 集中器信息分组取模失败,无效Key -102"); - continue; - } - - // 计算哈希分组ID - int hashGroupId = Math.Abs(collectorId.GetHashCode() % TotalShards); - - // 获取或创建分组(避免重复查找) - if (!focusHashGroups.TryGetValue(hashGroupId, out var group)) - { - group = new Dictionary>(); - focusHashGroups[hashGroupId] = group; - } - - // 将当前集中器数据加入分组 - group[collectorId] = ammetersDictionary; - } - - if (focusHashGroups == null) - { - _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 集中器信息分组取模失败 -103"); - return; - } - - //根据分组创建线程批处理集中器 - foreach (var group in focusHashGroups) - { - //TODO _meterReadingIssuedRepository 需要优化 - //_ = Task.Run(async () => { await CreatePublishTask(eventName, group.Value); }); - await CreatePublishTask(eventName, group.Value); - } - - //await Task.CompletedTask; - } - catch (Exception) - { - - throw; - } - } - - /// - /// 创建发布任务 - /// - /// - /// - /// - private async Task CreatePublishTask(string eventName, Dictionary> focusGroup) - { - foreach (var focusInfo in focusGroup) - { - foreach (var ammeterInfo in focusInfo.Value) - { - var ammeter = ammeterInfo.Value; - - if (string.IsNullOrWhiteSpace(ammeter.ItemCodes)) - { - _logger.LogError($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}数据采集指令生成失败,采集项为空,-101"); - continue; - } - - //载波的不处理 - if (ammeter.MeteringPort == (int)MeterLinkProtocolEnum.Carrierwave) - { - _logger.LogError($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}数据采集指令生成失败,载波不处理,-102"); - continue; - } - - if (ammeter.State.Equals(2)) - { - _logger.LogWarning($"{nameof(CreatePublishTask)} {ammeter.Name} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}状态为禁用,不处理"); - continue; - } - - ////排除1天未在线的集中器生成指令 或 排除集中器配置为自动上报的集中器 - //if (!IsGennerateCmd(ammeter.LastTime, -1)) - //{ - // _logger.LogInformation($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name},采集时间:{ammeter.LastTime},已超过1天未在线,不生成指令"); - // continue; - //} - - if (string.IsNullOrWhiteSpace(ammeter.AreaCode)) - { - _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{ammeter.ID},集中器通信区号为空"); - continue; - } - if (string.IsNullOrWhiteSpace(ammeter.Address)) - { - _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{ammeter.ID},集中器通信地址为空"); - continue; - } - if (Convert.ToInt32(ammeter.Address) > 65535) - { - _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{ammeter.ID},集中器通信地址无效,确保大于65535"); - continue; - } - if (ammeter.MeteringCode <= 0 || ammeter.MeteringCode > 2033) - { - _logger.LogError($"{nameof(CreatePublishTask)} 表ID:{ammeter.ID},非有效测量点号({ammeter.MeteringCode})"); - continue; - } - - List tempCodes = ammeter.ItemCodes.Deserialize>()!; - - //TODO:自动上报数据只主动采集1类数据。 - if (ammeter.AutomaticReport.Equals(1)) - { - var tempSubCodes = new List(); - if (tempCodes.Contains("0C_49")) - { - tempSubCodes.Add("0C_49"); - } - - if (tempSubCodes.Contains("0C_149")) - { - tempSubCodes.Add("0C_149"); - } - - if (ammeter.ItemCodes.Contains("10_97")) - { - tempSubCodes.Add("10_97"); - } - - if (tempSubCodes == null || tempSubCodes.Count <= 0) - { - _logger.LogInformation($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}自动上报数据主动采集1类数据时数据类型为空"); - continue; - } - else - { - tempCodes = tempSubCodes; - } - } - - List evenMessageInfoList = new List(); - foreach (var tempItem in tempCodes) - { - //排除已发送日冻结和月冻结采集项配置 - if (DayFreezeCodes.Contains(tempItem)) - { - continue; - } - - if (MonthFreezeCodes.Contains(tempItem)) - { - continue; - } - - var itemCodeArr = tempItem.Split('_'); - var aFN = (AFN)itemCodeArr[0].HexToDec(); - var fn = int.Parse(itemCodeArr[1]); - byte[] dataInfos = null; - if (ammeter.AutomaticReport.Equals(1) && aFN == AFN.请求实时数据) - { - //实时数据 - dataInfos = Build3761SendData.BuildAmmeterReadRealTimeDataSendCmd(ammeter.FocusAddress, ammeter.MeteringCode, (ATypeOfDataItems)fn); - } - else - { - //特殊表暂不处理 - if (TelemetryPacketBuilder.AFNHandlers.TryGetValue(tempItem, out var handler)) - { - dataInfos = handler(ammeter.FocusAddress, fn, ammeter.MeteringCode); - } - else - { - _logger.LogWarning($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}采集项{tempItem}无效编码。"); - continue; - } - } - //TODO:特殊表 - - if (dataInfos == null || dataInfos.Length <= 0) - { - _logger.LogWarning($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}采集项{tempItem}未能正确获取报文。"); - continue; - } - - var evenMessageInfo = new ScheduledMeterReadingIssuedEventMessage - { - Message = dataInfos!, - DeviceNo = ammeter.FocusAddress, - MessageId = NewId.NextGuid().ToString(), - TimeDensity = eventName, - WasSuccessful = false, - }; - await _capBus.PublishAsync(eventName, evenMessageInfo); - evenMessageInfoList.Add(evenMessageInfo); - } - - await _meterReadingIssuedRepository.InsertManyAsync(evenMessageInfoList); - } - } - } - + /// /// 指定时间对比当前时间 /// diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index 8d0d154..50ed9e0 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -34,6 +34,7 @@ namespace JiShe.CollectBus.Subscribers /// /// The logger. /// The TCP service. + /// The Device pepository. /// The service provider. public WorkerSubscriberAppService(ILogger logger, ITcpService tcpService, @@ -69,7 +70,7 @@ namespace JiShe.CollectBus.Subscribers var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); if (device != null) { - await _tcpService.SendAsync(device.ClientId, receivedMessage.Message); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); } } @@ -96,7 +97,7 @@ namespace JiShe.CollectBus.Subscribers var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); if (device != null) { - await _tcpService.SendAsync(device.ClientId, receivedMessage.Message); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); } } @@ -123,7 +124,7 @@ namespace JiShe.CollectBus.Subscribers var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); if (device != null) { - await _tcpService.SendAsync(device.ClientId, receivedMessage.Message); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); } } @@ -152,7 +153,7 @@ namespace JiShe.CollectBus.Subscribers var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); if (device != null) { - await _tcpService.SendAsync(device.ClientId, receivedMessage.Message); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); } } @@ -179,7 +180,7 @@ namespace JiShe.CollectBus.Subscribers var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); if (device != null) { - await _tcpService.SendAsync(device.ClientId, receivedMessage.Message); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); } } @@ -206,7 +207,7 @@ namespace JiShe.CollectBus.Subscribers var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); if (device != null) { - await _tcpService.SendAsync(device.ClientId, receivedMessage.Message); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); } } } diff --git a/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs b/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs new file mode 100644 index 0000000..c3b4886 --- /dev/null +++ b/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs @@ -0,0 +1,42 @@ +using System.Threading; +using System.Threading.Tasks; +using Hangfire; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.ScheduledMeterReading; +using Microsoft.Extensions.Logging; +using Volo.Abp.BackgroundWorkers.Hangfire; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Uow; + +namespace JiShe.CollectBus.Workers +{ + /// + /// 构建待处理的下发指令任务处理 + /// + public class CreateToBeIssueTaskWorker : HangfireBackgroundWorkerBase, ITransientDependency, ICollectWorker + { + private readonly ILogger _logger; + private readonly IScheduledMeterReadingService _scheduledMeterReadingService; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// 定时任务 + public CreateToBeIssueTaskWorker(ILogger logger, IScheduledMeterReadingService scheduledMeterReadingService) + { + _logger = logger; + RecurringJobId = nameof(CreateToBeIssueTaskWorker); + CronExpression = $"*/{1} * * * *"; ; + this._scheduledMeterReadingService = scheduledMeterReadingService; + } + + + public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) + { + _logger.LogWarning($"构建待处理的下发指令任务处理开始"); + //await _scheduledMeterReadingService.CreateToBeIssueTasks(); + _logger.LogWarning($"构建待处理的下发指令任务处理结束"); + } + } +} diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs b/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs index 90c032f..9be418a 100644 --- a/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs @@ -26,14 +26,18 @@ namespace JiShe.CollectBus.Workers { _logger = logger; RecurringJobId = nameof(SubscriberFifteenMinuteWorker); - CronExpression = Cron.Hourly(15); + CronExpression = $"*/{15} * * * *"; this._scheduledMeterReadingService = scheduledMeterReadingService; } public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { - await _scheduledMeterReadingService.AmmeterScheduledMeterFifteenMinuteReading(); + _logger.LogWarning($"15分钟采集数据开始"); + //await _scheduledMeterReadingService.AmmeterScheduledMeterFifteenMinuteReading(); + //await _scheduledMeterReadingService.WatermeterScheduledMeterFifteenMinuteReading(); + + _logger.LogWarning($"15分钟采集数据结束"); //using (var uow = LazyServiceProvider.LazyGetRequiredService().Begin()) //{ // Logger.LogInformation("Executed MyLogWorker..!"); diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs b/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs index f20cf86..94e323d 100644 --- a/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs @@ -18,7 +18,7 @@ namespace JiShe.CollectBus.Workers private readonly IScheduledMeterReadingService _scheduledMeterReadingService; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The logger. /// 定时任务 @@ -26,14 +26,18 @@ namespace JiShe.CollectBus.Workers { _logger = logger; RecurringJobId = nameof(SubscriberFiveMinuteWorker); - CronExpression = Cron.Hourly(15); + CronExpression = $"*/{5} * * * *"; this._scheduledMeterReadingService = scheduledMeterReadingService; } public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { - await _scheduledMeterReadingService.AmmeterScheduledMeterFifteenMinuteReading(); + _logger.LogWarning($"5分钟采集数据开始"); + //await _scheduledMeterReadingService.AmmeterScheduledMeterFiveMinuteReading(); + //await _scheduledMeterReadingService.WatermeterScheduledMeterFiveMinuteReading(); + + _logger.LogWarning($"5分钟采集数据结束"); } } } diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs b/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs index 2132cdf..d7c8325 100644 --- a/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs @@ -18,7 +18,7 @@ namespace JiShe.CollectBus.Workers private readonly IScheduledMeterReadingService _scheduledMeterReadingService; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The logger. /// 定时任务 @@ -26,14 +26,19 @@ namespace JiShe.CollectBus.Workers { _logger = logger; RecurringJobId = nameof(SubscriberOneMinuteWorker); - CronExpression = Cron.Hourly(15); + CronExpression = $"*/{1} * * * *"; this._scheduledMeterReadingService = scheduledMeterReadingService; } public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { - await _scheduledMeterReadingService.AmmeterScheduledMeterFifteenMinuteReading(); + _logger.LogWarning($"1分钟采集数据开始"); + //await _scheduledMeterReadingService.AmmeterScheduledMeterOneMinuteReading(); + + //await _scheduledMeterReadingService.WatermeterScheduledMeterOneMinuteReading(); + + _logger.LogWarning($"1分钟采集数据结束"); } } } diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs b/src/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs new file mode 100644 index 0000000..67d9abe --- /dev/null +++ b/src/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.BuildSendDatas +{ + /// + /// 待下发的指令生产任务数据 + /// + public class TasksToBeIssueModel + { + /// + /// 下个任务时间 + /// + public DateTime NextTask { get; set; } + + /// + /// 采集时间间隔,1分钟,5分钟,15分钟 + /// + public int TimeDensity { get; set; } + } +} diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs b/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs index 9f5c3ec..8ad2a39 100644 --- a/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs +++ b/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs @@ -12,22 +12,22 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.Common.BuildSendDatas { /// - /// 构建下发报文 + /// 构建下发报文,只适用与定时抄读 /// public static class TelemetryPacketBuilder { /// /// 构建报文的委托 /// - /// - /// - /// - public delegate byte[] AFNDelegate(string address, int fn, int pn = 0); + /// + /// + /// + public delegate byte[] AFNDelegate(TelemetryPacketRequest request); /// /// 编码与方法的映射表 /// - public static readonly Dictionary AFNHandlers = new(); + public static readonly Dictionary AFNHandlersDictionary = new(); static TelemetryPacketBuilder() { @@ -35,56 +35,33 @@ namespace JiShe.CollectBus.Common.BuildSendDatas var methods = typeof(TelemetryPacketBuilder).GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (var method in methods) { - if (method.Name.StartsWith("AFN") && method.Name.EndsWith("_Send")) + if (method.Name.StartsWith("AFN") && method.Name.EndsWith("_Fn_Send")) { - // 提取编码部分(例如 "AFN0D_F184_Send" -> "0D_184") - string code = method.Name[3..^5].Replace("F", ""); // 移除前缀和后缀,替换F为_ + string code = method.Name; var delegateInstance = (AFNDelegate)Delegate.CreateDelegate(typeof(AFNDelegate), method); - AFNHandlers[code] = delegateInstance; + AFNHandlersDictionary[code] = delegateInstance; } } } #region AFN_00H 确认∕否认 - public static byte[] AFN00_F1_Send(string address,int fn,int pn = 0) + public static byte[] AFN00_Fn_Send(TelemetryPacketRequest request) { var reqParameter = new ReqParameter2() { AFN = AFN.确认或否认, FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, + A = request.FocusAddress, Seq = new Seq() { TpV = TpV.附加信息域中无时间标签, FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, + CON = CON.需要对该帧进行确认, PRSEQ = 0, }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN00_F3_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.确认或否认, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn + MSA = Build3761SendData.GetMSA(request.FocusAddress), + Pn = request.Pn, + Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); return bytes; @@ -93,23 +70,23 @@ namespace JiShe.CollectBus.Common.BuildSendDatas #region AFN_01H 复位命令 - public static byte[] AFN01_F1_Send(string address, int fn, int pn = 0) + public static byte[] AFN01_Fn_Send(TelemetryPacketRequest request) { var reqParameter = new ReqParameter2() { AFN = AFN.复位, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, + FunCode = (int)CMasterStationFunCode.复位命令, + A = request.FocusAddress, Seq = new Seq() { TpV = TpV.附加信息域中无时间标签, FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, + CON = CON.需要对该帧进行确认, + PRSEQ = 10, }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn + MSA = Build3761SendData.GetMSA(request.FocusAddress), + Pn = request.Pn, + Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); return bytes; @@ -118,13 +95,13 @@ namespace JiShe.CollectBus.Common.BuildSendDatas #region AFN_02H 链路接口检测 - public static byte[] AFN02_F2_Send(string address, int fn, int pn = 0) + public static byte[] AFN02_Fn_Send(TelemetryPacketRequest request) { var reqParameter = new ReqParameter2() { AFN = AFN.链路接口检测, FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, + A = request.FocusAddress, Seq = new Seq() { TpV = TpV.附加信息域中无时间标签, @@ -132,9 +109,9 @@ namespace JiShe.CollectBus.Common.BuildSendDatas CON = CON.不需要对该帧进行确认, PRSEQ = 0, }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn + MSA = Build3761SendData.GetMSA(request.FocusAddress), + Pn = request.Pn, + Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); return bytes; @@ -142,113 +119,48 @@ namespace JiShe.CollectBus.Common.BuildSendDatas #endregion #region AFN_04H 设置参数 - public static byte[] AFN04_F3_Send(string address, int fn, int pn = 0) + public static byte[] AFN04_Fn_Send(TelemetryPacketRequest request) { var reqParameter = new ReqParameter2() { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, + AFN = AFN.设置参数, + FunCode = (int)CMasterStationFunCode.请求1级数据, + A = request.FocusAddress, Seq = new Seq() { TpV = TpV.附加信息域中无时间标签, FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, + CON = CON.需要对该帧进行确认, + PRSEQ = 10, }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn + MSA = Build3761SendData.GetMSA(request.FocusAddress), + Pn = request.Pn, + Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); return bytes; } - public static byte[] AFN04_F10_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN04_F66_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN04_F88_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } #endregion #region AFN_05H 控制命令 - public static byte[] AFN05_F31_Send(string address, int fn, int pn = 0) + public static byte[] AFN05_Fn_Send(TelemetryPacketRequest request) { var reqParameter = new ReqParameter2() { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, + AFN = AFN.控制命令, + FunCode = (int)CMasterStationFunCode.请求1级数据, + A = request.FocusAddress, Seq = new Seq() { TpV = TpV.附加信息域中无时间标签, FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, + CON = CON.需要对该帧进行确认, + PRSEQ = 10, }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn + MSA = Build3761SendData.GetMSA(request.FocusAddress), + Pn = request.Pn, + Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); return bytes; @@ -256,13 +168,13 @@ namespace JiShe.CollectBus.Common.BuildSendDatas #endregion #region AFN_09H 请求终端配置及信息 - public static byte[] AFN09_F1_Send(string address, int fn, int pn = 0) + public static byte[] AFN09_Fn_Send(TelemetryPacketRequest request) { var reqParameter = new ReqParameter2() { - AFN = AFN.链路接口检测, + AFN = AFN.请求终端配置, FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, + A = request.FocusAddress, Seq = new Seq() { TpV = TpV.附加信息域中无时间标签, @@ -270,45 +182,24 @@ namespace JiShe.CollectBus.Common.BuildSendDatas CON = CON.不需要对该帧进行确认, PRSEQ = 0, }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn + MSA = Build3761SendData.GetMSA(request.FocusAddress), + Pn = request.Pn, + Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); return bytes; } - public static byte[] AFN09_F9_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } #endregion #region AFN_0AH 查询参数 - public static byte[] AFN0A_F10_Send(string address, int fn, int pn = 0) + public static byte[] AFN0A_Fn_Send(TelemetryPacketRequest request) { var reqParameter = new ReqParameter2() { - AFN = AFN.链路接口检测, + AFN = AFN.查询参数, FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, + A = request.FocusAddress, Seq = new Seq() { TpV = TpV.附加信息域中无时间标签, @@ -316,53 +207,9 @@ namespace JiShe.CollectBus.Common.BuildSendDatas CON = CON.不需要对该帧进行确认, PRSEQ = 0, }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0A_F66_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0A_F88_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn + MSA = Build3761SendData.GetMSA(request.FocusAddress), + Pn = request.Pn, + Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); return bytes; @@ -370,1450 +217,47 @@ namespace JiShe.CollectBus.Common.BuildSendDatas #endregion #region AFN_0CH 请求一类数据 - public static byte[] AFN0C_F2_Send(string address, int fn, int pn = 0) + public static byte[] AFN0C_Fn_Send(TelemetryPacketRequest request) { var reqParameter = new ReqParameter2() { - AFN = AFN.链路接口检测, + AFN = AFN.请求实时数据, FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, + A = request.FocusAddress, Seq = new Seq() { TpV = TpV.附加信息域中无时间标签, FIRFIN = FIRFIN.单帧, CON = CON.不需要对该帧进行确认, - PRSEQ = 0, + PRSEQ = 2, }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn + MSA = Build3761SendData.GetMSA(request.FocusAddress), + Pn = request.Pn, + Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); return bytes; } - - public static byte[] AFN0C_F25_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0C_F33_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0C_F49_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0C_F129_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0C_F130_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0C_F131_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0C_F132_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0C_F145_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0C_F149_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0C_F188_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - #endregion #region AFN_0DH 请求二类数据 - public static byte[] AFN0D_F3_Send(string address, int fn, int pn = 0) + public static byte[] AFN0D_Fn_Send(TelemetryPacketRequest request) { var reqParameter = new ReqParameter2() { - AFN = AFN.链路接口检测, + AFN = AFN.请求历史数据, FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, + A = request.FocusAddress, Seq = new Seq() { TpV = TpV.附加信息域中无时间标签, FIRFIN = FIRFIN.单帧, CON = CON.不需要对该帧进行确认, - PRSEQ = 0, + PRSEQ = 2, }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F4_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F11_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F19_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F81_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F82_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F83_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F84_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F85_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F86_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F87_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - public static byte[] AFN0D_F88_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F89_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F90_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F91_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F92_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F93_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F94_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F95_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - public static byte[] AFN0D_F96_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - public static byte[] AFN0D_F97_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F98_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F99_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F100_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F101_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F102_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F103_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F104_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F105_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - public static byte[] AFN0D_F106_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F107_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F108_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F145_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F146_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F147_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F148_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F161_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F162_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - public static byte[] AFN0D_F163_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F164_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F165_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F166_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F167_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F1618_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F177_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F178_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F179_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - public static byte[] AFN0D_F180_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F181_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F182_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F183_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F184_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F190_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F193_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN0D_F195_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn + MSA = Build3761SendData.GetMSA(request.FocusAddress), + Pn = request.Pn, + Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); return bytes; @@ -1821,13 +265,13 @@ namespace JiShe.CollectBus.Common.BuildSendDatas #endregion #region AFN10H 数据转发 - public static byte[] AFN10_F4_Send(string address, int fn, int pn = 0) + public static byte[] AFN10_Fn_Send(TelemetryPacketRequest request) { var reqParameter = new ReqParameter2() { - AFN = AFN.链路接口检测, + AFN = AFN.数据转发, FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, + A = request.FocusAddress, Seq = new Seq() { TpV = TpV.附加信息域中无时间标签, @@ -1835,230 +279,11 @@ namespace JiShe.CollectBus.Common.BuildSendDatas CON = CON.不需要对该帧进行确认, PRSEQ = 0, }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn + MSA = Build3761SendData.GetMSA(request.FocusAddress), + Pn = request.Pn, + Fn = request.Fn }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN10_F94_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN10_F97_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - public static byte[] AFN10_F101_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN10_F102_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN10_F103_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN10_F104_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN10_F105_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN10_F106_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN10_F107_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; - } - - public static byte[] AFN10_F249_Send(string address, int fn, int pn = 0) - { - var reqParameter = new ReqParameter2() - { - AFN = AFN.链路接口检测, - FunCode = (int)CMasterStationFunCode.请求2级数据, - A = address, - Seq = new Seq() - { - TpV = TpV.附加信息域中无时间标签, - FIRFIN = FIRFIN.单帧, - CON = CON.不需要对该帧进行确认, - PRSEQ = 0, - }, - MSA = Build3761SendData.GetMSA(address), - Pn = pn, - Fn = fn - }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter,request.DataUnit); return bytes; } diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketRequest.cs b/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketRequest.cs new file mode 100644 index 0000000..d22f923 --- /dev/null +++ b/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketRequest.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.BuildSendDatas +{ + /// + /// 报文构建参数 + /// + public class TelemetryPacketRequest + { + /// + /// 集中器地址 + /// + public string FocusAddress { get; set; } + + /// + /// 抄读功能码 + /// + public int Fn { get; set; } + + /// + /// 抄读计量点,也就是终端电表对应端口 + /// + public int Pn { get; set; } + + /// + /// 透明转发单元 + /// + public List DataUnit { get; set; } + } +} diff --git a/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs b/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs index 859e86e..12e9053 100644 --- a/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/FreeRedisConst.cs @@ -29,13 +29,18 @@ namespace JiShe.CollectBus.Common.Consts public const string FifteenMinuteAcquisitionTimeInterval = $"Fifteen"; /// - /// 缓存电表信息 + /// 缓存表计信息,{0}=>系统类型,{1}=>表计类别 /// - public const string CacheAmmeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:{"{1}"}:AmmeterInfo:"; + public const string CacheMeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:MeterInfo:{"{1}"}:{"{2}"}:"; /// - /// 缓存水表信息 + /// 缓存待下发的指令生产任务数据,{0}=>系统类型,{1}=>表计类别,{2}=>采集频率 /// - public const string CacheWatermeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:{"{1}"}:WatermeterInfo:"; + public const string CacheTasksToBeIssuedKey = $"{CacheBasicDirectoryKey}{"{0}"}:TaskInfo:{"{1}"}:{"{2}"}"; + + /// + /// 缓存表计下发指令数据集,{0}=>系统类型,{1}=>表计类别,{2}=>采集频率 + /// + public const string CacheTelemetryPacketInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:TelemetryPacket:{"{1}"}:{"{2}"}:"; } } diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs index cdab61a..ce3cc87 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs @@ -12,7 +12,7 @@ namespace JiShe.CollectBus.IotSystems.MessageIssueds /// /// 消息内容 /// - public byte[] Message { get; set; } + public string MessageHexString { get; set; } /// /// 集中器编号 @@ -33,5 +33,10 @@ namespace JiShe.CollectBus.IotSystems.MessageIssueds /// 是否下发成功 /// public bool WasSuccessful { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreationTime { get; set; } } } -- 2.47.2 From 8334b5ee490a10459e8c1f76adf905b338048c74 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Tue, 18 Mar 2025 16:20:08 +0800 Subject: [PATCH 024/139] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 44 +++++++++---------- ...nergySystemScheduledMeterReadingService.cs | 3 +- .../Consts/RedisConst.cs | 2 + 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index b27c630..e477551 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -37,18 +37,16 @@ namespace JiShe.CollectBus.ScheduledMeterReading private readonly ILogger _logger; private readonly ICapPublisher _capBus; private readonly IRepository _meterReadingIssuedRepository; - private readonly IUnitOfWorkManager _unitOfWorkManager; public BasicScheduledMeterReadingService( ILogger logger, ICapPublisher capBus, - IRepository meterReadingIssuedRepository, IUnitOfWorkManager unitOfWorkManager) + IRepository meterReadingIssuedRepository) { _capBus = capBus; _logger = logger; _meterReadingIssuedRepository = meterReadingIssuedRepository; - _unitOfWorkManager = unitOfWorkManager; } /// @@ -81,7 +79,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// public virtual async Task CreateToBeIssueTasks() { - var redisCacheKey = $"{FreeRedisConst.CacheBasicDirectoryKey}{SystemType}:TaskInfo:*"; + var redisCacheKey = $"{RedisConst.CacheBasicDirectoryKey}{SystemType}:TaskInfo:*"; var taskInfos = await FreeRedisProvider.Instance.KeysAsync(redisCacheKey); if (taskInfos == null || taskInfos.Length <= 0) { @@ -104,7 +102,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading string timeDensity = tempArryay[4];//采集频率 //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(FreeRedisConst.CacheMeterInfoKey, SystemType, meteryType, timeDensity)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, meteryType, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -185,7 +183,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheMeterInfoKey, SystemType, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}{item.Key}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}{item.Key}"; #if DEBUG //每次缓存时,删除缓存,避免缓存数据错误 @@ -252,7 +250,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading NextTask = DateTime.Now.AddMinutes(1) }; - var taskRedisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, itemTimeDensity.Key); + var taskRedisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, itemTimeDensity.Key); await FreeRedisProvider.Instance.SetAsync(taskRedisCacheKey, nextTask); } @@ -267,7 +265,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的电表信息 int timeDensity = 5; - var redisKeyList = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -306,7 +304,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading NextTask = DateTime.Now.AddMinutes(timeDensity) }; - var redisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, timeDensity); + var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, timeDensity); await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); _logger.LogInformation($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理完成"); @@ -321,7 +319,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的电表信息 int timeDensity = 5; - var redisKeyList = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -360,7 +358,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading NextTask = DateTime.Now.AddMinutes(timeDensity) }; - var redisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, timeDensity); + var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, timeDensity); await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); _logger.LogInformation($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集电表数据处理完成"); @@ -377,7 +375,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading //获取缓存中的电表信息 int timeDensity = 15; - var redisKeyList = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -419,7 +417,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading NextTask = DateTime.Now.AddMinutes(timeDensity) }; - var redisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, timeDensity); + var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, timeDensity); await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); stopwatch.Stop(); @@ -502,7 +500,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading foreach (var focusInfo in focusGroup) { //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}{focusInfo.Key}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}{focusInfo.Key}"; foreach (var ammeterInfo in focusInfo.Value) { @@ -703,7 +701,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheMeterInfoKey, SystemType, MeterTypeEnum.WaterMeter, itemTimeDensity.Key)}{item.Key}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, MeterTypeEnum.WaterMeter, itemTimeDensity.Key)}{item.Key}"; Dictionary keyValuePairs = new Dictionary(); foreach (var subItem in item) { @@ -720,7 +718,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading NextTask = DateTime.Now.AddMinutes(1) }; - var taskRedisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, itemTimeDensity.Key); + var taskRedisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, itemTimeDensity.Key); await FreeRedisProvider.Instance.SetAsync(taskRedisCacheKey, nextTask); } _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化水表缓存数据完成"); @@ -734,7 +732,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的水表信息 int timeDensity = 5; - var redisKeyList = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -773,7 +771,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading NextTask = DateTime.Now.AddMinutes(timeDensity) }; - var redisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity); + var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity); await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); @@ -789,7 +787,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading //获取缓存中的电表信息 int timeDensity = 5; - var redisKeyList = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -828,7 +826,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading NextTask = DateTime.Now.AddMinutes(timeDensity) }; - var redisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity); + var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity); await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); @@ -843,7 +841,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的电表信息 int timeDensity = 15; - var redisKeyList = $"{string.Format(FreeRedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -882,7 +880,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading NextTask = DateTime.Now.AddMinutes(timeDensity) }; - var redisCacheKey = string.Format(FreeRedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity); + var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity); await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); @@ -925,7 +923,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { string key = (string)item[0];//集中器地址对应的Redis缓存Key object[] fieldsAndValues = (object[])item[1];//缓存Key对应的Hash表数据集合 - var redisCacheKey = $"{string.Format(FreeRedisConst.CacheMeterInfoKey, SystemType, meterType, timeDensity)}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, meterType, timeDensity)}"; string focusAddress = key.Replace(redisCacheKey, "");//集中器地址 var meterHashs = new Dictionary(); diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 4662ca8..872a590 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -25,8 +25,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading public EnergySystemScheduledMeterReadingService(ILogger logger, ICapPublisher capBus, - IRepository meterReadingIssuedRepository, - IUnitOfWorkManager unitOfWorkManager) :base(logger, capBus, meterReadingIssuedRepository, unitOfWorkManager) + IRepository meterReadingIssuedRepository) :base(logger, capBus, meterReadingIssuedRepository) { } diff --git a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs index 6dd398e..1447d4c 100644 --- a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs @@ -42,5 +42,7 @@ namespace JiShe.CollectBus.Common.Consts /// 缓存表计下发指令数据集,{0}=>系统类型,{1}=>表计类别,{2}=>采集频率 /// public const string CacheTelemetryPacketInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:TelemetryPacket:{"{1}"}:{"{2}"}:"; + + public const string CacheAmmeterFocusKey = "CacheAmmeterFocusKey"; } } -- 2.47.2 From 69cad7bdee366cf365e5a7427675d09848eab68a Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Tue, 18 Mar 2025 17:10:54 +0800 Subject: [PATCH 025/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 5 +++-- src/JiShe.CollectBus.Common/Consts/RedisConst.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index e477551..09c5126 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -398,7 +398,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading { foreach (var ammerterItem in focusItem.Value) { - _= _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, ammerterItem.Value); + //todo 可能需要优化,如果使用等待,会很慢,但使用不等待,mongodb 连接池又没法抗住,先发送微妙级的延时队列消息,暂时先这样处理 + _= _capBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, ammerterItem.Value); meterTaskInfosList.Add(ammerterItem.Value); } } @@ -408,7 +409,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //删除任务数据 - await FreeRedisProvider.Instance.DelAsync(redisKeyList); + await FreeRedisProvider.Instance.DelAsync(oneMinutekeyList); //缓存下一个时间的任务 TasksToBeIssueModel nextTask = new TasksToBeIssueModel() diff --git a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs index 1447d4c..9b921c9 100644 --- a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs @@ -11,7 +11,7 @@ namespace JiShe.CollectBus.Common.Consts /// /// 缓存基础目录 /// - public const string CacheBasicDirectoryKey = "CollectBus"; + public const string CacheBasicDirectoryKey = "CollectBus:"; /// /// 1分钟采集间隔 -- 2.47.2 From 7f56b6e91f06688edd0235437094aa4bf5e48ad2 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Tue, 18 Mar 2025 21:21:35 +0800 Subject: [PATCH 026/139] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 2 +- .../Subscribers/WorkerSubscriberAppService.cs | 12 ++++++------ .../ScheduledMeterReadingIssuedEventMessage.cs | 17 ++++++++++++++--- .../IotSystems/MeterReadingRecords.cs | 16 ++++++++++++++++ 4 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords.cs diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 09c5126..5c766da 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -641,7 +641,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading var evenMessageInfo = new ScheduledMeterReadingIssuedEventMessage { - MessageHexString = Convert.ToHexString(dataInfos), + IssuedMessageHexString = Convert.ToHexString(dataInfos), DeviceNo = ammeter.FocusAddress, MessageId = NewId.NextGuid().ToString(), TimeDensity = timeDensity, diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index 50ed9e0..7a98a09 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -70,7 +70,7 @@ namespace JiShe.CollectBus.Subscribers var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); if (device != null) { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.IssuedMessageHexString)); } } @@ -97,7 +97,7 @@ namespace JiShe.CollectBus.Subscribers var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); if (device != null) { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.IssuedMessageHexString)); } } @@ -124,7 +124,7 @@ namespace JiShe.CollectBus.Subscribers var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); if (device != null) { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.IssuedMessageHexString)); } } @@ -153,7 +153,7 @@ namespace JiShe.CollectBus.Subscribers var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); if (device != null) { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.IssuedMessageHexString)); } } @@ -180,7 +180,7 @@ namespace JiShe.CollectBus.Subscribers var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); if (device != null) { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.IssuedMessageHexString)); } } @@ -207,7 +207,7 @@ namespace JiShe.CollectBus.Subscribers var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); if (device != null) { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.IssuedMessageHexString)); } } } diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs index ce3cc87..9bf229e 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs @@ -7,12 +7,12 @@ namespace JiShe.CollectBus.IotSystems.MessageIssueds /// /// 定时抄读Kafka消息实体,1分钟、5分钟、15分钟 /// - public class ScheduledMeterReadingIssuedEventMessage : AggregateRoot + public class ScheduledMeterReadingIssuedEventMessage { /// - /// 消息内容 + /// 下发消息内容 /// - public string MessageHexString { get; set; } + public string IssuedMessageHexString { get; set; } /// /// 集中器编号 @@ -38,5 +38,16 @@ namespace JiShe.CollectBus.IotSystems.MessageIssueds /// 创建时间 /// public DateTime CreationTime { get; set; } + + /// + /// 消息上报内容 + /// + public string? ReceivedMessageHexString { get; set; } + + /// + /// 消息上报时间 + /// + public DateTime? ReceivedTime { get; set; } + } } diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords.cs new file mode 100644 index 0000000..4e8c48c --- /dev/null +++ b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities; + +namespace JiShe.CollectBus.IotSystems +{ + /// + /// 抄读记录表,包含下发报文和回复报文,以及是否迁移 + /// + public class MeterReadingRecords : AggregateRoot + { + } +} -- 2.47.2 From 76fe43ae5450a57ea4816d8eecf8d869888e48dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E7=9B=8A?= Date: Tue, 18 Mar 2025 22:43:24 +0800 Subject: [PATCH 027/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=AD=98=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 177 +++++++++++++----- ...nergySystemScheduledMeterReadingService.cs | 5 +- .../Subscribers/WorkerSubscriberAppService.cs | 24 +-- .../Enums/RecordsDataMigrationStatusEnum.cs | 39 ++++ ...ScheduledMeterReadingIssuedEventMessage.cs | 24 +-- .../IotSystems/MeterReadingRecords.cs | 16 -- .../BasicMeterReadingRecords.cs | 114 +++++++++++ .../MeterFifteenMinuteReadingRecords.cs | 18 ++ .../MeterFiveMinuteReadingRecords.cs | 18 ++ .../MeterOneMinuteReadingRecords.cs | 18 ++ .../MongoDB/CollectBusMongoDbContext.cs | 5 +- 11 files changed, 354 insertions(+), 104 deletions(-) create mode 100644 src/JiShe.CollectBus.Common/Enums/RecordsDataMigrationStatusEnum.cs delete mode 100644 src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords.cs create mode 100644 src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/BasicMeterReadingRecords.cs create mode 100644 src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFifteenMinuteReadingRecords.cs create mode 100644 src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFiveMinuteReadingRecords.cs create mode 100644 src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterOneMinuteReadingRecords.cs diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 5c766da..2f144de 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -18,6 +18,7 @@ using JiShe.CollectBus.GatherItem; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Workers; @@ -36,17 +37,21 @@ namespace JiShe.CollectBus.ScheduledMeterReading { private readonly ILogger _logger; private readonly ICapPublisher _capBus; - private readonly IRepository _meterReadingIssuedRepository; + private readonly IRepository _fifteenMinuteReadingRecordRepository; + private readonly IRepository _fiveMinuteReadingRecordRepository; + private readonly IRepository _oneMinuteReadingRecordRepository; public BasicScheduledMeterReadingService( ILogger logger, ICapPublisher capBus, - IRepository meterReadingIssuedRepository) + IRepository fifteenMinuteReadingRecordRepository, IRepository fiveMinuteReadingRecordRepository, IRepository oneMinuteReadingRecordRepository) { _capBus = capBus; _logger = logger; - _meterReadingIssuedRepository = meterReadingIssuedRepository; + _oneMinuteReadingRecordRepository = oneMinuteReadingRecordRepository; + _fiveMinuteReadingRecordRepository = fiveMinuteReadingRecordRepository; + _fifteenMinuteReadingRecordRepository = fifteenMinuteReadingRecordRepository; } /// @@ -186,8 +191,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}{item.Key}"; #if DEBUG - //每次缓存时,删除缓存,避免缓存数据错误 - await FreeRedisProvider.Instance.DelAsync(redisCacheKey); + //每次缓存时,删除缓存,避免缓存数据错误 + await FreeRedisProvider.Instance.DelAsync(redisCacheKey); #else //每次缓存时,删除缓存,避免缓存数据错误 await FreeRedisProvider.Instance.DelAsync(redisCacheKey); @@ -274,29 +279,40 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.Ammeter).ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), MeterTypeEnum.Ammeter.ToString()); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); return; } - List meterTaskInfosList = new List(); + List meterTaskInfosList = new List(); //将取出的缓存任务数据发送到Kafka消息队列中 foreach (var focusItem in meterTaskInfos) { foreach (var ammerterItem in focusItem.Value) { - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, ammerterItem.Value); + var tempMsg = new ScheduledMeterReadingIssuedEventMessage() + { + MessageHexString = ammerterItem.Value.IssuedMessageHexString, + MessageId = ammerterItem.Value.IssuedMessageId, + FocusAddress = ammerterItem.Value.FocusAddress, + TimeDensity = timeDensity.ToString(), + }; + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + meterTaskInfosList.Add(ammerterItem.Value); } } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingIssuedRepository.InsertManyAsync(meterTaskInfosList); + await _oneMinuteReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } + //删除任务数据 + await FreeRedisProvider.Instance.DelAsync(oneMinutekeyList); + //缓存下一个时间的任务 TasksToBeIssueModel nextTask = new TasksToBeIssueModel() { @@ -320,37 +336,48 @@ namespace JiShe.CollectBus.ScheduledMeterReading //获取缓存中的电表信息 int timeDensity = 5; var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; - var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + var fiveMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + if (fiveMinutekeyList == null || fiveMinutekeyList.Length <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-101"); return; } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.Ammeter).ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fiveMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.Ammeter).ToString()); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); return; } - List meterTaskInfosList = new List(); + List meterTaskInfosList = new List(); //将取出的缓存任务数据发送到Kafka消息队列中 foreach (var focusItem in meterTaskInfos) { foreach (var ammerterItem in focusItem.Value) { - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, ammerterItem.Value); + var tempMsg = new ScheduledMeterReadingIssuedEventMessage() + { + MessageHexString = ammerterItem.Value.IssuedMessageHexString, + MessageId = ammerterItem.Value.IssuedMessageId, + FocusAddress = ammerterItem.Value.FocusAddress, + TimeDensity = timeDensity.ToString(), + }; + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + meterTaskInfosList.Add(ammerterItem.Value); } } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingIssuedRepository.InsertManyAsync(meterTaskInfosList); + await _fiveMinuteReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } + //删除任务数据 + await FreeRedisProvider.Instance.DelAsync(fiveMinutekeyList); + //缓存下一个时间的任务 TasksToBeIssueModel nextTask = new TasksToBeIssueModel() { @@ -376,40 +403,47 @@ namespace JiShe.CollectBus.ScheduledMeterReading //获取缓存中的电表信息 int timeDensity = 15; var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; - var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + var fifteenMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + if (fifteenMinutekeyList == null || fifteenMinutekeyList.Length <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-101"); return; } - + //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), MeterTypeEnum.Ammeter.ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fifteenMinutekeyList, timeDensity.ToString(), MeterTypeEnum.Ammeter.ToString()); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); return; } - List meterTaskInfosList = new List(); + List meterTaskInfosList = new List(); //将取出的缓存任务数据发送到Kafka消息队列中 foreach (var focusItem in meterTaskInfos) { foreach (var ammerterItem in focusItem.Value) { - //todo 可能需要优化,如果使用等待,会很慢,但使用不等待,mongodb 连接池又没法抗住,先发送微妙级的延时队列消息,暂时先这样处理 - _= _capBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, ammerterItem.Value); + var tempMsg = new ScheduledMeterReadingIssuedEventMessage() + { + MessageHexString = ammerterItem.Value.IssuedMessageHexString, + MessageId = ammerterItem.Value.IssuedMessageId, + FocusAddress = ammerterItem.Value.FocusAddress, + TimeDensity = timeDensity.ToString(), + }; + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + meterTaskInfosList.Add(ammerterItem.Value); } } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingIssuedRepository.InsertManyAsync(meterTaskInfosList); + await _fifteenMinuteReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } //删除任务数据 - await FreeRedisProvider.Instance.DelAsync(oneMinutekeyList); + await FreeRedisProvider.Instance.DelAsync(fifteenMinutekeyList); //缓存下一个时间的任务 TasksToBeIssueModel nextTask = new TasksToBeIssueModel() @@ -420,7 +454,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, timeDensity); await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); - + stopwatch.Stop(); _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} {timeDensity}分钟采集电表数据处理完成,共消耗{stopwatch.ElapsedMilliseconds}毫秒。"); @@ -586,7 +620,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } } - Dictionary keyValuePairs = new Dictionary(); + Dictionary keyValuePairs = new Dictionary(); foreach (var tempItem in tempCodes) { @@ -639,16 +673,24 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - var evenMessageInfo = new ScheduledMeterReadingIssuedEventMessage + + + var meterReadingRecords = new BasicMeterReadingRecords() { + MeterAddress = ammeter.AmmerterAddress, + MeterId = ammeter.ID, + MeterType = MeterTypeEnum.Ammeter, + FocusAddress = ammeter.FocusAddress, + FocusID = ammeter.FocusID, + AFN = aFN, + Fn = fn, + Pn = ammeter.MeteringCode, + IssuedMessageId = GuidGenerator.Create().ToString(), IssuedMessageHexString = Convert.ToHexString(dataInfos), - DeviceNo = ammeter.FocusAddress, - MessageId = NewId.NextGuid().ToString(), - TimeDensity = timeDensity, - WasSuccessful = false, - CreationTime = currentTime, }; - keyValuePairs.TryAdd($"{ammeter.ID}_{tempItem}", evenMessageInfo); + meterReadingRecords.CreateDataId(GuidGenerator.Create()); + + keyValuePairs.TryAdd($"{ammeter.ID}_{tempItem}", meterReadingRecords); } await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); } @@ -742,29 +784,40 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); return; } - List meterTaskInfosList = new List(); + List meterTaskInfosList = new List(); //将取出的缓存任务数据发送到Kafka消息队列中 foreach (var focusItem in meterTaskInfos) { foreach (var ammerterItem in focusItem.Value) { - await _capBus.PublishAsync(ProtocolConst.WatermeterSubscriberWorkerOneMinuteIssuedEventName, ammerterItem.Value); + var tempMsg = new ScheduledMeterReadingIssuedEventMessage() + { + MessageHexString = ammerterItem.Value.IssuedMessageHexString, + MessageId = ammerterItem.Value.IssuedMessageId, + FocusAddress = ammerterItem.Value.FocusAddress, + TimeDensity = timeDensity.ToString(), + }; + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + meterTaskInfosList.Add(ammerterItem.Value); } } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingIssuedRepository.InsertManyAsync(meterTaskInfosList); + await _oneMinuteReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } + //删除任务数据 + await FreeRedisProvider.Instance.DelAsync(oneMinutekeyList); + //缓存下一个时间的任务 TasksToBeIssueModel nextTask = new TasksToBeIssueModel() { @@ -789,37 +842,48 @@ namespace JiShe.CollectBus.ScheduledMeterReading //获取缓存中的电表信息 int timeDensity = 5; var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; - var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + var fiveMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + if (fiveMinutekeyList == null || fiveMinutekeyList.Length <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-101"); return; } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fiveMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); return; } - List meterTaskInfosList = new List(); + List meterTaskInfosList = new List(); //将取出的缓存任务数据发送到Kafka消息队列中 foreach (var focusItem in meterTaskInfos) { foreach (var ammerterItem in focusItem.Value) { - await _capBus.PublishAsync(ProtocolConst.WatermeterSubscriberWorkerFiveMinuteIssuedEventName, ammerterItem.Value); + var tempMsg = new ScheduledMeterReadingIssuedEventMessage() + { + MessageHexString = ammerterItem.Value.IssuedMessageHexString, + MessageId = ammerterItem.Value.IssuedMessageId, + FocusAddress = ammerterItem.Value.FocusAddress, + TimeDensity = timeDensity.ToString(), + }; + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + meterTaskInfosList.Add(ammerterItem.Value); } } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingIssuedRepository.InsertManyAsync(meterTaskInfosList); + await _fiveMinuteReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } + //删除任务数据 + await FreeRedisProvider.Instance.DelAsync(fiveMinutekeyList); + //缓存下一个时间的任务 TasksToBeIssueModel nextTask = new TasksToBeIssueModel() { @@ -829,7 +893,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity); await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); - + _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集水表数据处理完成"); } @@ -843,37 +907,48 @@ namespace JiShe.CollectBus.ScheduledMeterReading //获取缓存中的电表信息 int timeDensity = 15; var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; - var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + var fifteenMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + if (fifteenMinutekeyList == null || fifteenMinutekeyList.Length <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-101"); return; } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fifteenMinutekeyList, timeDensity.ToString(), MeterTypeEnum.WaterMeter.ToString()); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); return; } - List meterTaskInfosList = new List(); + List meterTaskInfosList = new List(); //将取出的缓存任务数据发送到Kafka消息队列中 foreach (var focusItem in meterTaskInfos) { foreach (var ammerterItem in focusItem.Value) { - await _capBus.PublishAsync(ProtocolConst.WatermeterSubscriberWorkerFifteenMinuteIssuedEventName, ammerterItem.Value); + var tempMsg = new ScheduledMeterReadingIssuedEventMessage() + { + MessageHexString = ammerterItem.Value.IssuedMessageHexString, + MessageId = ammerterItem.Value.IssuedMessageId, + FocusAddress = ammerterItem.Value.FocusAddress, + TimeDensity = timeDensity.ToString(), + }; + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + meterTaskInfosList.Add(ammerterItem.Value); } } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingIssuedRepository.InsertManyAsync(meterTaskInfosList); + await _fifteenMinuteReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } + //删除任务数据 + await FreeRedisProvider.Instance.DelAsync(fifteenMinutekeyList); + //缓存下一个时间的任务 TasksToBeIssueModel nextTask = new TasksToBeIssueModel() { @@ -953,7 +1028,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading return meterInfos; } - + /// /// 指定时间对比当前时间 /// diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 872a590..5a26013 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -7,6 +7,7 @@ using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.GatherItem; using JiShe.CollectBus.IotSystems.MessageIssueds; +using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; @@ -24,8 +25,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading { public EnergySystemScheduledMeterReadingService(ILogger logger, - ICapPublisher capBus, - IRepository meterReadingIssuedRepository) :base(logger, capBus, meterReadingIssuedRepository) + ICapPublisher capBus, + IRepository fifteenMinuteReadingRecordRepository, IRepository fiveMinuteReadingRecordRepository, IRepository oneMinuteReadingRecordRepository) :base(logger, capBus, fifteenMinuteReadingRecordRepository, fiveMinuteReadingRecordRepository, oneMinuteReadingRecordRepository) { } diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index 7a98a09..62748be 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -67,10 +67,10 @@ namespace JiShe.CollectBus.Subscribers } else { - var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); + var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.FocusAddress); if (device != null) { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.IssuedMessageHexString)); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); } } @@ -94,10 +94,10 @@ namespace JiShe.CollectBus.Subscribers } else { - var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); + var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.FocusAddress); if (device != null) { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.IssuedMessageHexString)); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); } } @@ -121,10 +121,10 @@ namespace JiShe.CollectBus.Subscribers } else { - var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); + var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.FocusAddress); if (device != null) { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.IssuedMessageHexString)); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); } } @@ -150,10 +150,10 @@ namespace JiShe.CollectBus.Subscribers } else { - var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); + var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.FocusAddress); if (device != null) { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.IssuedMessageHexString)); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); } } @@ -177,10 +177,10 @@ namespace JiShe.CollectBus.Subscribers } else { - var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); + var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.FocusAddress); if (device != null) { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.IssuedMessageHexString)); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); } } @@ -204,10 +204,10 @@ namespace JiShe.CollectBus.Subscribers } else { - var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.DeviceNo); + var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.FocusAddress); if (device != null) { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.IssuedMessageHexString)); + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); } } } diff --git a/src/JiShe.CollectBus.Common/Enums/RecordsDataMigrationStatusEnum.cs b/src/JiShe.CollectBus.Common/Enums/RecordsDataMigrationStatusEnum.cs new file mode 100644 index 0000000..544be42 --- /dev/null +++ b/src/JiShe.CollectBus.Common/Enums/RecordsDataMigrationStatusEnum.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Enums +{ + /// + /// 数据迁移状态 + /// + public enum RecordsDataMigrationStatusEnum + { + /// + /// 未开始 + /// + NotStarted = 0, + + /// + /// 进行中 + /// + InProgress = 1, + + /// + /// 已完成 + /// + Completed = 2, + + /// + /// 已取消 + /// + Cancelled = 3, + + /// + /// 失败 + /// + Failed = 4, + } +} diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs index 9bf229e..49fce87 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs @@ -12,12 +12,12 @@ namespace JiShe.CollectBus.IotSystems.MessageIssueds /// /// 下发消息内容 /// - public string IssuedMessageHexString { get; set; } + public string MessageHexString { get; set; } /// /// 集中器编号 /// - public string DeviceNo { get; set; } + public string FocusAddress { get; set; } /// /// 采集时间间隔,通过Kafka主题区分(分钟,如15) @@ -29,25 +29,5 @@ namespace JiShe.CollectBus.IotSystems.MessageIssueds /// public string MessageId { get; set; } - /// - /// 是否下发成功 - /// - public bool WasSuccessful { get; set; } - - /// - /// 创建时间 - /// - public DateTime CreationTime { get; set; } - - /// - /// 消息上报内容 - /// - public string? ReceivedMessageHexString { get; set; } - - /// - /// 消息上报时间 - /// - public DateTime? ReceivedTime { get; set; } - } } diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords.cs deleted file mode 100644 index 4e8c48c..0000000 --- a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Volo.Abp.Domain.Entities; - -namespace JiShe.CollectBus.IotSystems -{ - /// - /// 抄读记录表,包含下发报文和回复报文,以及是否迁移 - /// - public class MeterReadingRecords : AggregateRoot - { - } -} diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/BasicMeterReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/BasicMeterReadingRecords.cs new file mode 100644 index 0000000..315bbda --- /dev/null +++ b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/BasicMeterReadingRecords.cs @@ -0,0 +1,114 @@ +using JiShe.CollectBus.Common.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities; + +namespace JiShe.CollectBus.IotSystems.MeterReadingRecords +{ + /// + /// 抄读记录基类 + /// + public class BasicMeterReadingRecords : AggregateRoot + { + + /// + /// 是否手动操作 + /// + public bool ManualOrNot { get; set; } + + /// + /// 下发消息内容 + /// + public string IssuedMessageHexString { get; set; } + + /// + /// 下发消息Id + /// + public string IssuedMessageId { get; set; } + + /// + /// 集中器ID + /// + public int FocusID { get; set; } + + /// + /// 集中器地址 + /// + public string FocusAddress { get; set; } + + /// + /// 表Id + /// + public int MeterId { get; set; } + + /// + /// 表地址 + /// + public string MeterAddress { get; set; } + + + /// + /// 表类型 + /// + public MeterTypeEnum MeterType { get; set; } + + /// + /// AFN功能码 + /// + public AFN AFN { get; set; } + + /// + /// 抄读功能码 + /// + public int Fn { get; set; } + + /// + /// 抄读计量点 + /// + public int Pn { get; set; } + + + /// + /// 是否下发成功 + /// + public bool WasSuccessful { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreationTime { get; set; } + + /// + /// 消息上报内容 + /// + public string? ReceivedMessageHexString { get; set; } + + /// + /// 消息上报时间 + /// + public DateTime? ReceivedTime { get; set; } + + /// + /// 上报消息Id + /// + public string ReceivedMessageId { get; set; } + + /// + /// 数据迁移状态 + /// + public RecordsDataMigrationStatusEnum MigrationStatus { get; set; } + + /// + /// 数据迁移时间 + /// + public DateTime? MigrationTime { get; set; } + + public void CreateDataId(Guid Id) + { + this.Id = Id; + } + } +} diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFifteenMinuteReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFifteenMinuteReadingRecords.cs new file mode 100644 index 0000000..acc7163 --- /dev/null +++ b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFifteenMinuteReadingRecords.cs @@ -0,0 +1,18 @@ +using JiShe.CollectBus.Common.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities; + +namespace JiShe.CollectBus.IotSystems.MeterReadingRecords +{ + /// + /// 15分钟抄读记录表,包含下发报文和回复报文,以及是否迁移 + /// + public class MeterFifteenMinuteReadingRecords : BasicMeterReadingRecords + { + + } +} diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFiveMinuteReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFiveMinuteReadingRecords.cs new file mode 100644 index 0000000..eeb69a2 --- /dev/null +++ b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFiveMinuteReadingRecords.cs @@ -0,0 +1,18 @@ +using JiShe.CollectBus.Common.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities; + +namespace JiShe.CollectBus.IotSystems.MeterReadingRecords +{ + /// + /// 5分钟抄读记录表,包含下发报文和回复报文,以及是否迁移 + /// + public class MeterFiveMinuteReadingRecords : BasicMeterReadingRecords + { + + } +} diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterOneMinuteReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterOneMinuteReadingRecords.cs new file mode 100644 index 0000000..35b7630 --- /dev/null +++ b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterOneMinuteReadingRecords.cs @@ -0,0 +1,18 @@ +using JiShe.CollectBus.Common.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities; + +namespace JiShe.CollectBus.IotSystems.MeterReadingRecords +{ + /// + /// 1分钟抄读记录表,包含下发报文和回复报文,以及是否迁移 + /// + public class MeterOneMinuteReadingRecords : BasicMeterReadingRecords + { + + } +} diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs index 06dfd1a..5c73029 100644 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs @@ -1,6 +1,7 @@ using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Protocols; using MongoDB.Driver; using Volo.Abp.Data; @@ -23,7 +24,9 @@ public class CollectBusMongoDbContext : AbpMongoDbContext, ICollectBusMongoDbCon public IMongoCollection Devices => Collection(); public IMongoCollection ProtocolInfos => Collection(); - public IMongoCollection MeterReadingIssued => Collection(); + public IMongoCollection FifteenMinuteReadingRecords => Collection(); + public IMongoCollection MeterFiveMinuteReadingRecords => Collection(); + public IMongoCollection MeterOneMinuteReadingRecords => Collection(); protected override void CreateModel(IMongoModelBuilder modelBuilder) { -- 2.47.2 From 83d8785ff48518beb19e9bc7c91b97aa09d89916 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Wed, 19 Mar 2025 14:31:04 +0800 Subject: [PATCH 028/139] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=8A=A5=E6=96=87?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=EF=BC=8C=E6=90=AD=E5=BB=BA=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DataMigration/IDataMigrationService.cs | 20 +++ .../Options/DataMigrationOptions.cs | 39 +++++ .../Consumers/ReceivedConsumer.cs | 3 +- .../DataMigration/DataMigrationService.cs | 153 ++++++++++++++++++ .../BasicScheduledMeterReadingService.cs | 50 +++--- ...nergySystemScheduledMeterReadingService.cs | 3 +- .../Subscribers/SubscriberAppService.cs | 12 +- .../MeterFifteenMinuteReadingRecords.cs | 18 --- .../MeterFiveMinuteReadingRecords.cs | 18 --- .../MeterOneMinuteReadingRecords.cs | 18 --- ...adingRecords.cs => MeterReadingRecords.cs} | 14 +- src/JiShe.CollectBus.Host/appsettings.json | 2 +- .../MongoDB/CollectBusMongoDbContext.cs | 5 +- .../Abstracts/BaseProtocolPlugin.cs | 2 +- .../Interfaces/IProtocolPlugin.cs | 2 +- .../StandardProtocolPlugin.cs | 17 +- 16 files changed, 274 insertions(+), 102 deletions(-) create mode 100644 src/JiShe.CollectBus.Application.Contracts/DataMigration/IDataMigrationService.cs create mode 100644 src/JiShe.CollectBus.Application.Contracts/DataMigration/Options/DataMigrationOptions.cs create mode 100644 src/JiShe.CollectBus.Application/DataMigration/DataMigrationService.cs delete mode 100644 src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFifteenMinuteReadingRecords.cs delete mode 100644 src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFiveMinuteReadingRecords.cs delete mode 100644 src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterOneMinuteReadingRecords.cs rename src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/{BasicMeterReadingRecords.cs => MeterReadingRecords.cs} (89%) diff --git a/src/JiShe.CollectBus.Application.Contracts/DataMigration/IDataMigrationService.cs b/src/JiShe.CollectBus.Application.Contracts/DataMigration/IDataMigrationService.cs new file mode 100644 index 0000000..bbfa581 --- /dev/null +++ b/src/JiShe.CollectBus.Application.Contracts/DataMigration/IDataMigrationService.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.DataMigration +{ + /// + /// 数据迁移服务 + /// + public interface IDataMigrationService + { + /// + /// 开始迁移 + /// + /// + Task StartMigrationAsync(); + } +} diff --git a/src/JiShe.CollectBus.Application.Contracts/DataMigration/Options/DataMigrationOptions.cs b/src/JiShe.CollectBus.Application.Contracts/DataMigration/Options/DataMigrationOptions.cs new file mode 100644 index 0000000..7174cad --- /dev/null +++ b/src/JiShe.CollectBus.Application.Contracts/DataMigration/Options/DataMigrationOptions.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.DataMigration.Options +{ + /// + /// 数据迁移配置 + /// + public class DataMigrationOptions + { + /// + /// MongoDb每批处理量 + /// + public int MongoDbDataBatchSize { get; set; } = 1000; + + /// + /// 批量处理通道容量 + /// + public int ChannelCapacity { get; set; } = 100; + + /// + /// 数据库 每批处理量 + /// + public int SqlBulkBatchSize { get; set; } = 1000; + + /// + /// 数据库 每批处理超时时间 + /// + public int SqlBulkTimeout { get; set; } = 60; + + /// + /// 处理器数量 + /// + public int ProcessorsCount { get; set; } = 4; + } +} diff --git a/src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs b/src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs index 4e00864..9309c55 100644 --- a/src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs +++ b/src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using JiShe.CollectBus.Protocol.Contracts.Models; using MassTransit; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -48,7 +49,7 @@ namespace JiShe.CollectBus.Consumers var list = new List(); foreach (var contextItem in context.Message) { - await protocolPlugin.AnalyzeAsync(contextItem.Message); + await protocolPlugin.AnalyzeAsync(contextItem.Message); list.Add(contextItem.Message); } await _messageReceivedEventRepository.InsertManyAsync(list); diff --git a/src/JiShe.CollectBus.Application/DataMigration/DataMigrationService.cs b/src/JiShe.CollectBus.Application/DataMigration/DataMigrationService.cs new file mode 100644 index 0000000..4aa3a7e --- /dev/null +++ b/src/JiShe.CollectBus.Application/DataMigration/DataMigrationService.cs @@ -0,0 +1,153 @@ +using JiShe.CollectBus.DataMigration.Options; +using JiShe.CollectBus.IotSystems.MeterReadingRecords; +using LiteDB; +using Microsoft.Extensions.Options; +using System; +using System.Data; +using System.Linq; +using System.Threading.Channels; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace JiShe.CollectBus.DataMigration +{ + /// + /// 数据迁移服务 + /// + public class DataMigrationService: CollectBusAppService, IDataMigrationService + { + private readonly IRepository _meterReadingRecordsRepository; + private readonly DataMigrationOptions _options; + + + public DataMigrationService(IOptions options, + IRepository meterReadingRecordsRepository) + { + _options = options.Value; + _meterReadingRecordsRepository = meterReadingRecordsRepository; + } + + /// + /// 开始迁移 + /// + /// + public async Task StartMigrationAsync() + { + var rawDataChannel = Channel.CreateBounded(new BoundedChannelOptions(_options.ChannelCapacity) + { + SingleWriter = false, + SingleReader = false, + FullMode = BoundedChannelFullMode.Wait + }); + + var cleanDataChannel = Channel.CreateBounded(new BoundedChannelOptions(_options.ChannelCapacity) + { + SingleWriter = false, + SingleReader = false, + FullMode = BoundedChannelFullMode.Wait + }); + + // 启动生产者和消费者 + var producer = Task.Run(() => ProduceDataAsync(rawDataChannel.Writer)); + + var processors = Enumerable.Range(0, _options.ProcessorsCount) + .Select(_ => Task.Run(() => ProcessDataAsync(rawDataChannel.Reader, cleanDataChannel.Writer))) + .ToArray(); + + var consumer = Task.Run(() => ConsumeDataAsync(cleanDataChannel.Reader)); + + await Task.WhenAll(new[] { producer }.Union(processors).Union(new[] { consumer })); + } + + /// + /// 生产者,生产数据,主要是从MongoDB中读取数据 + /// + /// + /// + private async Task ProduceDataAsync(ChannelWriter writer) + { + while (true) + { + var queryable = await _meterReadingRecordsRepository.GetQueryableAsync(); + var batchRecords = queryable.Where(d => d.MigrationStatus == Common.Enums.RecordsDataMigrationStatusEnum.NotStarted) + .Take(_options.MongoDbDataBatchSize) + .ToArray(); + + if (batchRecords == null || batchRecords.Length == 0) + { + writer.Complete(); + break; + } + + await writer.WriteAsync(batchRecords); + } + } + + /// + /// 清洗数据 + /// + /// + /// + /// + private async Task ProcessDataAsync(ChannelReader reader, ChannelWriter writer) + { + await foreach (var batch in reader.ReadAllAsync()) + { + //var dataTable = new DataTable(); + //dataTable.Columns.Add("Id", typeof(string)); + //dataTable.Columns.Add("CleanName", typeof(string)); + //dataTable.Columns.Add("ProcessedTime", typeof(DateTime)); + + //foreach (var doc in batch) + //{ + // // 业务清洗逻辑 + // var cleanName = doc["name"].AsString.Trim().ToUpper(); + // dataTable.Rows.Add( + // doc["_id"].ToString(), + // cleanName, + // DateTime.UtcNow); + //} + + //await writer.WriteAsync(dataTable); + + // 批量更新标记 + var ids = batch.Select(d => d.Id).ToArray(); + foreach (var item in batch) + { + item.MigrationStatus = Common.Enums.RecordsDataMigrationStatusEnum.InProgress; + item.MigrationTime = DateTime.Now; + } + + await _meterReadingRecordsRepository.UpdateManyAsync(batch); + } + writer.Complete(); + } + + /// + /// 消费清洗后的数据入库 + /// + /// + /// + private async Task ConsumeDataAsync(ChannelReader reader) + { + //await using var connection = new SqlConnection(_sqlConnectionString); + //await connection.OpenAsync(); + + //await foreach (var dataTable in reader.ReadAllAsync()) + //{ + // using var bulkCopy = new SqlBulkCopy(connection) + // { + // DestinationTableName = "CleanData", + // BatchSize = 5000, + // BulkCopyTimeout = 300 + // }; + + // bulkCopy.ColumnMappings.Add("Id", "Id"); + // bulkCopy.ColumnMappings.Add("CleanName", "CleanName"); + // bulkCopy.ColumnMappings.Add("ProcessedTime", "ProcessedTime"); + + // await bulkCopy.WriteToServerAsync(dataTable); + //} + } + } +} diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 2f144de..9c569d1 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -37,21 +37,17 @@ namespace JiShe.CollectBus.ScheduledMeterReading { private readonly ILogger _logger; private readonly ICapPublisher _capBus; - private readonly IRepository _fifteenMinuteReadingRecordRepository; - private readonly IRepository _fiveMinuteReadingRecordRepository; - private readonly IRepository _oneMinuteReadingRecordRepository; + private readonly IRepository _meterReadingRecordsRepository; public BasicScheduledMeterReadingService( ILogger logger, ICapPublisher capBus, - IRepository fifteenMinuteReadingRecordRepository, IRepository fiveMinuteReadingRecordRepository, IRepository oneMinuteReadingRecordRepository) + IRepository meterReadingRecordsRepository) { _capBus = capBus; _logger = logger; - _oneMinuteReadingRecordRepository = oneMinuteReadingRecordRepository; - _fiveMinuteReadingRecordRepository = fiveMinuteReadingRecordRepository; - _fifteenMinuteReadingRecordRepository = fifteenMinuteReadingRecordRepository; + _meterReadingRecordsRepository = meterReadingRecordsRepository; } /// @@ -279,14 +275,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), MeterTypeEnum.Ammeter.ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), MeterTypeEnum.Ammeter.ToString()); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); return; } - List meterTaskInfosList = new List(); + List meterTaskInfosList = new List(); //将取出的缓存任务数据发送到Kafka消息队列中 foreach (var focusItem in meterTaskInfos) @@ -307,7 +303,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _oneMinuteReadingRecordRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordsRepository.InsertManyAsync(meterTaskInfosList); } //删除任务数据 @@ -344,14 +340,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fiveMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.Ammeter).ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fiveMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.Ammeter).ToString()); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); return; } - List meterTaskInfosList = new List(); + List meterTaskInfosList = new List(); //将取出的缓存任务数据发送到Kafka消息队列中 foreach (var focusItem in meterTaskInfos) @@ -372,7 +368,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _fiveMinuteReadingRecordRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordsRepository.InsertManyAsync(meterTaskInfosList); } //删除任务数据 @@ -411,14 +407,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fifteenMinutekeyList, timeDensity.ToString(), MeterTypeEnum.Ammeter.ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fifteenMinutekeyList, timeDensity.ToString(), MeterTypeEnum.Ammeter.ToString()); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); return; } - List meterTaskInfosList = new List(); + List meterTaskInfosList = new List(); //将取出的缓存任务数据发送到Kafka消息队列中 foreach (var focusItem in meterTaskInfos) @@ -439,7 +435,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _fifteenMinuteReadingRecordRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordsRepository.InsertManyAsync(meterTaskInfosList); } //删除任务数据 @@ -620,7 +616,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } } - Dictionary keyValuePairs = new Dictionary(); + Dictionary keyValuePairs = new Dictionary(); foreach (var tempItem in tempCodes) { @@ -675,7 +671,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading - var meterReadingRecords = new BasicMeterReadingRecords() + var meterReadingRecords = new MeterReadingRecords() { MeterAddress = ammeter.AmmerterAddress, MeterId = ammeter.ID, @@ -784,14 +780,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); return; } - List meterTaskInfosList = new List(); + List meterTaskInfosList = new List(); //将取出的缓存任务数据发送到Kafka消息队列中 foreach (var focusItem in meterTaskInfos) @@ -812,7 +808,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _oneMinuteReadingRecordRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordsRepository.InsertManyAsync(meterTaskInfosList); } //删除任务数据 @@ -850,14 +846,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fiveMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fiveMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); return; } - List meterTaskInfosList = new List(); + List meterTaskInfosList = new List(); //将取出的缓存任务数据发送到Kafka消息队列中 foreach (var focusItem in meterTaskInfos) @@ -878,7 +874,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _fiveMinuteReadingRecordRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordsRepository.InsertManyAsync(meterTaskInfosList); } //删除任务数据 @@ -915,14 +911,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fifteenMinutekeyList, timeDensity.ToString(), MeterTypeEnum.WaterMeter.ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fifteenMinutekeyList, timeDensity.ToString(), MeterTypeEnum.WaterMeter.ToString()); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); return; } - List meterTaskInfosList = new List(); + List meterTaskInfosList = new List(); //将取出的缓存任务数据发送到Kafka消息队列中 foreach (var focusItem in meterTaskInfos) @@ -943,7 +939,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _fifteenMinuteReadingRecordRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordsRepository.InsertManyAsync(meterTaskInfosList); } //删除任务数据 diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 5a26013..045c9c2 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -25,8 +25,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { public EnergySystemScheduledMeterReadingService(ILogger logger, - ICapPublisher capBus, - IRepository fifteenMinuteReadingRecordRepository, IRepository fiveMinuteReadingRecordRepository, IRepository oneMinuteReadingRecordRepository) :base(logger, capBus, fifteenMinuteReadingRecordRepository, fiveMinuteReadingRecordRepository, oneMinuteReadingRecordRepository) + ICapPublisher capBus, IRepository _meterReadingRecordsRepository) :base(logger, capBus, _meterReadingRecordsRepository) { } diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index 9df8c39..fc05bc7 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -5,8 +5,10 @@ using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using JiShe.CollectBus.Protocol.Contracts.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using TouchSocket.Sockets; @@ -23,6 +25,7 @@ namespace JiShe.CollectBus.Subscribers private readonly IRepository _messageReceivedHeartbeatEventRepository; private readonly IRepository _messageReceivedEventRepository; private readonly IRepository _deviceRepository; + private readonly IRepository _meterReadingRecordsRepository; /// /// Initializes a new instance of the class. @@ -34,12 +37,13 @@ namespace JiShe.CollectBus.Subscribers /// The message received heartbeat event repository. /// The message received event repository. /// The device repository. + /// The device repository. public SubscriberAppService(ILogger logger, ITcpService tcpService, IServiceProvider serviceProvider, IRepository messageReceivedLoginEventRepository, IRepository messageReceivedHeartbeatEventRepository, IRepository messageReceivedEventRepository, - IRepository deviceRepository) + IRepository deviceRepository, IRepository meterReadingRecordsRepository) { _logger = logger; _tcpService = tcpService; @@ -48,6 +52,7 @@ namespace JiShe.CollectBus.Subscribers _messageReceivedHeartbeatEventRepository = messageReceivedHeartbeatEventRepository; _messageReceivedEventRepository = messageReceivedEventRepository; _deviceRepository = deviceRepository; + _meterReadingRecordsRepository = meterReadingRecordsRepository; } [CapSubscribe(ProtocolConst.SubscriberIssuedEventName)] @@ -90,8 +95,9 @@ namespace JiShe.CollectBus.Subscribers } else { - await protocolPlugin.AnalyzeAsync(receivedMessage); - await _messageReceivedEventRepository.InsertAsync(receivedMessage); + //todo 会根据不同的协议进行解析,然后做业务处理 + TB3761FN fN = await protocolPlugin.AnalyzeAsync(receivedMessage); + //await _messageReceivedEventRepository.InsertAsync(receivedMessage); } } diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFifteenMinuteReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFifteenMinuteReadingRecords.cs deleted file mode 100644 index acc7163..0000000 --- a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFifteenMinuteReadingRecords.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JiShe.CollectBus.Common.Enums; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Volo.Abp.Domain.Entities; - -namespace JiShe.CollectBus.IotSystems.MeterReadingRecords -{ - /// - /// 15分钟抄读记录表,包含下发报文和回复报文,以及是否迁移 - /// - public class MeterFifteenMinuteReadingRecords : BasicMeterReadingRecords - { - - } -} diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFiveMinuteReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFiveMinuteReadingRecords.cs deleted file mode 100644 index eeb69a2..0000000 --- a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterFiveMinuteReadingRecords.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JiShe.CollectBus.Common.Enums; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Volo.Abp.Domain.Entities; - -namespace JiShe.CollectBus.IotSystems.MeterReadingRecords -{ - /// - /// 5分钟抄读记录表,包含下发报文和回复报文,以及是否迁移 - /// - public class MeterFiveMinuteReadingRecords : BasicMeterReadingRecords - { - - } -} diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterOneMinuteReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterOneMinuteReadingRecords.cs deleted file mode 100644 index 35b7630..0000000 --- a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterOneMinuteReadingRecords.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JiShe.CollectBus.Common.Enums; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Volo.Abp.Domain.Entities; - -namespace JiShe.CollectBus.IotSystems.MeterReadingRecords -{ - /// - /// 1分钟抄读记录表,包含下发报文和回复报文,以及是否迁移 - /// - public class MeterOneMinuteReadingRecords : BasicMeterReadingRecords - { - - } -} diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/BasicMeterReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs similarity index 89% rename from src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/BasicMeterReadingRecords.cs rename to src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs index 315bbda..48e4cd8 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/BasicMeterReadingRecords.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs @@ -9,9 +9,9 @@ using Volo.Abp.Domain.Entities; namespace JiShe.CollectBus.IotSystems.MeterReadingRecords { /// - /// 抄读记录基类 + /// 抄读数据记录 /// - public class BasicMeterReadingRecords : AggregateRoot + public class MeterReadingRecords : AggregateRoot { /// @@ -55,6 +55,16 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// public MeterTypeEnum MeterType { get; set; } + /// + /// 项目ID + /// + public int ProjectID { get; set; } + + /// + /// 数据库业务ID + /// + public int DatabaseBusiID { get; set; } + /// /// AFN功能码 /// diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index ec1bd69..1c50ce4 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -34,7 +34,7 @@ "CorsOrigins": "http://localhost:4200,http://localhost:3100" }, "ConnectionStrings": { - "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin", + "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=200&minPoolSize=10&waitQueueTimeoutMS=5000", "Kafka": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092", "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs index 5c73029..2366553 100644 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs @@ -24,9 +24,8 @@ public class CollectBusMongoDbContext : AbpMongoDbContext, ICollectBusMongoDbCon public IMongoCollection Devices => Collection(); public IMongoCollection ProtocolInfos => Collection(); - public IMongoCollection FifteenMinuteReadingRecords => Collection(); - public IMongoCollection MeterFiveMinuteReadingRecords => Collection(); - public IMongoCollection MeterOneMinuteReadingRecords => Collection(); + public IMongoCollection MeterReadingRecords => Collection(); + protected override void CreateModel(IMongoModelBuilder modelBuilder) { diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index b69c541..609dcdb 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -55,7 +55,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts //await _protocolInfoCache.Get() } - public abstract Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null); + public abstract Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) where T : TB3761FN; /// /// 登录帧解析 diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs index bd08d60..5ad92c1 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs @@ -14,7 +14,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Interfaces Task AddAsync(); - Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null); + Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) where T : TB3761FN; Task LoginAsync(MessageReceivedLogin messageReceived); diff --git a/src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs index 79a92dd..a28cd2d 100644 --- a/src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs @@ -8,8 +8,8 @@ using JiShe.CollectBus.Protocol.Contracts.Models; using Newtonsoft.Json.Linq; namespace JiShe.CollectBus.Protocol -{ - public class StandardProtocolPlugin: BaseProtocolPlugin +{ + public class StandardProtocolPlugin : BaseProtocolPlugin { /// /// Initializes a new instance of the class. @@ -21,12 +21,15 @@ namespace JiShe.CollectBus.Protocol public sealed override ProtocolInfo Info => new(nameof(StandardProtocolPlugin), "376.1", "TCP", "376.1协议", "DTS1980"); - public override Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) + public override async Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) { var hexStringList = messageReceived.MessageHexString.StringToPairs(); var aTuple = (Tuple)hexStringList.GetAnalyzeValue(CommandChunkEnum.A); var afn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN); var fn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN); + + T analyze = default; + switch ((AFN)afn) { case AFN.确认或否认: @@ -35,15 +38,15 @@ namespace JiShe.CollectBus.Protocol case AFN.设置参数: break; case AFN.查询参数: break; case AFN.请求实时数据: - if (Enum.IsDefined(typeof(ATypeOfDataItems), fn)) //Enum.TryParse(afn.ToString(), out ATypeOfDataItems parseResult) + if (Enum.IsDefined(typeof(ATypeOfDataItems), fn)) { - AnalyzeReadingDataAsync(messageReceived, sendAction); + analyze = (T?)AnalyzeReadingDataAsync(messageReceived, sendAction); } break; case AFN.请求历史数据: if (Enum.IsDefined(typeof(IIdataTypeItems), fn)) { - AnalyzeReadingTdcDataAsync(messageReceived, sendAction); + analyze = (T?)AnalyzeReadingTdcDataAsync(messageReceived, sendAction); } break; case AFN.数据转发: @@ -51,7 +54,7 @@ namespace JiShe.CollectBus.Protocol break; } - throw new NotImplementedException(); + return await Task.FromResult(analyze); } #region 上行命令 -- 2.47.2 From e836522e3a79483ad832fe20957a2ac47214a01e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E7=9B=8A?= Date: Wed, 19 Mar 2025 22:52:53 +0800 Subject: [PATCH 029/139] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8A=84=E8=AF=BB?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E8=A1=A8=E8=87=AA=E5=AE=9A=E4=B9=89=E4=BB=93?= =?UTF-8?q?=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MongoDB/CollectBusMongoDbContext.cs | 28 +++++++++- .../MongoDB/CollectionHelper.cs | 25 +++++++++ .../IMeterReadingRecordRepository.cs | 35 ++++++++++++ .../MeterReadingRecordRepository.cs | 53 +++++++++++++++++++ 4 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 src/JiShe.CollectBus.MongoDB/MongoDB/CollectionHelper.cs create mode 100644 src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs create mode 100644 src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs index 2366553..c334325 100644 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs @@ -3,7 +3,10 @@ using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Protocols; +using MongoDB.Bson; using MongoDB.Driver; +using System; +using System.Collections.Generic; using Volo.Abp.Data; using Volo.Abp.MongoDB; using Volo.Abp.MultiTenancy; @@ -24,13 +27,34 @@ public class CollectBusMongoDbContext : AbpMongoDbContext, ICollectBusMongoDbCon public IMongoCollection Devices => Collection(); public IMongoCollection ProtocolInfos => Collection(); - public IMongoCollection MeterReadingRecords => Collection(); - + /// + /// 抄表记录,默认按天分表 + /// + public IMongoCollection MeterReadingRecordInfo => Database.GetCollection(DateTime.Now.GetCollectionName()); + protected override void CreateModel(IMongoModelBuilder modelBuilder) { base.CreateModel(modelBuilder); + modelBuilder.Entity(builder => + { + // 创建索引 + builder.ConfigureIndexes(index => + { + List> createIndexModels = new List>(); + createIndexModels.Add(new CreateIndexModel( + Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), + new CreateIndexOptions + { + Unique = true + } + )); + index.CreateMany(createIndexModels); + }); + + }); + modelBuilder.ConfigureCollectBus(); } } diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectionHelper.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectionHelper.cs new file mode 100644 index 0000000..8bc80ce --- /dev/null +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectionHelper.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.MongoDB +{ + /// + /// MongoDB集合帮助类 + /// + public static class CollectionHelper + { + /// + /// 获取集合名称 + /// + /// + /// + /// + public static string GetCollectionName(this DateTime time) + { + return $"{typeof(T).Name}{time:yyyyMMddHHmm}"; + } + } +} diff --git a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs new file mode 100644 index 0000000..7ab95e9 --- /dev/null +++ b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs @@ -0,0 +1,35 @@ +using JiShe.CollectBus.IotSystems.MeterReadingRecords; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace JiShe.CollectBus.Repository.MeterReadingRecord +{ + /// + /// 抄读仓储接口 + /// + public interface IMeterReadingRecordRepository: IRepository + { + /// + /// 批量插入 + /// + /// + /// + /// + /// + Task InsertManyAsync(List entities, DateTime dayTime, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 单个插入 + /// + /// + /// + /// + /// + Task InsertOneAsync(MeterReadingRecords entity, DateTime dayTime, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs new file mode 100644 index 0000000..87337ea --- /dev/null +++ b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs @@ -0,0 +1,53 @@ +using JiShe.CollectBus.IotSystems.MeterReadingRecords; +using JiShe.CollectBus.MongoDB; +using MongoDB.Bson; +using MongoDB.Driver; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; +using static System.Net.Mime.MediaTypeNames; + +namespace JiShe.CollectBus.Repository.MeterReadingRecord +{ + /// + /// 抄读记录仓储 + /// + public class MeterReadingRecordRepository : MongoDbRepository, IMeterReadingRecordRepository + { + public MeterReadingRecordRepository(IMongoDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + /// + /// 批量插入 + /// + /// + /// + /// + /// + public async Task InsertManyAsync(List entities, DateTime dayTime, CancellationToken cancellationToken = default(CancellationToken)) + { + var collection = await GetCollectionAsync(cancellationToken); + await collection.InsertManyAsync(entities); + } + + /// + /// 单条插入 + /// + /// + /// + /// + /// + public async Task InsertOneAsync(MeterReadingRecords entity,DateTime dayTime, CancellationToken cancellationToken = default(CancellationToken)) + { + var collection = await GetCollectionAsync(cancellationToken); + await collection.InsertOneAsync(entity); + } + } +} -- 2.47.2 From e7f94ceae4e9ffca4e081f0b22deb96da9278f89 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 20 Mar 2025 16:40:27 +0800 Subject: [PATCH 030/139] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E5=88=86=E5=BA=93?= =?UTF-8?q?=E5=88=86=E8=A1=A8=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...he.CollectBus.Application.Contracts.csproj | 1 + .../IWorkerSubscriberAppService.cs | 5 + .../CollectBusApplicationModule.cs | 2 + .../JiShe.CollectBus.Application.csproj | 3 - .../BasicScheduledMeterReadingService.cs | 43 +++++--- ...nergySystemScheduledMeterReadingService.cs | 4 +- .../Subscribers/WorkerSubscriberAppService.cs | 22 ++++ .../BuildSendDatas/Build3761SendData.cs | 1 + .../MeterReadingRecords.cs | 11 ++ .../CollectBusHostModule.cs | 1 - .../JiShe.CollectBus.Host.csproj | 1 - src/JiShe.CollectBus.Host/appsettings.json | 4 +- .../MongoDB/CollectBusMongoDbContext.cs | 45 +++++--- .../MongoDB/CollectBusMongoDbModule.cs | 19 +++- .../MongoDB/CollectionHelper.cs | 25 ----- .../IMeterReadingRecordRepository.cs | 21 ++-- .../MeterReadingRecordRepository.cs | 102 ++++++++++++++++-- .../ShardingStrategy/DayShardingStrategy.cs | 59 ++++++++++ .../ShardingStrategy/IShardingStrategy.cs | 36 +++++++ 19 files changed, 328 insertions(+), 77 deletions(-) delete mode 100644 src/JiShe.CollectBus.MongoDB/MongoDB/CollectionHelper.cs create mode 100644 src/JiShe.CollectBus.MongoDB/ShardingStrategy/DayShardingStrategy.cs create mode 100644 src/JiShe.CollectBus.MongoDB/ShardingStrategy/IShardingStrategy.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj index de972d1..b937879 100644 --- a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj +++ b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj @@ -26,6 +26,7 @@ + diff --git a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs index a7c4032..6d13b13 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs @@ -1,5 +1,7 @@ using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MeterReadingRecords; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Services; @@ -12,6 +14,9 @@ namespace JiShe.CollectBus.Subscribers { #region 电表消息采集 + + Task> AmmeterScheduledMeterOneMinuteReadingIssuedEventQuery(); + /// /// 1分钟采集电表数据下行消息消费订阅 /// diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 4efe690..0f4a3d0 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -13,6 +13,7 @@ using Volo.Abp.AspNetCore.Mvc.AntiForgery; using JiShe.CollectBus.FreeRedisProvider; using JiShe.CollectBus.Workers; using Volo.Abp.BackgroundWorkers.Hangfire; +using JiShe.CollectBus.MongoDB; namespace JiShe.CollectBus; @@ -22,6 +23,7 @@ namespace JiShe.CollectBus; typeof(AbpDddApplicationModule), typeof(AbpAutoMapperModule), typeof(AbpBackgroundWorkersHangfireModule), + typeof(CollectBusMongoDbModule), typeof(CollectBusFreeRedisModule), typeof(CollectBusFreeSqlModule) )] diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index 0102b4f..ad1ca1d 100644 --- a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -25,10 +25,7 @@ - - - diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 9c569d1..9358b3e 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -21,6 +21,8 @@ using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Repository; +using JiShe.CollectBus.Repository.MeterReadingRecord; using JiShe.CollectBus.Workers; using MassTransit; using MassTransit.Internals.GraphValidation; @@ -37,13 +39,13 @@ namespace JiShe.CollectBus.ScheduledMeterReading { private readonly ILogger _logger; private readonly ICapPublisher _capBus; - private readonly IRepository _meterReadingRecordsRepository; + private readonly IMeterReadingRecordRepository _meterReadingRecordsRepository; public BasicScheduledMeterReadingService( ILogger logger, ICapPublisher capBus, - IRepository meterReadingRecordsRepository) + IMeterReadingRecordRepository meterReadingRecordsRepository) { _capBus = capBus; _logger = logger; @@ -100,7 +102,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading //item 为 CacheTasksToBeIssuedKey 对应的缓存待下发的指令生产任务数据Redis Key tempArryay[0]=>CollectBus,tempArryay[1]=>SystemTypeConst,tempArryay[2]=>TaskInfo,tempArryay[3]=>表计类别,tempArryay[4]=>采集频率 var tempArryay = item.Split(":"); string meteryType = tempArryay[3];//表计类别 - string timeDensity = tempArryay[4];//采集频率 + int timeDensity = Convert.ToInt32(tempArryay[4]);//采集频率 //获取缓存中的电表信息 var redisKeyList = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, meteryType, timeDensity)}*"; @@ -114,7 +116,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (meteryType == MeterTypeEnum.Ammeter.ToString()) { // 解析结果(结果为嵌套数组) - var meterInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity, meteryType); + var meterInfos = await GetMeterRedisCacheData(oneMinutekeyList, $"{timeDensity}", meteryType); if (meterInfos == null || meterInfos.Count <= 0) { _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-104"); @@ -296,7 +298,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + _ = _capBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); meterTaskInfosList.Add(ammerterItem.Value); } @@ -361,7 +363,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + _= _capBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg); meterTaskInfosList.Add(ammerterItem.Value); } @@ -398,6 +400,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading //获取缓存中的电表信息 int timeDensity = 15; + var currentDateTime = DateTime.Now; + var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; var fifteenMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (fifteenMinutekeyList == null || fifteenMinutekeyList.Length <= 0) @@ -428,7 +432,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + _ = _capBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500) ,ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); meterTaskInfosList.Add(ammerterItem.Value); } @@ -439,7 +443,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //删除任务数据 - await FreeRedisProvider.Instance.DelAsync(fifteenMinutekeyList); + //await FreeRedisProvider.Instance.DelAsync(fifteenMinutekeyList); //缓存下一个时间的任务 TasksToBeIssueModel nextTask = new TasksToBeIssueModel() @@ -462,9 +466,19 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// 采集频率1分钟、5分钟、15分钟 /// 集中器数据分组 /// - private async Task AmmerterScheduledMeterReadingIssued(string timeDensity, Dictionary> focusGroup) + private async Task AmmerterScheduledMeterReadingIssued(int timeDensity, Dictionary> focusGroup) { - if (string.IsNullOrWhiteSpace(timeDensity) || focusGroup == null || focusGroup.Count <= 0) + if (timeDensity <= 0) + { + timeDensity = 1; + } + + if (timeDensity > 15) + { + timeDensity = 15; + } + + if ( focusGroup == null || focusGroup.Count <= 0) { _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 电表数据采集指令生成失败,参数异常,-101"); return; @@ -522,12 +536,13 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// 采集频率 /// 集中器号hash分组的集中器集合数据 /// - private async Task AmmerterCreatePublishTask(string timeDensity + private async Task AmmerterCreatePublishTask(int timeDensity , Dictionary> focusGroup) { - var HandlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; + var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; var currentTime = DateTime.Now; + var pendingCopyReadTime = currentTime.AddMinutes(timeDensity); foreach (var focusInfo in focusGroup) { //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 @@ -645,7 +660,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { string methonCode = $"AFN{aFNStr}_Fn_Send"; //特殊表暂不处理 - if (HandlerPacketBuilder != null && HandlerPacketBuilder.TryGetValue(methonCode + if (handlerPacketBuilder != null && handlerPacketBuilder.TryGetValue(methonCode , out var handler)) { dataInfos = handler(new TelemetryPacketRequest() @@ -673,6 +688,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading var meterReadingRecords = new MeterReadingRecords() { + PendingCopyReadTime = pendingCopyReadTime, + CreationTime = currentTime, MeterAddress = ammeter.AmmerterAddress, MeterId = ammeter.ID, MeterType = MeterTypeEnum.Ammeter, diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 045c9c2..d15e796 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -9,6 +9,8 @@ using JiShe.CollectBus.GatherItem; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; +using JiShe.CollectBus.Repository; +using JiShe.CollectBus.Repository.MeterReadingRecord; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; using Volo.Abp.Domain.Repositories; @@ -25,7 +27,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { public EnergySystemScheduledMeterReadingService(ILogger logger, - ICapPublisher capBus, IRepository _meterReadingRecordsRepository) :base(logger, capBus, _meterReadingRecordsRepository) + ICapPublisher capBus, IMeterReadingRecordRepository _meterReadingRecordsRepository) :base(logger, capBus, _meterReadingRecordsRepository) { } diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index 62748be..a0ea73a 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using DeviceDetectorNET.Parser.Device; using DotNetCore.CAP; @@ -6,8 +7,10 @@ using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using JiShe.CollectBus.Repository.MeterReadingRecord; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -27,6 +30,7 @@ namespace JiShe.CollectBus.Subscribers private readonly ITcpService _tcpService; private readonly IServiceProvider _serviceProvider; private readonly IRepository _deviceRepository; + private readonly IMeterReadingRecordRepository _meterReadingRecordsRepository; /// @@ -39,16 +43,34 @@ namespace JiShe.CollectBus.Subscribers public WorkerSubscriberAppService(ILogger logger, ITcpService tcpService, IRepository deviceRepository, + IMeterReadingRecordRepository meterReadingRecordsRepository, IServiceProvider serviceProvider) { _logger = logger; _tcpService = tcpService; _serviceProvider = serviceProvider; _deviceRepository = deviceRepository; + _meterReadingRecordsRepository = meterReadingRecordsRepository; } #region 电表消息采集 + + /// + /// 一分钟定时抄读任务消息消费订阅 + /// + /// + [HttpGet] + [Route("ammeter/oneminute/issued-eventQuery")] + public async Task> AmmeterScheduledMeterOneMinuteReadingIssuedEventQuery() + { + var currentDateTime = DateTime.Now; + + var list = await _meterReadingRecordsRepository.ParallelQueryAsync(currentDateTime.AddMinutes(-20), currentDateTime.AddMinutes(10)); + + return list; + } + /// /// 一分钟定时抄读任务消息消费订阅 /// diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs b/src/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs index d7a9ece..f4dd11d 100644 --- a/src/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs +++ b/src/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs @@ -36,6 +36,7 @@ namespace JiShe.CollectBus.Common.BuildSendDatas MSA.Add(i); } } + /// /// Gets the msa. /// diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs index 48e4cd8..9f854df 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Entities.Auditing; namespace JiShe.CollectBus.IotSystems.MeterReadingRecords { @@ -19,6 +20,11 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// public bool ManualOrNot { get; set; } + /// + /// 待抄读时间 + /// + public DateTime PendingCopyReadTime { get; set; } + /// /// 下发消息内容 /// @@ -111,6 +117,11 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// public RecordsDataMigrationStatusEnum MigrationStatus { get; set; } + /// + /// 数据结果,最终的解析报文结果值 + /// + public string DataResult { get; set; } + /// /// 数据迁移时间 /// diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs index 933641b..1283dfc 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs @@ -25,7 +25,6 @@ namespace JiShe.CollectBus.Host typeof(AbpAspNetCoreSerilogModule), typeof(AbpSwashbuckleModule), typeof(CollectBusApplicationModule), - typeof(CollectBusMongoDbModule), typeof(AbpCachingStackExchangeRedisModule), typeof(AbpBackgroundWorkersHangfireModule) )] diff --git a/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj b/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj index 5a549e5..01507cc 100644 --- a/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj +++ b/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj @@ -55,7 +55,6 @@ - diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 1c50ce4..50c459b 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -34,13 +34,13 @@ "CorsOrigins": "http://localhost:4200,http://localhost:3100" }, "ConnectionStrings": { - "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=200&minPoolSize=10&waitQueueTimeoutMS=5000", + "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", "Kafka": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092", "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" }, "Redis": { - "Configuration": "192.168.111.248:6379,password=123456abcD,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + "Configuration": "118.190.144.92:6379,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", "DefaultDB": "14", "HangfireDB": "15" }, diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs index c334325..d17a0d2 100644 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs @@ -1,11 +1,13 @@ using JiShe.CollectBus.IotSystems.Devices; -using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Protocols; +using JiShe.CollectBus.ShardingStrategy; using MongoDB.Bson; +using MongoDB.Bson.Serialization; using MongoDB.Driver; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using Volo.Abp.Data; using Volo.Abp.MongoDB; @@ -26,35 +28,48 @@ public class CollectBusMongoDbContext : AbpMongoDbContext, ICollectBusMongoDbCon public IMongoCollection MessageReceivedHeartbeats => Collection(); public IMongoCollection Devices => Collection(); public IMongoCollection ProtocolInfos => Collection(); - - /// - /// 抄表记录,默认按天分表 - /// - public IMongoCollection MeterReadingRecordInfo => Database.GetCollection(DateTime.Now.GetCollectionName()); - - + protected override void CreateModel(IMongoModelBuilder modelBuilder) { - base.CreateModel(modelBuilder); + base.CreateModel(modelBuilder); + modelBuilder.ConfigureCollectBus(); modelBuilder.Entity(builder => { // 创建索引 builder.ConfigureIndexes(index => { - List> createIndexModels = new List>(); - createIndexModels.Add(new CreateIndexModel( + //List> createIndexModels = new List>(); + //createIndexModels.Add(new CreateIndexModel( + // Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), + // new CreateIndexOptions + // { + // Unique = true + // } + // )); + + + //var indexKeys = Builders.IndexKeys + //.Ascending("CreationTime") + //.Ascending("OrderNumber"); + + //var indexOptions = new CreateIndexOptions + //{ + // Background = true, + // Name = "IX_CreationTime_OrderNumber" + //}; + //index.CreateOne( + //new CreateIndexModel(indexKeys, indexOptions)); + + index.CreateOne(new CreateIndexModel( Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), new CreateIndexOptions { Unique = true } )); - index.CreateMany(createIndexModels); }); }); - - modelBuilder.ConfigureCollectBus(); - } + } } diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs index 37f9377..d3d9559 100644 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs @@ -1,6 +1,15 @@ -using Microsoft.Extensions.DependencyInjection; +using JiShe.CollectBus.IotSystems.MeterReadingRecords; +using JiShe.CollectBus.Repository; +using JiShe.CollectBus.Repository.MeterReadingRecord; +using JiShe.CollectBus.ShardingStrategy; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using System; +using Volo.Abp; using Volo.Abp.AuditLogging.MongoDB; using Volo.Abp.BackgroundJobs.MongoDB; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.Modularity; using Volo.Abp.MongoDB; using Volo.Abp.Uow; @@ -20,6 +29,14 @@ public class CollectBusMongoDbModule : AbpModule context.Services.AddMongoDbContext(options => { options.AddDefaultRepositories(); + + // 注册分表策略 + context.Services.AddTransient( + typeof(IShardingStrategy<>), + typeof(DayShardingStrategy<>)); + + // 分表策略仓储 替换默认仓储 + options.AddRepository(); }); context.Services.AddAlwaysDisableUnitOfWorkTransaction(); diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectionHelper.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectionHelper.cs deleted file mode 100644 index 8bc80ce..0000000 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectionHelper.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.MongoDB -{ - /// - /// MongoDB集合帮助类 - /// - public static class CollectionHelper - { - /// - /// 获取集合名称 - /// - /// - /// - /// - public static string GetCollectionName(this DateTime time) - { - return $"{typeof(T).Name}{time:yyyyMMddHHmm}"; - } - } -} diff --git a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs index 7ab95e9..788dbb3 100644 --- a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs +++ b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs @@ -12,24 +12,31 @@ namespace JiShe.CollectBus.Repository.MeterReadingRecord /// /// 抄读仓储接口 /// - public interface IMeterReadingRecordRepository: IRepository + public interface IMeterReadingRecordRepository : IRepository { /// /// 批量插入 /// /// - /// - /// + /// /// - Task InsertManyAsync(List entities, DateTime dayTime, CancellationToken cancellationToken = default(CancellationToken)); + Task InsertManyAsync(List entities, + DateTime? dateTime); /// /// 单个插入 /// /// - /// - /// + /// /// - Task InsertOneAsync(MeterReadingRecords entity, DateTime dayTime, CancellationToken cancellationToken = default(CancellationToken)); + Task InsertAsync(MeterReadingRecords entity, DateTime? dateTime); + + /// + /// 多集合数据查询 + /// + /// + /// + /// + Task> ParallelQueryAsync(DateTime startTime, DateTime endTime); } } diff --git a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs index 87337ea..14e67d7 100644 --- a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs +++ b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs @@ -1,8 +1,10 @@ using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.MongoDB; +using JiShe.CollectBus.ShardingStrategy; using MongoDB.Bson; using MongoDB.Driver; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; @@ -19,35 +21,119 @@ namespace JiShe.CollectBus.Repository.MeterReadingRecord /// public class MeterReadingRecordRepository : MongoDbRepository, IMeterReadingRecordRepository { - public MeterReadingRecordRepository(IMongoDbContextProvider dbContextProvider) - : base(dbContextProvider) + + private readonly IShardingStrategy _shardingStrategy; + private readonly IMongoDbContextProvider _dbContextProvider; + + public MeterReadingRecordRepository( + IMongoDbContextProvider dbContextProvider, + IShardingStrategy shardingStrategy + ) + : base(dbContextProvider) { + _dbContextProvider = dbContextProvider; + _shardingStrategy = shardingStrategy; } /// /// 批量插入 /// /// - /// /// /// - public async Task InsertManyAsync(List entities, DateTime dayTime, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> InsertManyAsync(IEnumerable entities, bool autoSave = false, CancellationToken cancellationToken = default(CancellationToken)) { - var collection = await GetCollectionAsync(cancellationToken); + var collection = await GetShardedCollection(DateTime.Now); + await collection.InsertManyAsync(entities); + + return entities; + } + + /// + /// 批量插入 + /// + /// + /// + /// + public async Task InsertManyAsync(List entities, DateTime? dateTime) + { + var collection = await GetShardedCollection(dateTime); await collection.InsertManyAsync(entities); } + /// /// 单条插入 /// /// - /// /// /// - public async Task InsertOneAsync(MeterReadingRecords entity,DateTime dayTime, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task InsertAsync(MeterReadingRecords entity, bool autoSave = false, CancellationToken cancellationToken = default(CancellationToken)) { - var collection = await GetCollectionAsync(cancellationToken); + var collection = await GetShardedCollection(DateTime.Now); await collection.InsertOneAsync(entity); + return entity; + } + + + /// + /// 单条插入 + /// + /// + /// + /// + public async Task InsertAsync(MeterReadingRecords entity, DateTime? dateTime) + { + var collection = await GetShardedCollection(dateTime); + await collection.InsertOneAsync(entity); + return entity; + } + + /// + /// 多集合数据查询 + /// + /// + /// + /// + public async Task> ParallelQueryAsync(DateTime startTime, DateTime endTime) + { + var collectionNames = _shardingStrategy.GetQueryCollectionNames(startTime, endTime); + var database = await GetDatabaseAsync(); + + + var tasks = collectionNames.Select(async name => + { + var collection = database.GetCollection(name); + var filter = Builders.Filter.And( + Builders.Filter.Gte(x => x.CreationTime, startTime), + Builders.Filter.Lte(x => x.CreationTime, endTime) + ); + return await collection.Find(filter).ToListAsync(); + }); + + var results = await Task.WhenAll(tasks); + return results.SelectMany(r => r).ToList(); + } + + /// + /// 获得分片集合 + /// + /// + private async Task> GetShardedCollection(DateTime? dateTime) + { + var database = await GetDatabaseAsync(); + string collectionName = string.Empty; + + if (dateTime != null) + { + collectionName = _shardingStrategy.GetCollectionName(dateTime.Value); + } + else + { + collectionName = _shardingStrategy.GetCurrentCollectionName(); + } + + return database.GetCollection(collectionName); } } } diff --git a/src/JiShe.CollectBus.MongoDB/ShardingStrategy/DayShardingStrategy.cs b/src/JiShe.CollectBus.MongoDB/ShardingStrategy/DayShardingStrategy.cs new file mode 100644 index 0000000..75157e5 --- /dev/null +++ b/src/JiShe.CollectBus.MongoDB/ShardingStrategy/DayShardingStrategy.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace JiShe.CollectBus.ShardingStrategy +{ + /// + /// 按天分表策略 + /// + /// + public class DayShardingStrategy : IShardingStrategy + { + /// + /// 获取指定时间对应的集合名 + /// + /// + /// + public string GetCollectionName(DateTime dateTime) + { + var baseName = typeof(TEntity).Name; + return $"{baseName}_{dateTime:yyyyMMddHHmm}"; + } + + /// + /// 获取当前时间对应的集合名 + /// + /// + public string GetCurrentCollectionName() + { + var baseName = typeof(TEntity).Name; + return $"{baseName}_{DateTime.Now:yyyyMMddHHmm}"; + } + + /// + /// 用于查询时确定目标集合 + /// + /// + /// + /// + public IEnumerable GetQueryCollectionNames(DateTime? startTime, DateTime? endTime) + { + var months = new List(); + var current = startTime ?? DateTime.MinValue; + var end = endTime ?? DateTime.MaxValue; + var baseName = typeof(TEntity).Name; + + while (current <= end) + { + months.Add($"{baseName}_{current:yyyyMMddHHmm}"); + current = current.AddMonths(1); + } + + return months.Distinct(); + } + } +} diff --git a/src/JiShe.CollectBus.MongoDB/ShardingStrategy/IShardingStrategy.cs b/src/JiShe.CollectBus.MongoDB/ShardingStrategy/IShardingStrategy.cs new file mode 100644 index 0000000..151d5df --- /dev/null +++ b/src/JiShe.CollectBus.MongoDB/ShardingStrategy/IShardingStrategy.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.ShardingStrategy +{ + /// + /// 数据存储分片策略 + /// + /// + public interface IShardingStrategy + { + /// + /// 获取指定时间对应的集合名 + /// + /// + string GetCollectionName(DateTime dateTime); + + /// + /// 获取当前时间对应的集合名 + /// + /// + string GetCurrentCollectionName(); + + /// + /// 用于查询时确定目标集合 + /// + /// + /// + /// + IEnumerable GetQueryCollectionNames(DateTime? startTime = null, + DateTime? endTime = null); + } +} -- 2.47.2 From c88ce18f3aff34072c54dfcd13c3758a0e7b874b Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Fri, 21 Mar 2025 11:48:31 +0800 Subject: [PATCH 031/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...he.CollectBus.Application.Contracts.csproj | 1 - .../CollectBusApplicationModule.cs | 7 +- .../JiShe.CollectBus.Application.csproj | 1 + .../BasicScheduledMeterReadingService.cs | 6 +- ...nergySystemScheduledMeterReadingService.cs | 7 +- .../Subscribers/WorkerSubscriberAppService.cs | 34 ++++++---- .../Ammeters/AmmeterInfo.cs | 5 ++ .../MeterReadingRecords.cs | 6 +- .../CollectBusHostModule.cs | 1 + .../JiShe.CollectBus.Host.csproj | 3 +- .../MongoDB/CollectBusMongoDbContext.cs | 64 +++++++++---------- .../MongoDB/CollectBusMongoDbModule.cs | 4 +- .../IMeterReadingRecordRepository.cs | 18 ++++++ .../MeterReadingRecordRepository.cs | 27 +++++++- 14 files changed, 130 insertions(+), 54 deletions(-) diff --git a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj index b937879..de972d1 100644 --- a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj +++ b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj @@ -26,7 +26,6 @@ - diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 0f4a3d0..86b36e7 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -14,6 +14,7 @@ using JiShe.CollectBus.FreeRedisProvider; using JiShe.CollectBus.Workers; using Volo.Abp.BackgroundWorkers.Hangfire; using JiShe.CollectBus.MongoDB; +using JiShe.CollectBus.ScheduledMeterReading; namespace JiShe.CollectBus; @@ -23,7 +24,6 @@ namespace JiShe.CollectBus; typeof(AbpDddApplicationModule), typeof(AbpAutoMapperModule), typeof(AbpBackgroundWorkersHangfireModule), - typeof(CollectBusMongoDbModule), typeof(CollectBusFreeRedisModule), typeof(CollectBusFreeSqlModule) )] @@ -54,6 +54,11 @@ public class CollectBusApplicationModule : AbpModule { context.AddBackgroundWorkerAsync(type); } + + var dbContext = context.ServiceProvider.GetRequiredService(); + + //默认初始化表计信息 + dbContext.InitAmmeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); } } diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index ad1ca1d..94c4faf 100644 --- a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -26,6 +26,7 @@ + diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 9358b3e..bb3c743 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -189,10 +189,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}{item.Key}"; #if DEBUG - //每次缓存时,删除缓存,避免缓存数据错误 + //每次缓存时,删除缓存,避免缓存数据有不准确的问题 await FreeRedisProvider.Instance.DelAsync(redisCacheKey); #else - //每次缓存时,删除缓存,避免缓存数据错误 + //每次缓存时,删除缓存,避免缓存数据有不准确的问题 await FreeRedisProvider.Instance.DelAsync(redisCacheKey); #endif @@ -688,6 +688,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading var meterReadingRecords = new MeterReadingRecords() { + ProjectID = ammeter.ProjectID, + DatabaseBusiID = ammeter.DatabaseBusiID, PendingCopyReadTime = pendingCopyReadTime, CreationTime = currentTime, MeterAddress = ammeter.AmmerterAddress, diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index d15e796..24507fe 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -6,6 +6,7 @@ using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.GatherItem; +using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; @@ -62,7 +63,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading //[Route($"ammeter/list")] public override async Task> GetAmmeterInfoList(string gatherCode = "V4-Gather-8890") { - string sql = $@"SELECT C.ID,C.Name,C.FocusID,C.SingleRate,C.MeteringCode,C.Code AS BrandType,C.Baudrate,C.Password,C.MeteringPort,C.[Address] AS AmmerterAddress,C.TypeName,C.Protocol,C.TripState,C.[State],B.[Address],B.AreaCode,B.AutomaticReport,D.DataTypes,B.TimeDensity,A.GatherCode,C.Special,C.[ProjectID],B.AbnormalState,B.LastTime,CONCAT(B.AreaCode, B.[Address]) AS FocusAddress + string sql = $@"SELECT C.ID,C.Name,C.FocusID,C.SingleRate,C.MeteringCode,C.Code AS BrandType,C.Baudrate,C.Password,C.MeteringPort,C.[Address] AS AmmerterAddress,C.TypeName,C.Protocol,C.TripState,C.[State],B.[Address],B.AreaCode,B.AutomaticReport,D.DataTypes,B.TimeDensity,A.GatherCode,C.Special,C.[ProjectID],B.AbnormalState,B.LastTime,CONCAT(B.AreaCode, B.[Address]) AS FocusAddress,(select top 1 DatabaseBusiID from TB_Project where ID = B.ProjectID) AS DatabaseBusiID FROM TB_GatherInfo(NOLOCK) AS A INNER JOIN TB_FocusInfo(NOLOCK) AS B ON A.ID = B.GatherInfoID AND B.RemoveState >= 0 AND B.State>=0 INNER JOIN TB_AmmeterInfo(NOLOCK) AS C ON B.ID = C.FocusID AND C.State>= 0 AND C.State<100 @@ -114,7 +115,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading C.GatherCode, A.[ProjectID], B.AbnormalState, - B.LastTime + B.LastTime, + CONCAT(B.AreaCode, B.[Address]) AS FocusAddress, + (select top 1 DatabaseBusiID from TB_Project where ID = b.ProjectID) AS DatabaseBusiID FROM [dbo].[TB_WatermeterInfo](NOLOCK) AS A INNER JOIN [dbo].[TB_FocusInfo](NOLOCK) AS B ON A.FocusID=B.ID AND B.RemoveState >= 0 AND B.State>=0 INNER JOIN [dbo].[TB_GatherInfo](NOLOCK) AS C ON B.GatherInfoID=C.ID diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index a0ea73a..cde387c 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -66,9 +66,11 @@ namespace JiShe.CollectBus.Subscribers { var currentDateTime = DateTime.Now; - var list = await _meterReadingRecordsRepository.ParallelQueryAsync(currentDateTime.AddMinutes(-20), currentDateTime.AddMinutes(10)); + var list = await _meterReadingRecordsRepository.ParallelQueryAsync(currentDateTime.AddMinutes(-20), currentDateTime.AddMinutes(10)); return list; + + //return null; } /// @@ -136,19 +138,29 @@ namespace JiShe.CollectBus.Subscribers public async Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("15分钟采集电表数据下行消息消费队列开始处理"); - var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); - if (protocolPlugin == null) + try { - _logger.LogError("【15分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); - } - else - { - var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.FocusAddress); - if (device != null) + var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); + if (protocolPlugin == null) { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); - + _logger.LogError("【15分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); } + else + { + // var dd = await _meterReadingRecordsRepository.FirstOrDefaultAsync(d=>d.ManualOrNot== true); + + var device = await _deviceRepository.FirstOrDefaultAsync(a => a.Number == receivedMessage.FocusAddress); + if (device != null) + { + await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); + + } + } + } + catch (Exception ex) + { + + throw ex; } } #endregion diff --git a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs index eb191ca..fa21071 100644 --- a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs +++ b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs @@ -136,6 +136,11 @@ namespace JiShe.CollectBus.Ammeters /// public int ProjectID { get; set; } + /// + /// 数据库业务ID + /// + public int DatabaseBusiID { get; set; } + /// /// 是否异常集中器 0:正常,1异常 /// diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs index 9f854df..a75c032 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs @@ -55,7 +55,6 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// public string MeterAddress { get; set; } - /// /// 表类型 /// @@ -122,6 +121,11 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// public string DataResult { get; set; } + /// + /// 数据时间,如冻结时间、事件发生事件等 + /// + public DateTime? DataGenerationTimestamp { get; set; } + /// /// 数据迁移时间 /// diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs index 1283dfc..933641b 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs @@ -25,6 +25,7 @@ namespace JiShe.CollectBus.Host typeof(AbpAspNetCoreSerilogModule), typeof(AbpSwashbuckleModule), typeof(CollectBusApplicationModule), + typeof(CollectBusMongoDbModule), typeof(AbpCachingStackExchangeRedisModule), typeof(AbpBackgroundWorkersHangfireModule) )] diff --git a/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj b/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj index 01507cc..0b178ee 100644 --- a/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj +++ b/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -55,6 +55,7 @@ + diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs index d17a0d2..9199567 100644 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs @@ -34,42 +34,42 @@ public class CollectBusMongoDbContext : AbpMongoDbContext, ICollectBusMongoDbCon base.CreateModel(modelBuilder); modelBuilder.ConfigureCollectBus(); - modelBuilder.Entity(builder => - { - // 创建索引 - builder.ConfigureIndexes(index => - { - //List> createIndexModels = new List>(); - //createIndexModels.Add(new CreateIndexModel( - // Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), - // new CreateIndexOptions - // { - // Unique = true - // } - // )); + //modelBuilder.Entity(builder => + //{ + // // 创建索引 + // builder.ConfigureIndexes(index => + // { + // //List> createIndexModels = new List>(); + // //createIndexModels.Add(new CreateIndexModel( + // // Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), + // // new CreateIndexOptions + // // { + // // Unique = true + // // } + // // )); - //var indexKeys = Builders.IndexKeys - //.Ascending("CreationTime") - //.Ascending("OrderNumber"); + // //var indexKeys = Builders.IndexKeys + // //.Ascending("CreationTime") + // //.Ascending("OrderNumber"); - //var indexOptions = new CreateIndexOptions - //{ - // Background = true, - // Name = "IX_CreationTime_OrderNumber" - //}; - //index.CreateOne( - //new CreateIndexModel(indexKeys, indexOptions)); + // //var indexOptions = new CreateIndexOptions + // //{ + // // Background = true, + // // Name = "IX_CreationTime_OrderNumber" + // //}; + // //index.CreateOne( + // //new CreateIndexModel(indexKeys, indexOptions)); - index.CreateOne(new CreateIndexModel( - Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), - new CreateIndexOptions - { - Unique = true - } - )); - }); + // index.CreateOne(new CreateIndexModel( + // Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), + // new CreateIndexOptions + // { + // Unique = true + // } + // )); + // }); - }); + //}); } } diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs index d3d9559..9a5a0f0 100644 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs @@ -35,8 +35,8 @@ public class CollectBusMongoDbModule : AbpModule typeof(IShardingStrategy<>), typeof(DayShardingStrategy<>)); - // 分表策略仓储 替换默认仓储 - options.AddRepository(); + //// 分表策略仓储 替换默认仓储 + //options.AddRepository(); }); context.Services.AddAlwaysDisableUnitOfWorkTransaction(); diff --git a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs index 788dbb3..20b7809 100644 --- a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs +++ b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs @@ -1,4 +1,5 @@ using JiShe.CollectBus.IotSystems.MeterReadingRecords; +using MongoDB.Driver; using System; using System.Collections.Generic; using System.Linq; @@ -31,6 +32,23 @@ namespace JiShe.CollectBus.Repository.MeterReadingRecord /// Task InsertAsync(MeterReadingRecords entity, DateTime? dateTime); + /// + /// 单条更新 + /// + /// 过滤条件,示例:Builders.Filter.Eq(x => x.Id, filter.Id) + /// 包含待更新的内容,示例:Builders.Update.Set(x => x.Processed, true).Set(x => x.ProcessedTime, Clock.Now) + /// 数据实体,用于获取对应的分片库 + /// + Task UpdateOneAsync(FilterDefinition filter, UpdateDefinition update, MeterReadingRecords entity); + + /// + /// 单个获取 + /// + /// + /// + /// + Task FirOrDefaultAsync(MeterReadingRecords entity, DateTime? dateTime); + /// /// 多集合数据查询 /// diff --git a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs index 14e67d7..5a73b8d 100644 --- a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs +++ b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs @@ -10,8 +10,11 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.MongoDB; +using Volo.Abp.MongoDB.DistributedEvents; +using Volo.Abp.Timing; using static System.Net.Mime.MediaTypeNames; namespace JiShe.CollectBus.Repository.MeterReadingRecord @@ -85,7 +88,24 @@ namespace JiShe.CollectBus.Repository.MeterReadingRecord public async Task InsertAsync(MeterReadingRecords entity, DateTime? dateTime) { var collection = await GetShardedCollection(dateTime); - await collection.InsertOneAsync(entity); + await collection.InsertOneAsync(entity); + return entity; + } + + /// + /// 单条更新 + /// + /// 过滤条件,示例:Builders.Filter.Eq(x => x.Id, filter.Id) + /// 包含待更新的内容,示例:Builders.Update.Set(x => x.Processed, true).Set(x => x.ProcessedTime, Clock.Now) + /// 数据实体,用于获取对应的分片库 + /// + public async Task UpdateOneAsync(FilterDefinition filter, UpdateDefinition update, MeterReadingRecords entity) + { + var collection = await GetShardedCollection(entity.CreationTime); + + var dbContext = await DbContextProvider.GetDbContextAsync(); + + await collection.UpdateOneAsync(filter, update); return entity; } @@ -135,5 +155,10 @@ namespace JiShe.CollectBus.Repository.MeterReadingRecord return database.GetCollection(collectionName); } + + public Task FirOrDefaultAsync(MeterReadingRecords entity, DateTime? dateTime) + { + throw new NotImplementedException(); + } } } -- 2.47.2 From 37951e9496fb9b9c212bb64a7bb2bd8270a9b334 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Fri, 21 Mar 2025 14:29:05 +0800 Subject: [PATCH 032/139] =?UTF-8?q?=E5=AE=9A=E4=BD=8Dcap=E6=B6=88=E8=B4=B9?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MongoDB/CollectBusMongoDbContext.cs | 92 +++++++++++-------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs index 9199567..7940a79 100644 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs @@ -31,45 +31,59 @@ public class CollectBusMongoDbContext : AbpMongoDbContext, ICollectBusMongoDbCon protected override void CreateModel(IMongoModelBuilder modelBuilder) { + modelBuilder.Entity(builder => + { + builder.CreateCollectionOptions.Collation = new Collation(locale: "en_US", strength: CollationStrength.Secondary); + builder.ConfigureIndexes(indexes => + { + indexes.CreateOne( + new CreateIndexModel( + Builders.IndexKeys.Ascending("MyProperty"), + new CreateIndexOptions { Unique = true } + ) + ); + } + ); + + //// 创建索引 + //builder.ConfigureIndexes(index => + //{ + + + // //List> createIndexModels = new List>(); + // //createIndexModels.Add(new CreateIndexModel( + // // Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), + // // new CreateIndexOptions + // // { + // // Unique = true + // // } + // // )); + + + // //var indexKeys = Builders.IndexKeys + // //.Ascending("CreationTime") + // //.Ascending("OrderNumber"); + + // //var indexOptions = new CreateIndexOptions + // //{ + // // Background = true, + // // Name = "IX_CreationTime_OrderNumber" + // //}; + // //index.CreateOne( + // //new CreateIndexModel(indexKeys, indexOptions)); + + // //index.CreateOne(new CreateIndexModel( + // // Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), + // // new CreateIndexOptions + // // { + // // Unique = true + // // } + // // )); + //}); + + }); + base.CreateModel(modelBuilder); - modelBuilder.ConfigureCollectBus(); - - //modelBuilder.Entity(builder => - //{ - // // 创建索引 - // builder.ConfigureIndexes(index => - // { - // //List> createIndexModels = new List>(); - // //createIndexModels.Add(new CreateIndexModel( - // // Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), - // // new CreateIndexOptions - // // { - // // Unique = true - // // } - // // )); - - - // //var indexKeys = Builders.IndexKeys - // //.Ascending("CreationTime") - // //.Ascending("OrderNumber"); - - // //var indexOptions = new CreateIndexOptions - // //{ - // // Background = true, - // // Name = "IX_CreationTime_OrderNumber" - // //}; - // //index.CreateOne( - // //new CreateIndexModel(indexKeys, indexOptions)); - - // index.CreateOne(new CreateIndexModel( - // Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), - // new CreateIndexOptions - // { - // Unique = true - // } - // )); - // }); - - //}); + modelBuilder.ConfigureCollectBus(); } } -- 2.47.2 From 49548768e64ce16a374232af1cfb3f7dfa012e6d Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Fri, 21 Mar 2025 14:30:11 +0800 Subject: [PATCH 033/139] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusApplicationModule.cs | 10 ++++++++-- .../EnergySystem/CacheAppService.cs | 16 +--------------- .../Workers/EpiCollectWorker.cs | 6 +++++- .../Attributes/IgnoreJobAttribute.cs | 13 +++++++++++++ 4 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 src/JiShe.CollectBus.Common/Attributes/IgnoreJobAttribute.cs diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 0f4a3d0..394bd87 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -9,11 +9,14 @@ using Volo.Abp; using System.Reflection; using JiShe.CollectBus.FreeSql; using System; +using JiShe.CollectBus.Common.Extensions; using Volo.Abp.AspNetCore.Mvc.AntiForgery; using JiShe.CollectBus.FreeRedisProvider; using JiShe.CollectBus.Workers; using Volo.Abp.BackgroundWorkers.Hangfire; using JiShe.CollectBus.MongoDB; +using AutoMapper.Configuration.Annotations; +using JiShe.CollectBus.Common.Attributes; namespace JiShe.CollectBus; @@ -49,10 +52,13 @@ public class CollectBusApplicationModule : AbpModule { var assembly = Assembly.GetExecutingAssembly(); var types = assembly.GetTypes().Where(t => typeof(ICollectWorker).IsAssignableFrom(t) && !t.IsInterface).ToList(); - foreach (var type in types) { - context.AddBackgroundWorkerAsync(type); + var ignoreJob = type.GetCustomAttribute(); + if (ignoreJob == null) + { + context.AddBackgroundWorkerAsync(type); + } } } diff --git a/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs b/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs index 71b2595..f930b1c 100644 --- a/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs +++ b/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs @@ -14,11 +14,6 @@ namespace JiShe.CollectBus.EnergySystem { public class CacheAppService : CollectBusAppService, ICacheAppService { - //public async Task GetHashByKey(string key) - //{ - // await FreeRedisProvider.Instance.HGetAsync(key,); - //} - public async Task SetHashByKey(string key) { var data = await SqlProvider.Instance.Change(DbEnum.EnergyDB).Select().ToListAsync(); @@ -27,16 +22,7 @@ namespace JiShe.CollectBus.EnergySystem foreach (var group in groupData) { - try - { - var aa = group.ToDictionary(a => $"{a.ID}_{a.Address}" , b => b); - await FreeRedisProvider.Instance.HSetAsync($"{RedisConst.CacheAmmeterFocusKey}:{group.Key}", aa); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } + await FreeRedisProvider.Instance.HSetAsync($"{RedisConst.CacheAmmeterFocusKey}:{group.Key}", group.ToDictionary(a => $"{a.ID}_{a.Address}", b => b)); } } diff --git a/src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs b/src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs index e1c5510..12f3e37 100644 --- a/src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs @@ -1,6 +1,8 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using Hangfire; +using JiShe.CollectBus.Common.Attributes; using Microsoft.Extensions.Logging; using Volo.Abp.BackgroundWorkers.Hangfire; using Volo.Abp.DependencyInjection; @@ -8,6 +10,7 @@ using Volo.Abp.Uow; namespace JiShe.CollectBus.Workers { + [IgnoreJob] public class EpiCollectWorker : HangfireBackgroundWorkerBase, ITransientDependency,ICollectWorker { private readonly ILogger _logger; @@ -21,6 +24,7 @@ namespace JiShe.CollectBus.Workers _logger = logger; RecurringJobId = nameof(EpiCollectWorker); CronExpression = Cron.Daily(); + } diff --git a/src/JiShe.CollectBus.Common/Attributes/IgnoreJobAttribute.cs b/src/JiShe.CollectBus.Common/Attributes/IgnoreJobAttribute.cs new file mode 100644 index 0000000..464d1ee --- /dev/null +++ b/src/JiShe.CollectBus.Common/Attributes/IgnoreJobAttribute.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Attributes +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class IgnoreJobAttribute : Attribute + { + } +} -- 2.47.2 From a0fa1ccf9796269ef85d6d4d4a7aae72d6af9090 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Fri, 21 Mar 2025 15:07:38 +0800 Subject: [PATCH 034/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Subscribers/WorkerSubscriberAppService.cs | 4 +- .../MongoDB/CollectBusMongoDbContext.cs | 86 +++++++++---------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index cde387c..98df22b 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -91,7 +91,7 @@ namespace JiShe.CollectBus.Subscribers } else { - var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.FocusAddress); + var device = await _deviceRepository.FirstOrDefaultAsync(a => a.Number == receivedMessage.FocusAddress); if (device != null) { await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); @@ -118,7 +118,7 @@ namespace JiShe.CollectBus.Subscribers } else { - var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.FocusAddress); + var device = await _deviceRepository.FirstOrDefaultAsync(a => a.Number == receivedMessage.FocusAddress); if (device != null) { await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs index 7940a79..addb7c0 100644 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs @@ -31,57 +31,57 @@ public class CollectBusMongoDbContext : AbpMongoDbContext, ICollectBusMongoDbCon protected override void CreateModel(IMongoModelBuilder modelBuilder) { - modelBuilder.Entity(builder => - { - builder.CreateCollectionOptions.Collation = new Collation(locale: "en_US", strength: CollationStrength.Secondary); - builder.ConfigureIndexes(indexes => - { - indexes.CreateOne( - new CreateIndexModel( - Builders.IndexKeys.Ascending("MyProperty"), - new CreateIndexOptions { Unique = true } - ) - ); - } - ); + //modelBuilder.Entity(builder => + //{ + // builder.CreateCollectionOptions.Collation = new Collation(locale: "en_US", strength: CollationStrength.Secondary); + // builder.ConfigureIndexes(indexes => + // { + // indexes.CreateOne( + // new CreateIndexModel( + // Builders.IndexKeys.Ascending("MyProperty"), + // new CreateIndexOptions { Unique = true } + // ) + // ); + // } + // ); - //// 创建索引 - //builder.ConfigureIndexes(index => - //{ + // //// 创建索引 + // //builder.ConfigureIndexes(index => + // //{ - // //List> createIndexModels = new List>(); - // //createIndexModels.Add(new CreateIndexModel( - // // Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), - // // new CreateIndexOptions - // // { - // // Unique = true - // // } - // // )); + // // //List> createIndexModels = new List>(); + // // //createIndexModels.Add(new CreateIndexModel( + // // // Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), + // // // new CreateIndexOptions + // // // { + // // // Unique = true + // // // } + // // // )); - // //var indexKeys = Builders.IndexKeys - // //.Ascending("CreationTime") - // //.Ascending("OrderNumber"); + // // //var indexKeys = Builders.IndexKeys + // // //.Ascending("CreationTime") + // // //.Ascending("OrderNumber"); - // //var indexOptions = new CreateIndexOptions - // //{ - // // Background = true, - // // Name = "IX_CreationTime_OrderNumber" - // //}; - // //index.CreateOne( - // //new CreateIndexModel(indexKeys, indexOptions)); + // // //var indexOptions = new CreateIndexOptions + // // //{ + // // // Background = true, + // // // Name = "IX_CreationTime_OrderNumber" + // // //}; + // // //index.CreateOne( + // // //new CreateIndexModel(indexKeys, indexOptions)); - // //index.CreateOne(new CreateIndexModel( - // // Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), - // // new CreateIndexOptions - // // { - // // Unique = true - // // } - // // )); - //}); + // // //index.CreateOne(new CreateIndexModel( + // // // Builders.IndexKeys.Ascending(nameof(MeterReadingRecords)), + // // // new CreateIndexOptions + // // // { + // // // Unique = true + // // // } + // // // )); + // //}); - }); + //}); base.CreateModel(modelBuilder); modelBuilder.ConfigureCollectBus(); -- 2.47.2 From a857e3f865226c1b2193730626442d4e4bdcd1cb Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Fri, 21 Mar 2025 15:41:02 +0800 Subject: [PATCH 035/139] =?UTF-8?q?=E5=BF=BD=E7=95=A5hangfire=20token?= =?UTF-8?q?=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusApplicationModule.cs | 10 +++++----- src/JiShe.CollectBus.Host/CollectBusHostModule.cs | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index b13548f..daebf74 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -40,11 +40,11 @@ public class CollectBusApplicationModule : AbpModule options.AddMaps(validate: true); }); - Configure(options => - { - options.TokenCookie.Expiration = TimeSpan.FromDays(365); - options.AutoValidateIgnoredHttpMethods.Add("POST"); - }); + //Configure(options => + //{ + // options.TokenCookie.Expiration = TimeSpan.FromDays(365); + // options.AutoValidateIgnoredHttpMethods.Add("POST"); + //}); } public override void OnApplicationInitialization( diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs index 933641b..6153c34 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs @@ -81,6 +81,10 @@ namespace JiShe.CollectBus.Host app.UseAuditing(); app.UseAbpSerilogEnrichers(); app.UseUnitOfWork(); + app.UseHangfireDashboard("/hangfire", new DashboardOptions + { + IgnoreAntiforgeryToken = true + }); app.UseConfiguredEndpoints(endpoints => { endpoints.MapHealthChecks("/health", new HealthCheckOptions @@ -89,7 +93,6 @@ namespace JiShe.CollectBus.Host ResponseWriter = HealthCheckResponse.Writer }); }); - app.UseHangfireDashboard(); } } } -- 2.47.2 From 9da1745573270a2a145c4300f085321c17a327d8 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 24 Mar 2025 20:54:31 +0800 Subject: [PATCH 036/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...er.cs => ScheduledMeterReadingConsumer.cs} | 12 ++-- .../JiShe.CollectBus.Application.csproj | 3 +- .../BasicScheduledMeterReadingService.cs | 7 ++- ...nergySystemScheduledMeterReadingService.cs | 62 ++++++++++++++----- .../CollectBusHostModule.Configure.cs | 10 +++ .../CollectBusHostModule.cs | 2 +- 6 files changed, 71 insertions(+), 25 deletions(-) rename src/JiShe.CollectBus.Application/Consumers/{WorkerConsumer.cs => ScheduledMeterReadingConsumer.cs} (52%) diff --git a/src/JiShe.CollectBus.Application/Consumers/WorkerConsumer.cs b/src/JiShe.CollectBus.Application/Consumers/ScheduledMeterReadingConsumer.cs similarity index 52% rename from src/JiShe.CollectBus.Application/Consumers/WorkerConsumer.cs rename to src/JiShe.CollectBus.Application/Consumers/ScheduledMeterReadingConsumer.cs index 53ad836..cdc731a 100644 --- a/src/JiShe.CollectBus.Application/Consumers/WorkerConsumer.cs +++ b/src/JiShe.CollectBus.Application/Consumers/ScheduledMeterReadingConsumer.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.IotSystems.MessageIssueds; using MassTransit; using Microsoft.Extensions.Logging; using TouchSocket.Sockets; @@ -12,9 +13,9 @@ namespace JiShe.CollectBus.Consumers /// /// 定时抄读任务消费者 /// - public class WorkerConsumer : IConsumer + public class ScheduledMeterReadingConsumer : IConsumer { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ITcpService _tcpService; /// @@ -22,7 +23,7 @@ namespace JiShe.CollectBus.Consumers /// /// /// - public WorkerConsumer(ILogger logger, + public ScheduledMeterReadingConsumer(ILogger logger, ITcpService tcpService) { _logger = logger; @@ -30,9 +31,10 @@ namespace JiShe.CollectBus.Consumers } - public async Task Consume(ConsumeContext context) + public async Task Consume(ConsumeContext context) { - await _tcpService.SendAsync(context.Message.ClientId, context.Message.Message); + _logger.LogError($"{nameof(ScheduledMeterReadingConsumer)} 集中器的消息消费{context.Message.FocusAddress}"); + await _tcpService.SendAsync(context.Message.FocusAddress, context.Message.MessageHexString); } } } diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index 94c4faf..078728a 100644 --- a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -15,7 +15,8 @@ - + + diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index bb3c743..14e0a6a 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -26,6 +26,7 @@ using JiShe.CollectBus.Repository.MeterReadingRecord; using JiShe.CollectBus.Workers; using MassTransit; using MassTransit.Internals.GraphValidation; +using MassTransit.Transports; using Microsoft.Extensions.Logging; using Volo.Abp.Domain.Repositories; using static FreeSql.Internal.GlobalFilter; @@ -41,10 +42,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading private readonly ICapPublisher _capBus; private readonly IMeterReadingRecordRepository _meterReadingRecordsRepository; - public BasicScheduledMeterReadingService( ILogger logger, - ICapPublisher capBus, + ICapPublisher capBus, IMeterReadingRecordRepository meterReadingRecordsRepository) { _capBus = capBus; @@ -432,8 +432,11 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; + _ = _capBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500) ,ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); + //await _massTransitBus.Publish(tempMsg); + meterTaskInfosList.Add(ammerterItem.Value); } } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 24507fe..61cc2e5 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -12,6 +12,7 @@ using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; using JiShe.CollectBus.Repository; using JiShe.CollectBus.Repository.MeterReadingRecord; +using MassTransit; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; using Volo.Abp.Domain.Repositories; @@ -27,10 +28,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading public class EnergySystemScheduledMeterReadingService : BasicScheduledMeterReadingService { - public EnergySystemScheduledMeterReadingService(ILogger logger, - ICapPublisher capBus, IMeterReadingRecordRepository _meterReadingRecordsRepository) :base(logger, capBus, _meterReadingRecordsRepository) + public EnergySystemScheduledMeterReadingService(ILogger logger, + ICapPublisher capBus, IMeterReadingRecordRepository meterReadingRecordsRepository) : base(logger, capBus, meterReadingRecordsRepository) { - + } public sealed override string SystemType => SystemTypeConst.Energy; @@ -63,21 +64,50 @@ namespace JiShe.CollectBus.ScheduledMeterReading //[Route($"ammeter/list")] public override async Task> GetAmmeterInfoList(string gatherCode = "V4-Gather-8890") { - string sql = $@"SELECT C.ID,C.Name,C.FocusID,C.SingleRate,C.MeteringCode,C.Code AS BrandType,C.Baudrate,C.Password,C.MeteringPort,C.[Address] AS AmmerterAddress,C.TypeName,C.Protocol,C.TripState,C.[State],B.[Address],B.AreaCode,B.AutomaticReport,D.DataTypes,B.TimeDensity,A.GatherCode,C.Special,C.[ProjectID],B.AbnormalState,B.LastTime,CONCAT(B.AreaCode, B.[Address]) AS FocusAddress,(select top 1 DatabaseBusiID from TB_Project where ID = B.ProjectID) AS DatabaseBusiID - FROM TB_GatherInfo(NOLOCK) AS A - INNER JOIN TB_FocusInfo(NOLOCK) AS B ON A.ID = B.GatherInfoID AND B.RemoveState >= 0 AND B.State>=0 - INNER JOIN TB_AmmeterInfo(NOLOCK) AS C ON B.ID = C.FocusID AND C.State>= 0 AND C.State<100 - INNER JOIN TB_AmmeterGatherItem(NOLOCK) AS D ON C.ID = D.AmmeterID AND D.State>=0 - WHERE 1=1 and C.Special = 0 "; - //TODO 记得移除特殊表过滤 - if (!string.IsNullOrWhiteSpace(gatherCode)) + List ammeterInfos = new List(); + ammeterInfos.Add(new AmmeterInfo() { - sql = $@"{sql} AND A.GatherCode = '{gatherCode}'"; - } - return await SqlProvider.Instance.Change(DbEnum.EnergyDB) - .Ado - .QueryAsync(sql); + Baudrate = 2400, + FocusAddress = "402440506", + Name = "三相电表", + FocusID = 1, + DatabaseBusiID = 1, + MeteringCode = 2, + AmmerterAddress = "402410040506", + ID = 9980, + TypeName = 3, + }); + ammeterInfos.Add(new AmmeterInfo() + { + Baudrate = 2400, + FocusAddress = "542400504", + Name = "单相电表", + FocusID = 1, + DatabaseBusiID = 1, + MeteringCode = 2, + AmmerterAddress = "542410000504", + ID = 9981, + TypeName = 1, + }); + + return ammeterInfos; + + //string sql = $@"SELECT C.ID,C.Name,C.FocusID,C.SingleRate,C.MeteringCode,C.Code AS BrandType,C.Baudrate,C.Password,C.MeteringPort,C.[Address] AS AmmerterAddress,C.TypeName,C.Protocol,C.TripState,C.[State],B.[Address],B.AreaCode,B.AutomaticReport,D.DataTypes,B.TimeDensity,A.GatherCode,C.Special,C.[ProjectID],B.AbnormalState,B.LastTime,CONCAT(B.AreaCode, B.[Address]) AS FocusAddress,(select top 1 DatabaseBusiID from TB_Project where ID = B.ProjectID) AS DatabaseBusiID + // FROM TB_GatherInfo(NOLOCK) AS A + // INNER JOIN TB_FocusInfo(NOLOCK) AS B ON A.ID = B.GatherInfoID AND B.RemoveState >= 0 AND B.State>=0 + // INNER JOIN TB_AmmeterInfo(NOLOCK) AS C ON B.ID = C.FocusID AND C.State>= 0 AND C.State<100 + // INNER JOIN TB_AmmeterGatherItem(NOLOCK) AS D ON C.ID = D.AmmeterID AND D.State>=0 + // WHERE 1=1 and C.Special = 0 "; + ////TODO 记得移除特殊表过滤 + + //if (!string.IsNullOrWhiteSpace(gatherCode)) + //{ + // sql = $@"{sql} AND A.GatherCode = '{gatherCode}'"; + //} + //return await SqlProvider.Instance.Change(DbEnum.EnergyDB) + // .Ado + // .QueryAsync(sql); } /// diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 6ffa1d0..374eaa5 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -20,6 +20,7 @@ using JiShe.CollectBus.Plugins; using JiShe.CollectBus.Consumers; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.MessageIssueds; namespace JiShe.CollectBus.Host @@ -302,6 +303,9 @@ namespace JiShe.CollectBus.Host .SetTimeLimitStart(BatchTimeLimitStart.FromLast) .SetConcurrencyLimit(10)); }); + + rider.AddConsumer(); + rider.UsingKafka((c, cfg) => { cfg.Host(configuration.GetConnectionString("Kafka")); @@ -329,6 +333,12 @@ namespace JiShe.CollectBus.Host configurator.ConfigureConsumer(c); configurator.ConfigureConsumeTopology = false; }); + + //cfg.TopicEndpoint(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, ProtocolConst.SubscriberGroup, configurator => + //{ + // configurator.ConfigureConsumer(c); + // configurator.ConfigureConsumeTopology = false; + //}); }); }); }); diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs index 933641b..6f24b1e 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs @@ -25,7 +25,7 @@ namespace JiShe.CollectBus.Host typeof(AbpAspNetCoreSerilogModule), typeof(AbpSwashbuckleModule), typeof(CollectBusApplicationModule), - typeof(CollectBusMongoDbModule), + typeof(CollectBusMongoDbModule), typeof(AbpCachingStackExchangeRedisModule), typeof(AbpBackgroundWorkersHangfireModule) )] -- 2.47.2 From af9b0d8a77b08857014ae1ba419b27208eabf555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E7=9B=8A?= Date: Mon, 24 Mar 2025 21:55:22 +0800 Subject: [PATCH 037/139] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=A1=A8=E5=88=86?= =?UTF-8?q?=E7=89=87=E7=AD=96=E7=95=A5=E7=BB=9F=E4=B8=80=E5=87=BA=E5=8F=A3?= =?UTF-8?q?=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Subscribers/SubscriberAppService.cs | 53 +++++++++++++++---- .../Extensions/DateTimeExtensions.cs | 10 ++++ .../ShardingStrategy/DayShardingStrategy.cs | 9 ++-- .../Abstracts/BaseProtocolPlugin.cs | 2 +- .../Interfaces/IProtocolPlugin.cs | 2 +- 5 files changed, 59 insertions(+), 17 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index fc05bc7..7dbe586 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -1,7 +1,10 @@ using System; +using System.Linq; using System.Threading.Tasks; using DotNetCore.CAP; using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageReceiveds; @@ -9,6 +12,7 @@ using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using JiShe.CollectBus.Protocol.Contracts.Models; +using JiShe.CollectBus.Repository.MeterReadingRecord; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using TouchSocket.Sockets; @@ -16,16 +20,16 @@ using Volo.Abp.Domain.Repositories; namespace JiShe.CollectBus.Subscribers { - public class SubscriberAppService : CollectBusAppService, ISubscriberAppService,ICapSubscribe + public class SubscriberAppService : CollectBusAppService, ISubscriberAppService, ICapSubscribe { private readonly ILogger _logger; private readonly ITcpService _tcpService; private readonly IServiceProvider _serviceProvider; private readonly IRepository _messageReceivedLoginEventRepository; private readonly IRepository _messageReceivedHeartbeatEventRepository; - private readonly IRepository _messageReceivedEventRepository; + private readonly IRepository _messageReceivedEventRepository; private readonly IRepository _deviceRepository; - private readonly IRepository _meterReadingRecordsRepository; + private readonly IMeterReadingRecordRepository _meterReadingRecordsRepository; /// /// Initializes a new instance of the class. @@ -38,12 +42,12 @@ namespace JiShe.CollectBus.Subscribers /// The message received event repository. /// The device repository. /// The device repository. - public SubscriberAppService(ILogger logger, - ITcpService tcpService, IServiceProvider serviceProvider, - IRepository messageReceivedLoginEventRepository, - IRepository messageReceivedHeartbeatEventRepository, - IRepository messageReceivedEventRepository, - IRepository deviceRepository, IRepository meterReadingRecordsRepository) + public SubscriberAppService(ILogger logger, + ITcpService tcpService, IServiceProvider serviceProvider, + IRepository messageReceivedLoginEventRepository, + IRepository messageReceivedHeartbeatEventRepository, + IRepository messageReceivedEventRepository, + IRepository deviceRepository, IMeterReadingRecordRepository meterReadingRecordsRepository) { _logger = logger; _tcpService = tcpService; @@ -79,7 +83,7 @@ namespace JiShe.CollectBus.Subscribers throw new ArgumentOutOfRangeException(); } var device = await _deviceRepository.FindAsync(a => a.Number == issuedEventMessage.DeviceNo); - if (device!=null) + if (device != null) { await _tcpService.SendAsync(device.ClientId, issuedEventMessage.Message); } @@ -96,7 +100,34 @@ namespace JiShe.CollectBus.Subscribers else { //todo 会根据不同的协议进行解析,然后做业务处理 - TB3761FN fN = await protocolPlugin.AnalyzeAsync(receivedMessage); + TB3761 fN = await protocolPlugin.AnalyzeAsync(receivedMessage); + if(fN == null) + { + Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); + return; + } + var tb3761FN = fN.FnList.FirstOrDefault(); + if (tb3761FN == null) + { + Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); + return; + } + + //todo 查找是否有下发任务 + + + + await _meterReadingRecordsRepository.InsertAsync(new MeterReadingRecords() + { + ReceivedMessageHexString = receivedMessage.MessageHexString, + AFN = fN.Afn, + Fn = tb3761FN.Fn, + Pn = 0, + FocusAddress = "", + MeterAddress = "", + DataResult = tb3761FN.Text, + }); + //await _messageReceivedEventRepository.InsertAsync(receivedMessage); } } diff --git a/src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs b/src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs index b12778f..904d4b9 100644 --- a/src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs +++ b/src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs @@ -167,5 +167,15 @@ namespace JiShe.CollectBus.Common.Extensions ) ); } + + /// + /// 获取数据表分片策略 + /// + /// + /// + public static string GetDataTableShardingStrategy(this DateTime dateTime) + { + return $"{dateTime:yyyyMMddHHmm}"; + } } } diff --git a/src/JiShe.CollectBus.MongoDB/ShardingStrategy/DayShardingStrategy.cs b/src/JiShe.CollectBus.MongoDB/ShardingStrategy/DayShardingStrategy.cs index 75157e5..f26136d 100644 --- a/src/JiShe.CollectBus.MongoDB/ShardingStrategy/DayShardingStrategy.cs +++ b/src/JiShe.CollectBus.MongoDB/ShardingStrategy/DayShardingStrategy.cs @@ -1,4 +1,5 @@ -using System; +using JiShe.CollectBus.Common.Extensions; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -21,7 +22,7 @@ namespace JiShe.CollectBus.ShardingStrategy public string GetCollectionName(DateTime dateTime) { var baseName = typeof(TEntity).Name; - return $"{baseName}_{dateTime:yyyyMMddHHmm}"; + return $"{baseName}_{dateTime.GetDataTableShardingStrategy()}"; } /// @@ -31,7 +32,7 @@ namespace JiShe.CollectBus.ShardingStrategy public string GetCurrentCollectionName() { var baseName = typeof(TEntity).Name; - return $"{baseName}_{DateTime.Now:yyyyMMddHHmm}"; + return $"{baseName}_{DateTime.Now.GetDataTableShardingStrategy()}"; } /// @@ -49,7 +50,7 @@ namespace JiShe.CollectBus.ShardingStrategy while (current <= end) { - months.Add($"{baseName}_{current:yyyyMMddHHmm}"); + months.Add($"{baseName}_{current.GetDataTableShardingStrategy()}"); current = current.AddMonths(1); } diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index 609dcdb..4638983 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -55,7 +55,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts //await _protocolInfoCache.Get() } - public abstract Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) where T : TB3761FN; + public abstract Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) where T : TB3761; /// /// 登录帧解析 diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs index 5ad92c1..2f48cd2 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs @@ -14,7 +14,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Interfaces Task AddAsync(); - Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) where T : TB3761FN; + Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) where T : TB3761; Task LoginAsync(MessageReceivedLogin messageReceived); -- 2.47.2 From 4ad10606ff4766b76989eff6f475dc180c211223 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Wed, 26 Mar 2025 17:18:20 +0800 Subject: [PATCH 038/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=9B=86=E7=BE=A4?= =?UTF-8?q?=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EnergySystem/CacheAppService.cs | 5 ++- .../CollectBusHostModule.Configure.cs | 5 +++ src/JiShe.CollectBus.Host/appsettings.json | 34 ++++++++++++------- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs b/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs index f930b1c..d4ace87 100644 --- a/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs +++ b/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs @@ -9,6 +9,10 @@ using JiShe.CollectBus.EnergySystems.TableViews; using JiShe.CollectBus.FreeSql; using Volo.Abp.Domain.Repositories; using JiShe.CollectBus.IotSystems.PrepayModel; +using JiShe.CollectBus.Ammeters; +using JiShe.CollectBus.Common.BuildSendDatas; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Helpers; namespace JiShe.CollectBus.EnergySystem { @@ -24,7 +28,6 @@ namespace JiShe.CollectBus.EnergySystem { await FreeRedisProvider.Instance.HSetAsync($"{RedisConst.CacheAmmeterFocusKey}:{group.Key}", group.ToDictionary(a => $"{a.ID}_{a.Address}", b => b)); } - } } } diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 6ffa1d0..aaa8043 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -262,6 +262,11 @@ namespace JiShe.CollectBus.Host x.UseKafka(option => { option.Servers = kafka; + if (!Convert.ToBoolean(configuration["Kafka:EnableAuthorization"])) return; + option.MainConfig.Add("security.protocol", configuration["Kafka:SecurityProtocol"]); + option.MainConfig.Add("sasl.mechanism", configuration["Kafka:SaslMechanism"]); + option.MainConfig.Add("sasl.username", configuration["Kafka:SaslUserName"]); + option.MainConfig.Add("sasl.password", configuration["Kafka:SaslPassword"]); }); x.UseDashboard(); diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 50c459b..473d79e 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -1,4 +1,4 @@ - { +{ "Serilog": { "Using": [ "Serilog.Sinks.Console", @@ -20,7 +20,7 @@ { "Name": "Console" }, - { + { "Name": "File", "Args": { "path": "logs/logs-.txt", @@ -33,17 +33,18 @@ "SelfUrl": "http://localhost:44315", "CorsOrigins": "http://localhost:4200,http://localhost:3100" }, - "ConnectionStrings": { - "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", - "Kafka": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092", - "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", - "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" - }, - "Redis": { - "Configuration": "118.190.144.92:6379,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", - "DefaultDB": "14", - "HangfireDB": "15" - }, + "ConnectionStrings": { + "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", + //"Kafka": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092", + "Kafka": "8.148.227.21:9092,8.148.224.127:9092,8.138.38.208:9092", + "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", + "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" + }, + "Redis": { + "Configuration": "118.190.144.92:6379,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + "DefaultDB": "14", + "HangfireDB": "15" + }, "Jwt": { "Audience": "JiShe.CollectBus", "SecurityKey": "dzehzRz9a8asdfasfdadfasdfasdfafsdadfasbasdf=", @@ -80,5 +81,12 @@ "Password": "123456", "Port": 5672 } + }, + "Kafka": { + "EnableAuthorization": true, + "SecurityProtocol": "SASL_PLAINTEXT", + "SaslMechanism": "PLAIN", + "SaslUserName": "lixiao", + "SaslPassword": "lixiao1980" } } \ No newline at end of file -- 2.47.2 From a9b8323a3ad453658cb3a4f2220d025947bf3330 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 27 Mar 2025 08:38:19 +0800 Subject: [PATCH 039/139] =?UTF-8?q?=E6=9A=82=E5=AD=98=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Consumers/ReceivedConsumer.cs | 2 +- .../JiShe.CollectBus.Application.csproj | 6 +- .../Plugins/CloseMonitor.cs | 14 +-- .../Plugins/ServerMonitor.cs | 4 +- .../Plugins/TcpMonitor.cs | 114 +++++++++++------- .../Plugins/UdpMonitor.cs | 3 +- .../BasicScheduledMeterReadingService.cs | 2 +- ...nergySystemScheduledMeterReadingService.cs | 46 +++---- .../Subscribers/SubscriberAppService.cs | 16 ++- .../IotSystems/Devices/Device.cs | 6 + .../JiShe.CollectBus.Host.csproj | 4 +- src/JiShe.CollectBus.Host/appsettings.json | 6 +- .../Abstracts/BaseProtocolPlugin.cs | 8 +- 13 files changed, 138 insertions(+), 93 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs b/src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs index 9309c55..4f7b5eb 100644 --- a/src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs +++ b/src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs @@ -49,7 +49,7 @@ namespace JiShe.CollectBus.Consumers var list = new List(); foreach (var contextItem in context.Message) { - await protocolPlugin.AnalyzeAsync(contextItem.Message); + await protocolPlugin.AnalyzeAsync(contextItem.Message); list.Add(contextItem.Message); } await _messageReceivedEventRepository.InsertManyAsync(list); diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index 078728a..6b71b26 100644 --- a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -15,14 +15,14 @@ - + - - + + diff --git a/src/JiShe.CollectBus.Application/Plugins/CloseMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/CloseMonitor.cs index 19b66d8..f8651c7 100644 --- a/src/JiShe.CollectBus.Application/Plugins/CloseMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/CloseMonitor.cs @@ -7,10 +7,10 @@ using TouchSocket.Sockets; namespace JiShe.CollectBus.Plugins { - public partial class TcpCloseMonitor(ILogger logger) : PluginBase + public partial class TcpCloseMonitor(ILogger logger) : PluginBase, ITcpReceivedPlugin { - [GeneratorPlugin(typeof(ITcpReceivedPlugin))] - public async Task OnTcpReceived(ITcpSessionClient client, ReceivedDataEventArgs e) + + public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) { try { @@ -19,21 +19,21 @@ namespace JiShe.CollectBus.Plugins catch (CloseException ex) { logger.LogInformation("拦截到CloseException"); - client.Close(ex.Message); + await client.CloseAsync(ex.Message); } catch (Exception exx) { - // ignored + } finally { + } } } - public partial class UdpCloseMonitor(ILogger logger) : PluginBase + public partial class UdpCloseMonitor(ILogger logger) : PluginBase, IUdpReceivedPlugin { - [GeneratorPlugin(typeof(IUdpReceivedPlugin))] public Task OnUdpReceived(IUdpSessionBase client, UdpReceivedDataEventArgs e) { throw new NotImplementedException(); diff --git a/src/JiShe.CollectBus.Application/Plugins/ServerMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/ServerMonitor.cs index 39174db..2b3c15a 100644 --- a/src/JiShe.CollectBus.Application/Plugins/ServerMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/ServerMonitor.cs @@ -5,9 +5,8 @@ using TouchSocket.Sockets; namespace JiShe.CollectBus.Plugins { - public partial class ServerMonitor(ILogger logger) : PluginBase + public partial class ServerMonitor(ILogger logger) : PluginBase, IServerStartedPlugin, IServerStopedPlugin { - [GeneratorPlugin(typeof(IServerStartedPlugin))] public Task OnServerStarted(IServiceBase sender, ServiceStateEventArgs e) { switch (sender) @@ -32,7 +31,6 @@ namespace JiShe.CollectBus.Plugins return e.InvokeNext(); } - [GeneratorPlugin(typeof(IServerStopedPlugin))] public Task OnServerStoped(IServiceBase sender,ServiceStateEventArgs e) { logger.LogInformation("服务已停止"); diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index 8ff138a..b3b43d3 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -1,5 +1,7 @@ using System; +using System.Runtime.CompilerServices; using System.Threading.Tasks; +using DeviceDetectorNET.Parser.Device; using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Enums; @@ -20,7 +22,7 @@ using Volo.Abp.Domain.Repositories; namespace JiShe.CollectBus.Plugins { - public partial class TcpMonitor : PluginBase, ITransientDependency + public partial class TcpMonitor : PluginBase, ITransientDependency, ITcpReceivedPlugin, ITcpConnectingPlugin, ITcpConnectedPlugin, ITcpClosedPlugin { private readonly ICapPublisher _capBus; private readonly ILogger _logger; @@ -34,9 +36,9 @@ namespace JiShe.CollectBus.Plugins /// /// /// - public TcpMonitor(ICapPublisher capBus, - ILogger logger, - IRepository deviceRepository, + public TcpMonitor(ICapPublisher capBus, + ILogger logger, + IRepository deviceRepository, IDistributedCache ammeterInfoCache) { _capBus = capBus; @@ -45,8 +47,7 @@ namespace JiShe.CollectBus.Plugins _ammeterInfoCache = ammeterInfoCache; } - [GeneratorPlugin(typeof(ITcpReceivedPlugin))] - public async Task OnTcpReceived(ITcpSessionClient client, ReceivedDataEventArgs e) + public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) { var messageHexString = Convert.ToHexString(e.ByteBlock.Span); var hexStringList = messageHexString.StringToPairs(); @@ -55,15 +56,21 @@ namespace JiShe.CollectBus.Plugins var aTuple = (Tuple)hexStringList.GetAnalyzeValue(CommandChunkEnum.A); if (aFn.HasValue && fn.HasValue && aTuple != null && !string.IsNullOrWhiteSpace(aTuple.Item1)) { + var tcpSessionClient = (ITcpSessionClient)client; + + if ((AFN)aFn == AFN.链路接口检测) { switch (fn) { case 1: - await OnTcpLoginReceived(client, messageHexString, aTuple.Item1); + await OnTcpLoginReceived(tcpSessionClient, messageHexString, aTuple.Item1); break; case 3: - await OnTcpHeartbeatReceived(client, messageHexString, aTuple.Item1); + //心跳帧有两种情况: + //1. 集中器先有登录帧,再有心跳帧 + //2. 集中器没有登录帧,只有心跳帧 + await OnTcpHeartbeatReceived(tcpSessionClient, messageHexString, aTuple.Item1); break; default: _logger.LogError($"指令初步解析失败,指令内容:{messageHexString}"); @@ -72,7 +79,7 @@ namespace JiShe.CollectBus.Plugins } else { - await OnTcpNormalReceived(client, messageHexString, aTuple.Item1); + await OnTcpNormalReceived(tcpSessionClient, messageHexString, aTuple.Item1); } } else @@ -83,24 +90,31 @@ namespace JiShe.CollectBus.Plugins await e.InvokeNext(); } - [GeneratorPlugin(typeof(ITcpConnectingPlugin))] - public async Task OnTcpConnecting(ITcpSessionClient client, ConnectingEventArgs e) + //[GeneratorPlugin(typeof(ITcpConnectingPlugin))] + public async Task OnTcpConnecting(ITcpSession client, ConnectingEventArgs e) { - _logger.LogInformation($"[TCP] ID:{client.Id} IP:{client.GetIPPort()}正在连接中..."); + var tcpSessionClient = (ITcpSessionClient)client; + + _logger.LogInformation($"[TCP] ID:{tcpSessionClient.Id} IP:{client.GetIPPort()}正在连接中..."); await e.InvokeNext(); } - [GeneratorPlugin(typeof(ITcpConnectedPlugin))] - public async Task OnTcpConnected(ITcpSessionClient client, ConnectedEventArgs e) + //[GeneratorPlugin(typeof(ITcpConnectedPlugin))] + public async Task OnTcpConnected(ITcpSession client, ConnectedEventArgs e) { - _logger.LogInformation($"[TCP] ID:{client.Id} IP:{client.GetIPPort()}已连接"); + var tcpSessionClient = (ITcpSessionClient)client; + + + _logger.LogInformation($"[TCP] ID:{tcpSessionClient.Id} IP:{client.GetIPPort()}已连接"); await e.InvokeNext(); } - [GeneratorPlugin(typeof(ITcpClosedPlugin))] - public async Task OnTcpClosed(ITcpSessionClient client, ClosedEventArgs e) + //[GeneratorPlugin(typeof(ITcpClosedPlugin))]//ITcpSessionClient + public async Task OnTcpClosed(ITcpSession client, ClosedEventArgs e) { - var entity = await _deviceRepository.FindAsync(a=>a.ClientId == client.Id); + + var tcpSessionClient = (ITcpSessionClient)client; + var entity = await _deviceRepository.FindAsync(a => a.ClientId == tcpSessionClient.Id); if (entity != null) { entity.UpdateByOnClosed(); @@ -108,7 +122,7 @@ namespace JiShe.CollectBus.Plugins } else { - _logger.LogWarning($"[TCP] ID:{client.Id} IP:{client.GetIPPort()}已关闭连接,但采集程序检索失败"); + _logger.LogWarning($"[TCP] ID:{tcpSessionClient.Id} IP:{client.GetIPPort()}已关闭连接,但采集程序检索失败"); } await e.InvokeNext(); @@ -123,9 +137,24 @@ namespace JiShe.CollectBus.Plugins /// private async Task OnTcpLoginReceived(ITcpSessionClient client, string messageHexString, string deviceNo) { + string oldClientId = $"{client.Id}"; + + await client.ResetIdAsync(deviceNo); + + var entity = await _deviceRepository.FindAsync(a => a.Number == deviceNo); + if (entity == null) + { + await _deviceRepository.InsertAsync(new Device(deviceNo, oldClientId, DateTime.Now, DateTime.Now, DeviceStatus.Online)); + } + else + { + entity.UpdateByLoginAndHeartbeat(oldClientId); + await _deviceRepository.UpdateAsync(entity); + } + var messageReceivedLoginEvent = new MessageReceivedLogin { - ClientId = client.Id, + ClientId = deviceNo, ClientIp = client.IP, ClientPort = client.Port, MessageHexString = messageHexString, @@ -133,23 +162,36 @@ namespace JiShe.CollectBus.Plugins MessageId = NewId.NextGuid().ToString() }; await _capBus.PublishAsync(ProtocolConst.SubscriberReceivedLoginEventName, messageReceivedLoginEvent); - var entity = await _deviceRepository.FindAsync(a => a.Number == deviceNo); - if (entity == null) - { - await _deviceRepository.InsertAsync(new Device(deviceNo, client.Id,DateTime.Now, DateTime.Now, DeviceStatus.Online)); - } - else - { - entity.UpdateByLoginAndHeartbeat(client.Id); - await _deviceRepository.UpdateAsync(entity); - } } private async Task OnTcpHeartbeatReceived(ITcpSessionClient client, string messageHexString, string deviceNo) { + string clientId = deviceNo; + string oldClientId = $"{client.Id}"; + + var entity = await _deviceRepository.FindAsync(a => a.Number == deviceNo); + if (entity == null) //没有登录帧的设备,只有心跳帧 + { + await client.ResetIdAsync(clientId); + await _deviceRepository.InsertAsync(new Device(deviceNo, oldClientId, DateTime.Now, DateTime.Now, DeviceStatus.Online)); + } + else + { + if (clientId != oldClientId) + { + entity.UpdateByLoginAndHeartbeat(oldClientId); + } + else + { + entity.UpdateByLoginAndHeartbeat(); + } + + await _deviceRepository.UpdateAsync(entity); + } + var messageReceivedHeartbeatEvent = new MessageReceivedHeartbeat { - ClientId = client.Id, + ClientId = clientId, ClientIp = client.IP, ClientPort = client.Port, MessageHexString = messageHexString, @@ -157,16 +199,6 @@ namespace JiShe.CollectBus.Plugins MessageId = NewId.NextGuid().ToString() }; await _capBus.PublishAsync(ProtocolConst.SubscriberReceivedHeartbeatEventName, messageReceivedHeartbeatEvent); - var entity = await _deviceRepository.FindAsync(a => a.Number == deviceNo); - if (entity == null) - { - await _deviceRepository.InsertAsync(new Device(deviceNo, client.Id, DateTime.Now, DateTime.Now, DeviceStatus.Online)); - } - else - { - entity.UpdateByLoginAndHeartbeat(client.Id); - await _deviceRepository.UpdateAsync(entity); - } } private async Task OnTcpNormalReceived(ITcpSessionClient client, string messageHexString, string deviceNo) diff --git a/src/JiShe.CollectBus.Application/Plugins/UdpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/UdpMonitor.cs index f975a03..b6a576c 100644 --- a/src/JiShe.CollectBus.Application/Plugins/UdpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/UdpMonitor.cs @@ -5,9 +5,8 @@ using TouchSocket.Sockets; namespace JiShe.CollectBus.Plugins { - public partial class UdpMonitor : PluginBase + public partial class UdpMonitor : PluginBase, IUdpReceivedPlugin { - [GeneratorPlugin(typeof(IUdpReceivedPlugin))] public async Task OnUdpReceived(IUdpSessionBase client, UdpReceivedDataEventArgs e) { var udpSession = client as UdpSession; diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 14e0a6a..b1ea2d6 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -200,7 +200,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading foreach (var ammeter in item) { //处理ItemCode - if (string.IsNullOrWhiteSpace(ammeter.ItemCodes)) + if (string.IsNullOrWhiteSpace(ammeter.ItemCodes) && !string.IsNullOrWhiteSpace(ammeter.DataTypes)) { var itemArr = ammeter.DataTypes.Split(',').ToList(); diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 61cc2e5..70cecfa 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -70,44 +70,48 @@ namespace JiShe.CollectBus.ScheduledMeterReading { Baudrate = 2400, FocusAddress = "402440506", - Name = "三相电表", - FocusID = 1, + Name = "张家祠工务(三相电表)", + FocusID = 95780, DatabaseBusiID = 1, - MeteringCode = 2, + MeteringCode = 1, AmmerterAddress = "402410040506", - ID = 9980, + ID = 127035, TypeName = 3, + DataTypes = "449,503,581,582,583,584,585,586,587,588,589,590,591,592,593,594,597,598,599,600,601,602,603,604,605,606,607,608,661,663,677,679", + TimeDensity = 15, }); ammeterInfos.Add(new AmmeterInfo() { Baudrate = 2400, FocusAddress = "542400504", - Name = "单相电表", - FocusID = 1, + Name = "五号配(长芦二所四排)(单相电表)", + FocusID = 69280, DatabaseBusiID = 1, MeteringCode = 2, AmmerterAddress = "542410000504", - ID = 9981, + ID = 95594, TypeName = 1, + DataTypes = "581,589,592,597,601", + TimeDensity = 15, }); return ammeterInfos; - //string sql = $@"SELECT C.ID,C.Name,C.FocusID,C.SingleRate,C.MeteringCode,C.Code AS BrandType,C.Baudrate,C.Password,C.MeteringPort,C.[Address] AS AmmerterAddress,C.TypeName,C.Protocol,C.TripState,C.[State],B.[Address],B.AreaCode,B.AutomaticReport,D.DataTypes,B.TimeDensity,A.GatherCode,C.Special,C.[ProjectID],B.AbnormalState,B.LastTime,CONCAT(B.AreaCode, B.[Address]) AS FocusAddress,(select top 1 DatabaseBusiID from TB_Project where ID = B.ProjectID) AS DatabaseBusiID - // FROM TB_GatherInfo(NOLOCK) AS A - // INNER JOIN TB_FocusInfo(NOLOCK) AS B ON A.ID = B.GatherInfoID AND B.RemoveState >= 0 AND B.State>=0 - // INNER JOIN TB_AmmeterInfo(NOLOCK) AS C ON B.ID = C.FocusID AND C.State>= 0 AND C.State<100 - // INNER JOIN TB_AmmeterGatherItem(NOLOCK) AS D ON C.ID = D.AmmeterID AND D.State>=0 - // WHERE 1=1 and C.Special = 0 "; - ////TODO 记得移除特殊表过滤 + string sql = $@"SELECT C.ID,C.Name,C.FocusID,C.SingleRate,C.MeteringCode,C.Code AS BrandType,C.Baudrate,C.Password,C.MeteringPort,C.[Address] AS AmmerterAddress,C.TypeName,C.Protocol,C.TripState,C.[State],B.[Address],B.AreaCode,B.AutomaticReport,D.DataTypes,B.TimeDensity,A.GatherCode,C.Special,C.[ProjectID],B.AbnormalState,B.LastTime,CONCAT(B.AreaCode, B.[Address]) AS FocusAddress,(select top 1 DatabaseBusiID from TB_Project where ID = B.ProjectID) AS DatabaseBusiID + FROM TB_GatherInfo(NOLOCK) AS A + INNER JOIN TB_FocusInfo(NOLOCK) AS B ON A.ID = B.GatherInfoID AND B.RemoveState >= 0 AND B.State>=0 + INNER JOIN TB_AmmeterInfo(NOLOCK) AS C ON B.ID = C.FocusID AND C.State>= 0 AND C.State<100 + INNER JOIN TB_AmmeterGatherItem(NOLOCK) AS D ON C.ID = D.AmmeterID AND D.State>=0 + WHERE 1=1 and C.Special = 0 "; + //TODO 记得移除特殊表过滤 - //if (!string.IsNullOrWhiteSpace(gatherCode)) - //{ - // sql = $@"{sql} AND A.GatherCode = '{gatherCode}'"; - //} - //return await SqlProvider.Instance.Change(DbEnum.EnergyDB) - // .Ado - // .QueryAsync(sql); + if (!string.IsNullOrWhiteSpace(gatherCode)) + { + sql = $@"{sql} AND A.GatherCode = '{gatherCode}'"; + } + return await SqlProvider.Instance.Change(DbEnum.EnergyDB) + .Ado + .QueryAsync(sql); } /// diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index 7dbe586..b2f7400 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using DeviceDetectorNET.Parser.Device; using DotNetCore.CAP; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; @@ -62,6 +63,8 @@ namespace JiShe.CollectBus.Subscribers [CapSubscribe(ProtocolConst.SubscriberIssuedEventName)] public async Task IssuedEvent(IssuedEventMessage issuedEventMessage) { + _logger.LogWarning($"集中器地址{issuedEventMessage.ClientId} 回复下发内容IssuedEvent:{issuedEventMessage.MessageId}"); + switch (issuedEventMessage.Type) { case IssuedEventType.Heartbeat: @@ -82,11 +85,14 @@ namespace JiShe.CollectBus.Subscribers default: throw new ArgumentOutOfRangeException(); } - var device = await _deviceRepository.FindAsync(a => a.Number == issuedEventMessage.DeviceNo); - if (device != null) - { - await _tcpService.SendAsync(device.ClientId, issuedEventMessage.Message); - } + + //var device = await _deviceRepository.FindAsync(a => a.Number == issuedEventMessage.DeviceNo); + //if (device != null) + //{ + // await _tcpService.SendAsync(device.ClientId, issuedEventMessage.Message); + //} + + await _tcpService.SendAsync(issuedEventMessage.ClientId, issuedEventMessage.Message); } [CapSubscribe(ProtocolConst.SubscriberReceivedEventName)] diff --git a/src/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs b/src/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs index c31dbc9..a18c9d8 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs @@ -64,6 +64,12 @@ namespace JiShe.CollectBus.IotSystems.Devices Status = DeviceStatus.Online; } + public void UpdateByLoginAndHeartbeat() + { + LastOnlineTime = DateTime.Now; + Status = DeviceStatus.Online; + } + public void UpdateByOnClosed() { LastOfflineTime = DateTime.Now; diff --git a/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj b/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj index 0b178ee..b70752b 100644 --- a/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj +++ b/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj @@ -25,8 +25,8 @@ - - + + diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 50c459b..69bdcc1 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -5,11 +5,11 @@ "Serilog.Sinks.File" ], "MinimumLevel": { - "Default": "Warning", + "Default": "Information", "Override": { - "Microsoft": "Information", + "Microsoft": "Warning", "Volo.Abp": "Warning", - "Hangfire": "Information", + "Hangfire": "Warning", "DotNetCore.CAP": "Warning", "Serilog.AspNetCore": "Information", "Microsoft.EntityFrameworkCore": "Warning", diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index 4638983..978a056 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -602,7 +602,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts /// /// /// - public virtual TB3761FN AnalyzeReadingDataAsync(MessageReceived messageReceived, + public virtual TB3761 AnalyzeReadingDataAsync(MessageReceived messageReceived, Action? sendAction = null) { var hexStringList = messageReceived.MessageHexString.StringToPairs(); @@ -664,7 +664,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts } } - return tb3761Fn; + return tb3761; } /// @@ -673,7 +673,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts /// /// /// - public virtual TB3761FN AnalyzeReadingTdcDataAsync(MessageReceived messageReceived, + public virtual TB3761 AnalyzeReadingTdcDataAsync(MessageReceived messageReceived, Action? sendAction = null) { @@ -717,7 +717,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts } } - return tb3761Fn; + return tb3761; //var freezeDensity = (FreezeDensity)Convert.ToInt32(hexDatas.Skip(5).Take(1)); //var addMinute = 0; //switch (freezeDensity) -- 2.47.2 From 86edf4290afdfa98347b2834bfdd677bcb1d7e61 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Tue, 1 Apr 2025 08:54:51 +0800 Subject: [PATCH 040/139] =?UTF-8?q?=E6=B5=8B=E8=AF=95mongodb=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E5=86=99=E5=85=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 79 ++++++++++++++++++- ...nergySystemScheduledMeterReadingService.cs | 2 +- src/JiShe.CollectBus.Host/appsettings.json | 5 +- 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index bb3c743..ab2abb3 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; using DotNetCore.CAP; +using DotNetCore.CAP.Messages; using FreeSql; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common; @@ -14,6 +15,7 @@ using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.Enums; using JiShe.CollectBus.GatherItem; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; @@ -40,16 +42,18 @@ namespace JiShe.CollectBus.ScheduledMeterReading private readonly ILogger _logger; private readonly ICapPublisher _capBus; private readonly IMeterReadingRecordRepository _meterReadingRecordsRepository; - + private readonly IRepository _deviceRepository; public BasicScheduledMeterReadingService( ILogger logger, ICapPublisher capBus, + IRepository deviceRepository, IMeterReadingRecordRepository meterReadingRecordsRepository) { _capBus = capBus; _logger = logger; _meterReadingRecordsRepository = meterReadingRecordsRepository; + _deviceRepository = deviceRepository; } /// @@ -1058,5 +1062,78 @@ namespace JiShe.CollectBus.ScheduledMeterReading } #endregion + + /// + /// 测试MongoDB插入数据 + /// + /// + public async Task TestBatchMongoDBInsert(int totalRecords = 100_00) + { + int fetchSize = 500; + int processedSize = 4; + var tasks = new List>(); + var timer = Stopwatch.StartNew(); + List devices = new List(); + for (long timestamp = 0; timestamp < totalRecords; timestamp++) + { + var device = new Device(timestamp.ToString(),$"client{timestamp}",DateTime.Now, DateTime.Now, DeviceStatus.Online); + devices.Add(device); + + if (timestamp % fetchSize == 0) + { + await _deviceRepository.InsertManyAsync(devices); + devices = new List(); + } + } + timer.Stop(); + var message = $"批量插入{totalRecords} 条记录,总共耗时:{timer.ElapsedMilliseconds}毫秒"; + _logger.LogError(message); + } + + /// + /// 测试MongoDB插入数据 + /// + /// + public async Task TestBatchMongoDBInsert2(int totalRecords = 100_000) + { + int fetchSize = 500; + int processedSize = 4; + var tasks = new List>(); + var timer = Stopwatch.StartNew(); + List devices = new List(); + for (long timestamp = 0; timestamp < totalRecords; timestamp++) + { + var device = new Device(timestamp.ToString(), $"client{timestamp}", DateTime.Now, DateTime.Now, DeviceStatus.Online); + devices.Add(device); + } + + await _deviceRepository.InsertManyAsync(devices); + + timer.Stop(); + var message = $"批量插入{totalRecords} 条记录,总共耗时:{timer.ElapsedMilliseconds}毫秒"; + _logger.LogError(message); + } + + /// + /// 测试MongoDB插入数据 + /// + /// + public async Task TestSingleMongoDBInsert(int totalRecords = 100_00) + { + int fetchSize = 500; + int processedSize = 4; + var tasks = new List>(); + var timer = Stopwatch.StartNew(); + List devices = new List(); + for (long timestamp = 0; timestamp < totalRecords; timestamp++) + { + var device = new Device(timestamp.ToString(), $"client{timestamp}", DateTime.Now, DateTime.Now, DeviceStatus.Online); + devices.Add(device); + await _deviceRepository.InsertAsync(device); + } + timer.Stop(); + var message = $"单次插入{totalRecords} 条记录,总共耗时:{timer.ElapsedMilliseconds}毫秒"; + _logger.LogError(message); + } } } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 24507fe..5bb19fa 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -28,7 +28,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { public EnergySystemScheduledMeterReadingService(ILogger logger, - ICapPublisher capBus, IMeterReadingRecordRepository _meterReadingRecordsRepository) :base(logger, capBus, _meterReadingRecordsRepository) + ICapPublisher capBus, IMeterReadingRecordRepository _meterReadingRecordsRepository, IRepository deviceRepository) :base(logger, capBus, deviceRepository, _meterReadingRecordsRepository) { } diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 473d79e..26575c4 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -34,16 +34,17 @@ "CorsOrigins": "http://localhost:4200,http://localhost:3100" }, "ConnectionStrings": { - "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", + "Default": "mongodb://admin:lixiao1980@8.148.224.127:27017,8.148.224.21:27017,8.138.38.208:27017/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", //"Kafka": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092", "Kafka": "8.148.227.21:9092,8.148.224.127:9092,8.138.38.208:9092", "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" }, "Redis": { - "Configuration": "118.190.144.92:6379,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + "Configuration": "8.138.38.208:6379,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true,password=lixiao1980", "DefaultDB": "14", "HangfireDB": "15" + }, "Jwt": { "Audience": "JiShe.CollectBus", -- 2.47.2 From 5a294b437c97721f1704315c1c07bdbe29f895da Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Tue, 1 Apr 2025 22:50:34 +0800 Subject: [PATCH 041/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Plugins/TcpMonitor.cs | 27 ++++++++++---- .../BasicScheduledMeterReadingService.cs | 35 +++++++++++++------ ...nergySystemScheduledMeterReadingService.cs | 2 +- .../Subscribers/SubscriberAppService.cs | 7 ++-- .../CollectBusHostModule.Configure.cs | 24 ++++++++----- .../CollectBusHostModule.cs | 4 +-- .../Abstracts/BaseProtocolPlugin.cs | 12 ++++--- 7 files changed, 73 insertions(+), 38 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index b3b43d3..4801404 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -24,7 +24,7 @@ namespace JiShe.CollectBus.Plugins { public partial class TcpMonitor : PluginBase, ITransientDependency, ITcpReceivedPlugin, ITcpConnectingPlugin, ITcpConnectedPlugin, ITcpClosedPlugin { - private readonly ICapPublisher _capBus; + private readonly IPublishEndpoint _producerBus; private readonly ILogger _logger; private readonly IRepository _deviceRepository; private readonly IDistributedCache _ammeterInfoCache; @@ -32,16 +32,16 @@ namespace JiShe.CollectBus.Plugins /// /// /// - /// + /// /// /// /// - public TcpMonitor(ICapPublisher capBus, + public TcpMonitor(IPublishEndpoint producerBus, ILogger logger, IRepository deviceRepository, IDistributedCache ammeterInfoCache) { - _capBus = capBus; + _producerBus = producerBus; _logger = logger; _deviceRepository = deviceRepository; _ammeterInfoCache = ammeterInfoCache; @@ -161,7 +161,9 @@ namespace JiShe.CollectBus.Plugins DeviceNo = deviceNo, MessageId = NewId.NextGuid().ToString() }; - await _capBus.PublishAsync(ProtocolConst.SubscriberReceivedLoginEventName, messageReceivedLoginEvent); + //await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedLoginEventName, messageReceivedLoginEvent); + + await _producerBus.Publish( messageReceivedLoginEvent); } private async Task OnTcpHeartbeatReceived(ITcpSessionClient client, string messageHexString, string deviceNo) @@ -198,12 +200,13 @@ namespace JiShe.CollectBus.Plugins DeviceNo = deviceNo, MessageId = NewId.NextGuid().ToString() }; - await _capBus.PublishAsync(ProtocolConst.SubscriberReceivedHeartbeatEventName, messageReceivedHeartbeatEvent); + //await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedHeartbeatEventName, messageReceivedHeartbeatEvent); + await _producerBus.Publish(messageReceivedHeartbeatEvent); } private async Task OnTcpNormalReceived(ITcpSessionClient client, string messageHexString, string deviceNo) { - await _capBus.PublishAsync(ProtocolConst.SubscriberReceivedEventName, new MessageReceived + await _producerBus.Publish(new MessageReceived { ClientId = client.Id, ClientIp = client.IP, @@ -212,6 +215,16 @@ namespace JiShe.CollectBus.Plugins DeviceNo = deviceNo, MessageId = NewId.NextGuid().ToString() }); + + //await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedEventName, new MessageReceived + //{ + // ClientId = client.Id, + // ClientIp = client.IP, + // ClientPort = client.Port, + // MessageHexString = messageHexString, + // DeviceNo = deviceNo, + // MessageId = NewId.NextGuid().ToString() + //}); } } } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index b1ea2d6..f65e51f 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -39,15 +39,15 @@ namespace JiShe.CollectBus.ScheduledMeterReading public abstract class BasicScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService { private readonly ILogger _logger; - private readonly ICapPublisher _capBus; + private readonly IPublishEndpoint _producerBus; private readonly IMeterReadingRecordRepository _meterReadingRecordsRepository; public BasicScheduledMeterReadingService( ILogger logger, - ICapPublisher capBus, + IPublishEndpoint producerBus, IMeterReadingRecordRepository meterReadingRecordsRepository) { - _capBus = capBus; + _producerBus = producerBus; _logger = logger; _meterReadingRecordsRepository = meterReadingRecordsRepository; } @@ -298,8 +298,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - _ = _capBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); - + //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + _= _producerBus.Publish(tempMsg); + + meterTaskInfosList.Add(ammerterItem.Value); } } @@ -363,7 +365,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - _= _capBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg); + //_= _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg); + + _ = _producerBus.Publish(tempMsg); meterTaskInfosList.Add(ammerterItem.Value); } @@ -433,9 +437,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading TimeDensity = timeDensity.ToString(), }; - _ = _capBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500) ,ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); + //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500) ,ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); - //await _massTransitBus.Publish(tempMsg); + _ = _producerBus.Publish(tempMsg); meterTaskInfosList.Add(ammerterItem.Value); } @@ -823,7 +827,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + //await _producerBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + + _ = _producerBus.Publish(tempMsg); + meterTaskInfosList.Add(ammerterItem.Value); } @@ -889,7 +896,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + //await _producerBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + + _ = _producerBus.Publish(tempMsg); + meterTaskInfosList.Add(ammerterItem.Value); } @@ -954,7 +964,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + //await _producerBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + + _ = _producerBus.Publish(tempMsg); + meterTaskInfosList.Add(ammerterItem.Value); } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 70cecfa..78276e9 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -29,7 +29,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { public EnergySystemScheduledMeterReadingService(ILogger logger, - ICapPublisher capBus, IMeterReadingRecordRepository meterReadingRecordsRepository) : base(logger, capBus, meterReadingRecordsRepository) + IPublishEndpoint producerBus, IMeterReadingRecordRepository meterReadingRecordsRepository) : base(logger, producerBus, meterReadingRecordsRepository) { } diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index b2f7400..242b216 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -62,19 +62,18 @@ namespace JiShe.CollectBus.Subscribers [CapSubscribe(ProtocolConst.SubscriberIssuedEventName)] public async Task IssuedEvent(IssuedEventMessage issuedEventMessage) - { - _logger.LogWarning($"集中器地址{issuedEventMessage.ClientId} 回复下发内容IssuedEvent:{issuedEventMessage.MessageId}"); - + { switch (issuedEventMessage.Type) { case IssuedEventType.Heartbeat: - _logger.LogInformation($"IssuedEvent:{issuedEventMessage.MessageId}"); + _logger.LogWarning($"集中器地址{issuedEventMessage.ClientId} 心跳回复下发内容:{issuedEventMessage.Serialize()}"); var heartbeatEntity = await _messageReceivedHeartbeatEventRepository.GetAsync(a => a.MessageId == issuedEventMessage.MessageId); heartbeatEntity.AckTime = Clock.Now; heartbeatEntity.IsAck = true; await _messageReceivedHeartbeatEventRepository.UpdateAsync(heartbeatEntity); break; case IssuedEventType.Login: + _logger.LogWarning($"集中器地址{issuedEventMessage.ClientId} 登录回复下发内容:{issuedEventMessage.Serialize()}"); var loginEntity = await _messageReceivedLoginEventRepository.GetAsync(a => a.MessageId == issuedEventMessage.MessageId); loginEntity.AckTime = Clock.Now; loginEntity.IsAck = true; diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 374eaa5..f0a02c9 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -21,6 +21,7 @@ using JiShe.CollectBus.Consumers; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.MessageIssueds; +using Confluent.Kafka; namespace JiShe.CollectBus.Host @@ -279,9 +280,13 @@ namespace JiShe.CollectBus.Host /// The configuration. public void ConfigureMassTransit(ServiceConfigurationContext context, IConfiguration configuration) { + + //context.Services.AddSingleton(); + context.Services.AddMassTransit(x => { x.UsingInMemory(); + x.AddConfigureEndpointsCallback((c, name, cfg) => { @@ -308,37 +313,38 @@ namespace JiShe.CollectBus.Host rider.UsingKafka((c, cfg) => { + cfg.Host(configuration.GetConnectionString("Kafka")); cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedHeartbeatEventName, ProtocolConst.SubscriberGroup, configurator => { configurator.ConfigureConsumer(c); - configurator.ConfigureConsumeTopology = false; + configurator.AutoOffsetReset = AutoOffsetReset.Earliest; }); cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedLoginEventName, ProtocolConst.SubscriberGroup, configurator => { configurator.ConfigureConsumer(c); - configurator.ConfigureConsumeTopology = false; + configurator.AutoOffsetReset = AutoOffsetReset.Earliest; }); cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedEventName, ProtocolConst.SubscriberGroup, configurator => { configurator.ConfigureConsumer(c); - configurator.ConfigureConsumeTopology = false; + configurator.AutoOffsetReset = AutoOffsetReset.Earliest; }); cfg.TopicEndpoint(ProtocolConst.SubscriberIssuedEventName, ProtocolConst.SubscriberGroup, configurator => { configurator.ConfigureConsumer(c); - configurator.ConfigureConsumeTopology = false; + configurator.AutoOffsetReset = AutoOffsetReset.Earliest; }); - //cfg.TopicEndpoint(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, ProtocolConst.SubscriberGroup, configurator => - //{ - // configurator.ConfigureConsumer(c); - // configurator.ConfigureConsumeTopology = false; - //}); + cfg.TopicEndpoint(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, ProtocolConst.SubscriberGroup, configurator => + { + configurator.ConfigureConsumer(c); + configurator.AutoOffsetReset = AutoOffsetReset.Earliest; + }); }); }); }); diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs index 6f24b1e..263be23 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs @@ -43,8 +43,8 @@ namespace JiShe.CollectBus.Host ConfigureNetwork(context, configuration); ConfigureJwtAuthentication(context, configuration); ConfigureHangfire(context); - ConfigureCap(context, configuration); - //ConfigureMassTransit(context, configuration); + //ConfigureCap(context, configuration); + ConfigureMassTransit(context, configuration); ConfigureAuditLog(context); ConfigureCustom(context, configuration); } diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index 978a056..2fbe138 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -11,12 +11,13 @@ using JiShe.CollectBus.Protocol.Contracts.AnalysisData; using Microsoft.Extensions.DependencyInjection; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.Protocols; +using MassTransit; namespace JiShe.CollectBus.Protocol.Contracts.Abstracts { public abstract class BaseProtocolPlugin : IProtocolPlugin { - private readonly ICapPublisher _capBus; + private readonly IPublishEndpoint _producerBus; private readonly ILogger _logger; private readonly IRepository _protocolInfoRepository; @@ -36,7 +37,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts _logger = serviceProvider.GetRequiredService>(); _protocolInfoRepository = serviceProvider.GetRequiredService>(); - _capBus = serviceProvider.GetRequiredService(); + _producerBus = serviceProvider.GetRequiredService(); } public abstract ProtocolInfo Info { get; } @@ -86,7 +87,8 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); + //await _producerBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); + await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); } /// @@ -124,7 +126,9 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts Fn = 1 }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); + //await _producerBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); + + await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); } } -- 2.47.2 From 838ef197e27663f5ad6bfc655257e0e4fcbc2979 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Wed, 2 Apr 2025 09:42:04 +0800 Subject: [PATCH 042/139] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E4=B8=BB=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusHostModule.Configure.cs | 55 ++++++++++++------- .../Extensions/ProtocolConstExtensions.cs | 29 ++++++++++ 2 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index f0a02c9..3516bbb 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -22,6 +22,7 @@ using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.MessageIssueds; using Confluent.Kafka; +using MassTransit.SqlTransport.Topology; namespace JiShe.CollectBus.Host @@ -273,6 +274,11 @@ namespace JiShe.CollectBus.Host } + /// + /// Configures the mass transit. + /// + /// The context. + /// The configuration. /// /// Configures the mass transit. /// @@ -280,20 +286,29 @@ namespace JiShe.CollectBus.Host /// The configuration. public void ConfigureMassTransit(ServiceConfigurationContext context, IConfiguration configuration) { + var consumerConfig = new ConsumerConfig { GroupId = ProtocolConst.SubscriberGroup }; + var producerConfig = new ProducerConfig(); - //context.Services.AddSingleton(); - - context.Services.AddMassTransit(x => + context.Services + .ConfigureKafkaTestOptions(options => { - x.UsingInMemory(); - - x.AddConfigureEndpointsCallback((c, name, cfg) => - { - cfg.UseDelayedRedelivery(r => r.Intervals(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(30))); - cfg.UseMessageRetry(r => r.Immediate(5)); - cfg.UseInMemoryOutbox(c); - }); +#if DEBUG + options.CleanTopicsOnStart = true;// 测试时,每次启动都删除topic,生产环境不需要 +#endif + options.CreateTopicsIfNotExists = true; + options.TopicNames = ProtocolConstExtensions.GetAllTopicNames(); + }) + .AddMassTransit(x => + { + x.UsingInMemory((context, cfg) => cfg.ConfigureEndpoints(context)); + + //x.AddConfigureEndpointsCallback((c, name, cfg) => + //{ + // cfg.UseDelayedRedelivery(r => r.Intervals(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(30))); + // cfg.UseMessageRetry(r => r.Immediate(5)); + // cfg.UseInMemoryOutbox(c); + //}); x.AddRider(rider => { @@ -310,37 +325,37 @@ namespace JiShe.CollectBus.Host }); rider.AddConsumer(); - + rider.UsingKafka((c, cfg) => { - - cfg.Host(configuration.GetConnectionString("Kafka")); + List hosts = new List() { "121.42.242.91:29092", "121.42.242.91:39092", "121.42.242.91:49092" }; + cfg.Host(hosts); - cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedHeartbeatEventName, ProtocolConst.SubscriberGroup, configurator => + cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedHeartbeatEventName, consumerConfig, configurator => { - configurator.ConfigureConsumer(c); configurator.AutoOffsetReset = AutoOffsetReset.Earliest; + configurator.ConfigureConsumer(c); }); - cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedLoginEventName, ProtocolConst.SubscriberGroup, configurator => + cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedLoginEventName, consumerConfig, configurator => { configurator.ConfigureConsumer(c); configurator.AutoOffsetReset = AutoOffsetReset.Earliest; }); - cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedEventName, ProtocolConst.SubscriberGroup, configurator => + cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedEventName, consumerConfig, configurator => { configurator.ConfigureConsumer(c); configurator.AutoOffsetReset = AutoOffsetReset.Earliest; }); - cfg.TopicEndpoint(ProtocolConst.SubscriberIssuedEventName, ProtocolConst.SubscriberGroup, configurator => + cfg.TopicEndpoint(ProtocolConst.SubscriberIssuedEventName, consumerConfig, configurator => { configurator.ConfigureConsumer(c); configurator.AutoOffsetReset = AutoOffsetReset.Earliest; }); - cfg.TopicEndpoint(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, ProtocolConst.SubscriberGroup, configurator => + cfg.TopicEndpoint(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, consumerConfig, configurator => { configurator.ConfigureConsumer(c); configurator.AutoOffsetReset = AutoOffsetReset.Earliest; diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs b/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs new file mode 100644 index 0000000..9455144 --- /dev/null +++ b/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts +{ + public class ProtocolConstExtensions + { + /// + /// 自动获取 ProtocolConst 类中所有 Kafka 主题名称 + /// (通过反射筛选 public const string 且字段名以 "EventName" 结尾的常量) + /// + public static List GetAllTopicNames() + { + return typeof(ProtocolConst) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(f => + f.IsLiteral && + !f.IsInitOnly && + f.FieldType == typeof(string) && + f.Name.EndsWith("EventName")) // 通过命名规则过滤主题字段 + .Select(f => (string)f.GetRawConstantValue()!) + .ToList(); + } + } +} -- 2.47.2 From c57bd15b92819204cb8807e08c4b98c039222fc5 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Wed, 2 Apr 2025 14:06:40 +0800 Subject: [PATCH 043/139] =?UTF-8?q?=E5=B0=81=E8=A3=85IoTDBProvider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 7 + .../Attribute/ATTRIBUTEColumnAttribute.cs | 16 ++ .../Attribute/FIELDColumnAttribute.cs | 16 ++ .../Attribute/TAGColumnAttribute.cs | 16 ++ .../CollectBusIoTDBModule.cs | 19 ++ .../DeviceMetadata.cs | 24 ++ .../DevicePathBuilder.cs | 37 +++ .../IIoTDBProvider.cs | 38 +++ .../IoTDBProvider.cs | 262 ++++++++++++++++++ .../IoTEntity.cs | 19 ++ .../JiShe.CollectBus.IoTDBProvider.csproj | 12 + .../Options/IoTDBOptions.cs | 32 +++ .../Options/PagedResult.cs | 25 ++ .../Options/QueryCondition.cs | 27 ++ .../Options/QueryOptions.cs | 27 ++ 15 files changed, 577 insertions(+) create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Attribute/ATTRIBUTEColumnAttribute.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Attribute/FIELDColumnAttribute.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Attribute/TAGColumnAttribute.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/DeviceMetadata.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/DevicePathBuilder.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/IIoTDBProvider.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/IoTDBProvider.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/IoTEntity.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Options/PagedResult.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Options/QueryCondition.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index 674eca4..0a7a6e7 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -31,6 +31,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.FreeSql", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.FreeRedisProvider", "src\JiShe.CollectBus.FreeRedisProvider\JiShe.CollectBus.FreeRedisProvider.csproj", "{C06C4082-638F-2996-5FED-7784475766C1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.IoTDBProvider", "src\JiShe.CollectBus.IoTDBProvider\JiShe.CollectBus.IoTDBProvider.csproj", "{A3F3C092-0A25-450B-BF6A-5983163CBEF5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -89,6 +91,10 @@ Global {C06C4082-638F-2996-5FED-7784475766C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {C06C4082-638F-2996-5FED-7784475766C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {C06C4082-638F-2996-5FED-7784475766C1}.Release|Any CPU.Build.0 = Release|Any CPU + {A3F3C092-0A25-450B-BF6A-5983163CBEF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3F3C092-0A25-450B-BF6A-5983163CBEF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3F3C092-0A25-450B-BF6A-5983163CBEF5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3F3C092-0A25-450B-BF6A-5983163CBEF5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -107,6 +113,7 @@ Global {8BA01C3D-297D-42DF-BD63-EF07202A0A67} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {FE0457D9-4038-4A17-8808-DCAD06CFC0A0} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {C06C4082-638F-2996-5FED-7784475766C1} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {A3F3C092-0A25-450B-BF6A-5983163CBEF5} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Attribute/ATTRIBUTEColumnAttribute.cs b/src/JiShe.CollectBus.IoTDBProvider/Attribute/ATTRIBUTEColumnAttribute.cs new file mode 100644 index 0000000..42820bc --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Attribute/ATTRIBUTEColumnAttribute.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// Column分类标记特性(TAG字段) + /// + [AttributeUsage(AttributeTargets.Property)] + public class TAGColumnAttribute : Attribute + { + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Attribute/FIELDColumnAttribute.cs b/src/JiShe.CollectBus.IoTDBProvider/Attribute/FIELDColumnAttribute.cs new file mode 100644 index 0000000..2c48a65 --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Attribute/FIELDColumnAttribute.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// Column分类标记特性(FIELD字段) + /// + [AttributeUsage(AttributeTargets.Property)] + public class FIELDColumnAttribute : Attribute + { + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Attribute/TAGColumnAttribute.cs b/src/JiShe.CollectBus.IoTDBProvider/Attribute/TAGColumnAttribute.cs new file mode 100644 index 0000000..28f6a46 --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Attribute/TAGColumnAttribute.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// Column分类标记特性(ATTRIBUTE字段) + /// + [AttributeUsage(AttributeTargets.Property)] + public class ATTRIBUTEColumnAttribute : Attribute + { + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs b/src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs new file mode 100644 index 0000000..5a8902c --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Modularity; + +namespace JiShe.CollectBus.IoTDBProvider +{ + public class CollectBusIoTDBModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.Configure(context.Services.GetConfiguration().GetSection(nameof(IoTDBOptions))); + context.Services.AddSingleton(); + } + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/DeviceMetadata.cs b/src/JiShe.CollectBus.IoTDBProvider/DeviceMetadata.cs new file mode 100644 index 0000000..dea65fb --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/DeviceMetadata.cs @@ -0,0 +1,24 @@ +using Apache.IoTDB; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// 设备元数据 + /// + public class DeviceMetadata + { + public List Measurements { get; } = new(); + public List Tags { get; } = new(); + + public List GetDataTypes() + { + // 根据实际类型映射TSDataType + return Measurements.Select(_ => TSDataType.TEXT).ToList(); + } + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/DevicePathBuilder.cs b/src/JiShe.CollectBus.IoTDBProvider/DevicePathBuilder.cs new file mode 100644 index 0000000..ef6d7e4 --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/DevicePathBuilder.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// 设备路径构建器 + /// + public static class DevicePathBuilder + { + /// + /// 构建存储组路径 + /// + /// + /// + public static string BuildStorageGroupPath() where T : IoTEntity + { + var type = typeof(T); + return $"root.{type.GetProperty("SystemName")?.Name}.{type.GetProperty("ProjectCode")?.Name}"; + } + + /// + /// 构建设备路径 + /// + /// + /// + /// + public static string BuildDevicePath(T entity) where T : IoTEntity + { + return $"root.{entity.SystemName}.{entity.ProjectCode}.{entity.DeviceId}"; + } + } + +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/IIoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/IIoTDBProvider.cs new file mode 100644 index 0000000..026a95a --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/IIoTDBProvider.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// IoTDB数据源 + /// + public interface IIoTDBProvider + { + /// + /// 插入数据 + /// + /// + /// + /// + Task InsertAsync(T entity) where T : IoTEntity; + + /// + /// 批量插入数据 + /// + /// + /// + /// + Task BatchInsertAsync(IEnumerable entities) where T : IoTEntity; + + /// + /// 查询数据 + /// + /// + /// + /// + Task> QueryAsync(QueryOptions options) where T : IoTEntity, new(); + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/IoTDBProvider.cs new file mode 100644 index 0000000..f2a5546 --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/IoTDBProvider.cs @@ -0,0 +1,262 @@ +using Apache.IoTDB; +using Apache.IoTDB.DataStructure; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// IoTDB数据源 + /// + public class IoTDBProvider : IIoTDBProvider, IDisposable + { + private readonly IoTDBOptions _options; + private readonly SessionPool _sessionPool; + private static readonly ConcurrentDictionary _metadataCache = new(); + + public IoTDBProvider(IOptions options) + { + _options = options.Value; + _sessionPool = new SessionPool( + _options.ClusterList, + _options.UserName, + _options.Password, + _options.PoolSize); + _sessionPool.Open(false).Wait(); + } + + /// + /// 获取设备元数据 + /// + /// + /// + private DeviceMetadata GetMetadata() where T : IoTEntity + { + return _metadataCache.GetOrAdd(typeof(T), type => + { + var metadata = new DeviceMetadata(); + foreach (var prop in type.GetProperties()) + { + var attr = prop.GetCustomAttribute(); + if (attr != null) + { + metadata.Tags.Add(prop.Name); + } + else if (prop.Name != nameof(IoTEntity.Timestamp)) + { + metadata.Measurements.Add(prop.Name); + } + } + return metadata; + }); + } + + /// + /// 插入数据 + /// + /// + /// + /// + public async Task InsertAsync(T entity) where T : IoTEntity + { + var metadata = GetMetadata(); + var storageGroup = DevicePathBuilder.BuildStorageGroupPath(); + await EnsureStorageGroupCreated(storageGroup); + + var tablet = BuildTablet(new[] { entity }, metadata); + await _sessionPool.InsertAlignedTabletAsync(tablet); + } + + /// + /// 批量插入数据 + /// + /// + /// + /// + public async Task BatchInsertAsync(IEnumerable entities) where T : IoTEntity + { + var metadata = GetMetadata(); + var storageGroup = DevicePathBuilder.BuildStorageGroupPath(); + await EnsureStorageGroupCreated(storageGroup); + + var batchSize = 1000; + var batches = entities.Chunk(batchSize); + + foreach (var batch in batches) + { + var tablet = BuildTablet(batch, metadata); + await _sessionPool.InsertAlignedTabletAsync(tablet); + } + } + + /// + /// 构建表模型 + /// + /// + /// + /// + /// + private Tablet BuildTablet(IEnumerable entities, DeviceMetadata metadata) where T : IoTEntity + { + var devicePath = DevicePathBuilder.BuildDevicePath(entities.First()); + var timestamps = new List(); + var values = new List>(); + + foreach (var entity in entities) + { + timestamps.Add(entity.Timestamp); + var rowValues = new List(); + foreach (var measurement in metadata.Measurements) + { + var value = typeof(T).GetProperty(measurement)?.GetValue(entity); + rowValues.Add(value ?? DBNull.Value); + } + values.Add(rowValues); + } + + return new Tablet( + devicePath, + metadata.Measurements, + metadata.GetDataTypes(), + values, + timestamps + ) + { + Tags = metadata.Tags.ToDictionary( + t => t, + t => typeof(T).GetProperty(t)?.GetValue(entities.First())?.ToString()) + }; + } + + /// + /// 查询数据 + /// + /// + /// + /// + public async Task> QueryAsync(QueryOptions options) where T : IoTEntity, new() + { + var query = BuildQuery(options); + var sessionDataSet = await _sessionPool.ExecuteQueryStatementAsync(query); + + var result = new PagedResult + { + TotalCount = await GetTotalCount(options), + Items = ParseResults(sessionDataSet, options.PageSize) + }; + + return result; + } + + /// + /// 构建查询语句 + /// + /// + /// + /// + private string BuildQuery(QueryOptions options) where T : IoTEntity + { + var metadata = GetMetadata(); + var sb = new StringBuilder("SELECT "); + sb.AppendJoin(", ", metadata.Measurements); + sb.Append($" FROM {DevicePathBuilder.BuildStorageGroupPath()}"); + + if (options.Conditions.Any()) + { + sb.Append(" WHERE "); + sb.AppendJoin(" AND ", options.Conditions.Select(TranslateCondition)); + } + + sb.Append($" LIMIT {options.PageSize} OFFSET {options.Page * options.PageSize}"); + return sb.ToString(); + } + + /// + /// 将查询条件转换为SQL语句 + /// + /// + /// + /// + private string TranslateCondition(QueryCondition condition) + { + return condition.Operator switch + { + ">" => $"{condition.Field} > {condition.Value}", + "<" => $"{condition.Field} < {condition.Value}", + "=" => $"{condition.Field} = '{condition.Value}'", + _ => throw new NotSupportedException($"Operator {condition.Operator} not supported") + }; + } + + /// + /// 获取查询条件的总数量 + /// + /// + /// + /// + private async Task GetTotalCount(QueryOptions options) where T : IoTEntity + { + var countQuery = $"SELECT COUNT(*) FROM {DevicePathBuilder.BuildStorageGroupPath()}"; + if (options.Conditions.Any()) + { + countQuery += " WHERE " + string.Join(" AND ", options.Conditions.Select(TranslateCondition)); + } + + var result = await _sessionPool.ExecuteQueryStatementAsync(countQuery); + return result.HasNext() ? Convert.ToInt32(result.Next().Values[0]) : 0; + } + + /// + /// 解析查询结果 + /// + /// + /// + /// + /// + private IEnumerable ParseResults(SessionDataSet dataSet, int pageSize) where T : IoTEntity, new() + { + var results = new List(); + var metadata = GetMetadata(); + + while (dataSet.HasNext() && results.Count < pageSize) + { + var record = dataSet.Next(); + var entity = new T + { + Timestamp = record.Timestamps + }; + + foreach (var measurement in metadata.Measurements) + { + var value = record.GetValue(measurement); + typeof(T).GetProperty(measurement)?.SetValue(entity, value); + } + + results.Add(entity); + } + return results; + } + + private async Task EnsureStorageGroupCreated(string storageGroup) + { + if (!await _sessionPool.CheckStorageGroupExists(storageGroup)) + { + await _sessionPool.SetStorageGroupAsync(storageGroup); + } + } + + /// + /// 释放资源 + /// + public void Dispose() + { + _sessionPool?.Close().Wait(); + } + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/IoTEntity.cs b/src/JiShe.CollectBus.IoTDBProvider/IoTEntity.cs new file mode 100644 index 0000000..7c277df --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/IoTEntity.cs @@ -0,0 +1,19 @@ +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// IoT实体基类 + /// + public abstract class IoTEntity + { + [TAGColumn] + public string SystemName { get; set; } + + [TAGColumn] + public string ProjectCode { get; set; } + + [TAGColumn] + public string DeviceId { get; set; } + + public long Timestamp { get; set; } + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj b/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj new file mode 100644 index 0000000..37504aa --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + enable + enable + + + + + + diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs b/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs new file mode 100644 index 0000000..5cdd256 --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// IOTDB配置 + /// + public class IoTDBOptions + { + /// + /// 集群列表 + /// + public List ClusterList { get; set; } + /// + /// 用户名 + /// + public string UserName { get; set; } + /// + /// 密码 + /// + public string Password { get; set; } + + /// + /// 连接池大小 + /// + public int PoolSize { get; set; } = 3; + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/PagedResult.cs b/src/JiShe.CollectBus.IoTDBProvider/Options/PagedResult.cs new file mode 100644 index 0000000..b707355 --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Options/PagedResult.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// 查询结果 + /// + /// + public class PagedResult + { + /// + /// 总条数 + /// + public int TotalCount { get; set; } + + /// + /// 数据集合 + /// + public IEnumerable Items { get; set; } + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/QueryCondition.cs b/src/JiShe.CollectBus.IoTDBProvider/Options/QueryCondition.cs new file mode 100644 index 0000000..b1aa53b --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Options/QueryCondition.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// 查询条件 + /// + public class QueryCondition + { + /// + /// 字段 + /// + public string Field { get; set; } + /// + /// 操作符 + /// + public string Operator { get; set; } + /// + /// 值 + /// + public object Value { get; set; } + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs b/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs new file mode 100644 index 0000000..019bd28 --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// 查询条件 + /// + public class QueryOptions + { + /// + /// 分页 + /// + public int Page { get; set; } + /// + /// 分页大小 + /// + public int PageSize { get; set; } + /// + /// 查询条件 + /// + public List Conditions { get; } = new(); + } +} -- 2.47.2 From a5f806c481be56fa1b84dfe9b309ce18c6cb20a1 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Wed, 2 Apr 2025 17:23:52 +0800 Subject: [PATCH 044/139] =?UTF-8?q?IoTDB=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Plugins/TcpMonitor.cs | 1 - .../Attribute/ATTRIBUTEColumnAttribute.cs | 6 +- .../Attribute/FIELDColumnAttribute.cs | 2 +- .../Attribute/TAGColumnAttribute.cs | 4 +- .../DeviceMetadata.cs | 24 ---- .../{ => Interface}/IIoTDBProvider.cs | 0 .../IoTEntity.cs | 19 --- .../JiShe.CollectBus.IoTDBProvider.csproj | 3 +- .../Provider/DeviceMetadata.cs | 30 +++++ .../{ => Provider}/DevicePathBuilder.cs | 26 ++-- .../{ => Provider}/IoTDBProvider.cs | 116 +++++++++++------- .../Provider/IoTEntity.cs | 37 ++++++ 12 files changed, 163 insertions(+), 105 deletions(-) delete mode 100644 src/JiShe.CollectBus.IoTDBProvider/DeviceMetadata.cs rename src/JiShe.CollectBus.IoTDBProvider/{ => Interface}/IIoTDBProvider.cs (100%) delete mode 100644 src/JiShe.CollectBus.IoTDBProvider/IoTEntity.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs rename src/JiShe.CollectBus.IoTDBProvider/{ => Provider}/DevicePathBuilder.cs (72%) rename src/JiShe.CollectBus.IoTDBProvider/{ => Provider}/IoTDBProvider.cs (70%) create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index 4801404..7083387 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -57,7 +57,6 @@ namespace JiShe.CollectBus.Plugins if (aFn.HasValue && fn.HasValue && aTuple != null && !string.IsNullOrWhiteSpace(aTuple.Item1)) { var tcpSessionClient = (ITcpSessionClient)client; - if ((AFN)aFn == AFN.链路接口检测) { diff --git a/src/JiShe.CollectBus.IoTDBProvider/Attribute/ATTRIBUTEColumnAttribute.cs b/src/JiShe.CollectBus.IoTDBProvider/Attribute/ATTRIBUTEColumnAttribute.cs index 42820bc..1780bf9 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Attribute/ATTRIBUTEColumnAttribute.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Attribute/ATTRIBUTEColumnAttribute.cs @@ -5,12 +5,12 @@ using System.Text; using System.Threading.Tasks; namespace JiShe.CollectBus.IoTDBProvider -{ +{ /// - /// Column分类标记特性(TAG字段) + /// Column分类标记特性(ATTRIBUTE字段),也就是属性字段 /// [AttributeUsage(AttributeTargets.Property)] - public class TAGColumnAttribute : Attribute + public class ATTRIBUTEColumnAttribute : Attribute { } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Attribute/FIELDColumnAttribute.cs b/src/JiShe.CollectBus.IoTDBProvider/Attribute/FIELDColumnAttribute.cs index 2c48a65..2f8470b 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Attribute/FIELDColumnAttribute.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Attribute/FIELDColumnAttribute.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.IoTDBProvider { /// - /// Column分类标记特性(FIELD字段) + /// Column分类标记特性(FIELD字段),数据列字段 /// [AttributeUsage(AttributeTargets.Property)] public class FIELDColumnAttribute : Attribute diff --git a/src/JiShe.CollectBus.IoTDBProvider/Attribute/TAGColumnAttribute.cs b/src/JiShe.CollectBus.IoTDBProvider/Attribute/TAGColumnAttribute.cs index 28f6a46..b6149fe 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Attribute/TAGColumnAttribute.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Attribute/TAGColumnAttribute.cs @@ -7,10 +7,10 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.IoTDBProvider { /// - /// Column分类标记特性(ATTRIBUTE字段) + /// Column分类标记特性(TAG字段),标签字段 /// [AttributeUsage(AttributeTargets.Property)] - public class ATTRIBUTEColumnAttribute : Attribute + public class TAGColumnAttribute : Attribute { } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/DeviceMetadata.cs b/src/JiShe.CollectBus.IoTDBProvider/DeviceMetadata.cs deleted file mode 100644 index dea65fb..0000000 --- a/src/JiShe.CollectBus.IoTDBProvider/DeviceMetadata.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Apache.IoTDB; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.IoTDBProvider -{ - /// - /// 设备元数据 - /// - public class DeviceMetadata - { - public List Measurements { get; } = new(); - public List Tags { get; } = new(); - - public List GetDataTypes() - { - // 根据实际类型映射TSDataType - return Measurements.Select(_ => TSDataType.TEXT).ToList(); - } - } -} diff --git a/src/JiShe.CollectBus.IoTDBProvider/IIoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs similarity index 100% rename from src/JiShe.CollectBus.IoTDBProvider/IIoTDBProvider.cs rename to src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs diff --git a/src/JiShe.CollectBus.IoTDBProvider/IoTEntity.cs b/src/JiShe.CollectBus.IoTDBProvider/IoTEntity.cs deleted file mode 100644 index 7c277df..0000000 --- a/src/JiShe.CollectBus.IoTDBProvider/IoTEntity.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace JiShe.CollectBus.IoTDBProvider -{ - /// - /// IoT实体基类 - /// - public abstract class IoTEntity - { - [TAGColumn] - public string SystemName { get; set; } - - [TAGColumn] - public string ProjectCode { get; set; } - - [TAGColumn] - public string DeviceId { get; set; } - - public long Timestamp { get; set; } - } -} diff --git a/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj b/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj index 37504aa..717b0f6 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj +++ b/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj @@ -6,7 +6,8 @@ enable - + + diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs new file mode 100644 index 0000000..8e84776 --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs @@ -0,0 +1,30 @@ +using Apache.IoTDB; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// 设备元数据 + /// + public class DeviceMetadata + { + /// + /// 测量值集合,用于构建Table的测量值,也就是field参数 + /// + public List Measurements { get; } = new(); + + /// + /// 列类型集合,用于构建Table的列类型,也就是columnCategory参数 + /// + public List ColumnCategories { get; } = new(); + + /// + /// 值类型集合,用于构建Table的值类型,也就是dataType参数 + /// + public ListDataTypes { get; } = new(); + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/DevicePathBuilder.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs similarity index 72% rename from src/JiShe.CollectBus.IoTDBProvider/DevicePathBuilder.cs rename to src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs index ef6d7e4..3166ca6 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/DevicePathBuilder.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs @@ -11,27 +11,29 @@ namespace JiShe.CollectBus.IoTDBProvider /// public static class DevicePathBuilder { - /// - /// 构建存储组路径 - /// - /// - /// - public static string BuildStorageGroupPath() where T : IoTEntity - { - var type = typeof(T); - return $"root.{type.GetProperty("SystemName")?.Name}.{type.GetProperty("ProjectCode")?.Name}"; - } - /// /// 构建设备路径 /// /// /// /// - public static string BuildDevicePath(T entity) where T : IoTEntity + public static string GetDeviceId(T entity) where T : IoTEntity { return $"root.{entity.SystemName}.{entity.ProjectCode}.{entity.DeviceId}"; } + + + /// + /// 获取表名称 + /// + /// + /// + /// + public static string GetTableName() where T : IoTEntity + { + var type = typeof(T); + return $"{type.Name}"; + } } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs similarity index 70% rename from src/JiShe.CollectBus.IoTDBProvider/IoTDBProvider.cs rename to src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs index f2a5546..449884a 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/IoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs @@ -17,17 +17,20 @@ namespace JiShe.CollectBus.IoTDBProvider public class IoTDBProvider : IIoTDBProvider, IDisposable { private readonly IoTDBOptions _options; - private readonly SessionPool _sessionPool; + private readonly TableSessionPool _sessionPool; private static readonly ConcurrentDictionary _metadataCache = new(); public IoTDBProvider(IOptions options) { _options = options.Value; - _sessionPool = new SessionPool( - _options.ClusterList, - _options.UserName, - _options.Password, - _options.PoolSize); + + _sessionPool = new TableSessionPool.Builder() + .SetNodeUrls(_options.ClusterList) + .SetUsername(_options.UserName) + .SetPassword(_options.Password) + .SetFetchSize(_options.PoolSize) + .Build(); + _sessionPool.Open(false).Wait(); } @@ -43,14 +46,27 @@ namespace JiShe.CollectBus.IoTDBProvider var metadata = new DeviceMetadata(); foreach (var prop in type.GetProperties()) { - var attr = prop.GetCustomAttribute(); - if (attr != null) + //标签列 + var attrTAG = prop.GetCustomAttribute(); + if (attrTAG != null) { - metadata.Tags.Add(prop.Name); + metadata.ColumnCategories.Add(ColumnCategory.TAG); } - else if (prop.Name != nameof(IoTEntity.Timestamp)) + + //属性列 + var attrATTRIBUTE = prop.GetCustomAttribute(); + if (attrATTRIBUTE != null) { + metadata.ColumnCategories.Add(ColumnCategory.ATTRIBUTE); + } + + //数据列 + var attrFIELD = prop.GetCustomAttribute(); + if (attrFIELD != null) + { + metadata.ColumnCategories.Add(ColumnCategory.FIELD); metadata.Measurements.Add(prop.Name); + metadata.DataTypes.Add(GetDataTypeFromStr(prop.PropertyType.Name)); } } return metadata; @@ -66,11 +82,9 @@ namespace JiShe.CollectBus.IoTDBProvider public async Task InsertAsync(T entity) where T : IoTEntity { var metadata = GetMetadata(); - var storageGroup = DevicePathBuilder.BuildStorageGroupPath(); - await EnsureStorageGroupCreated(storageGroup); var tablet = BuildTablet(new[] { entity }, metadata); - await _sessionPool.InsertAlignedTabletAsync(tablet); + await _sessionPool.InsertAsync(tablet); } /// @@ -81,9 +95,7 @@ namespace JiShe.CollectBus.IoTDBProvider /// public async Task BatchInsertAsync(IEnumerable entities) where T : IoTEntity { - var metadata = GetMetadata(); - var storageGroup = DevicePathBuilder.BuildStorageGroupPath(); - await EnsureStorageGroupCreated(storageGroup); + var metadata = GetMetadata(); var batchSize = 1000; var batches = entities.Chunk(batchSize); @@ -91,7 +103,7 @@ namespace JiShe.CollectBus.IoTDBProvider foreach (var batch in batches) { var tablet = BuildTablet(batch, metadata); - await _sessionPool.InsertAlignedTabletAsync(tablet); + await _sessionPool.InsertAsync(tablet); } } @@ -104,34 +116,33 @@ namespace JiShe.CollectBus.IoTDBProvider /// private Tablet BuildTablet(IEnumerable entities, DeviceMetadata metadata) where T : IoTEntity { - var devicePath = DevicePathBuilder.BuildDevicePath(entities.First()); + var deviceId = DevicePathBuilder.GetDeviceId(entities.First()); var timestamps = new List(); var values = new List>(); foreach (var entity in entities) { - timestamps.Add(entity.Timestamp); + timestamps.Add(entity.Timestamps); var rowValues = new List(); foreach (var measurement in metadata.Measurements) { var value = typeof(T).GetProperty(measurement)?.GetValue(entity); - rowValues.Add(value ?? DBNull.Value); + if(value == null) + { + throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,属性{measurement}值为空,不符合IoTDB设计标准,请赋值以后重新处理。"); + } + rowValues.Add(value); } values.Add(rowValues); } return new Tablet( - devicePath, + deviceId, metadata.Measurements, - metadata.GetDataTypes(), + metadata.DataTypes, values, timestamps - ) - { - Tags = metadata.Tags.ToDictionary( - t => t, - t => typeof(T).GetProperty(t)?.GetValue(entities.First())?.ToString()) - }; + ); } /// @@ -165,7 +176,7 @@ namespace JiShe.CollectBus.IoTDBProvider var metadata = GetMetadata(); var sb = new StringBuilder("SELECT "); sb.AppendJoin(", ", metadata.Measurements); - sb.Append($" FROM {DevicePathBuilder.BuildStorageGroupPath()}"); + sb.Append($" FROM {DevicePathBuilder.GetTableName()}"); if (options.Conditions.Any()) { @@ -202,7 +213,7 @@ namespace JiShe.CollectBus.IoTDBProvider /// private async Task GetTotalCount(QueryOptions options) where T : IoTEntity { - var countQuery = $"SELECT COUNT(*) FROM {DevicePathBuilder.BuildStorageGroupPath()}"; + var countQuery = $"SELECT COUNT(*) FROM {DevicePathBuilder.GetTableName()}"; if (options.Conditions.Any()) { countQuery += " WHERE " + string.Join(" AND ", options.Conditions.Select(TranslateCondition)); @@ -224,33 +235,35 @@ namespace JiShe.CollectBus.IoTDBProvider var results = new List(); var metadata = GetMetadata(); + var properties = typeof(T).GetProperties(); + while (dataSet.HasNext() && results.Count < pageSize) { var record = dataSet.Next(); var entity = new T { - Timestamp = record.Timestamps + Timestamps = record.Timestamps }; + foreach (var measurement in metadata.Measurements) { - var value = record.GetValue(measurement); - typeof(T).GetProperty(measurement)?.SetValue(entity, value); + var value = record.Values; + + var prop = properties.FirstOrDefault(p => + p.Name.Equals(measurement, StringComparison.OrdinalIgnoreCase)); + if (prop != null) + { + typeof(T).GetProperty(measurement)?.SetValue(entity, value); + } + } results.Add(entity); } return results; } - - private async Task EnsureStorageGroupCreated(string storageGroup) - { - if (!await _sessionPool.CheckStorageGroupExists(storageGroup)) - { - await _sessionPool.SetStorageGroupAsync(storageGroup); - } - } - + /// /// 释放资源 /// @@ -258,5 +271,24 @@ namespace JiShe.CollectBus.IoTDBProvider { _sessionPool?.Close().Wait(); } + + private TSDataType GetDataTypeFromStr(string str) + { + return str switch + { + "BOOLEAN" => TSDataType.BOOLEAN, + "INT32" => TSDataType.INT32, + "INT64" => TSDataType.INT64, + "FLOAT" => TSDataType.FLOAT, + "DOUBLE" => TSDataType.DOUBLE, + "TEXT" => TSDataType.TEXT, + "NULLTYPE" => TSDataType.NONE, + "TIMESTAMP" => TSDataType.TIMESTAMP, + "DATE" => TSDataType.DATE, + "BLOB" => TSDataType.BLOB, + "STRING" => TSDataType.STRING, + _ => TSDataType.STRING + }; + } } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs new file mode 100644 index 0000000..2b090de --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs @@ -0,0 +1,37 @@ +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// IoT实体基类 + /// + public abstract class IoTEntity + { + /// + /// 系统名称 + /// + [TAGColumn] + public string SystemName { get; set; } + + /// + /// 项目编码 + /// + [TAGColumn] + public string ProjectCode { get; set; } + + /// + /// 设备类型集中器、电表、水表、流量计、传感器等 + /// + [TAGColumn] + public string DeviceType { get; set; } + + /// + /// 设备ID + /// + [TAGColumn] + public string DeviceId { get; set; } + + /// + /// 时间戳 + /// + public long Timestamps { get; set; } + } +} -- 2.47.2 From e1d4126db08262f4247418cea8cdc350003b51dc Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Wed, 2 Apr 2025 17:46:33 +0800 Subject: [PATCH 045/139] =?UTF-8?q?iotdb=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusApplicationModule.cs | 2 ++ .../Samples/SampleAppService.cs | 30 ++++++++++++++++- .../Ammeters/ElectricityMeter.cs | 24 ++++++++++++++ .../JiShe.CollectBus.Domain.csproj | 1 + .../CollectBusHostModule.Configure.cs | 16 +++++----- src/JiShe.CollectBus.Host/appsettings.json | 32 +++++++++++-------- .../Provider/IoTEntity.cs | 4 +-- 7 files changed, 85 insertions(+), 24 deletions(-) create mode 100644 src/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 86b36e7..085ab8e 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -15,6 +15,7 @@ using JiShe.CollectBus.Workers; using Volo.Abp.BackgroundWorkers.Hangfire; using JiShe.CollectBus.MongoDB; using JiShe.CollectBus.ScheduledMeterReading; +using JiShe.CollectBus.IoTDBProvider; namespace JiShe.CollectBus; @@ -25,6 +26,7 @@ namespace JiShe.CollectBus; typeof(AbpAutoMapperModule), typeof(AbpBackgroundWorkersHangfireModule), typeof(CollectBusFreeRedisModule), + typeof(CollectBusIoTDBModule), typeof(CollectBusFreeSqlModule) )] public class CollectBusApplicationModule : AbpModule diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 6235a55..f76ce54 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -1,6 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; +using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.FreeSql; +using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.PrepayModel; using Microsoft.AspNetCore.Authorization; @@ -8,6 +11,31 @@ namespace JiShe.CollectBus.Samples; public class SampleAppService : CollectBusAppService, ISampleAppService { + + private readonly IIoTDBProvider _iotDBProvider; + + public SampleAppService(IIoTDBProvider iotDBProvider) + { + _iotDBProvider = iotDBProvider; + } + + + public async Task AddReadingAsync() + { + ElectricityMeter meter = new ElectricityMeter() + { + SystemName = "Energy", + DeviceId = "402440506" + , + DeviceType = "Ammeter", + Current = 10, + MeterModel = "DDZY-1980", + ProjectCode = "10059", + Voltage = 10 + }; + await _iotDBProvider.InsertAsync(meter); + } + public Task GetAsync() { return Task.FromResult( diff --git a/src/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs b/src/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs new file mode 100644 index 0000000..d335fca --- /dev/null +++ b/src/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs @@ -0,0 +1,24 @@ +using JiShe.CollectBus.IoTDBProvider; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Ammeters +{ + public class ElectricityMeter : IoTEntity + { + [ATTRIBUTEColumn] + public string MeterModel { get; set; } + + [FIELDColumn] + public double Voltage { get; set; } + + [FIELDColumn] + public double Current { get; set; } + + [FIELDColumn] + public double Power => Voltage * Current; + } +} diff --git a/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj b/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj index 20068a5..16eef7f 100644 --- a/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj +++ b/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj @@ -24,6 +24,7 @@ + diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 3516bbb..40f498e 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -290,15 +290,15 @@ namespace JiShe.CollectBus.Host var producerConfig = new ProducerConfig(); context.Services - .ConfigureKafkaTestOptions(options => - { +// .ConfigureKafkaTestOptions(options => +// { -#if DEBUG - options.CleanTopicsOnStart = true;// 测试时,每次启动都删除topic,生产环境不需要 -#endif - options.CreateTopicsIfNotExists = true; - options.TopicNames = ProtocolConstExtensions.GetAllTopicNames(); - }) +//#if DEBUG +// options.CleanTopicsOnStart = true;// 测试时,每次启动都删除topic,生产环境不需要 +//#endif +// options.CreateTopicsIfNotExists = true; +// options.TopicNames = ProtocolConstExtensions.GetAllTopicNames(); +// }) .AddMassTransit(x => { x.UsingInMemory((context, cfg) => cfg.ConfigureEndpoints(context)); diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 69bdcc1..b506272 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -1,4 +1,4 @@ - { +{ "Serilog": { "Using": [ "Serilog.Sinks.Console", @@ -20,7 +20,7 @@ { "Name": "Console" }, - { + { "Name": "File", "Args": { "path": "logs/logs-.txt", @@ -33,17 +33,17 @@ "SelfUrl": "http://localhost:44315", "CorsOrigins": "http://localhost:4200,http://localhost:3100" }, - "ConnectionStrings": { - "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", - "Kafka": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092", - "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", - "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" - }, - "Redis": { - "Configuration": "118.190.144.92:6379,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", - "DefaultDB": "14", - "HangfireDB": "15" - }, + "ConnectionStrings": { + "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", + "Kafka": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092", + "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", + "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" + }, + "Redis": { + "Configuration": "118.190.144.92:6379,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + "DefaultDB": "14", + "HangfireDB": "15" + }, "Jwt": { "Audience": "JiShe.CollectBus", "SecurityKey": "dzehzRz9a8asdfasfdadfasdfasdfafsdadfasbasdf=", @@ -80,5 +80,11 @@ "Password": "123456", "Port": 5672 } + }, + "IoTDBOptions": { + "UserName": "root", + "Password": "root", + "ClusterList": [ "192.168.56.102:6667"], + "PoolSize": 10 } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs index 2b090de..45964fa 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs @@ -30,8 +30,8 @@ public string DeviceId { get; set; } /// - /// 时间戳 + /// 当前时间戳,单位秒 /// - public long Timestamps { get; set; } + public long Timestamps { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); } } -- 2.47.2 From 80355a409ab2b2a9f783598a153b04f19aacf394 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Wed, 2 Apr 2025 17:54:12 +0800 Subject: [PATCH 046/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 7 +++ .../Workers/CreateToBeIssueTaskWorker.cs | 2 - .../Workers/SubscriberFifteenMinuteWorker.cs | 2 - .../Workers/SubscriberFiveMinuteWorker.cs | 3 - .../Workers/SubscriberOneMinuteWorker.cs | 2 - .../JiShe.CollectBus.DbMigrator.csproj | 6 +- src/JiShe.CollectBus.Host/appsettings.json | 10 ++-- .../CollectBusKafkaModule.cs | 11 ++++ .../ConsumerService.cs | 58 +++++++++++++++++++ .../JiShe.CollectBus.Kafka.csproj | 14 +++++ .../ProducerService.cs | 28 +++++++++ 11 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/ConsumerService.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj create mode 100644 src/JiShe.CollectBus.KafkaProducer/ProducerService.cs diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index 674eca4..62bfe8b 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -31,6 +31,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.FreeSql", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.FreeRedisProvider", "src\JiShe.CollectBus.FreeRedisProvider\JiShe.CollectBus.FreeRedisProvider.csproj", "{C06C4082-638F-2996-5FED-7784475766C1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka", "src\JiShe.CollectBus.KafkaProducer\JiShe.CollectBus.Kafka.csproj", "{919F4CDB-5C82-4371-B209-403B408DA248}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -89,6 +91,10 @@ Global {C06C4082-638F-2996-5FED-7784475766C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {C06C4082-638F-2996-5FED-7784475766C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {C06C4082-638F-2996-5FED-7784475766C1}.Release|Any CPU.Build.0 = Release|Any CPU + {919F4CDB-5C82-4371-B209-403B408DA248}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {919F4CDB-5C82-4371-B209-403B408DA248}.Debug|Any CPU.Build.0 = Debug|Any CPU + {919F4CDB-5C82-4371-B209-403B408DA248}.Release|Any CPU.ActiveCfg = Release|Any CPU + {919F4CDB-5C82-4371-B209-403B408DA248}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -107,6 +113,7 @@ Global {8BA01C3D-297D-42DF-BD63-EF07202A0A67} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {FE0457D9-4038-4A17-8808-DCAD06CFC0A0} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {C06C4082-638F-2996-5FED-7784475766C1} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {919F4CDB-5C82-4371-B209-403B408DA248} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs b/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs index c3b4886..b936bbb 100644 --- a/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs @@ -34,9 +34,7 @@ namespace JiShe.CollectBus.Workers public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { - _logger.LogWarning($"构建待处理的下发指令任务处理开始"); //await _scheduledMeterReadingService.CreateToBeIssueTasks(); - _logger.LogWarning($"构建待处理的下发指令任务处理结束"); } } } diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs b/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs index 9be418a..f1bf5a1 100644 --- a/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs @@ -33,11 +33,9 @@ namespace JiShe.CollectBus.Workers public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { - _logger.LogWarning($"15分钟采集数据开始"); //await _scheduledMeterReadingService.AmmeterScheduledMeterFifteenMinuteReading(); //await _scheduledMeterReadingService.WatermeterScheduledMeterFifteenMinuteReading(); - _logger.LogWarning($"15分钟采集数据结束"); //using (var uow = LazyServiceProvider.LazyGetRequiredService().Begin()) //{ // Logger.LogInformation("Executed MyLogWorker..!"); diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs b/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs index 94e323d..2e491d6 100644 --- a/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs @@ -33,11 +33,8 @@ namespace JiShe.CollectBus.Workers public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { - _logger.LogWarning($"5分钟采集数据开始"); //await _scheduledMeterReadingService.AmmeterScheduledMeterFiveMinuteReading(); //await _scheduledMeterReadingService.WatermeterScheduledMeterFiveMinuteReading(); - - _logger.LogWarning($"5分钟采集数据结束"); } } } diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs b/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs index d7c8325..82b979b 100644 --- a/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs @@ -33,12 +33,10 @@ namespace JiShe.CollectBus.Workers public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { - _logger.LogWarning($"1分钟采集数据开始"); //await _scheduledMeterReadingService.AmmeterScheduledMeterOneMinuteReading(); //await _scheduledMeterReadingService.WatermeterScheduledMeterOneMinuteReading(); - _logger.LogWarning($"1分钟采集数据结束"); } } } diff --git a/src/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.csproj b/src/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.csproj index a333f5a..e163f7e 100644 --- a/src/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.csproj +++ b/src/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.csproj @@ -10,9 +10,9 @@ - - - + + + diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 26575c4..0936310 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -34,14 +34,14 @@ "CorsOrigins": "http://localhost:4200,http://localhost:3100" }, "ConnectionStrings": { - "Default": "mongodb://admin:lixiao1980@8.148.224.127:27017,8.148.224.21:27017,8.138.38.208:27017/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", - //"Kafka": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092", - "Kafka": "8.148.227.21:9092,8.148.224.127:9092,8.138.38.208:9092", + "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", + "Kafka": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092", + //"Kafka": "8.148.227.21:9092,8.148.224.127:9092,8.138.38.208:9092", "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" }, "Redis": { - "Configuration": "8.138.38.208:6379,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true,password=lixiao1980", + "Configuration": "118.190.144.92:6379,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", "DefaultDB": "14", "HangfireDB": "15" @@ -84,7 +84,7 @@ } }, "Kafka": { - "EnableAuthorization": true, + "EnableAuthorization": false, "SecurityProtocol": "SASL_PLAINTEXT", "SaslMechanism": "PLAIN", "SaslUserName": "lixiao", diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs new file mode 100644 index 0000000..b307155 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Modularity; + +namespace JiShe.CollectBus.KafkaProducer +{ + public class CollectBusKafkaModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + } + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/ConsumerService.cs new file mode 100644 index 0000000..3d1d4e0 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/ConsumerService.cs @@ -0,0 +1,58 @@ +using Confluent.Kafka; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka +{ + public abstract class ConsumerService : BackgroundService + { + private readonly IConsumer _consumer; + + private readonly ILogger> _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The consumer. + /// The logger. + protected ConsumerService(IConfiguration configuration, IConsumer consumer, ILogger> logger) + { + _consumer = consumer; + _logger = logger; + var consumerConfig = new ConsumerConfig + { + BootstrapServers = configuration["Kafka:BootstrapServers"], + GroupId = "InventoryConsumerGroup", + AutoOffsetReset = AutoOffsetReset.Earliest + }; + + _consumer = new ConsumerBuilder(consumerConfig).Build(); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _consumer.Subscribe("InventoryUpdates"); + + while (!stoppingToken.IsCancellationRequested) + { + + var consumeResult = _consumer.Consume(stoppingToken); + + var message = consumeResult.Message.Value; + + await ProcessMessageAsync(consumeResult); + } + + _consumer.Close(); + await Task.CompletedTask; + } + protected abstract Task ProcessMessageAsync(ConsumeResult consumer); + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj b/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj new file mode 100644 index 0000000..72b2660 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/src/JiShe.CollectBus.KafkaProducer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/ProducerService.cs new file mode 100644 index 0000000..6651b59 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/ProducerService.cs @@ -0,0 +1,28 @@ +using Confluent.Kafka; +using Microsoft.Extensions.Configuration; +using Volo.Abp.DependencyInjection; + +namespace JiShe.CollectBus.Kafka +{ + public class ProducerService : ITransientDependency + { + private readonly IProducer _producer; + + public ProducerService(IConfiguration configuration) + { + var producerConfig = new ProducerConfig + { + BootstrapServers = configuration["Kafka:BootstrapServers"] + }; + + _producer = new ProducerBuilder(producerConfig).Build(); + } + + public async Task ProduceAsync(string topic, T message) + { + var msg = new Message { Value = message }; + + await _producer.ProduceAsync(topic, msg); + } + } +} -- 2.47.2 From 5772ce906d9dc0afe838892ea4cd8388fdaeaa97 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 3 Apr 2025 15:38:31 +0800 Subject: [PATCH 047/139] =?UTF-8?q?=E5=AE=8C=E5=96=84IOTDB=20Provider?= =?UTF-8?q?=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/SampleAppService.cs | 5 +- .../AttributeInfo/NumericalOrderAttribute.cs | 28 + .../Extensions/EnumExtensions.cs | 68 ++ .../Helpers/CommonHelper.cs | 764 ++++++++++++++++++ .../Helpers/JsonHelper.cs | 28 + .../Helpers/SelectResult.cs | 35 + .../Interface/IIoTDBProvider.cs | 15 +- .../JiShe.CollectBus.IoTDBProvider.csproj | 3 + .../Options/QueryOptions.cs | 5 + .../Provider/DeviceMetadata.cs | 8 +- .../Provider/IoTDBProvider.cs | 396 ++++++--- 11 files changed, 1252 insertions(+), 103 deletions(-) create mode 100644 src/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs create mode 100644 src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs create mode 100644 src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs create mode 100644 src/JiShe.CollectBus.Common/Helpers/SelectResult.cs diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index f76ce54..9c2a86f 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -25,15 +25,14 @@ public class SampleAppService : CollectBusAppService, ISampleAppService ElectricityMeter meter = new ElectricityMeter() { SystemName = "Energy", - DeviceId = "402440506" - , + DeviceId = "402440506", DeviceType = "Ammeter", Current = 10, MeterModel = "DDZY-1980", ProjectCode = "10059", Voltage = 10 }; - await _iotDBProvider.InsertAsync(meter); + await _iotDBProvider.InsertAsync(meter,2); } public Task GetAsync() diff --git a/src/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs b/src/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs new file mode 100644 index 0000000..a062f16 --- /dev/null +++ b/src/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.AttributeInfo +{ + /// + /// 排序序号 + /// + public class NumericalOrderAttribute : Attribute + { + /// + /// 序号 + /// + public int Index { get; set; } + + /// + /// 排序序号 + /// + /// + public NumericalOrderAttribute(int index) + { + Index = index; + } + } +} diff --git a/src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs b/src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..222984e --- /dev/null +++ b/src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs @@ -0,0 +1,68 @@ +using System; + +namespace JiShe.CollectBus.Common.Extensions +{ + public static class EnumExtensions + { + /// + /// 将枚举转换为字典 + /// + /// + /// + public static Dictionary ToDictionary() where TEnum : Enum + { + return Enum.GetValues(typeof(TEnum)) + .Cast() + .ToDictionary( + e => e.ToString(), + e => Convert.ToInt32(e) + ); + } + + /// + /// 将枚举转换为字典 + /// + /// + /// + public static Dictionary ToEnumDictionary() where TEnum : Enum + { + return Enum.GetValues(typeof(TEnum)) + .Cast() + .ToDictionary( + e => e.ToString(), + e => e + ); + } + + /// + /// 将枚举转换为字典 + /// + /// + /// + public static Dictionary ToValueNameDictionary() where TEnum : Enum + { + return Enum.GetValues(typeof(TEnum)) + .Cast() + .ToDictionary( + e => Convert.ToInt32(e), + e => e.ToString() + ); + } + + /// + /// 将枚举转换为字典 + /// + /// + /// + public static Dictionary ToEnumNameDictionary() where TEnum : Enum + { + return Enum.GetValues(typeof(TEnum)) + .Cast() + .ToDictionary( + e => e, + e => e.ToString() + ); + } + + } +} diff --git a/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs new file mode 100644 index 0000000..fa38d1d --- /dev/null +++ b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs @@ -0,0 +1,764 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using JiShe.CollectBus.Common.AttributeInfo; + +namespace JiShe.CollectBus.Common.Helpers +{ + public static class CommonHelper + { + /// + /// 获得无符号GUID + /// + /// + public static string GetGUID() + { + return Guid.NewGuid().ToString("N"); + } + + /// + /// 获取时间戳 + /// + /// 是否返回秒,false返回毫秒 + /// + public static long GetTimeStampTen(bool isSeconds) + { + if (isSeconds) + { + return DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + } + else + { + return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + } + } + + /// + /// 获取指定长度的随机数 + /// + /// + /// + public static string GetRandomNumber(int length = 8) + { + if (length <= 8) + { + length = 8; + } + + if (length > 31) + { + length = 32; + } + + var randomArray = RandomNumberGenerator.GetBytes(length); + StringBuilder stringBuilder = new StringBuilder(); + foreach (var item in randomArray) + { + stringBuilder.Append(item); + } + + return stringBuilder.ToString().Substring(0, length); + } + + /// + /// C#反射遍历对象属性获取键值对 + /// + /// 对象类型 + /// 对象 + public static Dictionary GetClassProperties(T model) + { + Type t = model.GetType(); + List propertyList = new List(); + + PropertyInfo[] tempPropertyList = t.GetProperties(); + if (tempPropertyList != null && tempPropertyList.Length > 0) + { + propertyList.AddRange(tempPropertyList); + } + + var parentPropertyInfo = t.BaseType?.GetProperties(); + if (parentPropertyInfo != null && parentPropertyInfo.Length > 0) + { + foreach (var item in parentPropertyInfo) + { + if (!propertyList.Any(d => d.Name == item.Name)) //如果子类已经包含父类的属性或者字段,跳过不处理 + { + propertyList.Add(item); + } + } + } + + Dictionary resultData = new Dictionary(); + + foreach (PropertyInfo item in propertyList) + { + resultData.Add(item.Name, item.GetValue(model, null)); + } + + return resultData; + } + + /// + /// C#反射遍历对象属性,将键值对赋值给属性 + /// + public static object SetClassProperties(string typeModel, Dictionary keyValues) + { + if (keyValues.Count <= 0) + { + return null; + } + + Type tType = Type.GetType(typeModel); + + + PropertyInfo[] PropertyList = tType.GetProperties(); + + object objModel = tType.Assembly.CreateInstance(tType.FullName); + foreach (PropertyInfo item in PropertyList) + { + if (keyValues.ContainsKey(item.Name)) + { + object objectValue = keyValues[item.Name]; + item.SetValue(objModel, objectValue); + } + } + + return objModel; + } + + /// + /// 取得某月的第一天 + /// + /// 要取得月份第一天的时间 + /// + public static DateTime FirstDayOfMonth(this DateTime datetime) + { + return datetime.AddDays(1 - datetime.Day); + } + + /// + /// 取得某月的最后一天 + /// + /// 要取得月份最后一天的时间 + /// + public static DateTime LastDayOfMonth(this DateTime datetime) + { + return datetime.AddDays(1 - datetime.Day).AddMonths(1).AddDays(-1); + } + + /// + /// 取得某月第一天0点以及最后一天的23:59:59时间范围 + /// + /// + /// + public static Tuple GetMonthDateRange(this DateTime datetime) + { + var lastDayOfMonthDate = LastDayOfMonth(datetime); + return new Tuple(datetime.FirstDayOfMonth(), new DateTime(lastDayOfMonthDate.Year, lastDayOfMonthDate.Month, lastDayOfMonthDate.Day, 23, 59, 59)); + } + + /// + /// 取得某一天0点到当月最后一天的23:59:59时间范围 + /// + /// + /// + public static Tuple GetCurrentDateToLastDayRange(this DateTime datetime) + { + var lastDayOfMonthDate = LastDayOfMonth(datetime); + return new Tuple(datetime.Date, new DateTime(lastDayOfMonthDate.Year, lastDayOfMonthDate.Month, lastDayOfMonthDate.Day, 23, 59, 59)); + } + + /// + /// 取得某一天0点到23:59:59时间范围 + /// + /// + /// + public static Tuple GetCurrentDateRange(this DateTime datetime) + { + return new Tuple(datetime.Date, new DateTime(datetime.Year, datetime.Month, datetime.Day, 23, 59, 59)); + } + + /// + /// 获取指定枚举的所有 Attribute 说明以及value组成的键值对 + /// + /// 对象类 + /// false获取字段、true获取属性 + /// + public static List GetEnumAttributeList(Type type, bool getPropertie = false) + { + if (type == null) + { + return null; + } + + List selectResults = new List(); + List memberInfos = new List(); + + if (getPropertie == false) + { + FieldInfo[] fieldArray = type.GetFields(); + if (null == fieldArray || fieldArray.Length <= 0) + { + return null; + } + + memberInfos.AddRange(fieldArray); + //获取父类的字段 + var parentFieldInfo = type.BaseType?.GetFields(); + if (parentFieldInfo != null && parentFieldInfo.Length > 0) + { + foreach (var item in parentFieldInfo) + { + if (!memberInfos.Any(d => d.Name == item.Name)) //如果子类已经包含父类的属性或者字段,跳过不处理 + { + memberInfos.Add(item); + } + } + } + } + else + { + PropertyInfo[] properties = type.GetProperties(); + if (null == properties || properties.Length <= 0) + { + return null; + } + + memberInfos.AddRange(properties); + //获取父类的属性 + var parentPropertyInfo = type.BaseType?.GetProperties(); + if (parentPropertyInfo != null && parentPropertyInfo.Length > 0) + { + foreach (var item in parentPropertyInfo) + { + if (!memberInfos.Any(d => d.Name == item.Name)) //如果子类已经包含父类的属性或者字段,跳过不处理 + { + memberInfos.Add(item); + } + } + } + } + + foreach (var item in memberInfos) + { + DescriptionAttribute[] EnumAttributes = + (DescriptionAttribute[])item.GetCustomAttributes(typeof(DescriptionAttribute), false); + + dynamic infoObject = null; + if (getPropertie == false) + { + infoObject = (FieldInfo)item; + } + else + { + infoObject = (PropertyInfo)item; + } + + if (EnumAttributes.Length > 0) + { + SelectResult selectResult = new SelectResult() + { + Key = Convert.ToInt32(infoObject.GetValue(null)).ToString(), + Value = EnumAttributes[0].Description, + }; + + selectResults.Add(selectResult); + } + + DisplayAttribute[] DisplayAttributes = + (DisplayAttribute[])item.GetCustomAttributes(typeof(DisplayAttribute), false); + if (DisplayAttributes.Length > 0) + { + SelectResult selectResult = + selectResults.FirstOrDefault(e => e.Key == Convert.ToInt32(infoObject.GetValue(null)).ToString()); + if (selectResult != null) + { + selectResult.SecondValue = DisplayAttributes[0].Name; + } + } + } + + return selectResults; + } + + /// + /// 获取指定枚举的所有 Attribute 说明以及value组成的键值对 + /// + /// 对象类 + /// 第三个标签类型 + /// 第三个标签类型取值名称 + /// false获取字段、true获取属性 + /// + public static List GetEnumAttributeListWithThirdValue(Type type, Type thirdAttributeType, string thirdAttributePropertieName, bool getPropertie = false) + { + if (type == null) + { + return null; + } + + List selectResults = new List(); + List memberInfos = new List(); + + if (getPropertie == false) + { + FieldInfo[] EnumInfo = type.GetFields(); + if (null == EnumInfo || EnumInfo.Length <= 0) + { + return null; + } + memberInfos.AddRange(EnumInfo); + var parentFieldInfo = type.BaseType?.GetFields(); + if (parentFieldInfo != null && parentFieldInfo.Length > 0) + { + memberInfos.AddRange(parentFieldInfo); + } + } + else + { + PropertyInfo[] EnumInfo = type.GetProperties(); + if (null == EnumInfo || EnumInfo.Length <= 0) + { + return null; + } + memberInfos.AddRange(EnumInfo); + var parentPropertyInfo = type.BaseType?.GetProperties(); + if (parentPropertyInfo != null && parentPropertyInfo.Length > 0) + { + memberInfos.AddRange(parentPropertyInfo); + } + } + + foreach (var item in memberInfos) + { + var thirdAttributes = item. + GetCustomAttributes(thirdAttributeType, false); + if (thirdAttributes == null || thirdAttributes.Length <= 0) + { + continue; + } + + DescriptionAttribute[] descriptionAttributes = (DescriptionAttribute[])item. + GetCustomAttributes(typeof(DescriptionAttribute), false); + + dynamic infoObject = null; + if (getPropertie == false) + { + infoObject = (FieldInfo)item; + } + else + { + infoObject = (PropertyInfo)item; + } + + if (descriptionAttributes.Length > 0) + { + SelectResult selectResult = new SelectResult() + { + Key = infoObject.Name, + Value = descriptionAttributes[0].Description, + }; + + selectResults.Add(selectResult); + } + + DisplayAttribute[] displayAttributes = (DisplayAttribute[])item. + GetCustomAttributes(typeof(DisplayAttribute), false); + if (displayAttributes.Length > 0) + { + SelectResult selectResult = selectResults.FirstOrDefault(e => e.Key == infoObject.Name); + if (selectResult != null) + { + selectResult.SecondValue = displayAttributes[0].Name; + } + } + + if (thirdAttributes.Length > 0 && !string.IsNullOrWhiteSpace(thirdAttributePropertieName)) + { + foreach (var attr in thirdAttributes) + { + // 使用反射获取特性的属性值 + var properties = thirdAttributeType.GetProperties(); + foreach (var prop in properties) + { + // 假设你要获取特性的某个属性值,例如 TypeName + if (prop.Name == thirdAttributePropertieName) + { + object value = prop.GetValue(attr); + SelectResult selectResult = selectResults.FirstOrDefault(e => e.Key == infoObject.Name); + if (selectResult != null) + { + selectResult.ThirdValue = value?.ToString(); // 将属性值赋给 ThirdValue + } + break; // 如果找到了需要的属性,可以跳出循环 + } + } + } + } + } + + return selectResults; + } + + /// + /// 获取指定类、指定常量值的Description说明 + /// 包含直接继承的父级字段 + /// + /// 对象类 + /// + /// + public static List> GetTypeDescriptionListToTuple(Type t, bool getPropertie = false) + { + if (t == null) + { + return null; + } + + List memberInfos = new List(); + + if (getPropertie == false) + { + FieldInfo[] fieldInfo = t.GetFields(); + if (null == fieldInfo || fieldInfo.Length <= 0) + { + return null; + } + + memberInfos.AddRange(fieldInfo); + var parentFieldInfo = t.BaseType?.GetFields(); + if (parentFieldInfo != null && parentFieldInfo.Length > 0) + { + foreach (var item in parentFieldInfo) + { + if (!memberInfos.Any(d => d.Name == item.Name)) //如果子类已经包含父类的属性或者字段,跳过不处理 + { + memberInfos.Add(item); + } + } + } + } + else + { + PropertyInfo[] fieldInfo = t.GetProperties(); + if (null == fieldInfo || fieldInfo.Length <= 0) + { + return null; + } + + memberInfos.AddRange(fieldInfo); + var parentPropertyInfo = t.BaseType?.GetProperties(); + if (parentPropertyInfo != null && parentPropertyInfo.Length > 0) + { + foreach (var item in parentPropertyInfo) + { + if (!memberInfos.Any(d => d.Name == item.Name)) //如果子类已经包含父类的属性或者字段,跳过不处理 + { + memberInfos.Add(item); + } + } + } + } + + List> tuples = new List>(); + + foreach (var item in memberInfos) + { + DescriptionAttribute[] descriptionAttribute = + (DescriptionAttribute[])item.GetCustomAttributes(typeof(DescriptionAttribute), false); + + NumericalOrderAttribute[] indexAttributes = + (NumericalOrderAttribute[])item.GetCustomAttributes(typeof(NumericalOrderAttribute), false); + + + if (descriptionAttribute.Length > 0 && indexAttributes.Length > 0) + { + Tuple tuple = new Tuple(item.Name, + descriptionAttribute[0].Description, indexAttributes[0].Index); + tuples.Add(tuple); + } + } + + return tuples; + } + + /// + /// 获取指定类、指定常量值的常量说明 + /// + /// 常量字段名称 + ///属性还是字段 + /// + public static string GetTypeDescriptionName(string fieldName, bool getPropertie = false) + { + if (string.IsNullOrEmpty(fieldName)) + { + return ""; + } + + MemberInfo memberInfo = null; + if (getPropertie == false) + { + memberInfo = typeof(T).GetField(fieldName); + } + else + { + memberInfo = typeof(T).GetProperty(fieldName); + } + + if (null == memberInfo) + { + return ""; + } + + DescriptionAttribute[] EnumAttributes = + (DescriptionAttribute[])memberInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); + if (EnumAttributes.Length <= 0) + { + return ""; + } + + return EnumAttributes[0].Description; + } + + /// + /// 获取指定命名空间下指定常量值的常量说明 + /// + /// 常量字段名称 + /// 命名空间,主要用来找到对应程序集 + ///属性还是字段 + /// + public static string GetTypeDescriptionName(string fieldName, string assemblyName, bool getPropertie = false) + { + if (string.IsNullOrWhiteSpace(fieldName) || string.IsNullOrWhiteSpace(assemblyName)) + { + return null; + } + + string desc = ""; + foreach (var item in GetEnumList(assemblyName)) + { + desc = GetTypeDescriptionName(item, fieldName, getPropertie); + if (!string.IsNullOrEmpty(desc)) + { + break; + } + } + + return desc; + } + + /// + /// 获取指定类、指定常量值的常量说明 + /// + /// 对象类 + /// 常量字段名称 + ///属性还是字段 + /// + public static string GetTypeDescriptionName(this Type t, string fieldName, bool getPropertie = false) + { + if (string.IsNullOrWhiteSpace(fieldName)) + { + return ""; + } + + MemberInfo memberInfo = null; + if (getPropertie == false) + { + memberInfo = t.GetField(fieldName); + } + else + { + memberInfo = t.GetProperty(fieldName); + } + + if (null != memberInfo) + { + DescriptionAttribute[] EnumAttributes = + (DescriptionAttribute[])memberInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); + if (EnumAttributes.Length > 0) + { + return EnumAttributes[0].Description; + } + } + + return ""; + } + + /// + /// 扩展方法,获得枚举值集合 + /// + ///枚举的DisplayName + public static List GetEnumList() where T : Enum + { + List enumList = new List(); + foreach (T value in Enum.GetValues(typeof(T))) + { + enumList.Add(value); + } + + return enumList; + } + + private static List GetEnumList(string assemblyName) + { + if (!String.IsNullOrEmpty(assemblyName)) + { + Assembly assembly = Assembly.Load(assemblyName); + List ts = assembly.GetTypes().Where(x => x.GetTypeInfo().IsClass).ToList(); + return ts; + } + + return new List(); + } + + /// + /// 扩展方法,获得枚举的Display值 + /// + ///枚举值 + ///当枚举值没有定义DisplayNameAttribute,是否使用枚举名代替,默认是使用 + ///属性还是字段 + ///枚举的DisplayName + public static string GetDisplayName(this Enum value, Boolean nameInstead = true, bool getPropertie = false) + { + Type type = value.GetType(); + string name = Enum.GetName(type, value); + if (name == null) + { + return null; + } + + DisplayAttribute attribute = null; + + if (getPropertie == false) + { + attribute = Attribute.GetCustomAttribute(type.GetField(name), typeof(DisplayAttribute)) as DisplayAttribute; + } + else + { + attribute = + Attribute.GetCustomAttribute(type.GetProperty(name), typeof(DisplayAttribute)) as DisplayAttribute; + } + + + if (attribute == null && nameInstead == true) + { + return name; + } + + return attribute == null ? null : attribute.Name; + } + + /// + /// 获取枚举的描述信息 + /// + /// + /// + public static string GetEnumDescription(this Enum value) + { + var name = value.ToString(); + var field = value.GetType().GetField(name); + if (field == null) return name; + + var att = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute), false); + + return att == null ? field.Name : ((DescriptionAttribute)att).Description; + } + + + + /// + /// 将传入的字符串中间部分字符替换成特殊字符 + /// + /// 需要替换的字符串 + /// 前保留长度 + /// 尾保留长度 + /// 特殊字符 + /// 被特殊字符替换的字符串 + public static string ReplaceWithSpecialChar(this string value, int startLen = 1, int endLen = 1, + char specialChar = '*') + { + if (string.IsNullOrEmpty(value)) + { + return value; + } + + try + { + + if (value.Length <= startLen + endLen) + { + var temStartVal = value.Substring(0, startLen); + return $"{temStartVal}{"".PadLeft(endLen, specialChar)}"; + } + + if (value.Length == 10 && endLen == 1) + { + endLen = 3; + } + + var startVal = value.Substring(0, startLen); + var endVal = value.Substring(value.Length - endLen); + if (value.Length == 2) + { + endVal = ""; + } + + value = $"{startVal}{endVal.PadLeft(value.Length - startLen, specialChar)}"; + } + catch (Exception) + { + throw; + } + + return value; + } + + /// + /// Linux下字体名称转换 + /// + /// + /// + public static string GetLinuxFontFamily(this string fontValue) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + if (fontValue == "楷体") + { + fontValue = "KaiTi"; + } + else if (fontValue == "隶书") + { + fontValue = "LiSu"; + } + else if (fontValue == "宋体") + { + fontValue = "SimSun"; + } + else if (fontValue == "微软雅黑") + { + fontValue = "Microsoft YaHei"; + } + else if (fontValue == "新宋体") + { + fontValue = "NSimSun"; + } + else if (fontValue == "仿宋") + { + fontValue = "FangSong"; + } + else if (fontValue == "黑体") + { + fontValue = "SimHei"; + } + + } + + return fontValue; + } + } +} diff --git a/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs b/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs index 029ae1f..bdd46f6 100644 --- a/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs +++ b/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs @@ -123,4 +123,32 @@ namespace JiShe.CollectBus.Common.Helpers writer.WriteStringValue(value.ToString(_dateFormatString)); } } + + /// + /// Unix格式时间格式化 + /// + public class UnixTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + if (long.TryParse(reader.GetString(), out long timestamp)) + return DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime; + } + + if (reader.TokenType == JsonTokenType.Number) + { + long timestamp = reader.GetInt64(); + return DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime; + } + return reader.GetDateTime(); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + long timestamp = new DateTimeOffset(value).ToUnixTimeSeconds(); + writer.WriteStringValue(timestamp.ToString()); + } + } } diff --git a/src/JiShe.CollectBus.Common/Helpers/SelectResult.cs b/src/JiShe.CollectBus.Common/Helpers/SelectResult.cs new file mode 100644 index 0000000..4ccaf99 --- /dev/null +++ b/src/JiShe.CollectBus.Common/Helpers/SelectResult.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Helpers +{ + /// + /// 下拉框选项元素 + /// + public class SelectResult + { + /// + /// 下拉框 键 + /// + public string Key { get; set; } + + /// + /// 下拉框 值 + /// + public string Value { get; set; } + + /// + /// 下拉框 值2 + /// + public string SecondValue { get; set; } + + /// + /// 下拉框 值3 + /// + public object ThirdValue { get; set; } + + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs index 026a95a..71cc741 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs @@ -16,16 +16,27 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// /// + /// 构建表模型方式,1 根据实体《T》直接显示指定Tag,2根据实体《T》的名称指定表名 /// - Task InsertAsync(T entity) where T : IoTEntity; + Task InsertAsync(T entity, int buildTabletMode) where T : IoTEntity; /// /// 批量插入数据 /// /// /// + /// 构建表模型方式,1 根据实体《T》直接显示指定Tag,2根据实体《T》的名称指定表名 /// - Task BatchInsertAsync(IEnumerable entities) where T : IoTEntity; + Task BatchInsertAsync(IEnumerable entities, int buildTabletMode) where T : IoTEntity; + + + /// + /// 删除数据 + /// + /// + /// + /// + Task DeleteAsync(QueryOptions options) where T : IoTEntity; /// /// 查询数据 diff --git a/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj b/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj index 717b0f6..137cee3 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj +++ b/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj @@ -10,4 +10,7 @@ + + + diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs b/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs index 019bd28..d04cd48 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs @@ -11,6 +11,11 @@ namespace JiShe.CollectBus.IoTDBProvider /// public class QueryOptions { + /// + /// 表名或标签名 + /// + public required string TableNameOrTagName { get; set; } + /// /// 分页 /// diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs index 8e84776..0d5b408 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs @@ -13,17 +13,17 @@ namespace JiShe.CollectBus.IoTDBProvider public class DeviceMetadata { /// - /// 测量值集合,用于构建Table的测量值,也就是field参数 + /// 测量值集合,用于构建Table的测量值,也就是columnNames参数 /// - public List Measurements { get; } = new(); + public List ColumnNames { get; } = new(); /// - /// 列类型集合,用于构建Table的列类型,也就是columnCategory参数 + /// 列类型集合,用于构建Table的列类型,也就是columnCategories参数 /// public List ColumnCategories { get; } = new(); /// - /// 值类型集合,用于构建Table的值类型,也就是dataType参数 + /// 值类型集合,用于构建Table的值类型,也就是dataTypes参数 /// public ListDataTypes { get; } = new(); } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs index 449884a..39e07c0 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs @@ -1,5 +1,8 @@ using Apache.IoTDB; using Apache.IoTDB.DataStructure; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Helpers; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.Collections.Concurrent; @@ -19,8 +22,9 @@ namespace JiShe.CollectBus.IoTDBProvider private readonly IoTDBOptions _options; private readonly TableSessionPool _sessionPool; private static readonly ConcurrentDictionary _metadataCache = new(); + private readonly ILogger _logger; - public IoTDBProvider(IOptions options) + public IoTDBProvider(IOptions options, ILogger logger) { _options = options.Value; @@ -32,58 +36,20 @@ namespace JiShe.CollectBus.IoTDBProvider .Build(); _sessionPool.Open(false).Wait(); + _logger = logger; } - /// - /// 获取设备元数据 - /// - /// - /// - private DeviceMetadata GetMetadata() where T : IoTEntity - { - return _metadataCache.GetOrAdd(typeof(T), type => - { - var metadata = new DeviceMetadata(); - foreach (var prop in type.GetProperties()) - { - //标签列 - var attrTAG = prop.GetCustomAttribute(); - if (attrTAG != null) - { - metadata.ColumnCategories.Add(ColumnCategory.TAG); - } - - //属性列 - var attrATTRIBUTE = prop.GetCustomAttribute(); - if (attrATTRIBUTE != null) - { - metadata.ColumnCategories.Add(ColumnCategory.ATTRIBUTE); - } - - //数据列 - var attrFIELD = prop.GetCustomAttribute(); - if (attrFIELD != null) - { - metadata.ColumnCategories.Add(ColumnCategory.FIELD); - metadata.Measurements.Add(prop.Name); - metadata.DataTypes.Add(GetDataTypeFromStr(prop.PropertyType.Name)); - } - } - return metadata; - }); - } - /// /// 插入数据 /// /// /// /// - public async Task InsertAsync(T entity) where T : IoTEntity + public async Task InsertAsync(T entity, int buildTabletMode) where T : IoTEntity { var metadata = GetMetadata(); - var tablet = BuildTablet(new[] { entity }, metadata); + var tablet = BuildTablet(new[] { entity }, metadata, buildTabletMode); await _sessionPool.InsertAsync(tablet); } @@ -93,58 +59,46 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// /// - public async Task BatchInsertAsync(IEnumerable entities) where T : IoTEntity + public async Task BatchInsertAsync(IEnumerable entities, int buildTabletMode) where T : IoTEntity { - var metadata = GetMetadata(); + var metadata = GetMetadata(); var batchSize = 1000; var batches = entities.Chunk(batchSize); foreach (var batch in batches) { - var tablet = BuildTablet(batch, metadata); + var tablet = BuildTablet(batch, metadata, buildTabletMode); await _sessionPool.InsertAsync(tablet); } } + /// - /// 构建表模型 + /// 删除数据 /// /// - /// - /// + /// /// - private Tablet BuildTablet(IEnumerable entities, DeviceMetadata metadata) where T : IoTEntity + public async Task DeleteAsync(QueryOptions options) where T : IoTEntity { - var deviceId = DevicePathBuilder.GetDeviceId(entities.First()); - var timestamps = new List(); - var values = new List>(); + var query = BuildDeleteSQL(options); + var sessionDataSet = await _sessionPool.ExecuteQueryStatementAsync(query); - foreach (var entity in entities) + if (!sessionDataSet.HasNext()) { - timestamps.Add(entity.Timestamps); - var rowValues = new List(); - foreach (var measurement in metadata.Measurements) - { - var value = typeof(T).GetProperty(measurement)?.GetValue(entity); - if(value == null) - { - throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,属性{measurement}值为空,不符合IoTDB设计标准,请赋值以后重新处理。"); - } - rowValues.Add(value); - } - values.Add(rowValues); + _logger.LogWarning($"{typeof(T).Name} 删除数据时,没有返回受影响记录数量。"); + return 0; } - return new Tablet( - deviceId, - metadata.Measurements, - metadata.DataTypes, - values, - timestamps - ); + //获取唯一结果行 + var row = sessionDataSet.Next(); + return row.Values[0]; } + + + /// /// 查询数据 /// @@ -153,7 +107,7 @@ namespace JiShe.CollectBus.IoTDBProvider /// public async Task> QueryAsync(QueryOptions options) where T : IoTEntity, new() { - var query = BuildQuery(options); + var query = BuildQuerySQL(options); var sessionDataSet = await _sessionPool.ExecuteQueryStatementAsync(query); var result = new PagedResult @@ -165,18 +119,75 @@ namespace JiShe.CollectBus.IoTDBProvider return result; } + /// + /// 构建表模型 + /// + /// + /// 表实体 + /// 设备元数据 + /// 构建表模型方式,1 根据实体《T》直接显示指定Tag,2根据实体《T》的名称指定表名 + /// + private Tablet BuildTablet(IEnumerable entities, DeviceMetadata metadata, int buildTabletMode) where T : IoTEntity + { + var timestamps = new List(); + var values = new List>(); + + foreach (var entity in entities) + { + timestamps.Add(entity.Timestamps); + var rowValues = new List(); + foreach (var measurement in metadata.ColumnNames) + { + var value = typeof(T).GetProperty(measurement)?.GetValue(entity); + if (value == null) + { + throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,属性{measurement}值为空,不符合IoTDB设计标准,请赋值以后重新处理。"); + } + rowValues.Add(value); + } + values.Add(rowValues); + } + + if (buildTabletMode == 1) + { + return new Tablet( + DevicePathBuilder.GetDeviceId(entities.First()), + metadata.ColumnNames, + metadata.ColumnCategories, + metadata.DataTypes, + values, + timestamps + ); + } + else if (buildTabletMode == 2) + { + return new Tablet( + DevicePathBuilder.GetTableName(), + metadata.ColumnNames, + metadata.ColumnCategories, + metadata.DataTypes, + values, + timestamps + ); + } + else + { + throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,buildTabletMode参数值不正确,请赋值以后重新处理。"); + } + } + /// /// 构建查询语句 /// /// /// /// - private string BuildQuery(QueryOptions options) where T : IoTEntity + private string BuildQuerySQL(QueryOptions options) where T : IoTEntity { var metadata = GetMetadata(); var sb = new StringBuilder("SELECT "); - sb.AppendJoin(", ", metadata.Measurements); - sb.Append($" FROM {DevicePathBuilder.GetTableName()}"); + sb.AppendJoin(", ", metadata.ColumnNames); + sb.Append($" FROM {options.TableNameOrTagName}"); if (options.Conditions.Any()) { @@ -188,6 +199,30 @@ namespace JiShe.CollectBus.IoTDBProvider return sb.ToString(); } + /// + /// 构建删除语句 + /// + /// + /// + /// + private string BuildDeleteSQL(QueryOptions options) where T : IoTEntity + { + var metadata = GetMetadata(); + var sb = new StringBuilder("DELETE "); + + sb.Append($" FROM {options.TableNameOrTagName}"); + + sb.AppendJoin(", ", metadata.ColumnNames); + + if (options.Conditions.Any()) + { + sb.Append(" WHERE "); + sb.AppendJoin(" AND ", options.Conditions.Select(TranslateCondition)); + } + + return sb.ToString(); + } + /// /// 将查询条件转换为SQL语句 /// @@ -213,7 +248,7 @@ namespace JiShe.CollectBus.IoTDBProvider /// private async Task GetTotalCount(QueryOptions options) where T : IoTEntity { - var countQuery = $"SELECT COUNT(*) FROM {DevicePathBuilder.GetTableName()}"; + var countQuery = $"SELECT COUNT(*) FROM {options.TableNameOrTagName}"; if (options.Conditions.Any()) { countQuery += " WHERE " + string.Join(" AND ", options.Conditions.Select(TranslateCondition)); @@ -246,7 +281,7 @@ namespace JiShe.CollectBus.IoTDBProvider }; - foreach (var measurement in metadata.Measurements) + foreach (var measurement in metadata.ColumnNames) { var value = record.Values; @@ -263,7 +298,7 @@ namespace JiShe.CollectBus.IoTDBProvider } return results; } - + /// /// 释放资源 /// @@ -272,23 +307,196 @@ namespace JiShe.CollectBus.IoTDBProvider _sessionPool?.Close().Wait(); } - private TSDataType GetDataTypeFromStr(string str) + /// + /// 获取设备元数据 + /// + /// + /// + private DeviceMetadata GetMetadata() where T : IoTEntity { - return str switch + return _metadataCache.GetOrAdd(typeof(T), type => { - "BOOLEAN" => TSDataType.BOOLEAN, - "INT32" => TSDataType.INT32, - "INT64" => TSDataType.INT64, - "FLOAT" => TSDataType.FLOAT, - "DOUBLE" => TSDataType.DOUBLE, - "TEXT" => TSDataType.TEXT, - "NULLTYPE" => TSDataType.NONE, - "TIMESTAMP" => TSDataType.TIMESTAMP, - "DATE" => TSDataType.DATE, - "BLOB" => TSDataType.BLOB, - "STRING" => TSDataType.STRING, - _ => TSDataType.STRING - }; + var columns = CollectColumnMetadata(type); + var metadata = BuildDeviceMetadata(columns); + return metadata; + }); + + //return _metadataCache.GetOrAdd(typeof(T), type => + //{ + // var metadata = new DeviceMetadata(); + // List> columns = new(); + // foreach (var prop in type.GetProperties()) + // { + // //标签列 + // var attrTAG = prop.GetCustomAttribute(); + // if (attrTAG != null) + // { + // columns.Add(Tuple.Create(prop.PropertyType.Name, ColumnCategory.TAG, GetDataTypeFromStr(prop.PropertyType.Name))); + // continue; + // } + + // //属性列 + // var attrATTRIBUTE = prop.GetCustomAttribute(); + // if (attrATTRIBUTE != null) + // { + // columns.Add(Tuple.Create(prop.PropertyType.Name, ColumnCategory.ATTRIBUTE, GetDataTypeFromStr(prop.PropertyType.Name))); + // continue; + // } + + // //数据列 + // var attrFIELD = prop.GetCustomAttribute(); + // if (attrFIELD != null) + // { + // columns.Add(Tuple.Create(prop.PropertyType.Name, ColumnCategory.FIELD, GetDataTypeFromStr(prop.PropertyType.Name))); + // } + // } + // var columnCategories = EnumExtensions.ToEnumDictionary(); + // foreach (var item in columnCategories) + // { + // if (item.Value == ColumnCategory.TAG) + // { + // metadata.ColumnNames.AddRange(columns.Where(d => d.Item2 == ColumnCategory.FIELD).Select(d => d.Item1).ToList()); + // metadata.ColumnCategories.AddRange(columns.Where(d => d.Item2 == ColumnCategory.TAG).Select(d => d.Item2).ToList()); + // metadata.DataTypes.AddRange(columns.Where(d => d.Item2 == ColumnCategory.TAG).Select(d => d.Item3).ToList()); + // } + // else if (item.Value == ColumnCategory.ATTRIBUTE) + // { + // metadata.ColumnNames.AddRange(columns.Where(d => d.Item2 == ColumnCategory.ATTRIBUTE).Select(d => d.Item1).ToList()); + // metadata.ColumnCategories.AddRange(columns.Where(d => d.Item2 == ColumnCategory.ATTRIBUTE).Select(d => d.Item2).ToList()); + // metadata.DataTypes.AddRange(columns.Where(d => d.Item2 == ColumnCategory.ATTRIBUTE).Select(d => d.Item3).ToList()); + // } + // else if (item.Value == ColumnCategory.FIELD) + // { + // metadata.ColumnNames.AddRange(columns.Where(d => d.Item2 == ColumnCategory.FIELD).Select(d => d.Item1).ToList()); + // metadata.ColumnCategories.AddRange(columns.Where(d => d.Item2 == ColumnCategory.FIELD).Select(d => d.Item2).ToList()); + // metadata.DataTypes.AddRange(columns.Where(d => d.Item2 == ColumnCategory.FIELD).Select(d => d.Item3).ToList()); + // } + // } + + // return metadata; + //}); } + + /// + /// 获取设备元数据的列 + /// + /// + /// + private List CollectColumnMetadata(Type type) + { + var columns = new List(); + + foreach (var prop in type.GetProperties()) + { + //按优先级顺序检查属性,避免重复反射 + ColumnInfo? column = prop.GetCustomAttribute() is not null ? new ColumnInfo( + name: prop.Name, //使用属性名 + category: ColumnCategory.TAG, + dataType: GetDataTypeFromTypeName(prop.PropertyType.Name) + ) : prop.GetCustomAttribute() is not null ? new ColumnInfo( + prop.Name, + ColumnCategory.ATTRIBUTE, + GetDataTypeFromTypeName(prop.PropertyType.Name) + ) : prop.GetCustomAttribute() is not null ? new ColumnInfo( + prop.Name, + ColumnCategory.FIELD, + GetDataTypeFromTypeName(prop.PropertyType.Name) + ) : null; + + if (column.HasValue) + { + columns.Add(column.Value); + } + } + return columns; + } + + /// + /// 构建设备元数据 + /// + /// + /// + private DeviceMetadata BuildDeviceMetadata(List columns) + { + var metadata = new DeviceMetadata(); + + //按业务逻辑顺序处理(TAG -> FIELD -> ATTRIBUTE) + var groupedColumns = columns + .GroupBy(c => c.Category) + .ToDictionary(g => g.Key, g => g.ToList()); + + ProcessCategory(groupedColumns, ColumnCategory.TAG, metadata); + ProcessCategory(groupedColumns, ColumnCategory.ATTRIBUTE, metadata); + ProcessCategory(groupedColumns, ColumnCategory.FIELD, metadata); + + return metadata; + } + + /// + /// 处理不同列类型的逻辑 + /// + /// + /// + /// + private void ProcessCategory(IReadOnlyDictionary> groupedColumns, ColumnCategory category, DeviceMetadata metadata) + { + if (groupedColumns.TryGetValue(category, out var cols)) + { + metadata.ColumnNames.AddRange(cols.Select(c => c.Name)); + metadata.ColumnCategories.AddRange(cols.Select(c => c.Category)); + metadata.DataTypes.AddRange(cols.Select(c => c.DataType)); + } + } + + /// + /// 数据列结构 + /// + private readonly struct ColumnInfo + { + public string Name { get; } + public ColumnCategory Category { get; } + public TSDataType DataType { get; } + + public ColumnInfo(string name, ColumnCategory category, TSDataType dataType) + { + Name = name; + Category = category; + DataType = dataType; + } + } + + /// + /// 根据类型名称获取对应的 IoTDB 数据类型 + /// + /// 类型名称(不区分大小写) + /// 对应的 TSDataType,默认返回 TSDataType.STRING + private TSDataType GetDataTypeFromTypeName(string typeName) + { + if (string.IsNullOrWhiteSpace(typeName)) + return TSDataType.STRING; + + return DataTypeMap.TryGetValue(typeName.Trim(), out var dataType) + ? dataType + : TSDataType.STRING; + } + + /// + /// 根据类型名称获取 IoTDB 数据类型 + /// + private readonly IReadOnlyDictionary DataTypeMap = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["BOOLEAN"] = TSDataType.BOOLEAN, + ["INT32"] = TSDataType.INT32, + ["INT64"] = TSDataType.INT64, + ["FLOAT"] = TSDataType.FLOAT, + ["DOUBLE"] = TSDataType.DOUBLE, + ["TEXT"] = TSDataType.TEXT, + ["NULLTYPE"] = TSDataType.NONE, + ["TIMESTAMP"] = TSDataType.TIMESTAMP, + ["DATE"] = TSDataType.DATE, + ["BLOB"] = TSDataType.BLOB, + ["STRING"] = TSDataType.STRING + }; } } -- 2.47.2 From 50639f6fcf204c42c60be43f254418b9bfc7250d Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 3 Apr 2025 16:46:26 +0800 Subject: [PATCH 048/139] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/SampleAppService.cs | 8 +- .../JiShe.CollectBus.Host.csproj | 6 ++ src/JiShe.CollectBus.Host/appsettings.json | 14 +--- .../Options/IoTDBOptions.cs | 10 +++ .../Provider/DevicePathBuilder.cs | 6 +- .../Provider/IoTDBProvider.cs | 75 +++++-------------- 6 files changed, 46 insertions(+), 73 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 9c2a86f..4b62555 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -6,6 +6,7 @@ using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.PrepayModel; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; namespace JiShe.CollectBus.Samples; @@ -20,11 +21,12 @@ public class SampleAppService : CollectBusAppService, ISampleAppService } - public async Task AddReadingAsync() + [HttpGet] + public async Task AddReadingAsync(int buildTabletMode = 1) { ElectricityMeter meter = new ElectricityMeter() { - SystemName = "Energy", + SystemName = "energy", DeviceId = "402440506", DeviceType = "Ammeter", Current = 10, @@ -32,7 +34,7 @@ public class SampleAppService : CollectBusAppService, ISampleAppService ProjectCode = "10059", Voltage = 10 }; - await _iotDBProvider.InsertAsync(meter,2); + await _iotDBProvider.InsertAsync(meter, buildTabletMode); } public Task GetAsync() diff --git a/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj b/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj index b70752b..7d9bc47 100644 --- a/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj +++ b/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj @@ -58,6 +58,12 @@ + + + Always + + + Always diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index b506272..05c99de 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -73,18 +73,12 @@ "Version": "V1" } ], - "Cap": { - "RabbitMq": { - "HostName": "118.190.144.92", - "UserName": "collectbus", - "Password": "123456", - "Port": 5672 - } - }, "IoTDBOptions": { "UserName": "root", "Password": "root", - "ClusterList": [ "192.168.56.102:6667"], - "PoolSize": 10 + "ClusterList": [ "192.168.56.102:6667" ], + "PoolSize": 10, + "DataBaseName": "energy", + "OpenDebugMode": true } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs b/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs index 5cdd256..6a762b1 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs @@ -11,6 +11,11 @@ namespace JiShe.CollectBus.IoTDBProvider /// public class IoTDBOptions { + /// + /// 数据库名称 + /// + public string DataBaseName { get; set; } + /// /// 集群列表 /// @@ -28,5 +33,10 @@ namespace JiShe.CollectBus.IoTDBProvider /// 连接池大小 /// public int PoolSize { get; set; } = 3; + + /// + /// 是否开启调试模式,生产环境请关闭,因为底层的实现方式,可能会导致内存持续增长。 + /// + public bool OpenDebugMode { get; set;} } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs index 3166ca6..cfab5c2 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs @@ -12,14 +12,14 @@ namespace JiShe.CollectBus.IoTDBProvider public static class DevicePathBuilder { /// - /// 构建设备路径 + /// 构建设备路径,由于路径的层级约束规范不能是纯数字字符,所以需要做特殊处理。 /// /// /// /// public static string GetDeviceId(T entity) where T : IoTEntity { - return $"root.{entity.SystemName}.{entity.ProjectCode}.{entity.DeviceId}"; + return $"root.{entity.SystemName.ToLower()}.`{entity.ProjectCode}`.`{entity.DeviceId}`"; } @@ -32,7 +32,7 @@ namespace JiShe.CollectBus.IoTDBProvider public static string GetTableName() where T : IoTEntity { var type = typeof(T); - return $"{type.Name}"; + return $"{type.Name.ToLower()}"; } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs index 39e07c0..b911876 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs @@ -32,10 +32,18 @@ namespace JiShe.CollectBus.IoTDBProvider .SetNodeUrls(_options.ClusterList) .SetUsername(_options.UserName) .SetPassword(_options.Password) + .SetDatabase(_options.DataBaseName) .SetFetchSize(_options.PoolSize) .Build(); _sessionPool.Open(false).Wait(); + if (_options.OpenDebugMode) + { + _sessionPool.OpenDebugMode(builder => + { + builder.AddConsole(); + }); + } _logger = logger; } @@ -50,7 +58,11 @@ namespace JiShe.CollectBus.IoTDBProvider var metadata = GetMetadata(); var tablet = BuildTablet(new[] { entity }, metadata, buildTabletMode); - await _sessionPool.InsertAsync(tablet); + var result = await _sessionPool.InsertAsync(tablet); + if (result <=0) + { + _logger.LogWarning($"{typeof(T).Name}插入数据没有成功"); + } } /// @@ -69,7 +81,11 @@ namespace JiShe.CollectBus.IoTDBProvider foreach (var batch in batches) { var tablet = BuildTablet(batch, metadata, buildTabletMode); - await _sessionPool.InsertAsync(tablet); + var result = await _sessionPool.InsertAsync(tablet); + if (result <= 0) + { + _logger.LogWarning($"{typeof(T).Name} 批量插入数据第{batch}批次没有成功,共{batches}批次。"); + } } } @@ -320,61 +336,6 @@ namespace JiShe.CollectBus.IoTDBProvider var metadata = BuildDeviceMetadata(columns); return metadata; }); - - //return _metadataCache.GetOrAdd(typeof(T), type => - //{ - // var metadata = new DeviceMetadata(); - // List> columns = new(); - // foreach (var prop in type.GetProperties()) - // { - // //标签列 - // var attrTAG = prop.GetCustomAttribute(); - // if (attrTAG != null) - // { - // columns.Add(Tuple.Create(prop.PropertyType.Name, ColumnCategory.TAG, GetDataTypeFromStr(prop.PropertyType.Name))); - // continue; - // } - - // //属性列 - // var attrATTRIBUTE = prop.GetCustomAttribute(); - // if (attrATTRIBUTE != null) - // { - // columns.Add(Tuple.Create(prop.PropertyType.Name, ColumnCategory.ATTRIBUTE, GetDataTypeFromStr(prop.PropertyType.Name))); - // continue; - // } - - // //数据列 - // var attrFIELD = prop.GetCustomAttribute(); - // if (attrFIELD != null) - // { - // columns.Add(Tuple.Create(prop.PropertyType.Name, ColumnCategory.FIELD, GetDataTypeFromStr(prop.PropertyType.Name))); - // } - // } - // var columnCategories = EnumExtensions.ToEnumDictionary(); - // foreach (var item in columnCategories) - // { - // if (item.Value == ColumnCategory.TAG) - // { - // metadata.ColumnNames.AddRange(columns.Where(d => d.Item2 == ColumnCategory.FIELD).Select(d => d.Item1).ToList()); - // metadata.ColumnCategories.AddRange(columns.Where(d => d.Item2 == ColumnCategory.TAG).Select(d => d.Item2).ToList()); - // metadata.DataTypes.AddRange(columns.Where(d => d.Item2 == ColumnCategory.TAG).Select(d => d.Item3).ToList()); - // } - // else if (item.Value == ColumnCategory.ATTRIBUTE) - // { - // metadata.ColumnNames.AddRange(columns.Where(d => d.Item2 == ColumnCategory.ATTRIBUTE).Select(d => d.Item1).ToList()); - // metadata.ColumnCategories.AddRange(columns.Where(d => d.Item2 == ColumnCategory.ATTRIBUTE).Select(d => d.Item2).ToList()); - // metadata.DataTypes.AddRange(columns.Where(d => d.Item2 == ColumnCategory.ATTRIBUTE).Select(d => d.Item3).ToList()); - // } - // else if (item.Value == ColumnCategory.FIELD) - // { - // metadata.ColumnNames.AddRange(columns.Where(d => d.Item2 == ColumnCategory.FIELD).Select(d => d.Item1).ToList()); - // metadata.ColumnCategories.AddRange(columns.Where(d => d.Item2 == ColumnCategory.FIELD).Select(d => d.Item2).ToList()); - // metadata.DataTypes.AddRange(columns.Where(d => d.Item2 == ColumnCategory.FIELD).Select(d => d.Item3).ToList()); - // } - // } - - // return metadata; - //}); } /// -- 2.47.2 From 000e6e627ef7b91d5413df7e35dc5f54aaca64a7 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Thu, 3 Apr 2025 18:05:17 +0800 Subject: [PATCH 049/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index b307155..6f7bc9e 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -1,6 +1,6 @@ using Volo.Abp.Modularity; -namespace JiShe.CollectBus.KafkaProducer +namespace JiShe.CollectBus.Kafka { public class CollectBusKafkaModule : AbpModule { -- 2.47.2 From 5d0a2088aeb60019fab4fd5ec9f853b864225f44 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 7 Apr 2025 10:56:40 +0800 Subject: [PATCH 050/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/SampleAppService.cs | 95 ++++++++++++++++++- src/JiShe.CollectBus.Host/appsettings.json | 4 +- .../Options/IoTDBOptions.cs | 9 +- .../Provider/IoTDBProvider.cs | 31 ++++-- 4 files changed, 128 insertions(+), 11 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 4b62555..9b853fd 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -1,12 +1,16 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Apache.IoTDB.DataStructure; +using Apache.IoTDB; +using Confluent.Kafka; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.PrepayModel; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; namespace JiShe.CollectBus.Samples; @@ -14,10 +18,12 @@ public class SampleAppService : CollectBusAppService, ISampleAppService { private readonly IIoTDBProvider _iotDBProvider; + private readonly IoTDBOptions _options; - public SampleAppService(IIoTDBProvider iotDBProvider) + public SampleAppService(IIoTDBProvider iotDBProvider, IOptions options) { _iotDBProvider = iotDBProvider; + _options = options.Value; } @@ -37,6 +43,93 @@ public class SampleAppService : CollectBusAppService, ISampleAppService await _iotDBProvider.InsertAsync(meter, buildTabletMode); } + + [HttpGet] + public async Task AddReading2Async() + { + var tableSessionPool = new TableSessionPool.Builder() + .SetNodeUrls(_options.ClusterList) + .SetUsername(_options.UserName) + .SetPassword(_options.Password) + .SetFetchSize(1024) + .Build(); + + await tableSessionPool.Open(false); + + + + await tableSessionPool.ExecuteNonQueryStatementAsync("CREATE DATABASE test1"); + await tableSessionPool.ExecuteNonQueryStatementAsync("CREATE DATABASE test2"); + + //await tableSessionPool.ExecuteNonQueryStatementAsync("use test2"); + + //// or use full qualified table name + //await tableSessionPool.ExecuteNonQueryStatementAsync( + // "create table test1.table1(region_id STRING TAG, plant_id STRING TAG, device_id STRING TAG, model STRING ATTRIBUTE, temperature FLOAT FIELD, humidity DOUBLE FIELD) with (TTL=3600000)"); + + //await tableSessionPool.ExecuteNonQueryStatementAsync( + // "create table table2(region_id STRING TAG, plant_id STRING TAG, color STRING ATTRIBUTE, temperature FLOAT FIELD, speed DOUBLE FIELD) with (TTL=6600000)"); + + //// show tables from current database + //var res = await tableSessionPool.ExecuteQueryStatementAsync("SHOW TABLES"); + //res.ShowTableNames(); + //while (res.HasNext()) Console.WriteLine(res.Next()); + //await res.Close(); + + //// show tables by specifying another database + //// using SHOW tables FROM + //res = await tableSessionPool.ExecuteQueryStatementAsync("SHOW TABLES FROM test1"); + //res.ShowTableNames(); + //while (res.HasNext()) Console.WriteLine(res.Next()); + //await res.Close(); + + var tableName = "testTable1"; + List columnNames = + new List { + "region_id", + "plant_id", + "device_id", + "model", + "temperature", + "humidity" }; + List dataTypes = + new List{ + TSDataType.STRING, + TSDataType.STRING, + TSDataType.STRING, + TSDataType.STRING, + TSDataType.FLOAT, + TSDataType.DOUBLE}; + List columnCategories = + new List{ + ColumnCategory.TAG, + ColumnCategory.TAG, + ColumnCategory.TAG, + ColumnCategory.ATTRIBUTE, + ColumnCategory.FIELD, + ColumnCategory.FIELD}; + var values = new List> { }; + var timestamps = new List { }; + for (long timestamp = 0; timestamp < 100; timestamp++) + { + timestamps.Add(timestamp); + values.Add(new List { "1", "5", "3", "A", 1.23F + timestamp, 111.1 + timestamp +}); + } + var tablet = new Tablet(tableName, columnNames, columnCategories, dataTypes, values, timestamps); + await tableSessionPool.InsertAsync(tablet); + + + //var res = await tableSessionPool.ExecuteQueryStatementAsync("select * from testTable1 " + // + "where region_id = '1' and plant_id in ('3', '5') and device_id = '3'"); + // res.ShowTableNames(); + // while (res.HasNext()) Console.WriteLine(res.Next()); + // await res.Close(); + + await tableSessionPool.Close(); + } + + public Task GetAsync() { return Task.FromResult( diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 05c99de..83eeb5b 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -77,8 +77,8 @@ "UserName": "root", "Password": "root", "ClusterList": [ "192.168.56.102:6667" ], - "PoolSize": 10, - "DataBaseName": "energy", + "PoolSize": 2, + "DataBaseName": "root.energy2", "OpenDebugMode": true } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs b/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs index 6a762b1..e9be4b4 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs @@ -27,12 +27,17 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// 密码 /// - public string Password { get; set; } + public string Password { get; set; } /// /// 连接池大小 /// - public int PoolSize { get; set; } = 3; + public int PoolSize { get; set; } = 2; + + /// + /// 查询时,每次查询的数据量,默认1024 + /// + public int FetchSize { get; set; } = 1024; /// /// 是否开启调试模式,生产环境请关闭,因为底层的实现方式,可能会导致内存持续增长。 diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs index b911876..eb83aca 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs @@ -2,6 +2,7 @@ using Apache.IoTDB.DataStructure; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; @@ -22,18 +23,24 @@ namespace JiShe.CollectBus.IoTDBProvider private readonly IoTDBOptions _options; private readonly TableSessionPool _sessionPool; private static readonly ConcurrentDictionary _metadataCache = new(); - private readonly ILogger _logger; + private readonly ILogger _logger; public IoTDBProvider(IOptions options, ILogger logger) { _options = options.Value; _sessionPool = new TableSessionPool.Builder() +#if DEBUG + .SetHost(_options.ClusterList.FirstOrDefault()?.Split(":")[0]) + .SetHost(_options.ClusterList.FirstOrDefault()?.Split(":")[1]) +#else .SetNodeUrls(_options.ClusterList) +#endif .SetUsername(_options.UserName) .SetPassword(_options.Password) + .SetFetchSize(_options.FetchSize) + .SetPoolSize(_options.PoolSize) .SetDatabase(_options.DataBaseName) - .SetFetchSize(_options.PoolSize) .Build(); _sessionPool.Open(false).Wait(); @@ -58,10 +65,23 @@ namespace JiShe.CollectBus.IoTDBProvider var metadata = GetMetadata(); var tablet = BuildTablet(new[] { entity }, metadata, buildTabletMode); - var result = await _sessionPool.InsertAsync(tablet); - if (result <=0) + int result = 0; + if (buildTabletMode == 1) { - _logger.LogWarning($"{typeof(T).Name}插入数据没有成功"); + result = await _sessionPool.InsertAsync(tablet); + } + else if(buildTabletMode == 2) + { + result = await _sessionPool.InsertAsync(tablet); + } + else + { + throw new Exception($"{nameof(InsertAsync)} 表模型{typeof(T).Name}记录入库时,buildTabletMode参数值不正确,请赋值以后重新处理。"); + } + + if (result <= 0) + { + _logger.LogError($"{typeof(T).Name}插入数据没有成功"); } } @@ -169,7 +189,6 @@ namespace JiShe.CollectBus.IoTDBProvider return new Tablet( DevicePathBuilder.GetDeviceId(entities.First()), metadata.ColumnNames, - metadata.ColumnCategories, metadata.DataTypes, values, timestamps -- 2.47.2 From 7696b7379be5510416b7fe2f065e5a82cc234c54 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 7 Apr 2025 16:44:25 +0800 Subject: [PATCH 051/139] =?UTF-8?q?=E5=AE=9E=E7=8E=B0SessionPool=E7=81=B5?= =?UTF-8?q?=E6=B4=BB=E5=88=87=E6=8D=A2=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/SampleAppService.cs | 106 ++------ src/JiShe.CollectBus.Host/appsettings.json | 7 +- .../CollectBusIoTDBModule.cs | 16 +- .../Context/IoTDBRuntimeContext.cs | 36 +++ .../Interface/IIoTDBProvider.cs | 14 +- .../Interface/IIoTDBSessionFactory.cs | 16 ++ .../Interface/IIoTDBSessionPool.cs | 35 +++ .../Options/IoTDBOptions.cs | 7 +- .../Options/QueryOptions.cs | 6 +- .../Provider/DevicePathBuilder.cs | 2 +- .../Provider/IoTDBProvider.cs | 231 ++++++++++-------- .../Provider/IoTDBSessionFactory.cs | 36 +++ .../Provider/IoTEntity.cs | 4 +- .../Provider/SessionPoolAdapter.cs | 74 ++++++ .../Provider/TableSessionPoolAdapter.cs | 72 ++++++ 15 files changed, 464 insertions(+), 198 deletions(-) create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Context/IoTDBRuntimeContext.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionFactory.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionPool.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBSessionFactory.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Provider/TableSessionPoolAdapter.cs diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 9b853fd..8990319 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -11,6 +11,7 @@ using JiShe.CollectBus.IotSystems.PrepayModel; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; +using JiShe.CollectBus.IoTDBProvider.Context; namespace JiShe.CollectBus.Samples; @@ -18,17 +19,20 @@ public class SampleAppService : CollectBusAppService, ISampleAppService { private readonly IIoTDBProvider _iotDBProvider; + private readonly IoTDBRuntimeContext _dbContext; private readonly IoTDBOptions _options; - public SampleAppService(IIoTDBProvider iotDBProvider, IOptions options) + public SampleAppService(IIoTDBProvider iotDBProvider, IOptions options, + IoTDBRuntimeContext dbContext) { _iotDBProvider = iotDBProvider; _options = options.Value; + _dbContext = dbContext; } [HttpGet] - public async Task AddReadingAsync(int buildTabletMode = 1) + public async Task UseSessionPool() { ElectricityMeter meter = new ElectricityMeter() { @@ -38,95 +42,31 @@ public class SampleAppService : CollectBusAppService, ISampleAppService Current = 10, MeterModel = "DDZY-1980", ProjectCode = "10059", - Voltage = 10 + Voltage = 10, + Timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), }; - await _iotDBProvider.InsertAsync(meter, buildTabletMode); + await _iotDBProvider.InsertAsync(meter); } [HttpGet] - public async Task AddReading2Async() + public async Task UseTableSessionPool() { - var tableSessionPool = new TableSessionPool.Builder() - .SetNodeUrls(_options.ClusterList) - .SetUsername(_options.UserName) - .SetPassword(_options.Password) - .SetFetchSize(1024) - .Build(); + //_dbContext.UseTableSessionPool = true; + _iotDBProvider.SwitchSessionPool(true); - await tableSessionPool.Open(false); - - - - await tableSessionPool.ExecuteNonQueryStatementAsync("CREATE DATABASE test1"); - await tableSessionPool.ExecuteNonQueryStatementAsync("CREATE DATABASE test2"); - - //await tableSessionPool.ExecuteNonQueryStatementAsync("use test2"); - - //// or use full qualified table name - //await tableSessionPool.ExecuteNonQueryStatementAsync( - // "create table test1.table1(region_id STRING TAG, plant_id STRING TAG, device_id STRING TAG, model STRING ATTRIBUTE, temperature FLOAT FIELD, humidity DOUBLE FIELD) with (TTL=3600000)"); - - //await tableSessionPool.ExecuteNonQueryStatementAsync( - // "create table table2(region_id STRING TAG, plant_id STRING TAG, color STRING ATTRIBUTE, temperature FLOAT FIELD, speed DOUBLE FIELD) with (TTL=6600000)"); - - //// show tables from current database - //var res = await tableSessionPool.ExecuteQueryStatementAsync("SHOW TABLES"); - //res.ShowTableNames(); - //while (res.HasNext()) Console.WriteLine(res.Next()); - //await res.Close(); - - //// show tables by specifying another database - //// using SHOW tables FROM - //res = await tableSessionPool.ExecuteQueryStatementAsync("SHOW TABLES FROM test1"); - //res.ShowTableNames(); - //while (res.HasNext()) Console.WriteLine(res.Next()); - //await res.Close(); - - var tableName = "testTable1"; - List columnNames = - new List { - "region_id", - "plant_id", - "device_id", - "model", - "temperature", - "humidity" }; - List dataTypes = - new List{ - TSDataType.STRING, - TSDataType.STRING, - TSDataType.STRING, - TSDataType.STRING, - TSDataType.FLOAT, - TSDataType.DOUBLE}; - List columnCategories = - new List{ - ColumnCategory.TAG, - ColumnCategory.TAG, - ColumnCategory.TAG, - ColumnCategory.ATTRIBUTE, - ColumnCategory.FIELD, - ColumnCategory.FIELD}; - var values = new List> { }; - var timestamps = new List { }; - for (long timestamp = 0; timestamp < 100; timestamp++) + ElectricityMeter meter = new ElectricityMeter() { - timestamps.Add(timestamp); - values.Add(new List { "1", "5", "3", "A", 1.23F + timestamp, 111.1 + timestamp -}); - } - var tablet = new Tablet(tableName, columnNames, columnCategories, dataTypes, values, timestamps); - await tableSessionPool.InsertAsync(tablet); - - - //var res = await tableSessionPool.ExecuteQueryStatementAsync("select * from testTable1 " - // + "where region_id = '1' and plant_id in ('3', '5') and device_id = '3'"); - // res.ShowTableNames(); - // while (res.HasNext()) Console.WriteLine(res.Next()); - // await res.Close(); - - await tableSessionPool.Close(); + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + Current = 10, + MeterModel = "DDZY-1980", + ProjectCode = "10059", + Voltage = 10, + Timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + }; + await _iotDBProvider.InsertAsync(meter); } diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 83eeb5b..3d51171 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -40,7 +40,7 @@ "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" }, "Redis": { - "Configuration": "118.190.144.92:6379,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + "Configuration": "118.190.144.92:6380,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", "DefaultDB": "14", "HangfireDB": "15" }, @@ -78,7 +78,8 @@ "Password": "root", "ClusterList": [ "192.168.56.102:6667" ], "PoolSize": 2, - "DataBaseName": "root.energy2", - "OpenDebugMode": true + "DataBaseName": "energy", + "OpenDebugMode": true, + "UseTableSessionPoolByDefault": false } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs b/src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs index 5a8902c..444ab4e 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; +using JiShe.CollectBus.IoTDBProvider.Context; +using JiShe.CollectBus.IoTDBProvider.Interface; +using JiShe.CollectBus.IoTDBProvider.Provider; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; @@ -13,7 +16,16 @@ namespace JiShe.CollectBus.IoTDBProvider public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.Configure(context.Services.GetConfiguration().GetSection(nameof(IoTDBOptions))); - context.Services.AddSingleton(); + + // 注册上下文为Scoped + context.Services.AddScoped(); + + // 注册Session工厂 + context.Services.AddSingleton(); + + // 注册Provider + context.Services.AddScoped(); + } } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Context/IoTDBRuntimeContext.cs b/src/JiShe.CollectBus.IoTDBProvider/Context/IoTDBRuntimeContext.cs new file mode 100644 index 0000000..41f48cb --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Context/IoTDBRuntimeContext.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider.Context +{ + /// + /// IoTDB SessionPool 运行时上下文 + /// + public class IoTDBRuntimeContext + { + private readonly bool _defaultValue; + + public IoTDBRuntimeContext(IOptions options) + { + _defaultValue = options.Value.UseTableSessionPoolByDefault; + UseTableSessionPool = _defaultValue; + } + + /// + /// 是否使用表模型存储, 默认false,使用tree模型存储 + /// + public bool UseTableSessionPool { get; set; } + + /// + /// 重置为默认值 + /// + public void ResetToDefault() + { + UseTableSessionPool = _defaultValue; + } + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs index 71cc741..b67f17d 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs @@ -7,27 +7,31 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.IoTDBProvider { /// - /// IoTDB数据源 + /// IoTDB数据源,数据库能同时存多个时序模型,但数据是完全隔离的,不能跨时序模型查询,通过连接字符串配置 /// public interface IIoTDBProvider { + /// + /// 切换 SessionPool + /// + /// 是否使用表模型 + void SwitchSessionPool(bool useTableSession); + /// /// 插入数据 /// /// /// - /// 构建表模型方式,1 根据实体《T》直接显示指定Tag,2根据实体《T》的名称指定表名 /// - Task InsertAsync(T entity, int buildTabletMode) where T : IoTEntity; + Task InsertAsync(T entity) where T : IoTEntity; /// /// 批量插入数据 /// /// /// - /// 构建表模型方式,1 根据实体《T》直接显示指定Tag,2根据实体《T》的名称指定表名 /// - Task BatchInsertAsync(IEnumerable entities, int buildTabletMode) where T : IoTEntity; + Task BatchInsertAsync(IEnumerable entities) where T : IoTEntity; /// diff --git a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionFactory.cs b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionFactory.cs new file mode 100644 index 0000000..b904ba0 --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionFactory.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider.Interface +{ + /// + /// Session 工厂接口 + /// + public interface IIoTDBSessionFactory + { + IIoTDBSessionPool GetSessionPool(bool useTableSession); + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionPool.cs b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionPool.cs new file mode 100644 index 0000000..be2c2b7 --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionPool.cs @@ -0,0 +1,35 @@ +using Apache.IoTDB.DataStructure; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider.Interface +{ + /// + /// Session 连接池 + /// + public interface IIoTDBSessionPool : IDisposable + { + /// + /// 打开连接池 + /// + /// + Task OpenAsync(); + + /// + /// 插入数据 + /// + /// + /// + Task InsertAsync(Tablet tablet); + + /// + /// 查询数据 + /// + /// + /// + Task ExecuteQueryStatementAsync(string sql); + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs b/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs index e9be4b4..62cbd92 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs @@ -12,7 +12,7 @@ namespace JiShe.CollectBus.IoTDBProvider public class IoTDBOptions { /// - /// 数据库名称 + /// 数据库名称,表模型才有,树模型为空 /// public string DataBaseName { get; set; } @@ -43,5 +43,10 @@ namespace JiShe.CollectBus.IoTDBProvider /// 是否开启调试模式,生产环境请关闭,因为底层的实现方式,可能会导致内存持续增长。 /// public bool OpenDebugMode { get; set;} + + /// + /// 是否使用表模型存储, 默认false,使用tree模型存储 + /// + public bool UseTableSessionPoolByDefault { get; set; } = false; } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs b/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs index d04cd48..3600f12 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs @@ -12,18 +12,20 @@ namespace JiShe.CollectBus.IoTDBProvider public class QueryOptions { /// - /// 表名或标签名 + /// 表模型的表名称或者树模型的设备路径 /// - public required string TableNameOrTagName { get; set; } + public required string TableNameOrTreePath { get; set; } /// /// 分页 /// public int Page { get; set; } + /// /// 分页大小 /// public int PageSize { get; set; } + /// /// 查询条件 /// diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs index cfab5c2..e170577 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs @@ -17,7 +17,7 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// /// - public static string GetDeviceId(T entity) where T : IoTEntity + public static string GetDevicePath(T entity) where T : IoTEntity { return $"root.{entity.SystemName.ToLower()}.`{entity.ProjectCode}`.`{entity.DeviceId}`"; } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs index eb83aca..4046ba9 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs @@ -2,6 +2,9 @@ using Apache.IoTDB.DataStructure; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.IoTDBProvider.Context; +using JiShe.CollectBus.IoTDBProvider.Interface; +using JiShe.CollectBus.IoTDBProvider.Provider; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -18,40 +21,54 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// IoTDB数据源 /// - public class IoTDBProvider : IIoTDBProvider, IDisposable + public class IoTDBProvider : IIoTDBProvider { - private readonly IoTDBOptions _options; - private readonly TableSessionPool _sessionPool; + private IIoTDBSessionPool _currentSession; private static readonly ConcurrentDictionary _metadataCache = new(); - private readonly ILogger _logger; + private readonly ILogger _logger; + private readonly IIoTDBSessionFactory _sessionFactory; + private readonly IoTDBRuntimeContext _runtimeContext; - public IoTDBProvider(IOptions options, ILogger logger) + public IoTDBProvider( + ILogger logger, + IIoTDBSessionFactory sessionFactory, + IoTDBRuntimeContext runtimeContext) { - _options = options.Value; - - _sessionPool = new TableSessionPool.Builder() -#if DEBUG - .SetHost(_options.ClusterList.FirstOrDefault()?.Split(":")[0]) - .SetHost(_options.ClusterList.FirstOrDefault()?.Split(":")[1]) -#else - .SetNodeUrls(_options.ClusterList) -#endif - .SetUsername(_options.UserName) - .SetPassword(_options.Password) - .SetFetchSize(_options.FetchSize) - .SetPoolSize(_options.PoolSize) - .SetDatabase(_options.DataBaseName) - .Build(); - - _sessionPool.Open(false).Wait(); - if (_options.OpenDebugMode) - { - _sessionPool.OpenDebugMode(builder => - { - builder.AddConsole(); - }); - } _logger = logger; + _sessionFactory = sessionFactory; + _runtimeContext = runtimeContext; + + RefreshSessionPool(); + } + + private void RefreshSessionPool() + { + try + { + _currentSession?.Dispose(); + _currentSession = _sessionFactory.GetSessionPool(_runtimeContext.UseTableSessionPool); + _currentSession.OpenAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + //自动回退到备用SessionPool + _logger.LogError($"{nameof(RefreshSessionPool)} 切换SessionPool失败,回退到默认配置:{ex.Serialize()}"); + _runtimeContext.UseTableSessionPool = !_runtimeContext.UseTableSessionPool; + _currentSession = _sessionFactory.GetSessionPool(_runtimeContext.UseTableSessionPool); + } + } + + /// + /// 切换 SessionPool + /// + /// + public void SwitchSessionPool(bool useTableSession) + { + if (_runtimeContext.UseTableSessionPool != useTableSession) + { + _runtimeContext.UseTableSessionPool = useTableSession; + RefreshSessionPool(); + } } /// @@ -60,24 +77,13 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// /// - public async Task InsertAsync(T entity, int buildTabletMode) where T : IoTEntity + public async Task InsertAsync(T entity) where T : IoTEntity { var metadata = GetMetadata(); - var tablet = BuildTablet(new[] { entity }, metadata, buildTabletMode); - int result = 0; - if (buildTabletMode == 1) - { - result = await _sessionPool.InsertAsync(tablet); - } - else if(buildTabletMode == 2) - { - result = await _sessionPool.InsertAsync(tablet); - } - else - { - throw new Exception($"{nameof(InsertAsync)} 表模型{typeof(T).Name}记录入库时,buildTabletMode参数值不正确,请赋值以后重新处理。"); - } + var tablet = BuildTablet(new[] { entity }, metadata); + + int result = await _currentSession.InsertAsync(tablet); if (result <= 0) { @@ -89,9 +95,8 @@ namespace JiShe.CollectBus.IoTDBProvider /// 批量插入数据 /// /// - /// /// - public async Task BatchInsertAsync(IEnumerable entities, int buildTabletMode) where T : IoTEntity + public async Task BatchInsertAsync(IEnumerable entities) where T : IoTEntity { var metadata = GetMetadata(); @@ -100,8 +105,8 @@ namespace JiShe.CollectBus.IoTDBProvider foreach (var batch in batches) { - var tablet = BuildTablet(batch, metadata, buildTabletMode); - var result = await _sessionPool.InsertAsync(tablet); + var tablet = BuildTablet(batch, metadata); + var result = await _currentSession.InsertAsync(tablet); if (result <= 0) { _logger.LogWarning($"{typeof(T).Name} 批量插入数据第{batch}批次没有成功,共{batches}批次。"); @@ -119,7 +124,7 @@ namespace JiShe.CollectBus.IoTDBProvider public async Task DeleteAsync(QueryOptions options) where T : IoTEntity { var query = BuildDeleteSQL(options); - var sessionDataSet = await _sessionPool.ExecuteQueryStatementAsync(query); + var sessionDataSet = await _currentSession.ExecuteQueryStatementAsync(query); if (!sessionDataSet.HasNext()) { @@ -132,9 +137,6 @@ namespace JiShe.CollectBus.IoTDBProvider return row.Values[0]; } - - - /// /// 查询数据 /// @@ -144,7 +146,7 @@ namespace JiShe.CollectBus.IoTDBProvider public async Task> QueryAsync(QueryOptions options) where T : IoTEntity, new() { var query = BuildQuerySQL(options); - var sessionDataSet = await _sessionPool.ExecuteQueryStatementAsync(query); + var sessionDataSet = await _currentSession.ExecuteQueryStatementAsync(query); var result = new PagedResult { @@ -156,17 +158,17 @@ namespace JiShe.CollectBus.IoTDBProvider } /// - /// 构建表模型 + /// 构建Tablet /// /// /// 表实体 - /// 设备元数据 - /// 构建表模型方式,1 根据实体《T》直接显示指定Tag,2根据实体《T》的名称指定表名 + /// 设备元数据 /// - private Tablet BuildTablet(IEnumerable entities, DeviceMetadata metadata, int buildTabletMode) where T : IoTEntity + private Tablet BuildTablet(IEnumerable entities, DeviceMetadata metadata) where T : IoTEntity { var timestamps = new List(); var values = new List>(); + var devicePaths = new HashSet(); foreach (var entity in entities) { @@ -175,40 +177,70 @@ namespace JiShe.CollectBus.IoTDBProvider foreach (var measurement in metadata.ColumnNames) { var value = typeof(T).GetProperty(measurement)?.GetValue(entity); - if (value == null) - { - throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,属性{measurement}值为空,不符合IoTDB设计标准,请赋值以后重新处理。"); - } - rowValues.Add(value); + rowValues.Add(value ?? new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,属性{measurement}值为空,不符合IoTDB设计标准,请赋值以后重新处理。")); } values.Add(rowValues); + if (!_runtimeContext.UseTableSessionPool)//树模型 + { + devicePaths.Add(DevicePathBuilder.GetDevicePath(entity)); + } + else + { + devicePaths.Add(DevicePathBuilder.GetTableName()); + } } - if (buildTabletMode == 1) + if (devicePaths.Count > 1) { - return new Tablet( - DevicePathBuilder.GetDeviceId(entities.First()), - metadata.ColumnNames, - metadata.DataTypes, - values, - timestamps - ); - } - else if (buildTabletMode == 2) - { - return new Tablet( - DevicePathBuilder.GetTableName(), - metadata.ColumnNames, - metadata.ColumnCategories, - metadata.DataTypes, - values, - timestamps - ); - } - else - { - throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,buildTabletMode参数值不正确,请赋值以后重新处理。"); + throw new Exception($"{nameof(BuildTablet)} 构建Tablet《{typeof(T).Name}》时,批量插入的设备路径不一致。"); } + + return _runtimeContext.UseTableSessionPool + ? BuildTableSessionTablet(metadata, devicePaths.First(), values, timestamps) + : BuildSessionTablet(metadata, devicePaths.First(), values, timestamps); + } + + /// + /// 构建tree模型的Tablet + /// + /// + /// + /// + /// + /// + private Tablet BuildSessionTablet(DeviceMetadata metadata, string devicePath, + List> values, List timestamps) + { + return new Tablet( + devicePath, + metadata.ColumnNames, + metadata.DataTypes, + values, + timestamps + ); + } + + /// + /// 构建表模型的Tablet + /// + /// + /// + /// + /// + /// + private Tablet BuildTableSessionTablet(DeviceMetadata metadata, string devicePath, + List> values, List timestamps) + { + var tablet = new Tablet( + devicePath, + metadata.ColumnNames, + metadata.ColumnCategories, + metadata.DataTypes, + values, + timestamps + ); + + return tablet; } /// @@ -222,7 +254,7 @@ namespace JiShe.CollectBus.IoTDBProvider var metadata = GetMetadata(); var sb = new StringBuilder("SELECT "); sb.AppendJoin(", ", metadata.ColumnNames); - sb.Append($" FROM {options.TableNameOrTagName}"); + sb.Append($" FROM {options.TableNameOrTreePath}"); if (options.Conditions.Any()) { @@ -243,9 +275,18 @@ namespace JiShe.CollectBus.IoTDBProvider private string BuildDeleteSQL(QueryOptions options) where T : IoTEntity { var metadata = GetMetadata(); - var sb = new StringBuilder("DELETE "); + var sb = new StringBuilder(); - sb.Append($" FROM {options.TableNameOrTagName}"); + if (!_runtimeContext.UseTableSessionPool) + { + sb.Append("DELETE "); + } + else + { + sb.Append("DROP "); + } + + sb.Append($" FROM {options.TableNameOrTreePath}"); sb.AppendJoin(", ", metadata.ColumnNames); @@ -283,13 +324,13 @@ namespace JiShe.CollectBus.IoTDBProvider /// private async Task GetTotalCount(QueryOptions options) where T : IoTEntity { - var countQuery = $"SELECT COUNT(*) FROM {options.TableNameOrTagName}"; + var countQuery = $"SELECT COUNT(*) FROM {options.TableNameOrTreePath}"; if (options.Conditions.Any()) { countQuery += " WHERE " + string.Join(" AND ", options.Conditions.Select(TranslateCondition)); } - var result = await _sessionPool.ExecuteQueryStatementAsync(countQuery); + var result = await _currentSession.ExecuteQueryStatementAsync(countQuery); return result.HasNext() ? Convert.ToInt32(result.Next().Values[0]) : 0; } @@ -334,14 +375,6 @@ namespace JiShe.CollectBus.IoTDBProvider return results; } - /// - /// 释放资源 - /// - public void Dispose() - { - _sessionPool?.Close().Wait(); - } - /// /// 获取设备元数据 /// @@ -453,7 +486,7 @@ namespace JiShe.CollectBus.IoTDBProvider private TSDataType GetDataTypeFromTypeName(string typeName) { if (string.IsNullOrWhiteSpace(typeName)) - return TSDataType.STRING; + return TSDataType.STRING; return DataTypeMap.TryGetValue(typeName.Trim(), out var dataType) ? dataType diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBSessionFactory.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBSessionFactory.cs new file mode 100644 index 0000000..a9b462a --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBSessionFactory.cs @@ -0,0 +1,36 @@ +using JiShe.CollectBus.IoTDBProvider.Interface; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider.Provider +{ + + /// + /// 实现带缓存的Session工厂 + /// + public class IoTDBSessionFactory : IIoTDBSessionFactory + { + private readonly IoTDBOptions _options; + private readonly ConcurrentDictionary _pools = new(); + + public IoTDBSessionFactory(IOptions options) + { + _options = options.Value; + } + + public IIoTDBSessionPool GetSessionPool(bool useTableSession) + { + return _pools.GetOrAdd(useTableSession, key => + { + return key + ? new TableSessionPoolAdapter(_options) + : new SessionPoolAdapter(_options); + }); + } + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs index 45964fa..d9bf4fa 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs @@ -30,8 +30,8 @@ public string DeviceId { get; set; } /// - /// 当前时间戳,单位秒 + /// 当前时间戳,单位毫秒 /// - public long Timestamps { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + public long Timestamps { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs new file mode 100644 index 0000000..81a69a9 --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs @@ -0,0 +1,74 @@ +using Apache.IoTDB.DataStructure; +using Apache.IoTDB; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using JiShe.CollectBus.IoTDBProvider.Interface; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.IoTDBProvider.Provider +{ + /// + /// 树模型连接池 + /// + public class SessionPoolAdapter : IIoTDBSessionPool + { + private readonly SessionPool _sessionPool; + private readonly IoTDBOptions _options; + + public SessionPoolAdapter(IoTDBOptions options) + { + _options = options; + _sessionPool = new SessionPool.Builder() + .SetNodeUrl(options.ClusterList) + .SetUsername(options.UserName) + .SetPassword(options.Password) + .SetFetchSize(options.FetchSize) + .SetPoolSize(options.PoolSize) + .Build(); + } + + /// + /// 打开连接池 + /// + /// + public async Task OpenAsync() + { + await _sessionPool.Open(false); + if (_options.OpenDebugMode) + { + _sessionPool.OpenDebugMode(builder => + { + builder.AddConsole(); + }); + } + } + + /// + /// 批量插入对齐时间序列数据 + /// + /// + /// + public async Task InsertAsync(Tablet tablet) + { + return await _sessionPool.InsertAlignedTabletAsync(tablet); + } + + /// + /// 查询数据 + /// + /// + /// + public async Task ExecuteQueryStatementAsync(string sql) + { + return await _sessionPool.ExecuteQueryStatementAsync(sql); + } + + public void Dispose() + { + _sessionPool?.Close().ConfigureAwait(false).GetAwaiter().GetResult(); + } + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/TableSessionPoolAdapter.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/TableSessionPoolAdapter.cs new file mode 100644 index 0000000..3676199 --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/TableSessionPoolAdapter.cs @@ -0,0 +1,72 @@ +using Apache.IoTDB.DataStructure; +using Apache.IoTDB; +using JiShe.CollectBus.IoTDBProvider.Interface; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.IoTDBProvider.Provider +{ + /// + /// 表模型Session连接池 + /// + public class TableSessionPoolAdapter : IIoTDBSessionPool + { + private readonly TableSessionPool _sessionPool; + private readonly IoTDBOptions _options; + + public TableSessionPoolAdapter(IoTDBOptions options) + { + _options = options; + _sessionPool = new TableSessionPool.Builder() + .SetNodeUrls(options.ClusterList) + .SetUsername(options.UserName) + .SetPassword(options.Password) + .SetFetchSize(options.FetchSize) + .SetPoolSize(options.PoolSize) + .SetDatabase(options.DataBaseName) + .Build(); + } + + /// + /// 打开连接池 + /// + /// + public async Task OpenAsync() + { + await _sessionPool.Open(false); + if (_options.OpenDebugMode) + { + _sessionPool.OpenDebugMode(builder => builder.AddConsole()); + } + } + + /// + /// 批量插入 + /// + /// + /// + public async Task InsertAsync(Tablet tablet) + { + return await _sessionPool.InsertAsync(tablet); + } + + /// + /// 查询数据 + /// + /// + /// + public async Task ExecuteQueryStatementAsync(string sql) + { + return await _sessionPool.ExecuteQueryStatementAsync(sql); + } + + public void Dispose() + { + _sessionPool?.Close().ConfigureAwait(false).GetAwaiter().GetResult(); + } + } +} -- 2.47.2 From 1b4c4dd8ff43e1379feacb7330caf7a7aa556ea3 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 7 Apr 2025 17:35:37 +0800 Subject: [PATCH 052/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusApplicationModule.cs | 9 ++- .../CollectBusDomainModule.cs | 2 +- .../CollectBusHostModule.Configure.cs | 64 +++++++++++++------ 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 085ab8e..4230150 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -16,6 +16,13 @@ using Volo.Abp.BackgroundWorkers.Hangfire; using JiShe.CollectBus.MongoDB; using JiShe.CollectBus.ScheduledMeterReading; using JiShe.CollectBus.IoTDBProvider; +using Confluent.Kafka.Admin; +using Confluent.Kafka; +using Microsoft.Extensions.Options; +using JiShe.CollectBus.Protocol.Contracts; +using System.Collections.Generic; +using Thrift; +using Microsoft.Extensions.Configuration; namespace JiShe.CollectBus; @@ -60,7 +67,7 @@ public class CollectBusApplicationModule : AbpModule var dbContext = context.ServiceProvider.GetRequiredService(); //默认初始化表计信息 - dbContext.InitAmmeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); + dbContext.InitAmmeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); } } diff --git a/src/JiShe.CollectBus.Domain/CollectBusDomainModule.cs b/src/JiShe.CollectBus.Domain/CollectBusDomainModule.cs index 64519c7..24945de 100644 --- a/src/JiShe.CollectBus.Domain/CollectBusDomainModule.cs +++ b/src/JiShe.CollectBus.Domain/CollectBusDomainModule.cs @@ -12,7 +12,7 @@ namespace JiShe.CollectBus; [DependsOn( typeof(CollectBusDomainSharedModule), typeof(AbpAuditLoggingDomainModule), - typeof(AbpCachingModule), + typeof(AbpCachingModule), typeof(AbpBackgroundJobsDomainModule) )] public class CollectBusDomainModule : AbpModule diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 40f498e..80145ab 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -23,6 +23,7 @@ using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.MessageIssueds; using Confluent.Kafka; using MassTransit.SqlTransport.Topology; +using Confluent.Kafka.Admin; namespace JiShe.CollectBus.Host @@ -286,29 +287,51 @@ namespace JiShe.CollectBus.Host /// The configuration. public void ConfigureMassTransit(ServiceConfigurationContext context, IConfiguration configuration) { + + + var adminClient = new AdminClientBuilder(new AdminClientConfig + { + BootstrapServers = configuration.GetConnectionString("Kafka") + }).Build(); + + try + { + var topics = ProtocolConstExtensions.GetAllTopicNames(); + List topicSpecifications = new List(); + foreach (var item in topics) + { + topicSpecifications.Add(new TopicSpecification + { + Name = item, + NumPartitions = 3, + ReplicationFactor = 1 + }); + } + adminClient.CreateTopicsAsync(topicSpecifications).ConfigureAwait(false).GetAwaiter().GetResult(); + } + catch (CreateTopicsException e) + { + if (e.Results[0].Error.Code != ErrorCode.TopicAlreadyExists) + { + throw; + } + } + + var consumerConfig = new ConsumerConfig { GroupId = ProtocolConst.SubscriberGroup }; var producerConfig = new ProducerConfig(); - context.Services -// .ConfigureKafkaTestOptions(options => -// { - -//#if DEBUG -// options.CleanTopicsOnStart = true;// 测试时,每次启动都删除topic,生产环境不需要 -//#endif -// options.CreateTopicsIfNotExists = true; -// options.TopicNames = ProtocolConstExtensions.GetAllTopicNames(); -// }) + context.Services .AddMassTransit(x => { x.UsingInMemory((context, cfg) => cfg.ConfigureEndpoints(context)); - //x.AddConfigureEndpointsCallback((c, name, cfg) => - //{ - // cfg.UseDelayedRedelivery(r => r.Intervals(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(30))); - // cfg.UseMessageRetry(r => r.Immediate(5)); - // cfg.UseInMemoryOutbox(c); - //}); + x.AddConfigureEndpointsCallback((c, name, cfg) => + { + cfg.UseDelayedRedelivery(r => r.Intervals(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(30))); + cfg.UseMessageRetry(r => r.Immediate(5)); + cfg.UseInMemoryOutbox(c); + }); x.AddRider(rider => { @@ -322,14 +345,15 @@ namespace JiShe.CollectBus.Host .SetTimeLimit(s: 1) .SetTimeLimitStart(BatchTimeLimitStart.FromLast) .SetConcurrencyLimit(10)); - }); - + }); rider.AddConsumer(); + rider.AddProducer(ProtocolConst.SubscriberReceivedLoginEventName); + rider.AddProducer(ProtocolConst.SubscriberReceivedHeartbeatEventName); + rider.UsingKafka((c, cfg) => { - List hosts = new List() { "121.42.242.91:29092", "121.42.242.91:39092", "121.42.242.91:49092" }; - cfg.Host(hosts); + cfg.Host(configuration.GetConnectionString("Kafka")); cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedHeartbeatEventName, consumerConfig, configurator => { -- 2.47.2 From 0dd95828d4320c916254079bcb1588fbd9775011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E7=9B=8A?= Date: Mon, 7 Apr 2025 21:34:05 +0800 Subject: [PATCH 053/139] =?UTF-8?q?=E8=BF=98=E5=8E=9FCAP=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 26 +++++++++---------- ...nergySystemScheduledMeterReadingService.cs | 2 +- .../CollectBusHostModule.cs | 4 +-- .../Abstracts/BaseProtocolPlugin.cs | 12 ++++----- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index f65e51f..b4d3a02 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -39,12 +39,12 @@ namespace JiShe.CollectBus.ScheduledMeterReading public abstract class BasicScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService { private readonly ILogger _logger; - private readonly IPublishEndpoint _producerBus; + private readonly ICapPublisher _producerBus; private readonly IMeterReadingRecordRepository _meterReadingRecordsRepository; public BasicScheduledMeterReadingService( ILogger logger, - IPublishEndpoint producerBus, + ICapPublisher producerBus, IMeterReadingRecordRepository meterReadingRecordsRepository) { _producerBus = producerBus; @@ -298,8 +298,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); - _= _producerBus.Publish(tempMsg); + _ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + //_= _producerBus.Publish(tempMsg); meterTaskInfosList.Add(ammerterItem.Value); @@ -365,9 +365,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - //_= _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg); + _= _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg); - _ = _producerBus.Publish(tempMsg); + //_ = _producerBus.Publish(tempMsg); meterTaskInfosList.Add(ammerterItem.Value); } @@ -437,9 +437,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading TimeDensity = timeDensity.ToString(), }; - //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500) ,ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); + _ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500) ,ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); - _ = _producerBus.Publish(tempMsg); + //_ = _producerBus.Publish(tempMsg); meterTaskInfosList.Add(ammerterItem.Value); } @@ -827,9 +827,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - //await _producerBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + await _producerBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); - _ = _producerBus.Publish(tempMsg); + //_ = _producerBus.Publish(tempMsg); meterTaskInfosList.Add(ammerterItem.Value); @@ -896,9 +896,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - //await _producerBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + await _producerBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); - _ = _producerBus.Publish(tempMsg); + //_ = _producerBus.Publish(tempMsg); meterTaskInfosList.Add(ammerterItem.Value); @@ -966,7 +966,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading }; //await _producerBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); - _ = _producerBus.Publish(tempMsg); + //_ = _producerBus.Publish(tempMsg); meterTaskInfosList.Add(ammerterItem.Value); diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 78276e9..75931c3 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -29,7 +29,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { public EnergySystemScheduledMeterReadingService(ILogger logger, - IPublishEndpoint producerBus, IMeterReadingRecordRepository meterReadingRecordsRepository) : base(logger, producerBus, meterReadingRecordsRepository) + ICapPublisher producerBus, IMeterReadingRecordRepository meterReadingRecordsRepository) : base(logger, producerBus, meterReadingRecordsRepository) { } diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs index 263be23..6f24b1e 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs @@ -43,8 +43,8 @@ namespace JiShe.CollectBus.Host ConfigureNetwork(context, configuration); ConfigureJwtAuthentication(context, configuration); ConfigureHangfire(context); - //ConfigureCap(context, configuration); - ConfigureMassTransit(context, configuration); + ConfigureCap(context, configuration); + //ConfigureMassTransit(context, configuration); ConfigureAuditLog(context); ConfigureCustom(context, configuration); } diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index 2fbe138..28cf6c2 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -17,7 +17,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts { public abstract class BaseProtocolPlugin : IProtocolPlugin { - private readonly IPublishEndpoint _producerBus; + private readonly ICapPublisher _producerBus; private readonly ILogger _logger; private readonly IRepository _protocolInfoRepository; @@ -37,7 +37,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts _logger = serviceProvider.GetRequiredService>(); _protocolInfoRepository = serviceProvider.GetRequiredService>(); - _producerBus = serviceProvider.GetRequiredService(); + _producerBus = serviceProvider.GetRequiredService(); } public abstract ProtocolInfo Info { get; } @@ -87,8 +87,8 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); - //await _producerBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); - await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); + await _producerBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); + //await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); } /// @@ -126,9 +126,9 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts Fn = 1 }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); - //await _producerBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); + await _producerBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); - await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); + //await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); } } -- 2.47.2 From 323ad75f8aa64aae0964fd206c5d64729e7fae81 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 7 Apr 2025 21:50:50 +0800 Subject: [PATCH 054/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Plugins/TcpMonitor.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index 7083387..8f37fb4 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -24,7 +24,7 @@ namespace JiShe.CollectBus.Plugins { public partial class TcpMonitor : PluginBase, ITransientDependency, ITcpReceivedPlugin, ITcpConnectingPlugin, ITcpConnectedPlugin, ITcpClosedPlugin { - private readonly IPublishEndpoint _producerBus; + private readonly ICapPublisher _producerBus; private readonly ILogger _logger; private readonly IRepository _deviceRepository; private readonly IDistributedCache _ammeterInfoCache; @@ -36,7 +36,7 @@ namespace JiShe.CollectBus.Plugins /// /// /// - public TcpMonitor(IPublishEndpoint producerBus, + public TcpMonitor(ICapPublisher producerBus, ILogger logger, IRepository deviceRepository, IDistributedCache ammeterInfoCache) @@ -160,9 +160,9 @@ namespace JiShe.CollectBus.Plugins DeviceNo = deviceNo, MessageId = NewId.NextGuid().ToString() }; - //await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedLoginEventName, messageReceivedLoginEvent); + await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedLoginEventName, messageReceivedLoginEvent); - await _producerBus.Publish( messageReceivedLoginEvent); + //await _producerBus.Publish( messageReceivedLoginEvent); } private async Task OnTcpHeartbeatReceived(ITcpSessionClient client, string messageHexString, string deviceNo) @@ -199,23 +199,13 @@ namespace JiShe.CollectBus.Plugins DeviceNo = deviceNo, MessageId = NewId.NextGuid().ToString() }; - //await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedHeartbeatEventName, messageReceivedHeartbeatEvent); - await _producerBus.Publish(messageReceivedHeartbeatEvent); + await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedHeartbeatEventName, messageReceivedHeartbeatEvent); + //await _producerBus.Publish(messageReceivedHeartbeatEvent); } private async Task OnTcpNormalReceived(ITcpSessionClient client, string messageHexString, string deviceNo) { - await _producerBus.Publish(new MessageReceived - { - ClientId = client.Id, - ClientIp = client.IP, - ClientPort = client.Port, - MessageHexString = messageHexString, - DeviceNo = deviceNo, - MessageId = NewId.NextGuid().ToString() - }); - - //await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedEventName, new MessageReceived + //await _producerBus.Publish(new MessageReceived //{ // ClientId = client.Id, // ClientIp = client.IP, @@ -224,6 +214,16 @@ namespace JiShe.CollectBus.Plugins // DeviceNo = deviceNo, // MessageId = NewId.NextGuid().ToString() //}); + + await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedEventName, new MessageReceived + { + ClientId = client.Id, + ClientIp = client.IP, + ClientPort = client.Port, + MessageHexString = messageHexString, + DeviceNo = deviceNo, + MessageId = NewId.NextGuid().ToString() + }); } } } -- 2.47.2 From 39ae8a7f38ff13fb2c4b648224586b7fad9aa65f Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Tue, 8 Apr 2025 17:44:42 +0800 Subject: [PATCH 055/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IWorkerSubscriberAppService.cs | 2 - .../DataMigration/DataMigrationService.cs | 40 ++++++++--------- .../Samples/SampleAppService.cs | 22 +++++++-- .../BasicScheduledMeterReadingService.cs | 36 ++++++++++----- ...nergySystemScheduledMeterReadingService.cs | 3 +- .../Subscribers/SubscriberAppService.cs | 3 +- .../Subscribers/WorkerSubscriberAppService.cs | 23 +--------- .../Ammeters/ElectricityMeter.cs | 12 +++++ ...ScheduledMeterReadingIssuedEventMessage.cs | 5 +++ .../MeterReadingRecords.cs | 36 +++++---------- src/JiShe.CollectBus.Host/appsettings.json | 2 +- .../Provider/IoTDBProvider.cs | 43 ++++++++++++++++-- .../MeterReadingRecordRepository.cs | 19 ++++++-- .../ProtocolConst.cs | 45 ++++++++++++++----- 14 files changed, 186 insertions(+), 105 deletions(-) diff --git a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs index 6d13b13..64dddf1 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs @@ -15,8 +15,6 @@ namespace JiShe.CollectBus.Subscribers #region 电表消息采集 - Task> AmmeterScheduledMeterOneMinuteReadingIssuedEventQuery(); - /// /// 1分钟采集电表数据下行消息消费订阅 /// diff --git a/src/JiShe.CollectBus.Application/DataMigration/DataMigrationService.cs b/src/JiShe.CollectBus.Application/DataMigration/DataMigrationService.cs index 4aa3a7e..f1f0c9b 100644 --- a/src/JiShe.CollectBus.Application/DataMigration/DataMigrationService.cs +++ b/src/JiShe.CollectBus.Application/DataMigration/DataMigrationService.cs @@ -66,21 +66,21 @@ namespace JiShe.CollectBus.DataMigration /// private async Task ProduceDataAsync(ChannelWriter writer) { - while (true) - { - var queryable = await _meterReadingRecordsRepository.GetQueryableAsync(); - var batchRecords = queryable.Where(d => d.MigrationStatus == Common.Enums.RecordsDataMigrationStatusEnum.NotStarted) - .Take(_options.MongoDbDataBatchSize) - .ToArray(); + //while (true) + //{ + // var queryable = await _meterReadingRecordsRepository.GetQueryableAsync(); + // var batchRecords = queryable.Where(d => d.MigrationStatus == Common.Enums.RecordsDataMigrationStatusEnum.NotStarted) + // .Take(_options.MongoDbDataBatchSize) + // .ToArray(); - if (batchRecords == null || batchRecords.Length == 0) - { - writer.Complete(); - break; - } + // if (batchRecords == null || batchRecords.Length == 0) + // { + // writer.Complete(); + // break; + // } - await writer.WriteAsync(batchRecords); - } + // await writer.WriteAsync(batchRecords); + //} } /// @@ -111,14 +111,14 @@ namespace JiShe.CollectBus.DataMigration //await writer.WriteAsync(dataTable); // 批量更新标记 - var ids = batch.Select(d => d.Id).ToArray(); - foreach (var item in batch) - { - item.MigrationStatus = Common.Enums.RecordsDataMigrationStatusEnum.InProgress; - item.MigrationTime = DateTime.Now; - } + //var ids = batch.Select(d => d.Id).ToArray(); + //foreach (var item in batch) + //{ + // item.MigrationStatus = Common.Enums.RecordsDataMigrationStatusEnum.InProgress; + // item.MigrationTime = DateTime.Now; + //} - await _meterReadingRecordsRepository.UpdateManyAsync(batch); + //await _meterReadingRecordsRepository.UpdateManyAsync(batch); } writer.Complete(); } diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 8990319..d2b2b47 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -12,28 +12,41 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using JiShe.CollectBus.IoTDBProvider.Context; +using Microsoft.Extensions.Logging; namespace JiShe.CollectBus.Samples; public class SampleAppService : CollectBusAppService, ISampleAppService { - + private readonly ILogger _logger; private readonly IIoTDBProvider _iotDBProvider; private readonly IoTDBRuntimeContext _dbContext; private readonly IoTDBOptions _options; public SampleAppService(IIoTDBProvider iotDBProvider, IOptions options, - IoTDBRuntimeContext dbContext) + IoTDBRuntimeContext dbContext, ILogger logger) { _iotDBProvider = iotDBProvider; _options = options.Value; _dbContext = dbContext; + _logger = logger; } [HttpGet] - public async Task UseSessionPool() + public async Task UseSessionPool(long timestamps) { + string? messageHexString = null; + if (timestamps == 0) + { + timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + _logger.LogError($"timestamps_{timestamps}"); + } + else + { + messageHexString = messageHexString + timestamps; + } + ElectricityMeter meter = new ElectricityMeter() { SystemName = "energy", @@ -43,7 +56,8 @@ public class SampleAppService : CollectBusAppService, ISampleAppService MeterModel = "DDZY-1980", ProjectCode = "10059", Voltage = 10, - Timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + IssuedMessageHexString = messageHexString, + Timestamps = timestamps, }; await _iotDBProvider.InsertAsync(meter); } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index b4d3a02..e9d0df6 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -6,6 +6,7 @@ using System.Net; using System.Threading.Tasks; using DotNetCore.CAP; using FreeSql; +using FreeSql.Internal.CommonProvider; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common; using JiShe.CollectBus.Common.BuildSendDatas; @@ -15,6 +16,7 @@ using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.GatherItem; +using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MessageReceiveds; @@ -39,17 +41,20 @@ namespace JiShe.CollectBus.ScheduledMeterReading public abstract class BasicScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService { private readonly ILogger _logger; - private readonly ICapPublisher _producerBus; - private readonly IMeterReadingRecordRepository _meterReadingRecordsRepository; + private readonly ICapPublisher _producerBus; + private readonly IIoTDBProvider _dbProvider; + private readonly IMeterReadingRecordRepository _meterReadingRecordRepository; public BasicScheduledMeterReadingService( ILogger logger, - ICapPublisher producerBus, - IMeterReadingRecordRepository meterReadingRecordsRepository) + ICapPublisher producerBus, + IMeterReadingRecordRepository meterReadingRecordRepository, + IIoTDBProvider dbProvider) { _producerBus = producerBus; _logger = logger; - _meterReadingRecordsRepository = meterReadingRecordsRepository; + _dbProvider = dbProvider; + _meterReadingRecordRepository = meterReadingRecordRepository; } /// @@ -268,6 +273,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的电表信息 int timeDensity = 5; + var currentTime = DateTime.Now; + var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) @@ -307,7 +314,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingRecordsRepository.InsertManyAsync(meterTaskInfosList); + //_dbProvider.SwitchSessionPool(true); + //await _dbProvider.InsertAsync(meterTaskInfosList); + + await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList,currentTime); } //删除任务数据 @@ -335,6 +345,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的电表信息 int timeDensity = 5; + var currentTime = DateTime.Now; + var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; var fiveMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (fiveMinutekeyList == null || fiveMinutekeyList.Length <= 0) @@ -374,7 +386,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingRecordsRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList,currentTime); } //删除任务数据 @@ -446,7 +458,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingRecordsRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList,currentDateTime); } //删除任务数据 @@ -706,6 +718,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusID = ammeter.FocusID, AFN = aFN, Fn = fn, + ItemCode = tempItem, + ManualOrNot = false, Pn = ammeter.MeteringCode, IssuedMessageId = GuidGenerator.Create().ToString(), IssuedMessageHexString = Convert.ToHexString(dataInfos), @@ -837,7 +851,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingRecordsRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } //删除任务数据 @@ -906,7 +920,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingRecordsRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } //删除任务数据 @@ -974,7 +988,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingRecordsRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } //删除任务数据 diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 75931c3..b1c8b18 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -6,6 +6,7 @@ using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.GatherItem; +using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; @@ -29,7 +30,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { public EnergySystemScheduledMeterReadingService(ILogger logger, - ICapPublisher producerBus, IMeterReadingRecordRepository meterReadingRecordsRepository) : base(logger, producerBus, meterReadingRecordsRepository) + ICapPublisher producerBus, IIoTDBProvider dbProvider, IMeterReadingRecordRepository meterReadingRecordRepository) : base(logger, producerBus, meterReadingRecordRepository, dbProvider) { } diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index 242b216..aa86101 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -130,9 +130,10 @@ namespace JiShe.CollectBus.Subscribers Pn = 0, FocusAddress = "", MeterAddress = "", - DataResult = tb3761FN.Text, }); + //todo 将解析结果插入IoTDB,时标从 + //await _messageReceivedEventRepository.InsertAsync(receivedMessage); } } diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index 98df22b..82672f6 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -54,24 +54,7 @@ namespace JiShe.CollectBus.Subscribers } - #region 电表消息采集 - - /// - /// 一分钟定时抄读任务消息消费订阅 - /// - /// - [HttpGet] - [Route("ammeter/oneminute/issued-eventQuery")] - public async Task> AmmeterScheduledMeterOneMinuteReadingIssuedEventQuery() - { - var currentDateTime = DateTime.Now; - - var list = await _meterReadingRecordsRepository.ParallelQueryAsync(currentDateTime.AddMinutes(-20), currentDateTime.AddMinutes(10)); - - return list; - - //return null; - } + #region 电表消息采集 /// /// 一分钟定时抄读任务消息消费订阅 @@ -146,9 +129,7 @@ namespace JiShe.CollectBus.Subscribers _logger.LogError("【15分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); } else - { - // var dd = await _meterReadingRecordsRepository.FirstOrDefaultAsync(d=>d.ManualOrNot== true); - + { var device = await _deviceRepository.FirstOrDefaultAsync(a => a.Number == receivedMessage.FocusAddress); if (device != null) { diff --git a/src/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs b/src/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs index d335fca..987013c 100644 --- a/src/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs +++ b/src/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs @@ -12,6 +12,18 @@ namespace JiShe.CollectBus.Ammeters [ATTRIBUTEColumn] public string MeterModel { get; set; } + /// + /// 下发消息内容 + /// + [FIELDColumn] + public string IssuedMessageHexString { get; set; } + + ///// + ///// 下发消息Id + ///// + //[FIELDColumn] + //public string IssuedMessageId { get; set; } + [FIELDColumn] public double Voltage { get; set; } diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs index 49fce87..41ba7ea 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs @@ -29,5 +29,10 @@ namespace JiShe.CollectBus.IotSystems.MessageIssueds /// public string MessageId { get; set; } + /// + /// 最后一次消息Id,用于在消费消息时检查上一个任务是否处理完。 + /// + public string LastMessageId { get; set; } + } } diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs index a75c032..153acdf 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs @@ -13,8 +13,7 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// 抄读数据记录 /// public class MeterReadingRecords : AggregateRoot - { - + { /// /// 是否手动操作 /// @@ -34,7 +33,7 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// 下发消息Id /// public string IssuedMessageId { get; set; } - + /// /// 集中器ID /// @@ -85,11 +84,15 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// public int Pn { get; set; } + /// + /// 采集项编码 + /// + public string ItemCode { get; set;} /// - /// 是否下发成功 + /// 是否超时 /// - public bool WasSuccessful { get; set; } + public bool IsTimeout { get; set; } = false; /// /// 创建时间 @@ -109,28 +112,13 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// /// 上报消息Id /// - public string ReceivedMessageId { get; set; } + public string ReceivedMessageId { get; set; } /// - /// 数据迁移状态 + /// 上报报文解析备注,异常情况下才有 /// - public RecordsDataMigrationStatusEnum MigrationStatus { get; set; } - - /// - /// 数据结果,最终的解析报文结果值 - /// - public string DataResult { get; set; } - - /// - /// 数据时间,如冻结时间、事件发生事件等 - /// - public DateTime? DataGenerationTimestamp { get; set; } - - /// - /// 数据迁移时间 - /// - public DateTime? MigrationTime { get; set; } - + public string ReceivedRemark { get; set; } + public void CreateDataId(Guid Id) { this.Id = Id; diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 3d51171..9694e44 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -40,7 +40,7 @@ "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" }, "Redis": { - "Configuration": "118.190.144.92:6380,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + "Configuration": "120.24.52.151:6380,password=1q2w3e!@#,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", "DefaultDB": "14", "HangfireDB": "15" }, diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs index 4046ba9..6ad2caa 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs @@ -15,6 +15,7 @@ using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; +using static Thrift.Protocol.Utilities.TJSONProtocolConstants; namespace JiShe.CollectBus.IoTDBProvider { @@ -30,7 +31,7 @@ namespace JiShe.CollectBus.IoTDBProvider private readonly IoTDBRuntimeContext _runtimeContext; public IoTDBProvider( - ILogger logger, + ILogger logger, IIoTDBSessionFactory sessionFactory, IoTDBRuntimeContext runtimeContext) { @@ -176,8 +177,21 @@ namespace JiShe.CollectBus.IoTDBProvider var rowValues = new List(); foreach (var measurement in metadata.ColumnNames) { - var value = typeof(T).GetProperty(measurement)?.GetValue(entity); - rowValues.Add(value ?? new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,属性{measurement}值为空,不符合IoTDB设计标准,请赋值以后重新处理。")); + PropertyInfo propertyInfo = typeof(T).GetProperty(measurement); + if (propertyInfo==null) + { + throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,没有找到{measurement}属性,属于异常情况。"); + } + var value = propertyInfo.GetValue(entity); + if (value != null) + { + rowValues.Add(value); + } + else + { + DataTypeValueMap.TryGetValue(propertyInfo.PropertyType.Name, out object defaultValue); + rowValues.Add(defaultValue); + } } values.Add(rowValues); if (!_runtimeContext.UseTableSessionPool)//树模型 @@ -286,7 +300,7 @@ namespace JiShe.CollectBus.IoTDBProvider sb.Append("DROP "); } - sb.Append($" FROM {options.TableNameOrTreePath}"); + sb.Append($" FROM {options.TableNameOrTreePath}"); sb.AppendJoin(", ", metadata.ColumnNames); @@ -509,7 +523,28 @@ namespace JiShe.CollectBus.IoTDBProvider ["TIMESTAMP"] = TSDataType.TIMESTAMP, ["DATE"] = TSDataType.DATE, ["BLOB"] = TSDataType.BLOB, + ["DECIMAL"] = TSDataType.STRING, ["STRING"] = TSDataType.STRING }; + + /// + /// 根据类型名称获取 IoTDB 数据默认值 + /// + private readonly IReadOnlyDictionary DataTypeValueMap = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["BOOLEAN"] = false, + ["INT32"] = 0, + ["INT64"] = 0, + ["FLOAT"] = 0.0f, + ["DOUBLE"] = 0.0d, + ["TEXT"] = string.Empty, + ["NULLTYPE"] = null, + ["TIMESTAMP"] = null, + ["DATE"] = null, + ["BLOB"] = null, + ["DECIMAL"] = "0.0", + ["STRING"] = string.Empty + }; } } diff --git a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs index 5a73b8d..0a5ab7a 100644 --- a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs +++ b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs @@ -109,6 +109,21 @@ namespace JiShe.CollectBus.Repository.MeterReadingRecord return entity; } + + /// + /// 单个获取 + /// + /// + /// + /// + /// + public async Task FirOrDefaultAsync(MeterReadingRecords entity, DateTime? dateTime) + { + var collection = await GetShardedCollection(dateTime); + await collection.findon + throw new NotImplementedException(); + } + /// /// 多集合数据查询 /// @@ -156,9 +171,5 @@ namespace JiShe.CollectBus.Repository.MeterReadingRecord return database.GetCollection(collectionName); } - public Task FirOrDefaultAsync(MeterReadingRecords entity, DateTime? dateTime) - { - throw new NotImplementedException(); - } } } diff --git a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs index 844a50b..66e6ede 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs @@ -18,50 +18,71 @@ namespace JiShe.CollectBus.Protocol.Contracts /// /// 1分钟采集电表数据下行消息主题 /// - public const string AmmeterSubscriberWorkerOneMinuteIssuedEventName = "issued.one.ammeter.event"; + public const string AmmeterSubscriberWorkerOneMinuteIssuedEventName = "issued.auto.one.ammeter.event"; /// /// 5分钟采集电表数据下行消息主题 /// - public const string AmmeterSubscriberWorkerFiveMinuteIssuedEventName = "issued.five.ammeter.event"; + public const string AmmeterSubscriberWorkerFiveMinuteIssuedEventName = "issued.auto.five.ammeter.event"; /// /// 15分钟采集电表数据下行消息主题 /// - public const string AmmeterSubscriberWorkerFifteenMinuteIssuedEventName = "issued.fifteen.ammeter.event"; + public const string AmmeterSubscriberWorkerFifteenMinuteIssuedEventName = "issued.auto.fifteen.ammeter.event"; /// - /// 其他采集数据下行消息主题,日冻结,月冻结、集中器版本号、定时阀控等 + /// 其他采集数据下行消息主题,日冻结,月冻结、集中器版本号控等 /// - public const string AmmeterSubscriberWorkerOtherIssuedEventName = "issued.other.ammeter.event"; + public const string AmmeterSubscriberWorkerOtherIssuedEventName = "issued.auto.other.ammeter.event"; + + /// + /// 电表自动阀控 + /// + public const string AmmeterSubscriberWorkerAutoValveControlIssuedEventName = "issued.auto.control.ammeter.event"; /// /// 电表手动阀控 /// - public const string AmmeterSubscriberWorkerManualValveControlIssuedEventName = "issued.control.ammeter.event"; + public const string AmmeterSubscriberWorkerManualValveControlIssuedEventName = "issued.manual.control.ammeter.event"; + + /// + /// 电表手动抄读 + /// + public const string AmmeterSubscriberWorkerManualValveReadingIssuedEventName = "issued.manual.reading.ammeter.event"; + #endregion #region 水表消息主题 /// /// 1分钟采集水表数据下行消息主题 /// - public const string WatermeterSubscriberWorkerOneMinuteIssuedEventName = "issued.one.watermeter.event"; + public const string WatermeterSubscriberWorkerOneMinuteIssuedEventName = "issued.auto.one.watermeter.event"; /// /// 5分钟采集水表数据下行消息主题 /// - public const string WatermeterSubscriberWorkerFiveMinuteIssuedEventName = "issued.five.watermeter.event"; + public const string WatermeterSubscriberWorkerFiveMinuteIssuedEventName = "issued.auto.five.watermeter.event"; /// /// 15分钟采集水表数据下行消息主题 /// - public const string WatermeterSubscriberWorkerFifteenMinuteIssuedEventName = "issued.fifteen.watermeter.event"; + public const string WatermeterSubscriberWorkerFifteenMinuteIssuedEventName = "issued.auto.fifteen.watermeter.event"; /// - /// 其他采集数据下行消息主题,日冻结,月冻结、集中器版本号、定时阀控等 + /// 其他采集数据下行消息主题,日冻结,月冻结、集中器版本号等 /// - public const string WatermeterSubscriberWorkerOtherIssuedEventName = "issued.other.watermeter.event"; + public const string WatermeterSubscriberWorkerOtherIssuedEventName = "issued.auto.other.watermeter.event"; + + /// + /// 水表自动阀控 + /// + public const string WatermeterSubscriberWorkerAutoValveControlIssuedEventName = "issued.auto.control.watermeter.event"; /// /// 水表手动阀控 /// - public const string WatermeterSubscriberWorkerManualValveControlIssuedEventName = "issued.control.watermeter.event"; + public const string WatermeterSubscriberWorkerManualValveControlIssuedEventName = "issued.manual.control.watermeter.event"; + + /// + /// 水表手动抄读 + /// + public const string WatermeterSubscriberWorkerManualValveReadingIssuedEventName = "issued.manual.reading.watermeter.event"; #endregion -- 2.47.2 From b42388e7dfd86b6b4786223e16edbfb2a2332771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E7=9B=8A?= Date: Tue, 8 Apr 2025 23:15:46 +0800 Subject: [PATCH 056/139] =?UTF-8?q?=E6=A2=B3=E7=90=86Kafka=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=EF=BC=8C=E4=BB=A5=E5=8F=8A=E8=AE=BE=E5=A4=87Hash?= =?UTF-8?q?=E5=88=86=E7=BB=84=E6=96=B9=E6=B3=95=E5=B0=81=E8=A3=85=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Subscribers/ISubscriberAppService.cs | 3 +- .../EnergySystem/EnergySystemAppService.cs | 34 +++++++------- .../Plugins/TcpMonitor.cs | 22 +++++++-- .../Subscribers/SubscriberAppService.cs | 47 ++++++++++++++----- .../Extensions/EnumExtensions.cs | 15 ++++++ .../Helpers/CommonHelper.cs | 34 ++++++++++++++ .../CollectBusHostModule.Configure.cs | 21 +++++---- .../MeterReadingRecordRepository.cs | 2 +- .../Abstracts/BaseProtocolPlugin.cs | 4 +- .../Extensions/ProtocolConstExtensions.cs | 37 +++++++++++++-- .../ProtocolConst.cs | 31 ++++++++++-- 11 files changed, 193 insertions(+), 57 deletions(-) diff --git a/src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs b/src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs index 22c79e6..f9bc706 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs @@ -7,7 +7,8 @@ namespace JiShe.CollectBus.Subscribers { public interface ISubscriberAppService : IApplicationService { - Task IssuedEvent(IssuedEventMessage issuedEventMessage); + Task LoginIssuedEvent(IssuedEventMessage issuedEventMessage); + Task HeartbeatIssuedEvent(IssuedEventMessage issuedEventMessage); Task ReceivedEvent(MessageReceived receivedMessage); Task ReceivedHeartbeatEvent(MessageReceivedHeartbeat receivedHeartbeatMessage); Task ReceivedLoginEvent(MessageReceivedLogin receivedLoginMessage); diff --git a/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs b/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs index 07db264..0542ede 100644 --- a/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs +++ b/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs @@ -71,7 +71,7 @@ namespace JiShe.CollectBus.EnergySystem return result; - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -108,7 +108,7 @@ namespace JiShe.CollectBus.EnergySystem foreach (var bytes in bytesList) { - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -149,7 +149,7 @@ namespace JiShe.CollectBus.EnergySystem }).ToList(); var bytes = Build3761SendData.BuildAmmeterParameterSetSendCmd(address, meterParameters); - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -178,7 +178,7 @@ namespace JiShe.CollectBus.EnergySystem { var dataUnit = Build645SendData.BuildReadMeterAddressSendDataUnit(detail.MeterAddress); var bytes =Build3761SendData.BuildTransparentForwardingSendCmd(address, detail.Port, detail.BaudRate.ToString(), dataUnit, StopBit.Stop1, Parity.None); - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -261,7 +261,7 @@ namespace JiShe.CollectBus.EnergySystem if (bytes != null) { - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -320,7 +320,7 @@ namespace JiShe.CollectBus.EnergySystem var bytes = Build3761SendData.BuildCommunicationParametersSetSendCmd(address, masterIP, materPort, backupIP, backupPort, input.Data.APN); - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -347,7 +347,7 @@ namespace JiShe.CollectBus.EnergySystem var address = $"{input.AreaCode}{input.Address}"; var bytes = Build3761SendData.BuildTerminalCalendarClockSendCmd(address); - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -375,7 +375,7 @@ namespace JiShe.CollectBus.EnergySystem bool isManual = !input.AreaCode.Equals("5110");//低功耗集中器不是长连接,在连接的那一刻再发送 var bytes = Build3761SendData.BuildConrCheckTimeSendCmd(address,DateTime.Now, isManual); - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -402,7 +402,7 @@ namespace JiShe.CollectBus.EnergySystem var address = $"{input.AreaCode}{input.Address}"; var bytes = Build3761SendData.BuildConrRebootSendCmd(address); - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -430,7 +430,7 @@ namespace JiShe.CollectBus.EnergySystem var address = $"{input.AreaCode}{input.Address}"; var pnList = input.Data.Split(',').Select(it => int.Parse(it)).ToList(); var bytes = Build3761SendData.BuildAmmeterParameterReadingSendCmd(address, pnList); - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -479,7 +479,7 @@ namespace JiShe.CollectBus.EnergySystem foreach (var bytes in bytesList) { - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -548,7 +548,7 @@ namespace JiShe.CollectBus.EnergySystem foreach (var bytes in bytesList) { - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -577,7 +577,7 @@ namespace JiShe.CollectBus.EnergySystem var address = $"{code.AreaCode}{code.Address}"; var bytes = Build3761SendData.BuildAmmeterReportCollectionItemsSetSendCmd(address,input.Detail.Pn, input.Detail.Unit,input.Detail.Cycle,input.Detail.BaseTime, input.Detail.CurveRatio,input.Detail.Details.Select(it => new PnFn(it.Pn,it.Fn)).ToList()); - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -605,7 +605,7 @@ namespace JiShe.CollectBus.EnergySystem { var address = $"{code.AreaCode}{code.Address}"; var bytes = Build3761SendData.BuildAmmeterAutoUpSwitchSetSendCmd(address, input.Detail.Pn,input.Detail.IsOpen); - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -631,7 +631,7 @@ namespace JiShe.CollectBus.EnergySystem var result = new BaseResultDto(); var address = $"{input.AreaCode}{input.Address}"; var bytes = Build3761SendData.BuildAmmeterReadAutoUpSwitchSendCmd(address, input.Detail.Pn); - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -658,7 +658,7 @@ namespace JiShe.CollectBus.EnergySystem { var address = $"{data.AreaCode}{data.Address}"; var bytes = Build3761SendData.BuildTerminalVersionInfoReadingSendCmd(address); - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, @@ -713,7 +713,7 @@ namespace JiShe.CollectBus.EnergySystem foreach (var bytes in bytesList) { - await _capBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage + await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index 8f37fb4..7224cdb 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -19,6 +19,7 @@ using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; +using static FreeSql.Internal.GlobalFilter; namespace JiShe.CollectBus.Plugins { @@ -78,7 +79,7 @@ namespace JiShe.CollectBus.Plugins } else { - await OnTcpNormalReceived(tcpSessionClient, messageHexString, aTuple.Item1); + await OnTcpNormalReceived(tcpSessionClient, messageHexString, aTuple.Item1,aFn.ToString()!.PadLeft(2,'0')); } } else @@ -160,7 +161,7 @@ namespace JiShe.CollectBus.Plugins DeviceNo = deviceNo, MessageId = NewId.NextGuid().ToString() }; - await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedLoginEventName, messageReceivedLoginEvent); + await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent); //await _producerBus.Publish( messageReceivedLoginEvent); } @@ -199,11 +200,19 @@ namespace JiShe.CollectBus.Plugins DeviceNo = deviceNo, MessageId = NewId.NextGuid().ToString() }; - await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedHeartbeatEventName, messageReceivedHeartbeatEvent); + await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent); //await _producerBus.Publish(messageReceivedHeartbeatEvent); } - private async Task OnTcpNormalReceived(ITcpSessionClient client, string messageHexString, string deviceNo) + /// + /// 正常帧处理,将不同的AFN进行分发 + /// + /// + /// + /// + /// + /// + private async Task OnTcpNormalReceived(ITcpSessionClient client, string messageHexString, string deviceNo,string aFn) { //await _producerBus.Publish(new MessageReceived //{ @@ -215,7 +224,10 @@ namespace JiShe.CollectBus.Plugins // MessageId = NewId.NextGuid().ToString() //}); - await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedEventName, new MessageReceived + + string topicName = string.Format(ProtocolConst.AFNTopicNameFormat, aFn); + + await _producerBus.PublishAsync(topicName, new MessageReceived { ClientId = client.Id, ClientIp = client.IP, diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index aa86101..a801454 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -60,9 +60,38 @@ namespace JiShe.CollectBus.Subscribers _meterReadingRecordsRepository = meterReadingRecordsRepository; } - [CapSubscribe(ProtocolConst.SubscriberIssuedEventName)] - public async Task IssuedEvent(IssuedEventMessage issuedEventMessage) + [CapSubscribe(ProtocolConst.SubscriberLoginIssuedEventName)] + public async Task LoginIssuedEvent(IssuedEventMessage issuedEventMessage) { + switch (issuedEventMessage.Type) + { + case IssuedEventType.Heartbeat: + break; + case IssuedEventType.Login: + _logger.LogWarning($"集中器地址{issuedEventMessage.ClientId} 登录回复下发内容:{issuedEventMessage.Serialize()}"); + var loginEntity = await _messageReceivedLoginEventRepository.GetAsync(a => a.MessageId == issuedEventMessage.MessageId); + loginEntity.AckTime = Clock.Now; + loginEntity.IsAck = true; + await _messageReceivedLoginEventRepository.UpdateAsync(loginEntity); + break; + case IssuedEventType.Data: + break; + default: + throw new ArgumentOutOfRangeException(); + } + + //var device = await _deviceRepository.FindAsync(a => a.Number == issuedEventMessage.DeviceNo); + //if (device != null) + //{ + // await _tcpService.SendAsync(device.ClientId, issuedEventMessage.Message); + //} + + await _tcpService.SendAsync(issuedEventMessage.ClientId, issuedEventMessage.Message); + } + + [CapSubscribe(ProtocolConst.SubscriberHeartbeatIssuedEventName)] + public async Task HeartbeatIssuedEvent(IssuedEventMessage issuedEventMessage) + { switch (issuedEventMessage.Type) { case IssuedEventType.Heartbeat: @@ -72,13 +101,6 @@ namespace JiShe.CollectBus.Subscribers heartbeatEntity.IsAck = true; await _messageReceivedHeartbeatEventRepository.UpdateAsync(heartbeatEntity); break; - case IssuedEventType.Login: - _logger.LogWarning($"集中器地址{issuedEventMessage.ClientId} 登录回复下发内容:{issuedEventMessage.Serialize()}"); - var loginEntity = await _messageReceivedLoginEventRepository.GetAsync(a => a.MessageId == issuedEventMessage.MessageId); - loginEntity.AckTime = Clock.Now; - loginEntity.IsAck = true; - await _messageReceivedLoginEventRepository.UpdateAsync(loginEntity); - break; case IssuedEventType.Data: break; default: @@ -130,15 +152,14 @@ namespace JiShe.CollectBus.Subscribers Pn = 0, FocusAddress = "", MeterAddress = "", + //DataResult = tb3761FN.Text, }); - //todo 将解析结果插入IoTDB,时标从 - //await _messageReceivedEventRepository.InsertAsync(receivedMessage); } } - [CapSubscribe(ProtocolConst.SubscriberReceivedHeartbeatEventName)] + [CapSubscribe(ProtocolConst.SubscriberHeartbeatReceivedEventName)] public async Task ReceivedHeartbeatEvent(MessageReceivedHeartbeat receivedHeartbeatMessage) { var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); @@ -153,7 +174,7 @@ namespace JiShe.CollectBus.Subscribers } } - [CapSubscribe(ProtocolConst.SubscriberReceivedLoginEventName)] + [CapSubscribe(ProtocolConst.SubscriberLoginReceivedEventName)] public async Task ReceivedLoginEvent(MessageReceivedLogin receivedLoginMessage) { var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); diff --git a/src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs b/src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs index 222984e..f54a3a4 100644 --- a/src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs +++ b/src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs @@ -49,6 +49,21 @@ namespace JiShe.CollectBus.Common.Extensions ); } + /// + /// 将枚举转换为字典 + /// + /// + /// + public static Dictionary ToNameValueDictionary() where TEnum : Enum + { + return Enum.GetValues(typeof(TEnum)) + .Cast() + .ToDictionary( + e => e.ToString(), + e => Convert.ToInt32(e) + ); + } + /// /// 将枚举转换为字典 /// diff --git a/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs index fa38d1d..8c980c5 100644 --- a/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs +++ b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs @@ -760,5 +760,39 @@ namespace JiShe.CollectBus.Common.Helpers return fontValue; } + + /// + /// 获取设备Id哈希值 + /// + /// + /// + /// + public static int GetDeviceHashCode(string deviceId, int TotalShards = 100) + { + // 计算哈希分组ID + return Math.Abs(deviceId.GetHashCode() % TotalShards); + } + + /// + /// 获取设备Id哈希分组 + /// + /// + /// + public static Dictionary> GetDeviceHashGroup(List deviceList) + { + Dictionary> keyValuePairs = new Dictionary>(); + foreach (var deviceId in deviceList) + { + var hashCode = GetDeviceHashCode(deviceId); + + if (!keyValuePairs.ContainsKey(hashCode.ToString())) + { + keyValuePairs.Add(hashCode.ToString(), new List()); + } + + keyValuePairs[hashCode.ToString()].Add(deviceId); + } + return keyValuePairs; + } } } diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 80145ab..32c39b5 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -257,6 +257,9 @@ namespace JiShe.CollectBus.Host /// The configuration. public void ConfigureCap(ServiceConfigurationContext context, IConfiguration configuration) { + List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); + topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); + context.Services.AddCap(x => { x.DefaultGroupName = ProtocolConst.SubscriberGroup; @@ -282,9 +285,7 @@ namespace JiShe.CollectBus.Host /// The configuration. /// /// Configures the mass transit. - /// - /// The context. - /// The configuration. + /// public void ConfigureMassTransit(ServiceConfigurationContext context, IConfiguration configuration) { @@ -296,7 +297,9 @@ namespace JiShe.CollectBus.Host try { - var topics = ProtocolConstExtensions.GetAllTopicNames(); + List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); + topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); + List topicSpecifications = new List(); foreach (var item in topics) { @@ -348,20 +351,20 @@ namespace JiShe.CollectBus.Host }); rider.AddConsumer(); - rider.AddProducer(ProtocolConst.SubscriberReceivedLoginEventName); - rider.AddProducer(ProtocolConst.SubscriberReceivedHeartbeatEventName); + rider.AddProducer(ProtocolConst.SubscriberLoginReceivedEventName); + rider.AddProducer(ProtocolConst.SubscriberHeartbeatReceivedEventName); rider.UsingKafka((c, cfg) => { cfg.Host(configuration.GetConnectionString("Kafka")); - cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedHeartbeatEventName, consumerConfig, configurator => + cfg.TopicEndpoint(ProtocolConst.SubscriberHeartbeatReceivedEventName, consumerConfig, configurator => { configurator.AutoOffsetReset = AutoOffsetReset.Earliest; configurator.ConfigureConsumer(c); }); - cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedLoginEventName, consumerConfig, configurator => + cfg.TopicEndpoint(ProtocolConst.SubscriberLoginReceivedEventName, consumerConfig, configurator => { configurator.ConfigureConsumer(c); configurator.AutoOffsetReset = AutoOffsetReset.Earliest; @@ -373,7 +376,7 @@ namespace JiShe.CollectBus.Host configurator.AutoOffsetReset = AutoOffsetReset.Earliest; }); - cfg.TopicEndpoint(ProtocolConst.SubscriberIssuedEventName, consumerConfig, configurator => + cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedEventName, consumerConfig, configurator => { configurator.ConfigureConsumer(c); configurator.AutoOffsetReset = AutoOffsetReset.Earliest; diff --git a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs index 0a5ab7a..7e34031 100644 --- a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs +++ b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs @@ -120,7 +120,7 @@ namespace JiShe.CollectBus.Repository.MeterReadingRecord public async Task FirOrDefaultAsync(MeterReadingRecords entity, DateTime? dateTime) { var collection = await GetShardedCollection(dateTime); - await collection.findon + //await collection.findon throw new NotImplementedException(); } diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index 28cf6c2..762a2ca 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -87,7 +87,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); - await _producerBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); + await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); //await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); } @@ -126,7 +126,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts Fn = 1 }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); - await _producerBus.PublishAsync(ProtocolConst.SubscriberIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); + await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); //await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); } diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs b/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs index 9455144..8c097e2 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs @@ -1,4 +1,6 @@ -using System; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -10,10 +12,10 @@ namespace JiShe.CollectBus.Protocol.Contracts public class ProtocolConstExtensions { /// - /// 自动获取 ProtocolConst 类中所有 Kafka 主题名称 + /// 自动获取 ProtocolConst 类中所有下行 Kafka 主题名称 /// (通过反射筛选 public const string 且字段名以 "EventName" 结尾的常量) /// - public static List GetAllTopicNames() + public static List GetAllTopicNamesByIssued() { return typeof(ProtocolConst) .GetFields(BindingFlags.Public | BindingFlags.Static) @@ -21,9 +23,36 @@ namespace JiShe.CollectBus.Protocol.Contracts f.IsLiteral && !f.IsInitOnly && f.FieldType == typeof(string) && - f.Name.EndsWith("EventName")) // 通过命名规则过滤主题字段 + f.Name.EndsWith("IssuedEventName")) // 通过命名规则过滤主题字段 .Select(f => (string)f.GetRawConstantValue()!) .ToList(); } + + /// + /// 自动获取 ProtocolConst 类中所有下行 Kafka 主题名称 + /// (通过反射筛选 public const string 且字段名以 "EventName" 结尾的常量) + /// + public static List GetAllTopicNamesByReceived() + { + //固定的上报主题 + var topicList = typeof(ProtocolConst) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(f => + f.IsLiteral && + !f.IsInitOnly && + f.FieldType == typeof(string) && + f.Name.EndsWith("ReceivedEventName")) // 通过命名规则过滤主题字段 + .Select(f => (string)f.GetRawConstantValue()!) + .ToList(); + + //动态上报主题,需根据协议的AFN功能码动态获取 + var afnList = EnumExtensions.ToNameValueDictionary(); + foreach (var item in afnList) + { + topicList.Add(string.Format(ProtocolConst.AFNTopicNameFormat, item.Value.ToString().PadLeft(2, '0'))); + } + + return topicList; + } } } diff --git a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs index 66e6ede..796023a 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs @@ -9,10 +9,28 @@ namespace JiShe.CollectBus.Protocol.Contracts public class ProtocolConst { public const string SubscriberGroup = "jishe.collectbus"; - public const string SubscriberIssuedEventName = "issued.event"; + /// + /// 心跳下行消息主题 + /// + public const string SubscriberHeartbeatIssuedEventName = "issued.heartbeat.event"; + /// + /// 登录下行消息主题 + /// + public const string SubscriberLoginIssuedEventName = "issued.login.event"; + + /// + /// 上行消息主题 + /// public const string SubscriberReceivedEventName = "received.event"; - public const string SubscriberReceivedHeartbeatEventName = "received.heartbeat.event"; - public const string SubscriberReceivedLoginEventName = "received.login.event"; + + /// + /// 心跳上行消息主题 + /// + public const string SubscriberHeartbeatReceivedEventName = "received.heartbeat.event"; + /// + /// 登录上行消息主题 + /// + public const string SubscriberLoginReceivedEventName = "received.login.event"; #region 电表消息主题 /// @@ -47,7 +65,7 @@ namespace JiShe.CollectBus.Protocol.Contracts /// 电表手动抄读 /// public const string AmmeterSubscriberWorkerManualValveReadingIssuedEventName = "issued.manual.reading.ammeter.event"; - + #endregion #region 水表消息主题 @@ -85,7 +103,10 @@ namespace JiShe.CollectBus.Protocol.Contracts public const string WatermeterSubscriberWorkerManualValveReadingIssuedEventName = "issued.manual.reading.watermeter.event"; #endregion - + /// + /// AFN上行主题格式 + /// + public const string AFNTopicNameFormat = "received.afn{0}.event"; } } -- 2.47.2 From beb11c76f534c1bd0e951f52c9565584095df3a3 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Wed, 9 Apr 2025 14:31:48 +0800 Subject: [PATCH 057/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Subscribers/WorkerSubscriberAppService.cs | 22 ++--- .../CollectBusHostModule.Configure.cs | 81 ++++++++++--------- .../CollectBusHostModule.cs | 1 + src/JiShe.CollectBus.Host/appsettings.json | 3 +- .../Extensions/ProtocolConstExtensions.cs | 11 ++- 5 files changed, 65 insertions(+), 53 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index 82672f6..4428fe4 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -148,7 +148,7 @@ namespace JiShe.CollectBus.Subscribers #region 水表消息采集 /// - /// 一分钟定时抄读任务消息消费订阅 + /// 1分钟采集水表数据下行消息消费订阅 /// /// /// @@ -157,11 +157,11 @@ namespace JiShe.CollectBus.Subscribers [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerOneMinuteIssuedEventName)] public async Task WatermeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { - _logger.LogInformation("1分钟采集电表数据下行消息消费队列开始处理"); + _logger.LogInformation("1分钟采集水表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); if (protocolPlugin == null) { - _logger.LogError("【1分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); + _logger.LogError("【1分钟采集水表数据下行消息消费队列开始处理】协议不存在!"); } else { @@ -175,20 +175,20 @@ namespace JiShe.CollectBus.Subscribers } /// - /// 5分钟采集电表数据下行消息消费订阅 + /// 5分钟采集水表数据下行消息消费订阅 /// /// /// [HttpPost] [Route("watermeter/fiveminute/issued-event")] - [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName)] + [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerFiveMinuteIssuedEventName)] public async Task WatermeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { - _logger.LogInformation("5分钟采集电表数据下行消息消费队列开始处理"); + _logger.LogInformation("5分钟采集水表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); if (protocolPlugin == null) { - _logger.LogError("【5分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); + _logger.LogError("【5分钟采集水表数据下行消息消费队列开始处理】协议不存在!"); } else { @@ -202,20 +202,20 @@ namespace JiShe.CollectBus.Subscribers } /// - /// 15分钟采集电表数据下行消息消费订阅 + /// 15分钟采集水表数据下行消息消费订阅 /// /// /// [HttpPost] [Route("watermeter/fifteenminute/issued-event")] - [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName)] + [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerFifteenMinuteIssuedEventName)] public async Task WatermeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { - _logger.LogInformation("15分钟采集电表数据下行消息消费队列开始处理"); + _logger.LogInformation("15分钟采集水表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); if (protocolPlugin == null) { - _logger.LogError("【15分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); + _logger.LogError("【15分钟采集水表数据下行消息消费队列开始处理】协议不存在!"); } else { diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 32c39b5..96e1e18 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -256,10 +256,7 @@ namespace JiShe.CollectBus.Host /// The context. /// The configuration. public void ConfigureCap(ServiceConfigurationContext context, IConfiguration configuration) - { - List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); - topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); - + { context.Services.AddCap(x => { x.DefaultGroupName = ProtocolConst.SubscriberGroup; @@ -287,40 +284,7 @@ namespace JiShe.CollectBus.Host /// Configures the mass transit. /// public void ConfigureMassTransit(ServiceConfigurationContext context, IConfiguration configuration) - { - - - var adminClient = new AdminClientBuilder(new AdminClientConfig - { - BootstrapServers = configuration.GetConnectionString("Kafka") - }).Build(); - - try - { - List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); - topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); - - List topicSpecifications = new List(); - foreach (var item in topics) - { - topicSpecifications.Add(new TopicSpecification - { - Name = item, - NumPartitions = 3, - ReplicationFactor = 1 - }); - } - adminClient.CreateTopicsAsync(topicSpecifications).ConfigureAwait(false).GetAwaiter().GetResult(); - } - catch (CreateTopicsException e) - { - if (e.Results[0].Error.Code != ErrorCode.TopicAlreadyExists) - { - throw; - } - } - - + { var consumerConfig = new ConsumerConfig { GroupId = ProtocolConst.SubscriberGroup }; var producerConfig = new ProducerConfig(); @@ -391,5 +355,46 @@ namespace JiShe.CollectBus.Host }); }); } + + /// + /// 配置Kafka主题 + /// + /// + /// + public void ConfigureKafkaTopic(ServiceConfigurationContext context, IConfiguration configuration) + { + var adminClient = new AdminClientBuilder(new AdminClientConfig + { + BootstrapServers = configuration.GetConnectionString("Kafka") + }).Build(); + + try + { + string serverTagName = configuration.GetSection("ServerTagName").Value!; + + List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(serverTagName); + topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived(serverTagName)); + + List topicSpecifications = new List(); + foreach (var item in topics) + { + topicSpecifications.Add(new TopicSpecification + { + Name = item, + NumPartitions = 3, + ReplicationFactor = 1 + }); + } + adminClient.CreateTopicsAsync(topicSpecifications).ConfigureAwait(false).GetAwaiter().GetResult(); + } + catch (CreateTopicsException e) + { + if (e.Results[0].Error.Code != ErrorCode.TopicAlreadyExists) + { + throw; + } + } + + } } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs index 6f24b1e..f18fcdb 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs @@ -45,6 +45,7 @@ namespace JiShe.CollectBus.Host ConfigureHangfire(context); ConfigureCap(context, configuration); //ConfigureMassTransit(context, configuration); + ConfigureKafkaTopic(context, configuration); ConfigureAuditLog(context); ConfigureCustom(context, configuration); } diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 9694e44..2ef7ded 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -81,5 +81,6 @@ "DataBaseName": "energy", "OpenDebugMode": true, "UseTableSessionPoolByDefault": false - } + }, + "ServerTagName": "JiSheCollectBus" } \ No newline at end of file diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs b/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs index 8c097e2..67abc75 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs @@ -15,24 +15,27 @@ namespace JiShe.CollectBus.Protocol.Contracts /// 自动获取 ProtocolConst 类中所有下行 Kafka 主题名称 /// (通过反射筛选 public const string 且字段名以 "EventName" 结尾的常量) /// - public static List GetAllTopicNamesByIssued() + public static List GetAllTopicNamesByIssued(string serverTagName) { - return typeof(ProtocolConst) + List topics = typeof(ProtocolConst) .GetFields(BindingFlags.Public | BindingFlags.Static) .Where(f => f.IsLiteral && !f.IsInitOnly && f.FieldType == typeof(string) && f.Name.EndsWith("IssuedEventName")) // 通过命名规则过滤主题字段 + //.Select(f => $"{serverTagName}.{(string)f.GetRawConstantValue()!}") .Select(f => (string)f.GetRawConstantValue()!) .ToList(); + + return topics; } /// /// 自动获取 ProtocolConst 类中所有下行 Kafka 主题名称 /// (通过反射筛选 public const string 且字段名以 "EventName" 结尾的常量) /// - public static List GetAllTopicNamesByReceived() + public static List GetAllTopicNamesByReceived(string serverTagName) { //固定的上报主题 var topicList = typeof(ProtocolConst) @@ -52,6 +55,8 @@ namespace JiShe.CollectBus.Protocol.Contracts topicList.Add(string.Format(ProtocolConst.AFNTopicNameFormat, item.Value.ToString().PadLeft(2, '0'))); } + //return topicList.Select(f => $"{serverTagName}.{f}").ToList(); + return topicList; } } -- 2.47.2 From 149f78278dd7e6403bc67a9fd26212cfcbbc3ac2 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Wed, 9 Apr 2025 14:33:20 +0800 Subject: [PATCH 058/139] =?UTF-8?q?kafka=E6=95=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 7 - .../Samples/SampleDto.cs | 14 +- .../CollectBusApplicationModule.cs | 51 +++- .../CustomKafkaDistributedEventBus.cs | 250 ++++++++++++++++++ .../Handlers/SampleHandler.cs | 46 ++++ .../JiShe.CollectBus.Application.csproj | 1 + .../Samples/SampleAppService.cs | 28 ++ .../Attributes/TopicNameAttribute.cs | 23 ++ src/JiShe.CollectBus.Host/appsettings.json | 43 ++- .../AdminClient/AdminClientService.cs | 111 ++++++++ .../AdminClient/IAdminClientService.cs | 34 +++ .../Attributes/TopicAttribute.cs | 29 ++ .../CollectBusKafkaModule.cs | 16 +- .../Consumer/ConsumerBackgroundService.cs | 54 ++++ .../Consumer/ConsumerService.cs | 58 ++++ .../Consumer/IConsumerService.cs | 12 + .../ConsumerService.cs | 58 ---- .../JiShe.CollectBus.Kafka.csproj | 2 +- .../Producer/IProducerService.cs | 14 + .../Producer/ProducerBaseService.cs | 64 +++++ .../Producer/ProducerService.cs | 31 +++ .../ProducerService.cs | 28 -- 22 files changed, 863 insertions(+), 111 deletions(-) create mode 100644 src/JiShe.CollectBus.Application/CustomKafkaDistributedEventBus.cs create mode 100644 src/JiShe.CollectBus.Application/Handlers/SampleHandler.cs create mode 100644 src/JiShe.CollectBus.Common/Attributes/TopicNameAttribute.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/Attributes/TopicAttribute.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerBackgroundService.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs delete mode 100644 src/JiShe.CollectBus.KafkaProducer/ConsumerService.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/Producer/ProducerBaseService.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs delete mode 100644 src/JiShe.CollectBus.KafkaProducer/ProducerService.cs diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index 62bfe8b..674eca4 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -31,8 +31,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.FreeSql", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.FreeRedisProvider", "src\JiShe.CollectBus.FreeRedisProvider\JiShe.CollectBus.FreeRedisProvider.csproj", "{C06C4082-638F-2996-5FED-7784475766C1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka", "src\JiShe.CollectBus.KafkaProducer\JiShe.CollectBus.Kafka.csproj", "{919F4CDB-5C82-4371-B209-403B408DA248}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -91,10 +89,6 @@ Global {C06C4082-638F-2996-5FED-7784475766C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {C06C4082-638F-2996-5FED-7784475766C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {C06C4082-638F-2996-5FED-7784475766C1}.Release|Any CPU.Build.0 = Release|Any CPU - {919F4CDB-5C82-4371-B209-403B408DA248}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {919F4CDB-5C82-4371-B209-403B408DA248}.Debug|Any CPU.Build.0 = Debug|Any CPU - {919F4CDB-5C82-4371-B209-403B408DA248}.Release|Any CPU.ActiveCfg = Release|Any CPU - {919F4CDB-5C82-4371-B209-403B408DA248}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -113,7 +107,6 @@ Global {8BA01C3D-297D-42DF-BD63-EF07202A0A67} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {FE0457D9-4038-4A17-8808-DCAD06CFC0A0} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {C06C4082-638F-2996-5FED-7784475766C1} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {919F4CDB-5C82-4371-B209-403B408DA248} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/src/JiShe.CollectBus.Application.Contracts/Samples/SampleDto.cs b/src/JiShe.CollectBus.Application.Contracts/Samples/SampleDto.cs index 02a9f19..6211273 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Samples/SampleDto.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Samples/SampleDto.cs @@ -1,6 +1,18 @@ -namespace JiShe.CollectBus.Samples; +using JiShe.CollectBus.Common.Attributes; +using Volo.Abp.EventBus; +namespace JiShe.CollectBus.Samples; + +[EventName("Sample.Kafka.Test")] +[TopicName("Test1")] public class SampleDto { public int Value { get; set; } } + +[EventName("Sample.Kafka.Test2")] +[TopicName("Test2")] +public class SampleDto2 +{ + public int Value { get; set; } +} diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index daebf74..1176dd5 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -16,8 +16,11 @@ using JiShe.CollectBus.Workers; using Volo.Abp.BackgroundWorkers.Hangfire; using JiShe.CollectBus.MongoDB; using JiShe.CollectBus.ScheduledMeterReading; -using AutoMapper.Configuration.Annotations; -using JiShe.CollectBus.Common.Attributes; +using Volo.Abp.EventBus.Kafka; +using Volo.Abp.Kafka; +using Microsoft.Extensions.Configuration; +using Volo.Abp.EventBus; +using Confluent.Kafka; namespace JiShe.CollectBus; @@ -28,35 +31,65 @@ namespace JiShe.CollectBus; typeof(AbpAutoMapperModule), typeof(AbpBackgroundWorkersHangfireModule), typeof(CollectBusFreeRedisModule), - typeof(CollectBusFreeSqlModule) + typeof(CollectBusFreeSqlModule), + typeof(AbpEventBusModule), + typeof(AbpKafkaModule) )] public class CollectBusApplicationModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { + var configuration = context.Services.GetConfiguration(); + context.Services.AddAutoMapperObjectMapper(); Configure(options => { options.AddMaps(validate: true); }); - //Configure(options => - //{ - // options.TokenCookie.Expiration = TimeSpan.FromDays(365); - // options.AutoValidateIgnoredHttpMethods.Add("POST"); - //}); + Configure(configuration.GetSection("Kafka")); + Configure(configuration.GetSection("Kafka:EventBus")); + + Configure(options => + { + options.ConfigureConsumer = config => + { + config.GroupId = configuration.GetValue("Kafka:Consumer:GroupId"); + config.EnableAutoCommit = configuration.GetValue("Kafka:Consumer:EnableAutoCommit"); + }; + }); + + Configure(options => + { + options.ConfigureProducer = config => + { + config.MessageTimeoutMs = configuration.GetValue("Kafka:Producer:MessageTimeoutMs"); + config.Acks = (Acks)configuration.GetValue("Kafka:Producer:Acks"); + }; + }); + + Configure(options => + { + options.ConfigureTopic = specification => + { + specification.ReplicationFactor = configuration.GetValue("Kafka:Topic:ReplicationFactor"); + specification.NumPartitions = configuration.GetValue("Kafka:Topic:NumPartitions"); + }; + }); } public override void OnApplicationInitialization( ApplicationInitializationContext context) { + context.ServiceProvider.GetRequiredService().Initialize(); + var assembly = Assembly.GetExecutingAssembly(); var types = assembly.GetTypes().Where(t => typeof(ICollectWorker).IsAssignableFrom(t) && !t.IsInterface).ToList(); foreach (var type in types) { context.AddBackgroundWorkerAsync(type); } - + var dbContext = context.ServiceProvider.GetRequiredService(); //默认初始化表计信息 diff --git a/src/JiShe.CollectBus.Application/CustomKafkaDistributedEventBus.cs b/src/JiShe.CollectBus.Application/CustomKafkaDistributedEventBus.cs new file mode 100644 index 0000000..aeca6fe --- /dev/null +++ b/src/JiShe.CollectBus.Application/CustomKafkaDistributedEventBus.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Confluent.Kafka; +using JiShe.CollectBus.Common.Attributes; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.EventBus.Kafka; +using Volo.Abp.EventBus.Local; +using Volo.Abp.Guids; +using Volo.Abp.Kafka; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Timing; +using Volo.Abp.Tracing; +using Volo.Abp.Uow; +using Volo.Abp.Threading; + + +namespace JiShe.CollectBus; + +[Dependency(ReplaceServices = true)] +[ExposeServices(typeof(IDistributedEventBus), typeof(CustomKafkaDistributedEventBus))] +public class CustomKafkaDistributedEventBus : DistributedEventBusBase, ISingletonDependency +{ + + protected AbpKafkaEventBusOptions AbpKafkaEventBusOptions { get; } + protected IKafkaMessageConsumerFactory MessageConsumerFactory { get; } + protected IKafkaSerializer Serializer { get; } + protected IProducerPool ProducerPool { get; } + protected ConcurrentDictionary> HandlerFactories { get; } + protected ConcurrentDictionary EventTypes { get; } + protected IKafkaMessageConsumer Consumer { get; private set; } = default!; + + + public CustomKafkaDistributedEventBus(IServiceScopeFactory serviceScopeFactory, + ICurrentTenant currentTenant, + IUnitOfWorkManager unitOfWorkManager, + IOptions abpDistributedEventBusOptions, + IGuidGenerator guidGenerator, + IClock clock, + IEventHandlerInvoker eventHandlerInvoker, + ILocalEventBus localEventBus, + ICorrelationIdProvider correlationIdProvider, + IOptions abpKafkaEventBusOptions, + IKafkaMessageConsumerFactory messageConsumerFactory, + IKafkaSerializer serializer, + IProducerPool producerPool) + : base(serviceScopeFactory, currentTenant, unitOfWorkManager, abpDistributedEventBusOptions, guidGenerator, clock, eventHandlerInvoker, localEventBus, correlationIdProvider) + { + AbpKafkaEventBusOptions = abpKafkaEventBusOptions.Value; + MessageConsumerFactory = messageConsumerFactory; + Serializer = serializer; + ProducerPool = producerPool; + HandlerFactories = new ConcurrentDictionary>(); + EventTypes = new ConcurrentDictionary(); + } + + + public void Initialize() + { + SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); + } + + public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) + { + var handlerFactories = GetOrCreateHandlerFactories(eventType); + + if (factory.IsInFactories(handlerFactories)) + { + return NullDisposable.Instance; + } + + handlerFactories.Add(factory); + + return new EventHandlerFactoryUnregistrar(this, eventType, factory); + } + + public override void Unsubscribe(Func action) + { + Check.NotNull(action, nameof(action)); + + GetOrCreateHandlerFactories(typeof(TEvent)) + .Locking(factories => + { + factories.RemoveAll( + factory => + { + var singleInstanceFactory = factory as SingleInstanceHandlerFactory; + if (singleInstanceFactory == null) + { + return false; + } + + var actionHandler = singleInstanceFactory.HandlerInstance as ActionEventHandler; + if (actionHandler == null) + { + return false; + } + + return actionHandler.Action == action; + }); + }); + } + + public override void Unsubscribe(Type eventType, IEventHandler handler) + { + GetOrCreateHandlerFactories(eventType) + .Locking(factories => + { + factories.RemoveAll( + factory => + factory is SingleInstanceHandlerFactory handlerFactory && + handlerFactory.HandlerInstance == handler + ); + }); + } + + public override void Unsubscribe(Type eventType, IEventHandlerFactory factory) + { + GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Remove(factory)); + } + + public override void UnsubscribeAll(Type eventType) + { + GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); + } + + protected override async Task PublishToEventBusAsync(Type eventType, object eventData) + { + var headers = new Headers + { + { "messageId", System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")) } + }; + + if (CorrelationIdProvider.Get() != null) + { + headers.Add(EventBusConsts.CorrelationIdHeaderName, System.Text.Encoding.UTF8.GetBytes(CorrelationIdProvider.Get()!)); + } + + var topicAttribute = eventType.GetCustomAttribute(); + + await PublishAsync( + topicAttribute==null?AbpKafkaEventBusOptions.TopicName:topicAttribute.Name, + eventType, + eventData, + headers + ); + } + + protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) + { + unitOfWork.AddOrReplaceDistributedEvent(eventRecord); + } + + protected override IEnumerable GetHandlerFactories(Type eventType) + { + var handlerFactoryList = new List(); + + foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))) + { + handlerFactoryList.Add( + new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value)); + } + + return handlerFactoryList.ToArray(); + } + + public override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) + { + throw new NotImplementedException(); + } + + public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + { + throw new NotImplementedException(); + } + + public override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) + { + throw new NotImplementedException(); + } + + protected override byte[] Serialize(object eventData) + { + return Serializer.Serialize(eventData); + } + + private List GetOrCreateHandlerFactories(Type eventType) + { + return HandlerFactories.GetOrAdd( + eventType, + type => + { + var eventName = EventNameAttribute.GetNameOrDefault(type); + EventTypes.GetOrAdd(eventName, eventType); + return new List(); + } + ); + } + + private Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers) + { + var eventName = EventNameAttribute.GetNameOrDefault(eventType); + var body = Serializer.Serialize(eventData); + + return PublishAsync(topicName, eventName, body, headers); + } + + private Task> PublishAsync( + string topicName, + string eventName, + byte[] body, + Headers headers) + { + var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); + + return producer.ProduceAsync( + topicName, + new Message + { + Key = eventName, + Value = body, + Headers = headers + }); + } + + private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType) + { + //Should trigger same type + if (handlerEventType == targetEventType) + { + return true; + } + + //Should trigger for inherited types + if (handlerEventType.IsAssignableFrom(targetEventType)) + { + return true; + } + + return false; + } +} diff --git a/src/JiShe.CollectBus.Application/Handlers/SampleHandler.cs b/src/JiShe.CollectBus.Application/Handlers/SampleHandler.cs new file mode 100644 index 0000000..292c8d6 --- /dev/null +++ b/src/JiShe.CollectBus.Application/Handlers/SampleHandler.cs @@ -0,0 +1,46 @@ +using JiShe.CollectBus.Samples; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; + +namespace JiShe.CollectBus.Handlers +{ + public class SampleHandler : IDistributedEventHandler, + ITransientDependency + { + + private readonly ILogger _logger; + + public SampleHandler(ILogger logger) + { + _logger = logger; + } + + public async Task HandleEventAsync(SampleDto eventData) + { + _logger.LogWarning($"topic Test1 message: {eventData.Value.ToString()}"); + } + } + + public class SampleHandler2 : IDistributedEventHandler, + ITransientDependency + { + + private readonly ILogger _logger; + + public SampleHandler2(ILogger logger) + { + _logger = logger; + } + + public async Task HandleEventAsync(SampleDto2 eventData) + { + _logger.LogWarning($"topic Test2 message: {eventData.Value.ToString()}"); + } + } +} diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index 94c4faf..efdeed7 100644 --- a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -23,6 +23,7 @@ + diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 6235a55..c45767e 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -3,11 +3,21 @@ using System.Threading.Tasks; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.IotSystems.PrepayModel; using Microsoft.AspNetCore.Authorization; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.EventBus.Kafka; namespace JiShe.CollectBus.Samples; public class SampleAppService : CollectBusAppService, ISampleAppService { + + private readonly IDistributedEventBus _distributedEventBus; + public SampleAppService(IDistributedEventBus distributedEventBus) + { + _distributedEventBus = distributedEventBus; + } + + public Task GetAsync() { return Task.FromResult( @@ -36,4 +46,22 @@ public class SampleAppService : CollectBusAppService, ISampleAppService var ammeterList = await SqlProvider.Instance.Change(DbEnum.PrepayDB).Select().Where(d => d.TB_CustomerID == 5).Take(10).ToListAsync(); return ammeterList; } + + [AllowAnonymous] + public async Task KafkaSendTest() + { + await _distributedEventBus.PublishAsync( + new SampleDto + { + Value = 123456, + } + ); + + await _distributedEventBus.PublishAsync( + new SampleDto2 + { + Value = 456789, + } + ); + } } diff --git a/src/JiShe.CollectBus.Common/Attributes/TopicNameAttribute.cs b/src/JiShe.CollectBus.Common/Attributes/TopicNameAttribute.cs new file mode 100644 index 0000000..7e404a5 --- /dev/null +++ b/src/JiShe.CollectBus.Common/Attributes/TopicNameAttribute.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.EventBus; +using Volo.Abp; + +namespace JiShe.CollectBus.Common.Attributes +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class TopicNameAttribute : Attribute + { + public virtual string Name { get; } + + public TopicNameAttribute(string name) + { + this.Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + } + + public string GetName(Type eventType) => this.Name; + } +} diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 0936310..28eedf6 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -41,7 +41,8 @@ "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" }, "Redis": { - "Configuration": "118.190.144.92:6379,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + //"Configuration": "118.190.144.92:6379,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + "Configuration": "127.0.0.1:6379,password=123456@qwer,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", "DefaultDB": "14", "HangfireDB": "15" @@ -83,11 +84,41 @@ "Port": 5672 } }, + //"Kafka": { + // "BootstrapServers": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092", + // "EnableAuthorization": false, + // "SecurityProtocol": "SASL_PLAINTEXT", + // "SaslMechanism": "PLAIN", + // "SaslUserName": "lixiao", + // "SaslPassword": "lixiao1980", + // "Topic": { + // "ReplicationFactor": 3, + // "NumPartitions": 1000 + // } "Kafka": { - "EnableAuthorization": false, - "SecurityProtocol": "SASL_PLAINTEXT", - "SaslMechanism": "PLAIN", - "SaslUserName": "lixiao", - "SaslPassword": "lixiao1980" + "Connections": { + "Default": { + "BootstrapServers": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092" + // "SecurityProtocol": "SASL_PLAINTEXT", + // "SaslMechanism": "PLAIN", + // "SaslUserName": "lixiao", + // "SaslPassword": "lixiao1980", + } + }, + "Consumer": { + "GroupId": "JiShe.CollectBus" + }, + "Producer": { + "MessageTimeoutMs": 6000, + "Acks": -1 + }, + "Topic": { + "ReplicationFactor": 3, + "NumPartitions": 1000 + }, + "EventBus": { + "GroupId": "JiShe.CollectBus", + "TopicName": "DefaultTopicName" + } } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs new file mode 100644 index 0000000..5b85ebf --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs @@ -0,0 +1,111 @@ +using Confluent.Kafka; +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Confluent.Kafka.Admin; +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; + +namespace JiShe.CollectBus.Kafka.AdminClient +{ + public class AdminClientService : IAdminClientService, ISingletonDependency + { + + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The logger. + public AdminClientService(IConfiguration configuration, ILogger logger) + { + _logger = logger; + GetInstance(configuration); + } + + /// + /// Gets or sets the instance. + /// + /// + /// The instance. + /// + public IAdminClient Instance { get; set; } = default; + + /// + /// Gets the instance. + /// + /// The configuration. + /// + public IAdminClient GetInstance(IConfiguration configuration) + { + var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]); + var adminClientConfig = new AdminClientConfig() + { + BootstrapServers = configuration["Kafka:BootstrapServers"], + }; + if (enableAuthorization) + { + adminClientConfig.SecurityProtocol = SecurityProtocol.SaslPlaintext; + adminClientConfig.SaslMechanism = SaslMechanism.Plain; + adminClientConfig.SaslUsername = configuration["Kafka:SaslUserName"]; + adminClientConfig.SaslPassword = configuration["Kafka:SaslPassword"]; + } + Instance = new AdminClientBuilder(adminClientConfig).Build(); + return Instance; + } + + /// + /// Checks the topic asynchronous. + /// + /// The topic. + /// + public async Task CheckTopicAsync(string topic) + { + var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(5)); + return await Task.FromResult(metadata.Topics.Exists(a => a.Topic == topic)); + } + + /// + /// Creates the topic if not exist asynchronous. + /// + /// Name of the topic. + /// The factor number. + /// The partition number. + public async Task CreateTopicIfNotExistAsync(string topicName, short factorNum, int partitionNum) + { + try + { + if (await CheckTopicAsync(topicName)) return; + + await Instance.CreateTopicsAsync(new TopicSpecification[] + { + new TopicSpecification { Name = topicName, ReplicationFactor = factorNum, NumPartitions = partitionNum } + }); + } + catch (CreateTopicsException e) + { + _logger.LogError($"An error occured creating topic {e.Results[0].Topic}: {e.Results[0].Error.Reason}"); + } + } + + /// + /// Deletes the topic asynchronous. + /// + /// Name of the topic. + public async Task DeleteTopicAsync(List topicName) + { + try + { + await Instance.DeleteTopicsAsync(topicName, null); + } + catch (DeleteTopicsException e) + { + _logger.LogError($"An error occured creating topic {e.Results[0].Topic}: {e.Results[0].Error.Reason}"); + } + } + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs b/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs new file mode 100644 index 0000000..bdb2d07 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka.AdminClient +{ + public interface IAdminClientService + { + /// + /// Checks the topic asynchronous. + /// + /// The topic. + /// + Task CheckTopicAsync(string topic); + + /// + /// Creates the topic if not exist asynchronous. + /// + /// Name of the topic. + /// The factor number. + /// The partition number. + /// + Task CreateTopicIfNotExistAsync(string topicName, short factorNum, int partitionNum); + + /// + /// Deletes the topic asynchronous. + /// + /// Name of the topic. + /// + Task DeleteTopicAsync(List topicName); + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/Attributes/TopicAttribute.cs b/src/JiShe.CollectBus.KafkaProducer/Attributes/TopicAttribute.cs new file mode 100644 index 0000000..4cb2fff --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/Attributes/TopicAttribute.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka.Attributes +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class TopicAttribute: Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The name. + public TopicAttribute(string name = "Default") + { + Name = name; + } + + /// + /// Gets or sets the name. + /// + /// + /// The name. + /// + public string Name { get; set; } + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index 6f7bc9e..44ddcee 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -1,4 +1,8 @@ -using Volo.Abp.Modularity; +using Confluent.Kafka; +using JiShe.CollectBus.Kafka.Consumer; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; namespace JiShe.CollectBus.Kafka { @@ -6,6 +10,16 @@ namespace JiShe.CollectBus.Kafka { public override void ConfigureServices(ServiceConfigurationContext context) { + var configuration = context.Services.GetConfiguration(); + + // 注册 Kafka 生产者 + context.Services.AddSingleton>(sp => + new ProducerBuilder( + configuration.GetSection("Kafka:ProducerConfig").Get() + ) + .Build()); + // 注册后台服务 + context.Services.AddHostedService(); } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerBackgroundService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerBackgroundService.cs new file mode 100644 index 0000000..e49a09e --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerBackgroundService.cs @@ -0,0 +1,54 @@ +using Confluent.Kafka; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka.Consumer +{ + public class ConsumerBackgroundService : BackgroundService + { + private readonly ConsumerService _consumerService; + private readonly ILogger _logger; + + public ConsumerBackgroundService( + ConsumerService consumerService, + ILogger logger) + { + _consumerService = consumerService; + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _consumerService.Subscribe("abp-kafka-topic"); + + while (!stoppingToken.IsCancellationRequested) + { + try + { + var result = _consumerService.Consume(stoppingToken); + await ProcessMessageAsync(result.Message.Value); + _consumerService.Commit(result); + } + catch (ConsumeException ex) + { + _logger.LogError(ex, $"Message consume error: {ex.Error.Reason}"); + } + } + } + + private async Task ProcessMessageAsync(string message) + { + // 使用 ABP 的异步处理机制 + await Task.Run(() => + { + _logger.LogInformation($"Processing message: {message}"); + // 这里可以触发 ABP 的领域事件 + }); + } + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs new file mode 100644 index 0000000..391a7fc --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -0,0 +1,58 @@ +using Confluent.Kafka; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Kafka.Attributes; +using Volo.Abp.DependencyInjection; +using JiShe.CollectBus.Kafka.AdminClient; + +namespace JiShe.CollectBus.Kafka.Consumer +{ + public abstract class ConsumerService : IConsumerService, IDisposable, ISingletonDependency + { + private readonly ILogger _logger; + private readonly IConsumer _consumer; + + public ConsumerService( + ILogger logger, + IConfiguration configuration) + { + _logger = logger; + + var consumerConfig = configuration.GetSection("Kafka:ConsumerConfig") + .Get(); + + _consumer = new ConsumerBuilder(consumerConfig) + .SetErrorHandler(OnConsumeError) + .Build(); + } + + public void Subscribe(string topic) + { + _consumer.Subscribe(topic); + _logger.LogInformation($"Subscribed to topic: {topic}"); + } + + public ConsumeResult Consume(CancellationToken cancellationToken) + { + return _consumer.Consume(cancellationToken); + } + + public void Commit(ConsumeResult result) + { + _consumer.Commit(result); + _logger.LogDebug($"Committed offset: {result.TopicPartitionOffset}"); + } + + private void OnConsumeError(IConsumer consumer, Error error) + { + _logger.LogError($"Kafka consumer error: {error.Reason}"); + } + + public void Dispose() + { + _consumer?.Close(); + _consumer?.Dispose(); + } + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs new file mode 100644 index 0000000..1319957 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka.Consumer +{ + public interface IConsumerService + { + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/ConsumerService.cs deleted file mode 100644 index 3d1d4e0..0000000 --- a/src/JiShe.CollectBus.KafkaProducer/ConsumerService.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Confluent.Kafka; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.Kafka -{ - public abstract class ConsumerService : BackgroundService - { - private readonly IConsumer _consumer; - - private readonly ILogger> _logger; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The consumer. - /// The logger. - protected ConsumerService(IConfiguration configuration, IConsumer consumer, ILogger> logger) - { - _consumer = consumer; - _logger = logger; - var consumerConfig = new ConsumerConfig - { - BootstrapServers = configuration["Kafka:BootstrapServers"], - GroupId = "InventoryConsumerGroup", - AutoOffsetReset = AutoOffsetReset.Earliest - }; - - _consumer = new ConsumerBuilder(consumerConfig).Build(); - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - _consumer.Subscribe("InventoryUpdates"); - - while (!stoppingToken.IsCancellationRequested) - { - - var consumeResult = _consumer.Consume(stoppingToken); - - var message = consumeResult.Message.Value; - - await ProcessMessageAsync(consumeResult); - } - - _consumer.Close(); - await Task.CompletedTask; - } - protected abstract Task ProcessMessageAsync(ConsumeResult consumer); - } -} diff --git a/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj b/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj index 72b2660..3175346 100644 --- a/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj +++ b/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs new file mode 100644 index 0000000..2c46bf4 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs @@ -0,0 +1,14 @@ +using Confluent.Kafka; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka.Producer +{ + public interface IProducerService + { + Task ProduceAsync(string topic, string message); + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerBaseService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerBaseService.cs new file mode 100644 index 0000000..cbe56a2 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerBaseService.cs @@ -0,0 +1,64 @@ +using Confluent.Kafka; +using Microsoft.Extensions.Configuration; +using Volo.Abp.DependencyInjection; + +namespace JiShe.CollectBus.Kafka.Producer +{ + public class ProducerBaseService + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public ProducerBaseService(IConfiguration configuration) + { + GetInstance(configuration); + } + + /// + /// Gets or sets the instance. + /// + /// + /// The instance. + /// + public IProducer Instance { get; set; } = default; + + /// + /// Gets the instance. + /// + /// The configuration. + /// + public IProducer GetInstance(IConfiguration configuration) + { + var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]); + var producerConfig = new ProducerConfig + { + BootstrapServers = configuration["Kafka:BootstrapServers"], + AllowAutoCreateTopics = true, + }; + + if (enableAuthorization) + { + producerConfig.SecurityProtocol = SecurityProtocol.SaslPlaintext; + producerConfig.SaslMechanism = SaslMechanism.Plain; + producerConfig.SaslUsername = configuration["Kafka:SaslUserName"]; + producerConfig.SaslPassword = configuration["Kafka:SaslPassword"]; + } + Instance = new ProducerBuilder(producerConfig).Build(); + return Instance; + } + + /// + /// Produces the asynchronous. + /// + /// The topic. + /// The message. + /// The cancellation token. + public async Task ProduceAsync(string topic, Message message, CancellationToken cancellationToken = default) + { + await Instance.ProduceAsync(topic, message, cancellationToken); + } + + + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs new file mode 100644 index 0000000..95c9a15 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Confluent.Kafka; +using Microsoft.Extensions.Configuration; +using Volo.Abp.DependencyInjection; + +namespace JiShe.CollectBus.Kafka.Producer +{ + public class ProducerService: IProducerService,ITransientDependency + { + + private readonly IProducer _producer; + + public ProducerService(IProducer producer) + { + _producer = producer; + } + + public async Task ProduceAsync(string topic, string message) + { + await _producer.ProduceAsync(topic, new Message + { + Key = null, + Value = message + }); + } + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/ProducerService.cs deleted file mode 100644 index 6651b59..0000000 --- a/src/JiShe.CollectBus.KafkaProducer/ProducerService.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Confluent.Kafka; -using Microsoft.Extensions.Configuration; -using Volo.Abp.DependencyInjection; - -namespace JiShe.CollectBus.Kafka -{ - public class ProducerService : ITransientDependency - { - private readonly IProducer _producer; - - public ProducerService(IConfiguration configuration) - { - var producerConfig = new ProducerConfig - { - BootstrapServers = configuration["Kafka:BootstrapServers"] - }; - - _producer = new ProducerBuilder(producerConfig).Build(); - } - - public async Task ProduceAsync(string topic, T message) - { - var msg = new Message { Value = message }; - - await _producer.ProduceAsync(topic, msg); - } - } -} -- 2.47.2 From 4cd7889dd4dd9061e66afe1fb2f2c28cb8971b24 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Wed, 9 Apr 2025 15:55:19 +0800 Subject: [PATCH 059/139] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JiShe.CollectBus.IoTDBProvider.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj b/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj index 137cee3..9153601 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj +++ b/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj @@ -7,7 +7,7 @@ - + -- 2.47.2 From d3cd390312f2c695e05bec8d7d96b1fc195d597a Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Wed, 9 Apr 2025 17:29:30 +0800 Subject: [PATCH 060/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 5 ++ ...nergySystemScheduledMeterReadingService.cs | 9 ++- .../Consts/CommonConst.cs | 24 ++++++++ .../Consts/RedisConst.cs | 12 ++-- .../Helpers/CommonHelper.cs | 56 +++++++++++-------- .../CollectBusHostModule.Configure.cs | 5 +- 6 files changed, 78 insertions(+), 33 deletions(-) create mode 100644 src/JiShe.CollectBus.Common/Consts/CommonConst.cs diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 1c56834..ccf7aa5 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -64,6 +64,11 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// public abstract string SystemType { get; } + /// + /// 应用服务器部署标记 + /// + public abstract string ServerTagName { get; } + /// ///电表日冻结采集项 /// diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index b1c8b18..e213da3 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -15,6 +15,7 @@ using JiShe.CollectBus.Repository; using JiShe.CollectBus.Repository.MeterReadingRecord; using MassTransit; using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Volo.Abp.Domain.Repositories; using Volo.Abp.Uow; @@ -28,15 +29,17 @@ namespace JiShe.CollectBus.ScheduledMeterReading //[Route($"/energy/app/scheduled")] public class EnergySystemScheduledMeterReadingService : BasicScheduledMeterReadingService { - + string serverTagName = string.Empty; public EnergySystemScheduledMeterReadingService(ILogger logger, - ICapPublisher producerBus, IIoTDBProvider dbProvider, IMeterReadingRecordRepository meterReadingRecordRepository) : base(logger, producerBus, meterReadingRecordRepository, dbProvider) + ICapPublisher producerBus, IIoTDBProvider dbProvider, IMeterReadingRecordRepository meterReadingRecordRepository,IConfiguration configuration) : base(logger, producerBus, meterReadingRecordRepository, dbProvider) { - + serverTagName = configuration.GetValue(CommonConst.ServerTagName)!; } public sealed override string SystemType => SystemTypeConst.Energy; + public sealed override string ServerTagName => serverTagName; + /// /// 获取采集项列表 /// diff --git a/src/JiShe.CollectBus.Common/Consts/CommonConst.cs b/src/JiShe.CollectBus.Common/Consts/CommonConst.cs new file mode 100644 index 0000000..ff38f22 --- /dev/null +++ b/src/JiShe.CollectBus.Common/Consts/CommonConst.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Consts +{ + /// + /// 常用常量管理 + /// + public class CommonConst + { + /// + /// 服务器标识 + /// + public const string ServerTagName = "ServerTagName"; + + /// + /// Kafka + /// + public const string Kafka = "Kafka"; + } +} diff --git a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs index 9b921c9..526c706 100644 --- a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs @@ -29,19 +29,19 @@ namespace JiShe.CollectBus.Common.Consts public const string FifteenMinuteAcquisitionTimeInterval = "Fifteen"; /// - /// 缓存表计信息,{0}=>系统类型,{1}=>表计类别 + /// 缓存表计信息,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 /// - public const string CacheMeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:MeterInfo:{"{1}"}:{"{2}"}:"; + public const string CacheMeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:MeterInfo:{"{2}"}:{"{3}"}:"; /// - /// 缓存待下发的指令生产任务数据,{0}=>系统类型,{1}=>表计类别,{2}=>采集频率 + /// 缓存待下发的指令生产任务数据,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 /// - public const string CacheTasksToBeIssuedKey = $"{CacheBasicDirectoryKey}{"{0}"}:TaskInfo:{"{1}"}:{"{2}"}"; + public const string CacheTasksToBeIssuedKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:TaskInfo:{"{2}"}:{"{3}"}"; /// - /// 缓存表计下发指令数据集,{0}=>系统类型,{1}=>表计类别,{2}=>采集频率 + /// 缓存表计下发指令数据集,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 /// - public const string CacheTelemetryPacketInfoKey = $"{CacheBasicDirectoryKey}{"{0}"}:TelemetryPacket:{"{1}"}:{"{2}"}:"; + public const string CacheTelemetryPacketInfoKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:TelemetryPacket:{"{2}"}:{"{3}"}:"; public const string CacheAmmeterFocusKey = "CacheAmmeterFocusKey"; } diff --git a/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs index 8c980c5..5cf77b1 100644 --- a/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs +++ b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs @@ -760,39 +760,51 @@ namespace JiShe.CollectBus.Common.Helpers return fontValue; } - + /// - /// 获取设备Id哈希值 + /// 固定Kafka主题分组数为50,避免动态计算 /// - /// - /// - /// - public static int GetDeviceHashCode(string deviceId, int TotalShards = 100) + private const int FixedGroupCount = 50; + + /// + /// 根据 deviceId 获取其所属分组ID(0~49) + /// + public static int GetGroupId(string deviceId) { - // 计算哈希分组ID - return Math.Abs(deviceId.GetHashCode() % TotalShards); + int hashCode = deviceId.GetHashCode(); + // 更安全的非负取模方式,兼容负数哈希码 + return (hashCode % FixedGroupCount + FixedGroupCount) % FixedGroupCount; } /// - /// 获取设备Id哈希分组 + /// 分组优化:使用数组替代字典,预初始化分组容器 /// - /// - /// - public static Dictionary> GetDeviceHashGroup(List deviceList) + public static List[] GroupDevices(List deviceList) { - Dictionary> keyValuePairs = new Dictionary>(); + //直接初始化分组,避免动态扩容 + List[] groups = new List[FixedGroupCount]; + for (int i = 0; i < FixedGroupCount; i++) + { + groups[i] = new List(capacity: deviceList.Count / FixedGroupCount + 1); + } + + // 单次遍历直接分配 foreach (var deviceId in deviceList) { - var hashCode = GetDeviceHashCode(deviceId); - - if (!keyValuePairs.ContainsKey(hashCode.ToString())) - { - keyValuePairs.Add(hashCode.ToString(), new List()); - } - - keyValuePairs[hashCode.ToString()].Add(deviceId); + int groupId = GetGroupId(deviceId); + groups[groupId].Add(deviceId); } - return keyValuePairs; + + return groups; + } + + /// + /// 通过 deviceId 直接定位分组 + /// + public static List FindDeviceGroup(List[] groups, string deviceId) + { + int groupId = GetGroupId(deviceId); + return groups[groupId]; } } } diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 477008c..269a12f 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -24,6 +24,7 @@ using JiShe.CollectBus.IotSystems.MessageIssueds; using Confluent.Kafka; using MassTransit.SqlTransport.Topology; using Confluent.Kafka.Admin; +using JiShe.CollectBus.Common.Consts; namespace JiShe.CollectBus.Host @@ -370,12 +371,12 @@ namespace JiShe.CollectBus.Host { var adminClient = new AdminClientBuilder(new AdminClientConfig { - BootstrapServers = configuration.GetConnectionString("Kafka") + BootstrapServers = configuration.GetConnectionString(CommonConst.Kafka) }).Build(); try { - string serverTagName = configuration.GetSection("ServerTagName").Value!; + string serverTagName = configuration.GetSection(CommonConst.ServerTagName).Value!; List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(serverTagName); topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived(serverTagName)); -- 2.47.2 From 7e0aa8169bd47838555335f6b7e4f385bac0b841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E7=9B=8A?= Date: Wed, 9 Apr 2025 23:11:36 +0800 Subject: [PATCH 061/139] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E7=BB=84=E8=B4=9F=E8=BD=BD=E6=8E=A7=E5=88=B6=EF=BC=8C=E7=94=A8?= =?UTF-8?q?=E4=BA=8EKafka=E4=B8=BB=E9=A2=98=E5=88=86=E5=8C=BA=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E6=95=B0=E9=87=8F=E7=9A=84=E5=9D=87=E8=A1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/SampleAppService.cs | 24 +++ .../BasicScheduledMeterReadingService.cs | 169 ++++++++---------- .../Helpers/CommonHelper.cs | 48 +---- .../Helpers/DeviceGroupBalanceControl.cs | 163 +++++++++++++++++ 4 files changed, 259 insertions(+), 145 deletions(-) create mode 100644 src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index d2b2b47..2b3962d 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using JiShe.CollectBus.IoTDBProvider.Context; using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Common.Helpers; namespace JiShe.CollectBus.Samples; @@ -83,6 +84,29 @@ public class SampleAppService : CollectBusAppService, ISampleAppService await _iotDBProvider.InsertAsync(meter); } + /// + /// 测试设备分组均衡控制算法 + /// + /// + /// + [HttpGet] + public async Task TestDeviceGroupBalanceControl(int deviceCount = 200000) + { + var deviceList = new List(); + for (int i = 0; i < deviceCount; i++) + { + deviceList.Add($"Device_{Guid.NewGuid()}"); + } + + // 初始化缓存 + DeviceGroupBalanceControl.InitializeCache(deviceList); + + // 打印分布统计 + DeviceGroupBalanceControl.PrintDistributionStats(); + + await Task.CompletedTask; + } + public Task GetAsync() { diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index ccf7aa5..5af174a 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -1,39 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using DotNetCore.CAP; -using DotNetCore.CAP.Messages; -using FreeSql; -using FreeSql.Internal.CommonProvider; +using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; -using JiShe.CollectBus.Common; using JiShe.CollectBus.Common.BuildSendDatas; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; -using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.Enums; using JiShe.CollectBus.GatherItem; using JiShe.CollectBus.IoTDBProvider; -using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; -using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; using JiShe.CollectBus.Protocol.Contracts; -using JiShe.CollectBus.Repository; using JiShe.CollectBus.Repository.MeterReadingRecord; -using JiShe.CollectBus.Workers; -using MassTransit; -using MassTransit.Internals.GraphValidation; -using MassTransit.Transports; using Microsoft.Extensions.Logging; -using Volo.Abp.Domain.Repositories; -using static FreeSql.Internal.GlobalFilter; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; namespace JiShe.CollectBus.ScheduledMeterReading { @@ -43,9 +27,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading public abstract class BasicScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService { private readonly ILogger _logger; - private readonly ICapPublisher _producerBus; + private readonly ICapPublisher _producerBus; private readonly IIoTDBProvider _dbProvider; - private readonly IMeterReadingRecordRepository _meterReadingRecordRepository; + private readonly IMeterReadingRecordRepository _meterReadingRecordRepository; public BasicScheduledMeterReadingService( ILogger logger, @@ -94,7 +78,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// public virtual async Task CreateToBeIssueTasks() { - var redisCacheKey = $"{RedisConst.CacheBasicDirectoryKey}{SystemType}:TaskInfo:*"; + var redisCacheKey = $"{RedisConst.CacheBasicDirectoryKey}{SystemType}:{ServerTagName}:TaskInfo:*"; var taskInfos = await FreeRedisProvider.Instance.KeysAsync(redisCacheKey); if (taskInfos == null || taskInfos.Length <= 0) { @@ -185,6 +169,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading throw new NullReferenceException($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据时,采集项类型数据为空"); } + List focusAddressDataList = new List();//用于处理Kafka主题分区数据的分发和处理。 + //根据采集频率分组,获得采集频率分组 var meterInfoGroupByTimeDensity = meterInfos.GroupBy(d => d.TimeDensity); foreach (var itemTimeDensity in meterInfoGroupByTimeDensity) @@ -198,7 +184,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}{item.Key}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}{item.Key}"; #if DEBUG //每次缓存时,删除缓存,避免缓存数据有不准确的问题 @@ -258,14 +244,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); } - //在缓存表信息数据的时候,新增下一个时间的自动处理任务,1分钟后执行 + //在缓存表信息数据的时候,新增下一个时间的自动处理任务,1分钟后执行所有的采集频率任务 TasksToBeIssueModel nextTask = new TasksToBeIssueModel() { TimeDensity = itemTimeDensity.Key, NextTask = DateTime.Now.AddMinutes(1) }; - var taskRedisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, itemTimeDensity.Key); + var taskRedisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key); await FreeRedisProvider.Instance.SetAsync(taskRedisCacheKey, nextTask); } @@ -279,10 +265,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading public virtual async Task AmmeterScheduledMeterOneMinuteReading() { //获取缓存中的电表信息 - int timeDensity = 5; + int timeDensity = 1; var currentTime = DateTime.Now; - var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; + var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.Ammeter); var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -324,21 +310,13 @@ namespace JiShe.CollectBus.ScheduledMeterReading //_dbProvider.SwitchSessionPool(true); //await _dbProvider.InsertAsync(meterTaskInfosList); - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList,currentTime); + await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentTime); } //删除任务数据 await FreeRedisProvider.Instance.DelAsync(oneMinutekeyList); + await CacheNextTaskData(timeDensity, MeterTypeEnum.Ammeter); - //缓存下一个时间的任务 - TasksToBeIssueModel nextTask = new TasksToBeIssueModel() - { - TimeDensity = timeDensity, - NextTask = DateTime.Now.AddMinutes(timeDensity) - }; - - var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, timeDensity); - await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); _logger.LogInformation($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理完成"); @@ -354,7 +332,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading int timeDensity = 5; var currentTime = DateTime.Now; - var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; + var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.Ammeter); var fiveMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (fiveMinutekeyList == null || fiveMinutekeyList.Length <= 0) { @@ -384,7 +362,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - _= _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg); + _ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg); //_ = _producerBus.Publish(tempMsg); @@ -393,21 +371,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList,currentTime); + await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentTime); } //删除任务数据 await FreeRedisProvider.Instance.DelAsync(fiveMinutekeyList); //缓存下一个时间的任务 - TasksToBeIssueModel nextTask = new TasksToBeIssueModel() - { - TimeDensity = timeDensity, - NextTask = DateTime.Now.AddMinutes(timeDensity) - }; - - var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, timeDensity); - await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); + await CacheNextTaskData(timeDensity, MeterTypeEnum.Ammeter); _logger.LogInformation($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集电表数据处理完成"); } @@ -425,7 +396,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading int timeDensity = 15; var currentDateTime = DateTime.Now; - var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}*"; + var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.Ammeter); var fifteenMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (fifteenMinutekeyList == null || fifteenMinutekeyList.Length <= 0) { @@ -456,7 +427,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading TimeDensity = timeDensity.ToString(), }; - _ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500) ,ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); + _ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); //_ = _producerBus.Publish(tempMsg); @@ -465,21 +436,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList,currentDateTime); + await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentDateTime); } //删除任务数据 //await FreeRedisProvider.Instance.DelAsync(fifteenMinutekeyList); //缓存下一个时间的任务 - TasksToBeIssueModel nextTask = new TasksToBeIssueModel() - { - TimeDensity = timeDensity, - NextTask = DateTime.Now.AddMinutes(timeDensity) - }; - - var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.Ammeter, timeDensity); - await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); + await CacheNextTaskData(timeDensity, MeterTypeEnum.Ammeter); stopwatch.Stop(); @@ -504,7 +468,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading timeDensity = 15; } - if ( focusGroup == null || focusGroup.Count <= 0) + if (focusGroup == null || focusGroup.Count <= 0) { _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 电表数据采集指令生成失败,参数异常,-101"); return; @@ -572,7 +536,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading foreach (var focusInfo in focusGroup) { //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 - var redisCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.Ammeter, timeDensity)}{focusInfo.Key}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}{focusInfo.Key}"; foreach (var ammeterInfo in focusInfo.Value) { @@ -732,7 +696,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading IssuedMessageHexString = Convert.ToHexString(dataInfos), }; meterReadingRecords.CreateDataId(GuidGenerator.Create()); - + keyValuePairs.TryAdd($"{ammeter.ID}_{tempItem}", meterReadingRecords); } await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); @@ -817,8 +781,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading public virtual async Task WatermeterScheduledMeterOneMinuteReading() { //获取缓存中的水表信息 - int timeDensity = 5; - var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; + int timeDensity = 1; + var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.WaterMeter); var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -858,21 +822,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } //删除任务数据 await FreeRedisProvider.Instance.DelAsync(oneMinutekeyList); //缓存下一个时间的任务 - TasksToBeIssueModel nextTask = new TasksToBeIssueModel() - { - TimeDensity = timeDensity, - NextTask = DateTime.Now.AddMinutes(timeDensity) - }; - - var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity); - await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); + await CacheNextTaskData(timeDensity, MeterTypeEnum.WaterMeter); _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理完成"); @@ -887,7 +844,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading //获取缓存中的电表信息 int timeDensity = 5; - var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; + var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.WaterMeter); var fiveMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (fiveMinutekeyList == null || fiveMinutekeyList.Length <= 0) { @@ -927,21 +884,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } //删除任务数据 await FreeRedisProvider.Instance.DelAsync(fiveMinutekeyList); //缓存下一个时间的任务 - TasksToBeIssueModel nextTask = new TasksToBeIssueModel() - { - TimeDensity = timeDensity, - NextTask = DateTime.Now.AddMinutes(timeDensity) - }; - - var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity); - await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); + await CacheNextTaskData(timeDensity, MeterTypeEnum.WaterMeter); _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集水表数据处理完成"); @@ -955,7 +905,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的电表信息 int timeDensity = 15; - var redisKeyList = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity)}*"; + var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.WaterMeter); var fifteenMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (fifteenMinutekeyList == null || fifteenMinutekeyList.Length <= 0) { @@ -964,7 +914,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fifteenMinutekeyList, timeDensity.ToString(), MeterTypeEnum.WaterMeter.ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fifteenMinutekeyList, timeDensity.ToString(), MeterTypeEnum.WaterMeter.ToString()); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); @@ -995,21 +945,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); + await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } //删除任务数据 await FreeRedisProvider.Instance.DelAsync(fifteenMinutekeyList); //缓存下一个时间的任务 - TasksToBeIssueModel nextTask = new TasksToBeIssueModel() - { - TimeDensity = timeDensity, - NextTask = DateTime.Now.AddMinutes(timeDensity) - }; - - var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, timeDensity); - await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); + await CacheNextTaskData(timeDensity, MeterTypeEnum.WaterMeter); _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集水表数据处理完成"); @@ -1094,7 +1037,37 @@ namespace JiShe.CollectBus.ScheduledMeterReading return true; } + /// + /// 缓存下一个时间的任务 + /// + /// 采集频率 + /// 表类型 + /// + private async Task CacheNextTaskData(int timeDensity, MeterTypeEnum meterType) + { + //缓存下一个时间的任务 + TasksToBeIssueModel nextTask = new TasksToBeIssueModel() + { + TimeDensity = timeDensity, + NextTask = DateTime.Now.AddMinutes(timeDensity) + }; + + var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, meterType, timeDensity); + await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); + } + + + /// + /// 获取缓存表计下发指令缓存key前缀 + /// + /// + /// + /// + private string GetTelemetryPacketCacheKeyPrefix(int timeDensity, MeterTypeEnum meterType) + { + return $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, ServerTagName, meterType, timeDensity)}*"; + } #endregion - + } } diff --git a/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs index 5cf77b1..33a0a38 100644 --- a/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs +++ b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs @@ -759,52 +759,6 @@ namespace JiShe.CollectBus.Common.Helpers } return fontValue; - } - - /// - /// 固定Kafka主题分组数为50,避免动态计算 - /// - private const int FixedGroupCount = 50; - - /// - /// 根据 deviceId 获取其所属分组ID(0~49) - /// - public static int GetGroupId(string deviceId) - { - int hashCode = deviceId.GetHashCode(); - // 更安全的非负取模方式,兼容负数哈希码 - return (hashCode % FixedGroupCount + FixedGroupCount) % FixedGroupCount; - } - - /// - /// 分组优化:使用数组替代字典,预初始化分组容器 - /// - public static List[] GroupDevices(List deviceList) - { - //直接初始化分组,避免动态扩容 - List[] groups = new List[FixedGroupCount]; - for (int i = 0; i < FixedGroupCount; i++) - { - groups[i] = new List(capacity: deviceList.Count / FixedGroupCount + 1); - } - - // 单次遍历直接分配 - foreach (var deviceId in deviceList) - { - int groupId = GetGroupId(deviceId); - groups[groupId].Add(deviceId); - } - - return groups; - } - - /// - /// 通过 deviceId 直接定位分组 - /// - public static List FindDeviceGroup(List[] groups, string deviceId) - { - int groupId = GetGroupId(deviceId); - return groups[groupId]; - } + } } } diff --git a/src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs b/src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs new file mode 100644 index 0000000..56fb46f --- /dev/null +++ b/src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Helpers +{ + /// + /// 设备组负载控制 + /// + public class DeviceGroupBalanceControl + { + /// + /// 设备组数量 + /// + private const int GroupCount = 50; + private static List[] _cachedGroups; + private static Dictionary _balancedMapping; + + /// + /// 初始化缓存并强制均衡 + /// + public static void InitializeCache(List deviceList) + { + // 步骤1: 生成均衡映射表 + _balancedMapping = CreateBalancedMapping(deviceList, GroupCount); + + // 步骤2: 根据映射表填充分组 + _cachedGroups = new List[GroupCount]; + for (int i = 0; i < GroupCount; i++) + { + _cachedGroups[i] = new List(capacity: deviceList.Count / GroupCount + 1); + } + + foreach (var deviceId in deviceList) + { + int groupId = _balancedMapping[deviceId]; + _cachedGroups[groupId].Add(deviceId); + } + } + + /// + /// 通过 deviceId 获取分组 + /// + public static List GetGroup(string deviceId) + { + if (_balancedMapping == null || _cachedGroups == null) + throw new InvalidOperationException("缓存未初始化"); + + int groupId = _balancedMapping[deviceId]; + return _cachedGroups[groupId]; + } + + + /// + /// 创建均衡映射表 + /// + /// 数据集合 + /// 分组数量 + /// 允许的最大偏差百分比 + /// + public static Dictionary CreateBalancedMapping(List deviceList, int groupCount, int maxDeviation = 5) + { + var mapping = new Dictionary(); + int targetPerGroup = deviceList.Count / groupCount; + int maxAllowed = (int)(targetPerGroup * (1 + maxDeviation / 100.0)); + + // 初始化分组计数器 + int[] groupCounters = new int[groupCount]; + + // 随机数生成器用于平衡分配 + Random rand = new Random(); + + foreach (var deviceId in deviceList) + { + int preferredGroup = GetGroupId(deviceId, groupCount); + + // 如果首选分组未满,直接分配 + if (groupCounters[preferredGroup] < maxAllowed) + { + mapping[deviceId] = preferredGroup; + groupCounters[preferredGroup]++; + } + else + { + // 寻找当前最空闲的分组 + int fallbackGroup = Array.IndexOf(groupCounters, groupCounters.Min()); + mapping[deviceId] = fallbackGroup; + groupCounters[fallbackGroup]++; + } + } + + return mapping; + } + + /// + /// 分析分组分布 + /// + /// + /// + /// + public static Dictionary AnalyzeDistribution(List deviceList, int groupCount) + { + Dictionary distribution = new Dictionary(); + foreach (var deviceId in deviceList) + { + int groupId = GetGroupId(deviceId, groupCount); + distribution[groupId] = distribution.TryGetValue(groupId, out var count) ? count + 1 : 1; + } + return distribution; + } + + /// + /// 获取设备ID对应的分组ID + /// + /// + /// + /// + public static int GetGroupId(string deviceId, int groupCount) + { + int hash = Fnv1aHash(deviceId); + // 双重取模确保分布均匀 + return (hash % groupCount + groupCount) % groupCount; + } + + /// + /// FNV-1a哈希算法 + /// + /// + /// + public static int Fnv1aHash(string input) + { + const uint fnvPrime = 16777619; + const uint fnvOffsetBasis = 2166136261; + + uint hash = fnvOffsetBasis; + foreach (char c in input) + { + hash ^= (byte)c; + hash *= fnvPrime; + } + return (int)hash; + } + + /// + /// 打印分组统计数据 + /// + public static void PrintDistributionStats() + { + var stats = _cachedGroups + .Select((group, idx) => new { GroupId = idx, Count = group.Count }) + .OrderBy(x => x.GroupId); + + Console.WriteLine("分组数据量统计:"); + foreach (var stat in stats) + { + Console.WriteLine($"Group {stat.GroupId}: {stat.Count} 条数据"); + } + } + + } +} -- 2.47.2 From 8257d6b1291e0b964db4a541c10291a2a96df866 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 10 Apr 2025 14:12:14 +0800 Subject: [PATCH 062/139] =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E7=8E=AF=E5=A2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 15 ++++++- ...nergySystemScheduledMeterReadingService.cs | 24 +++++++++++ .../Consts/RedisConst.cs | 5 +++ .../Helpers/DeviceGroupBalanceControl.cs | 42 ++++++++++++------- .../Helpers/JsonHelper.cs | 4 ++ .../JiShe.CollectBus.Common.csproj | 5 +++ .../CollectBusHostModule.Configure.cs | 7 ++-- .../CollectBusHostModule.cs | 2 +- src/JiShe.CollectBus.Host/appsettings.json | 13 +++--- .../Extensions/ProtocolConstExtensions.cs | 16 ++++--- .../ProtocolConst.cs | 2 +- 11 files changed, 103 insertions(+), 32 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 5af174a..a823798 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -184,6 +184,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } + focusAddressDataList.Add(item.Key); + var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}{item.Key}"; #if DEBUG @@ -255,6 +257,17 @@ namespace JiShe.CollectBus.ScheduledMeterReading await FreeRedisProvider.Instance.SetAsync(taskRedisCacheKey, nextTask); } + //初始化设备组负载控制 + if (focusAddressDataList == null || focusAddressDataList.Count <= 0) + { + _logger.LogError($"{nameof(InitAmmeterCacheData)} 初始化设备组负载控制失败,没有找到对应的设备信息"); + + } + else + { + DeviceGroupBalanceControl.InitializeCache(focusAddressDataList); + } + _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据完成"); } @@ -332,7 +345,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading int timeDensity = 5; var currentTime = DateTime.Now; - var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.Ammeter); + var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.Ammeter); var fiveMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (fiveMinutekeyList == null || fiveMinutekeyList.Length <= 0) { diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index e213da3..12453bc 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.GatherItem; using JiShe.CollectBus.IoTDBProvider; @@ -15,6 +16,7 @@ using JiShe.CollectBus.Repository; using JiShe.CollectBus.Repository.MeterReadingRecord; using MassTransit; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Volo.Abp.Domain.Repositories; @@ -169,5 +171,27 @@ namespace JiShe.CollectBus.ScheduledMeterReading .Ado .QueryAsync(sql); } + + + /// + /// 测试设备分组均衡控制算法 + /// + /// + /// + [HttpGet] + public async Task TestDeviceGroupBalanceControl(int deviceCount = 200000) + { + var deviceList = new List(); + for (int i = 0; i < deviceCount; i++) + { + deviceList.Add($"Device_{Guid.NewGuid()}"); + } + + + // 打印分布统计 + DeviceGroupBalanceControl.PrintDistributionStats(); + + await Task.CompletedTask; + } } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs index 526c706..ea4323b 100644 --- a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs @@ -43,6 +43,11 @@ namespace JiShe.CollectBus.Common.Consts /// public const string CacheTelemetryPacketInfoKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:TelemetryPacket:{"{2}"}:{"{3}"}:"; + /// + /// 缓存设备平衡关系映射结果,{0}=>系统类型,{1}=>应用服务部署标记 + /// + public const string CacheDeviceBalanceRelationMapResultKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:RelationMap"; + public const string CacheAmmeterFocusKey = "CacheAmmeterFocusKey"; } } diff --git a/src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs b/src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs index 56fb46f..c48bad3 100644 --- a/src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs +++ b/src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs @@ -1,8 +1,10 @@ -using System; +using JiShe.CollectBus.FreeRedisProvider; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; namespace JiShe.CollectBus.Common.Helpers { @@ -10,38 +12,42 @@ namespace JiShe.CollectBus.Common.Helpers /// 设备组负载控制 /// public class DeviceGroupBalanceControl - { + { /// - /// 设备组数量 + /// 分组集合 /// - private const int GroupCount = 50; private static List[] _cachedGroups; + + /// + /// 设备分组关系映射 + /// private static Dictionary _balancedMapping; + /// /// 初始化缓存并强制均衡 /// - public static void InitializeCache(List deviceList) + public static void InitializeCache(List deviceList,int groupCount = 50) { // 步骤1: 生成均衡映射表 - _balancedMapping = CreateBalancedMapping(deviceList, GroupCount); + _balancedMapping = CreateBalancedMapping(deviceList, groupCount); // 步骤2: 根据映射表填充分组 - _cachedGroups = new List[GroupCount]; - for (int i = 0; i < GroupCount; i++) + _cachedGroups = new List[groupCount]; + for (int i = 0; i < groupCount; i++) { - _cachedGroups[i] = new List(capacity: deviceList.Count / GroupCount + 1); + _cachedGroups[i] = new List(capacity: deviceList.Count / groupCount + 1); } foreach (var deviceId in deviceList) { int groupId = _balancedMapping[deviceId]; _cachedGroups[groupId].Add(deviceId); - } + } } /// - /// 通过 deviceId 获取分组 + /// 通过 deviceId 获取所在的分组集合 /// public static List GetGroup(string deviceId) { @@ -52,6 +58,17 @@ namespace JiShe.CollectBus.Common.Helpers return _cachedGroups[groupId]; } + /// + /// 通过 deviceId 获取分组Id + /// + public static int GetDeviceGroupId(string deviceId) + { + if (_balancedMapping == null || _cachedGroups == null) + throw new InvalidOperationException("缓存未初始化"); + + return _balancedMapping[deviceId]; + } + /// /// 创建均衡映射表 @@ -69,9 +86,6 @@ namespace JiShe.CollectBus.Common.Helpers // 初始化分组计数器 int[] groupCounters = new int[groupCount]; - // 随机数生成器用于平衡分配 - Random rand = new Random(); - foreach (var deviceId in deviceList) { int preferredGroup = GetGroupId(deviceId, groupCount); diff --git a/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs b/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs index bdd46f6..cbdef1e 100644 --- a/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs +++ b/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs @@ -30,6 +30,8 @@ namespace JiShe.CollectBus.Common.Helpers DefaultIgnoreCondition = JsonIgnoreCondition.Never, WriteIndented = false, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + IgnoreReadOnlyFields = true, + IgnoreReadOnlyProperties = true, }; } @@ -67,6 +69,8 @@ namespace JiShe.CollectBus.Common.Helpers DefaultIgnoreCondition = JsonIgnoreCondition.Never, WriteIndented = false, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + IgnoreReadOnlyFields = true, + IgnoreReadOnlyProperties = true, }; } diff --git a/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj b/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj index 687269f..448bf62 100644 --- a/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj +++ b/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj @@ -25,6 +25,11 @@ + + + + + diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 269a12f..35bb46b 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -375,11 +375,10 @@ namespace JiShe.CollectBus.Host }).Build(); try - { - string serverTagName = configuration.GetSection(CommonConst.ServerTagName).Value!; + { - List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(serverTagName); - topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived(serverTagName)); + List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); + topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); List topicSpecifications = new List(); foreach (var item in topics) diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs index 31f2651..aabc2ba 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs @@ -45,7 +45,7 @@ namespace JiShe.CollectBus.Host ConfigureHangfire(context); ConfigureCap(context, configuration); //ConfigureMassTransit(context, configuration); - ConfigureKafkaTopic(context, configuration); + //ConfigureKafkaTopic(context, configuration); ConfigureAuditLog(context); ConfigureCustom(context, configuration); } diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 001974f..2217c9e 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -35,12 +35,12 @@ }, "ConnectionStrings": { "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", - "Kafka": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092", + "Kafka": "192.168.0.151:29092,192.168.0.151:39092,192.168.0.151:49092", "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" }, "Redis": { - "Configuration": "120.24.52.151:6380,password=1q2w3e!@#,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + "Configuration": "192.168.0.151:6380,password=1q2w3e!@#,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", "DefaultDB": "14", "HangfireDB": "15" }, @@ -86,16 +86,17 @@ "SecurityProtocol": "SASL_PLAINTEXT", "SaslMechanism": "PLAIN", "SaslUserName": "lixiao", - "SaslPassword": "lixiao1980" + "SaslPassword": "lixiao1980", + "ServerTagName": "JiSheCollectBus", + "NumPartitions": 50 }, "IoTDBOptions": { "UserName": "root", "Password": "root", - "ClusterList": [ "192.168.56.102:6667" ], + "ClusterList": [ "192.168.0.151:6667" ], "PoolSize": 2, "DataBaseName": "energy", "OpenDebugMode": true, "UseTableSessionPoolByDefault": false - }, - "ServerTagName": "JiSheCollectBus" + } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs b/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs index 67abc75..0812aae 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs @@ -15,7 +15,7 @@ namespace JiShe.CollectBus.Protocol.Contracts /// 自动获取 ProtocolConst 类中所有下行 Kafka 主题名称 /// (通过反射筛选 public const string 且字段名以 "EventName" 结尾的常量) /// - public static List GetAllTopicNamesByIssued(string serverTagName) + public static List GetAllTopicNamesByIssued() { List topics = typeof(ProtocolConst) .GetFields(BindingFlags.Public | BindingFlags.Static) @@ -24,7 +24,6 @@ namespace JiShe.CollectBus.Protocol.Contracts !f.IsInitOnly && f.FieldType == typeof(string) && f.Name.EndsWith("IssuedEventName")) // 通过命名规则过滤主题字段 - //.Select(f => $"{serverTagName}.{(string)f.GetRawConstantValue()!}") .Select(f => (string)f.GetRawConstantValue()!) .ToList(); @@ -35,7 +34,7 @@ namespace JiShe.CollectBus.Protocol.Contracts /// 自动获取 ProtocolConst 类中所有下行 Kafka 主题名称 /// (通过反射筛选 public const string 且字段名以 "EventName" 结尾的常量) /// - public static List GetAllTopicNamesByReceived(string serverTagName) + public static List GetAllTopicNamesByReceived() { //固定的上报主题 var topicList = typeof(ProtocolConst) @@ -50,13 +49,20 @@ namespace JiShe.CollectBus.Protocol.Contracts //动态上报主题,需根据协议的AFN功能码动态获取 var afnList = EnumExtensions.ToNameValueDictionary(); + + //需要排除的AFN功能码 + var excludeItems = new List() { 6, 7, 8,15 }; + foreach (var item in afnList) { + if (excludeItems.Contains(item.Value)) + { + continue; + } + topicList.Add(string.Format(ProtocolConst.AFNTopicNameFormat, item.Value.ToString().PadLeft(2, '0'))); } - //return topicList.Select(f => $"{serverTagName}.{f}").ToList(); - return topicList; } } diff --git a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs index 796023a..707dfea 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs @@ -19,7 +19,7 @@ namespace JiShe.CollectBus.Protocol.Contracts public const string SubscriberLoginIssuedEventName = "issued.login.event"; /// - /// 上行消息主题 + /// 上行消息主题,测试使用 /// public const string SubscriberReceivedEventName = "received.event"; -- 2.47.2 From f1bec122a3eadc578c369de0302067c3f83da1ca Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Thu, 10 Apr 2025 16:58:32 +0800 Subject: [PATCH 063/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 7 +++++++ .../CustomKafkaDistributedEventBus.cs | 1 + .../Samples/SampleAppService.cs | 12 ++++++++++++ .../AdminClient/AdminClientService.cs | 2 +- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index 674eca4..5a8b98a 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -31,6 +31,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.FreeSql", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.FreeRedisProvider", "src\JiShe.CollectBus.FreeRedisProvider\JiShe.CollectBus.FreeRedisProvider.csproj", "{C06C4082-638F-2996-5FED-7784475766C1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka", "src\JiShe.CollectBus.KafkaProducer\JiShe.CollectBus.Kafka.csproj", "{F0288175-F0EC-48BD-945F-CF1512850943}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -89,6 +91,10 @@ Global {C06C4082-638F-2996-5FED-7784475766C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {C06C4082-638F-2996-5FED-7784475766C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {C06C4082-638F-2996-5FED-7784475766C1}.Release|Any CPU.Build.0 = Release|Any CPU + {F0288175-F0EC-48BD-945F-CF1512850943}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0288175-F0EC-48BD-945F-CF1512850943}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0288175-F0EC-48BD-945F-CF1512850943}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0288175-F0EC-48BD-945F-CF1512850943}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -107,6 +113,7 @@ Global {8BA01C3D-297D-42DF-BD63-EF07202A0A67} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {FE0457D9-4038-4A17-8808-DCAD06CFC0A0} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {C06C4082-638F-2996-5FED-7784475766C1} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {F0288175-F0EC-48BD-945F-CF1512850943} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/src/JiShe.CollectBus.Application/CustomKafkaDistributedEventBus.cs b/src/JiShe.CollectBus.Application/CustomKafkaDistributedEventBus.cs index aeca6fe..a439d13 100644 --- a/src/JiShe.CollectBus.Application/CustomKafkaDistributedEventBus.cs +++ b/src/JiShe.CollectBus.Application/CustomKafkaDistributedEventBus.cs @@ -65,6 +65,7 @@ public class CustomKafkaDistributedEventBus : DistributedEventBusBase, ISingleto public void Initialize() { + SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); } diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index c45767e..e006ef0 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -63,5 +63,17 @@ public class SampleAppService : CollectBusAppService, ISampleAppService Value = 456789, } ); + await _distributedEventBus.PublishAsync( + new SampleDto2 + { + Value = 159753, + } + ); + await _distributedEventBus.PublishAsync( + new SampleDto2 + { + Value = 159753, + } + ); } } diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs index 5b85ebf..e4b621b 100644 --- a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs @@ -81,7 +81,7 @@ namespace JiShe.CollectBus.Kafka.AdminClient { if (await CheckTopicAsync(topicName)) return; - await Instance.CreateTopicsAsync(new TopicSpecification[] + await Instance.CreateTopicsAsync(new[] { new TopicSpecification { Name = topicName, ReplicationFactor = factorNum, NumPartitions = partitionNum } }); -- 2.47.2 From 3a61b46036bcd0d9a2d2e2f2cce0b8fb590b8304 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 10 Apr 2025 17:06:53 +0800 Subject: [PATCH 064/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusApplicationModule.cs | 1 + .../Plugins/TcpMonitor.cs | 4 +- .../Subscribers/SubscriberAppService.cs | 31 ++++++--- .../Extensions/DateTimeExtensions.cs | 4 ++ .../IotSystems/AFNEntity/AFNDataEntity.cs | 15 +++++ .../CollectBusHostModule.cs | 2 +- src/JiShe.CollectBus.Host/appsettings.json | 4 +- .../MeterReadingRecordRepository.cs | 8 +-- .../ProtocolConst.cs | 66 ++++++++++++++++++- 9 files changed, 116 insertions(+), 19 deletions(-) create mode 100644 src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/AFNDataEntity.cs diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 3195961..78bc5eb 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -70,6 +70,7 @@ public class CollectBusApplicationModule : AbpModule //默认初始化表计信息 dbContext.InitAmmeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); + dbContext.InitWatermeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); } } diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index 7224cdb..ea84d9b 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -225,9 +225,9 @@ namespace JiShe.CollectBus.Plugins //}); - string topicName = string.Format(ProtocolConst.AFNTopicNameFormat, aFn); + //string topicName = string.Format(ProtocolConst.AFNTopicNameFormat, aFn); - await _producerBus.PublishAsync(topicName, new MessageReceived + await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedEventName, new MessageReceived { ClientId = client.Id, ClientIp = client.IP, diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index a801454..a9cb5f8 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -7,6 +7,7 @@ using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; @@ -31,6 +32,7 @@ namespace JiShe.CollectBus.Subscribers private readonly IRepository _messageReceivedEventRepository; private readonly IRepository _deviceRepository; private readonly IMeterReadingRecordRepository _meterReadingRecordsRepository; + private readonly IIoTDBProvider _dbProvider; /// /// Initializes a new instance of the class. @@ -48,7 +50,9 @@ namespace JiShe.CollectBus.Subscribers IRepository messageReceivedLoginEventRepository, IRepository messageReceivedHeartbeatEventRepository, IRepository messageReceivedEventRepository, - IRepository deviceRepository, IMeterReadingRecordRepository meterReadingRecordsRepository) + IRepository deviceRepository, + IIoTDBProvider dbProvider, + IMeterReadingRecordRepository meterReadingRecordsRepository) { _logger = logger; _tcpService = tcpService; @@ -58,6 +62,7 @@ namespace JiShe.CollectBus.Subscribers _messageReceivedEventRepository = messageReceivedEventRepository; _deviceRepository = deviceRepository; _meterReadingRecordsRepository = meterReadingRecordsRepository; + _dbProvider = dbProvider; } [CapSubscribe(ProtocolConst.SubscriberLoginIssuedEventName)] @@ -119,6 +124,8 @@ namespace JiShe.CollectBus.Subscribers [CapSubscribe(ProtocolConst.SubscriberReceivedEventName)] public async Task ReceivedEvent(MessageReceived receivedMessage) { + var currentTime = Clock.Now; + var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); if (protocolPlugin == null) { @@ -126,6 +133,7 @@ namespace JiShe.CollectBus.Subscribers } else { + //todo 会根据不同的协议进行解析,然后做业务处理 TB3761 fN = await protocolPlugin.AnalyzeAsync(receivedMessage); if(fN == null) @@ -140,11 +148,8 @@ namespace JiShe.CollectBus.Subscribers return; } - //todo 查找是否有下发任务 - - - - await _meterReadingRecordsRepository.InsertAsync(new MeterReadingRecords() + //报文入库 + var entity = new MeterReadingRecords() { ReceivedMessageHexString = receivedMessage.MessageHexString, AFN = fN.Afn, @@ -152,8 +157,18 @@ namespace JiShe.CollectBus.Subscribers Pn = 0, FocusAddress = "", MeterAddress = "", - //DataResult = tb3761FN.Text, - }); + }; + + //如果没数据,则插入,有数据则更新 + var updateEntity = await _meterReadingRecordsRepository.FirOrDefaultAsync(entity, currentTime); + if (updateEntity == null) + { + await _meterReadingRecordsRepository.InsertAsync(entity, currentTime); + } + + + _dbProvider.InsertAsync(); + //todo 查找是否有下发任务 //await _messageReceivedEventRepository.InsertAsync(receivedMessage); } diff --git a/src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs b/src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs index 904d4b9..7734288 100644 --- a/src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs +++ b/src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs @@ -175,7 +175,11 @@ namespace JiShe.CollectBus.Common.Extensions /// public static string GetDataTableShardingStrategy(this DateTime dateTime) { +#if DEBUG return $"{dateTime:yyyyMMddHHmm}"; +#else + return $"{dateTime:yyyyMMddHH}"; +#endif } } } diff --git a/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/AFNDataEntity.cs b/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/AFNDataEntity.cs new file mode 100644 index 0000000..386baf3 --- /dev/null +++ b/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/AFNDataEntity.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IotSystems.AFNEntity +{ + /// + /// AFN单项数据实体 + /// + public class AFNDataEntity + { + } +} diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs index aabc2ba..31f2651 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs @@ -45,7 +45,7 @@ namespace JiShe.CollectBus.Host ConfigureHangfire(context); ConfigureCap(context, configuration); //ConfigureMassTransit(context, configuration); - //ConfigureKafkaTopic(context, configuration); + ConfigureKafkaTopic(context, configuration); ConfigureAuditLog(context); ConfigureCustom(context, configuration); } diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 2217c9e..21ae13c 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -87,7 +87,6 @@ "SaslMechanism": "PLAIN", "SaslUserName": "lixiao", "SaslPassword": "lixiao1980", - "ServerTagName": "JiSheCollectBus", "NumPartitions": 50 }, "IoTDBOptions": { @@ -98,5 +97,6 @@ "DataBaseName": "energy", "OpenDebugMode": true, "UseTableSessionPoolByDefault": false - } + }, + "ServerTagName": "JiSheCollectBus" } \ No newline at end of file diff --git a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs index 7e34031..cb35b5c 100644 --- a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs +++ b/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs @@ -88,7 +88,7 @@ namespace JiShe.CollectBus.Repository.MeterReadingRecord public async Task InsertAsync(MeterReadingRecords entity, DateTime? dateTime) { var collection = await GetShardedCollection(dateTime); - await collection.InsertOneAsync(entity); + await collection.InsertOneAsync(entity); return entity; } @@ -103,8 +103,6 @@ namespace JiShe.CollectBus.Repository.MeterReadingRecord { var collection = await GetShardedCollection(entity.CreationTime); - var dbContext = await DbContextProvider.GetDbContextAsync(); - await collection.UpdateOneAsync(filter, update); return entity; } @@ -120,8 +118,8 @@ namespace JiShe.CollectBus.Repository.MeterReadingRecord public async Task FirOrDefaultAsync(MeterReadingRecords entity, DateTime? dateTime) { var collection = await GetShardedCollection(dateTime); - //await collection.findon - throw new NotImplementedException(); + var query = await collection.FindAsync(d => d.CreationTime == dateTime.Value && d.AFN == entity.AFN && d.Fn == entity.Fn && d.FocusAddress == entity.FocusAddress); + return await query.FirstOrDefaultAsync(); } /// diff --git a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs index 707dfea..e7caa1c 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs @@ -106,7 +106,71 @@ namespace JiShe.CollectBus.Protocol.Contracts /// /// AFN上行主题格式 /// - public const string AFNTopicNameFormat = "received.afn{0}.event"; + public const string AFNTopicNameFormat = "received.afn{0}h.event"; + /// + /// AFN00H上行主题格式 + /// + public const string SubscriberAFN00ReceivedEventNameTemp = "received.afn00h.event"; + + /// + /// AFN01H上行主题格式 + /// + public const string SubscriberAFN00HReceivedEventNameTemp = "received.afn01h.event"; + + /// + /// AFN02H上行主题格式 + /// + public const string SubscriberAFN01HReceivedEventNameTemp = "received.afn02h.event"; + + /// + /// AFN03H上行主题格式 + /// + public const string SubscriberAFN02HReceivedEventNameTemp = "received.afn03h.event"; + + /// + /// AFN04H上行主题格式 + /// + public const string SubscriberAFN04HReceivedEventNameTemp = "received.afn04h.event"; + + /// + /// AFN05H上行主题格式 + /// + public const string SubscriberAFN05HReceivedEventNameTemp = "received.afn05h.event"; + + /// + /// AFN09H上行主题格式 + /// + public const string SubscriberAFN09HReceivedEventNameTemp = "received.afn09h.event"; + + /// + /// AFN0AH上行主题格式 + /// + public const string SubscriberAFN0AHReceivedEventNameTemp = "received.afn10h.event"; + + /// + /// AFN0BH上行主题格式 + /// + public const string SubscriberAFN0BHReceivedEventNameTemp = "received.afn11h.event"; + + /// + /// AFN0CH上行主题格式 + /// + public const string SubscriberAFN0CHReceivedEventNameTemp = "received.afn12h.event"; + + /// + /// AFN0DH上行主题格式 + /// + public const string SubscriberAFN0DHReceivedEventNameTemp = "received.afn13h.event"; + + /// + /// AFN0EH上行主题格式 + /// + public const string SubscriberAFN0EHReceivedEventNameTemp = "received.afn14h.event"; + + /// + /// AFN10H上行主题格式 + /// + public const string SubscriberAFN10HReceivedEventNameTemp = "received.afn16h.event"; } } -- 2.47.2 From a0463d5ae1c259cf5a31760e59fa1c1e20fdaee3 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Thu, 10 Apr 2025 17:20:04 +0800 Subject: [PATCH 065/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JiShe.CollectBus.Host/appsettings.json | 41 ++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 21ae13c..4416fd2 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -81,13 +81,42 @@ "Port": 5672 } }, + //"Kafka": { + // "BootstrapServers": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092", + // "EnableAuthorization": false, + // "SecurityProtocol": "SASL_PLAINTEXT", + // "SaslMechanism": "PLAIN", + // "SaslUserName": "lixiao", + // "SaslPassword": "lixiao1980", + // "Topic": { + // "ReplicationFactor": 3, + // "NumPartitions": 1000 + // } "Kafka": { - "EnableAuthorization": false, - "SecurityProtocol": "SASL_PLAINTEXT", - "SaslMechanism": "PLAIN", - "SaslUserName": "lixiao", - "SaslPassword": "lixiao1980", - "NumPartitions": 50 + "Connections": { + "Default": { + "BootstrapServers": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092" + // "SecurityProtocol": "SASL_PLAINTEXT", + // "SaslMechanism": "PLAIN", + // "SaslUserName": "lixiao", + // "SaslPassword": "lixiao1980", + } + }, + "Consumer": { + "GroupId": "JiShe.CollectBus" + }, + "Producer": { + "MessageTimeoutMs": 6000, + "Acks": -1 + }, + "Topic": { + "ReplicationFactor": 3, + "NumPartitions": 1000 + }, + "EventBus": { + "GroupId": "JiShe.CollectBus", + "TopicName": "DefaultTopicName" + } }, "IoTDBOptions": { "UserName": "root", -- 2.47.2 From b5f929910c0b312cf8b1917150ddb7909e5b3b15 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 10 Apr 2025 23:31:43 +0800 Subject: [PATCH 066/139] =?UTF-8?q?=E5=B0=81=E8=A3=85=E5=8D=95=E4=B8=AA?= =?UTF-8?q?=E6=B5=8B=E7=82=B9=E6=95=B0=E6=8D=AE=E7=9A=84Tablet=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Plugins/TcpMonitor.cs | 1 + .../Subscribers/SubscriberAppService.cs | 2 +- .../IotSystems/AFNEntity/AFNDataEntity.cs | 6 +- .../Attribute/SingleMeasuringAttribute.cs | 16 +++ .../Provider/IoTDBProvider.cs | 103 +++++++++++++----- 5 files changed, 99 insertions(+), 29 deletions(-) create mode 100644 src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index ea84d9b..c348a3d 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -226,6 +226,7 @@ namespace JiShe.CollectBus.Plugins //string topicName = string.Format(ProtocolConst.AFNTopicNameFormat, aFn); + //todo 如何确定时标?目前集中器的采集频率,都是固定,数据上报的时候,根据当前时间,往后推测出应当采集的时间点作为时标。但是如果由于网络问题,数据一直没上报的情况改怎么计算? await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedEventName, new MessageReceived { diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index a9cb5f8..c22e4dc 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -167,7 +167,7 @@ namespace JiShe.CollectBus.Subscribers } - _dbProvider.InsertAsync(); + //_dbProvider.InsertAsync(); //todo 查找是否有下发任务 //await _messageReceivedEventRepository.InsertAsync(receivedMessage); diff --git a/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/AFNDataEntity.cs b/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/AFNDataEntity.cs index 386baf3..5f0e44c 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/AFNDataEntity.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/AFNDataEntity.cs @@ -1,4 +1,5 @@ -using System; +using JiShe.CollectBus.IoTDBProvider; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -9,7 +10,8 @@ namespace JiShe.CollectBus.IotSystems.AFNEntity /// /// AFN单项数据实体 /// - public class AFNDataEntity + public class AFNDataEntity:IoTEntity { + public string ItemCode { get; set; } } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs b/src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs new file mode 100644 index 0000000..f69fd99 --- /dev/null +++ b/src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDBProvider +{ + /// + /// 用于标识当前实体为单个测点,单侧点标识字段类型是Dictionary + /// + [AttributeUsage(AttributeTargets.Property)] + public class SingleMeasuringAttribute : Attribute + { + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs index 6ad2caa..504709e 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs @@ -83,13 +83,14 @@ namespace JiShe.CollectBus.IoTDBProvider var metadata = GetMetadata(); var tablet = BuildTablet(new[] { entity }, metadata); + + await _currentSession.InsertAsync(tablet); - int result = await _currentSession.InsertAsync(tablet); - - if (result <= 0) - { - _logger.LogError($"{typeof(T).Name}插入数据没有成功"); - } + //int result = await _currentSession.InsertAsync(tablet); + //if (result <= 0) + //{ + // _logger.LogError($"{typeof(T).Name}插入数据没有成功"); + //} } /// @@ -107,11 +108,12 @@ namespace JiShe.CollectBus.IoTDBProvider foreach (var batch in batches) { var tablet = BuildTablet(batch, metadata); - var result = await _currentSession.InsertAsync(tablet); - if (result <= 0) - { - _logger.LogWarning($"{typeof(T).Name} 批量插入数据第{batch}批次没有成功,共{batches}批次。"); - } + await _currentSession.InsertAsync(tablet); + //var result = await _currentSession.InsertAsync(tablet); + //if (result <= 0) + //{ + // _logger.LogWarning($"{typeof(T).Name} 批量插入数据第{batch}批次没有成功,共{batches}批次。"); + //} } } @@ -415,20 +417,52 @@ namespace JiShe.CollectBus.IoTDBProvider foreach (var prop in type.GetProperties()) { - //按优先级顺序检查属性,避免重复反射 - ColumnInfo? column = prop.GetCustomAttribute() is not null ? new ColumnInfo( - name: prop.Name, //使用属性名 - category: ColumnCategory.TAG, - dataType: GetDataTypeFromTypeName(prop.PropertyType.Name) - ) : prop.GetCustomAttribute() is not null ? new ColumnInfo( - prop.Name, - ColumnCategory.ATTRIBUTE, - GetDataTypeFromTypeName(prop.PropertyType.Name) - ) : prop.GetCustomAttribute() is not null ? new ColumnInfo( - prop.Name, - ColumnCategory.FIELD, - GetDataTypeFromTypeName(prop.PropertyType.Name) - ) : null; + //先获取Tag标签和属性标签 + ColumnInfo? column = prop.GetCustomAttribute() is not null ? new ColumnInfo( + name: prop.Name, + category: ColumnCategory.TAG, + dataType: GetDataTypeFromTypeName(prop.PropertyType.Name), false + ) : prop.GetCustomAttribute() is not null ? new ColumnInfo( + prop.Name, + ColumnCategory.ATTRIBUTE, + GetDataTypeFromTypeName(prop.PropertyType.Name),false + ) : null; + + //最先检查是不是单测点 + if (prop.GetCustomAttribute() is not null) + { + //单测点的情况下,字段类型是Dictionary + Dictionary keyValuePairs = prop.GetValue(null) as Dictionary; + column = new ColumnInfo( + keyValuePairs.Keys.First(), + ColumnCategory.FIELD, + GetDataTypeFromTypeName(prop.PropertyType.Name), false + ); + } + else + { + //不是单测点的情况下,直接获取字段名称作为测点名称 + column = prop.GetCustomAttribute() is not null ? new ColumnInfo( + prop.Name, + ColumnCategory.FIELD, + GetDataTypeFromTypeName(prop.PropertyType.Name), false + ) : null; + } + + ////按优先级顺序检查属性,避免重复反射 + //column = prop.GetCustomAttribute() is not null ? new ColumnInfo( + // name: prop.Name, //使用属性名 + // category: ColumnCategory.TAG, + // dataType: GetDataTypeFromTypeName(prop.PropertyType.Name) + //) : prop.GetCustomAttribute() is not null ? new ColumnInfo( + // prop.Name, + // ColumnCategory.ATTRIBUTE, + // GetDataTypeFromTypeName(prop.PropertyType.Name) + //) : prop.GetCustomAttribute() is not null ? new ColumnInfo( + // prop.Name, + // ColumnCategory.FIELD, + // GetDataTypeFromTypeName(prop.PropertyType.Name) + //) : null; if (column.HasValue) { @@ -480,15 +514,32 @@ namespace JiShe.CollectBus.IoTDBProvider /// private readonly struct ColumnInfo { + /// + /// 列名 + /// public string Name { get; } + + /// + /// 是否是单测点 + /// + public bool IsSingleMeasuring { get;} + + /// + /// 列类型 + /// public ColumnCategory Category { get; } + + /// + /// 数据类型 + /// public TSDataType DataType { get; } - public ColumnInfo(string name, ColumnCategory category, TSDataType dataType) + public ColumnInfo(string name, ColumnCategory category, TSDataType dataType,bool isSingleMeasuring) { Name = name; Category = category; DataType = dataType; + IsSingleMeasuring = isSingleMeasuring; } } -- 2.47.2 From 2beca87bb2388d93f4d9f1a5ae565cb7ebc1f26f Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Fri, 11 Apr 2025 11:56:23 +0800 Subject: [PATCH 067/139] =?UTF-8?q?=E4=BC=98=E5=8C=96IoTDB=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/SampleAppService.cs | 39 ++++- .../BasicScheduledMeterReadingService.cs | 4 +- ...ity.cs => SingleMeasuringAFNDataEntity.cs} | 8 +- .../Attribute/SingleMeasuringAttribute.cs | 8 +- .../Interface/IIoTDBProvider.cs | 10 +- .../Interface/IIoTDBSessionFactory.cs | 2 +- .../Provider/DeviceMetadata.cs | 5 + .../Provider/IoTDBProvider.cs | 147 +++++++++--------- .../Provider/IoTDBSessionFactory.cs | 20 ++- 9 files changed, 157 insertions(+), 86 deletions(-) rename src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/{AFNDataEntity.cs => SingleMeasuringAFNDataEntity.cs} (53%) diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 2b3962d..551dc60 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.Options; using JiShe.CollectBus.IoTDBProvider.Context; using Microsoft.Extensions.Logging; using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.IotSystems.AFNEntity; namespace JiShe.CollectBus.Samples; @@ -67,8 +68,21 @@ public class SampleAppService : CollectBusAppService, ISampleAppService [HttpGet] public async Task UseTableSessionPool() { - //_dbContext.UseTableSessionPool = true; - _iotDBProvider.SwitchSessionPool(true); + ElectricityMeter meter2 = new ElectricityMeter() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + Current = 10, + MeterModel = "DDZY-1980", + ProjectCode = "10059", + Voltage = 10, + Timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + }; + + await _iotDBProvider.InsertAsync(meter2); + + _dbContext.UseTableSessionPool = true; ElectricityMeter meter = new ElectricityMeter() { @@ -108,6 +122,27 @@ public class SampleAppService : CollectBusAppService, ISampleAppService } + /// + /// 测试单个测点数据项 + /// + /// + /// + [HttpGet] + public async Task TestSingleMeasuringAFNData(string measuring, object value) + { + SingleMeasuringAFNDataEntity meter = new SingleMeasuringAFNDataEntity() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + ProjectCode = "10059", + Timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + SingleMeasuring = new Tuple(measuring, value) + }; + await _iotDBProvider.InsertAsync(meter); + } + + public Task GetAsync() { return Task.FromResult( diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index a823798..7311c42 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -764,7 +764,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, MeterTypeEnum.WaterMeter, itemTimeDensity.Key)}{item.Key}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, MeterTypeEnum.WaterMeter, itemTimeDensity.Key)}{item.Key}"; Dictionary keyValuePairs = new Dictionary(); foreach (var subItem in item) { @@ -781,7 +781,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading NextTask = DateTime.Now.AddMinutes(1) }; - var taskRedisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, MeterTypeEnum.WaterMeter, itemTimeDensity.Key); + var taskRedisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, MeterTypeEnum.WaterMeter, itemTimeDensity.Key); await FreeRedisProvider.Instance.SetAsync(taskRedisCacheKey, nextTask); } _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化水表缓存数据完成"); diff --git a/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/AFNDataEntity.cs b/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs similarity index 53% rename from src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/AFNDataEntity.cs rename to src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs index 5f0e44c..f53ca21 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/AFNDataEntity.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs @@ -10,8 +10,12 @@ namespace JiShe.CollectBus.IotSystems.AFNEntity /// /// AFN单项数据实体 /// - public class AFNDataEntity:IoTEntity + public class SingleMeasuringAFNDataEntity:IoTEntity { - public string ItemCode { get; set; } + /// + /// 单项数据对象 + /// + [SingleMeasuring(nameof(SingleMeasuring))] + public Tuple SingleMeasuring { get; set; } } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs b/src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs index f69fd99..9a85a85 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs @@ -7,10 +7,16 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.IoTDBProvider { /// - /// 用于标识当前实体为单个测点,单侧点标识字段类型是Dictionary + /// 用于标识当前实体为单侧点模式,单侧点模式只有一个Filed标识字段,类型是Tuple,Item1=>测点名称,Item2=>测点值 /// [AttributeUsage(AttributeTargets.Property)] public class SingleMeasuringAttribute : Attribute { + public string FieldName { get; set;} + + public SingleMeasuringAttribute(string fieldName) + { + FieldName = fieldName; + } } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs index b67f17d..0aad03e 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs @@ -11,11 +11,11 @@ namespace JiShe.CollectBus.IoTDBProvider /// public interface IIoTDBProvider { - /// - /// 切换 SessionPool - /// - /// 是否使用表模型 - void SwitchSessionPool(bool useTableSession); + ///// + ///// 切换 SessionPool + ///// + ///// 是否使用表模型 + //void SwitchSessionPool(bool useTableSession); /// /// 插入数据 diff --git a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionFactory.cs b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionFactory.cs index b904ba0..c7d9b10 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionFactory.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionFactory.cs @@ -9,7 +9,7 @@ namespace JiShe.CollectBus.IoTDBProvider.Interface /// /// Session 工厂接口 /// - public interface IIoTDBSessionFactory + public interface IIoTDBSessionFactory:IDisposable { IIoTDBSessionPool GetSessionPool(bool useTableSession); } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs index 0d5b408..50aa8f7 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs @@ -12,6 +12,11 @@ namespace JiShe.CollectBus.IoTDBProvider /// public class DeviceMetadata { + /// + /// 是否有单测量值 + /// + public bool IsSingleMeasuring { get; set; } + /// /// 测量值集合,用于构建Table的测量值,也就是columnNames参数 /// diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs index 504709e..297d250 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs @@ -24,12 +24,14 @@ namespace JiShe.CollectBus.IoTDBProvider /// public class IoTDBProvider : IIoTDBProvider { - private IIoTDBSessionPool _currentSession; private static readonly ConcurrentDictionary _metadataCache = new(); private readonly ILogger _logger; private readonly IIoTDBSessionFactory _sessionFactory; private readonly IoTDBRuntimeContext _runtimeContext; + private IIoTDBSessionPool CurrentSession => + _sessionFactory.GetSessionPool(_runtimeContext.UseTableSessionPool); + public IoTDBProvider( ILogger logger, IIoTDBSessionFactory sessionFactory, @@ -39,39 +41,8 @@ namespace JiShe.CollectBus.IoTDBProvider _sessionFactory = sessionFactory; _runtimeContext = runtimeContext; - RefreshSessionPool(); } - - private void RefreshSessionPool() - { - try - { - _currentSession?.Dispose(); - _currentSession = _sessionFactory.GetSessionPool(_runtimeContext.UseTableSessionPool); - _currentSession.OpenAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - } - catch (Exception ex) - { - //自动回退到备用SessionPool - _logger.LogError($"{nameof(RefreshSessionPool)} 切换SessionPool失败,回退到默认配置:{ex.Serialize()}"); - _runtimeContext.UseTableSessionPool = !_runtimeContext.UseTableSessionPool; - _currentSession = _sessionFactory.GetSessionPool(_runtimeContext.UseTableSessionPool); - } - } - - /// - /// 切换 SessionPool - /// - /// - public void SwitchSessionPool(bool useTableSession) - { - if (_runtimeContext.UseTableSessionPool != useTableSession) - { - _runtimeContext.UseTableSessionPool = useTableSession; - RefreshSessionPool(); - } - } - + /// /// 插入数据 /// @@ -83,8 +54,8 @@ namespace JiShe.CollectBus.IoTDBProvider var metadata = GetMetadata(); var tablet = BuildTablet(new[] { entity }, metadata); - - await _currentSession.InsertAsync(tablet); + + await CurrentSession.InsertAsync(tablet); //int result = await _currentSession.InsertAsync(tablet); //if (result <= 0) @@ -108,7 +79,7 @@ namespace JiShe.CollectBus.IoTDBProvider foreach (var batch in batches) { var tablet = BuildTablet(batch, metadata); - await _currentSession.InsertAsync(tablet); + await CurrentSession.InsertAsync(tablet); //var result = await _currentSession.InsertAsync(tablet); //if (result <= 0) //{ @@ -127,7 +98,7 @@ namespace JiShe.CollectBus.IoTDBProvider public async Task DeleteAsync(QueryOptions options) where T : IoTEntity { var query = BuildDeleteSQL(options); - var sessionDataSet = await _currentSession.ExecuteQueryStatementAsync(query); + var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query); if (!sessionDataSet.HasNext()) { @@ -149,7 +120,7 @@ namespace JiShe.CollectBus.IoTDBProvider public async Task> QueryAsync(QueryOptions options) where T : IoTEntity, new() { var query = BuildQuerySQL(options); - var sessionDataSet = await _currentSession.ExecuteQueryStatementAsync(query); + var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query); var result = new PagedResult { @@ -180,22 +151,45 @@ namespace JiShe.CollectBus.IoTDBProvider foreach (var measurement in metadata.ColumnNames) { PropertyInfo propertyInfo = typeof(T).GetProperty(measurement); - if (propertyInfo==null) + if (propertyInfo == null) { throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,没有找到{measurement}属性,属于异常情况。"); } + var value = propertyInfo.GetValue(entity); - if (value != null) + if (propertyInfo.IsDefined(typeof(SingleMeasuringAttribute), false))//表示当前对象是单测点模式, { - rowValues.Add(value); + Tuple valueTuple = (Tuple)value!; + //获得当前Field对应的是的在measurement column中的序号,然后直接更新 metadata 中datetype对应序号的数据类型 + var indexOf = metadata.ColumnNames.IndexOf(measurement); + metadata.ColumnNames[indexOf] = valueTuple.Item1; + Type item2Type = valueTuple.Item2.GetType(); + metadata.DataTypes[indexOf] = GetDataTypeFromTypeName(valueTuple.Item2.GetType().Name); + + rowValues.Add(valueTuple.Item2); + + //TSDataType singleMeasuringTSDataType = + } else { - DataTypeValueMap.TryGetValue(propertyInfo.PropertyType.Name, out object defaultValue); - rowValues.Add(defaultValue); + if (value != null) + { + rowValues.Add(value); + } + else + { + //填充默认数据值 + DataTypeDefaultValueMap.TryGetValue(propertyInfo.PropertyType.Name, out object defaultValue); + + rowValues.Add(defaultValue); + } } + } + values.Add(rowValues); + if (!_runtimeContext.UseTableSessionPool)//树模型 { devicePaths.Add(DevicePathBuilder.GetDevicePath(entity)); @@ -346,7 +340,7 @@ namespace JiShe.CollectBus.IoTDBProvider countQuery += " WHERE " + string.Join(" AND ", options.Conditions.Select(TranslateCondition)); } - var result = await _currentSession.ExecuteQueryStatementAsync(countQuery); + var result = await CurrentSession.ExecuteQueryStatementAsync(countQuery); return result.HasNext() ? Convert.ToInt32(result.Next().Values[0]) : 0; } @@ -418,35 +412,40 @@ namespace JiShe.CollectBus.IoTDBProvider foreach (var prop in type.GetProperties()) { //先获取Tag标签和属性标签 - ColumnInfo? column = prop.GetCustomAttribute() is not null ? new ColumnInfo( - name: prop.Name, + ColumnInfo? column = prop.GetCustomAttribute() is not null ? new ColumnInfo( + name: prop.Name, category: ColumnCategory.TAG, - dataType: GetDataTypeFromTypeName(prop.PropertyType.Name), false + dataType: GetDataTypeFromTypeName(prop.PropertyType.Name), + false ) : prop.GetCustomAttribute() is not null ? new ColumnInfo( prop.Name, ColumnCategory.ATTRIBUTE, - GetDataTypeFromTypeName(prop.PropertyType.Name),false - ) : null; - - //最先检查是不是单测点 - if (prop.GetCustomAttribute() is not null) - { - //单测点的情况下,字段类型是Dictionary - Dictionary keyValuePairs = prop.GetValue(null) as Dictionary; - column = new ColumnInfo( - keyValuePairs.Keys.First(), - ColumnCategory.FIELD, - GetDataTypeFromTypeName(prop.PropertyType.Name), false - ); - } - else - { - //不是单测点的情况下,直接获取字段名称作为测点名称 - column = prop.GetCustomAttribute() is not null ? new ColumnInfo( + GetDataTypeFromTypeName(prop.PropertyType.Name), + false + ) : prop.GetCustomAttribute() is not null ? new ColumnInfo( prop.Name, ColumnCategory.FIELD, - GetDataTypeFromTypeName(prop.PropertyType.Name), false - ) : null; + GetDataTypeFromTypeName(prop.PropertyType.Name), + false) + : null; + + //最先检查是不是单侧点模式 + SingleMeasuringAttribute singleMeasuringAttribute = prop.GetCustomAttribute(); + + if (singleMeasuringAttribute != null && column == null) + { + //warning: 单侧点模式注意事项 + //Entity实体 字段类型是 Tuple,Item1=>测点名称,Item2=>测点值 + //只有一个Filed字段。 + //MeasuringName 默认为 SingleMeasuringAttribute.FieldName。 + //DateTye 默认为 string,在获取对用的Value值的时候在进行重置。 + + column = new ColumnInfo( + singleMeasuringAttribute.FieldName, + ColumnCategory.FIELD, + GetDataTypeFromTypeName(singleMeasuringAttribute.FieldName), + true + ); } ////按优先级顺序检查属性,避免重复反射 @@ -481,7 +480,13 @@ namespace JiShe.CollectBus.IoTDBProvider { var metadata = new DeviceMetadata(); - //按业务逻辑顺序处理(TAG -> FIELD -> ATTRIBUTE) + //先检查是不是单侧点模型 + if (columns.Any(c => c.IsSingleMeasuring)) + { + metadata.IsSingleMeasuring = true; + } + + //按业务逻辑顺序处理(TAG -> ATTRIBUTE -> FIELD) var groupedColumns = columns .GroupBy(c => c.Category) .ToDictionary(g => g.Key, g => g.ToList()); @@ -522,7 +527,7 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// 是否是单测点 /// - public bool IsSingleMeasuring { get;} + public bool IsSingleMeasuring { get; } /// /// 列类型 @@ -534,7 +539,7 @@ namespace JiShe.CollectBus.IoTDBProvider /// public TSDataType DataType { get; } - public ColumnInfo(string name, ColumnCategory category, TSDataType dataType,bool isSingleMeasuring) + public ColumnInfo(string name, ColumnCategory category, TSDataType dataType, bool isSingleMeasuring) { Name = name; Category = category; @@ -581,7 +586,7 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// 根据类型名称获取 IoTDB 数据默认值 /// - private readonly IReadOnlyDictionary DataTypeValueMap = + private readonly IReadOnlyDictionary DataTypeDefaultValueMap = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["BOOLEAN"] = false, diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBSessionFactory.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBSessionFactory.cs index a9b462a..cbc3869 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBSessionFactory.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBSessionFactory.cs @@ -17,6 +17,7 @@ namespace JiShe.CollectBus.IoTDBProvider.Provider { private readonly IoTDBOptions _options; private readonly ConcurrentDictionary _pools = new(); + private bool _disposed; public IoTDBSessionFactory(IOptions options) { @@ -25,12 +26,27 @@ namespace JiShe.CollectBus.IoTDBProvider.Provider public IIoTDBSessionPool GetSessionPool(bool useTableSession) { + if (_disposed) throw new ObjectDisposedException(nameof(IoTDBSessionFactory)); + return _pools.GetOrAdd(useTableSession, key => { - return key - ? new TableSessionPoolAdapter(_options) + var pool = key + ? (IIoTDBSessionPool)new TableSessionPoolAdapter(_options) : new SessionPoolAdapter(_options); + + pool.OpenAsync().ConfigureAwait(false).GetAwaiter().GetResult(); ; + return pool; }); } + + public void Dispose() + { + foreach (var pool in _pools.Values) + { + pool.Dispose(); + } + _pools.Clear(); + _disposed = true; + } } } -- 2.47.2 From f036e9546d9b9bff0983e3d4594eff06235b6497 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Fri, 11 Apr 2025 14:50:46 +0800 Subject: [PATCH 068/139] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=8D=95=E4=BE=A7?= =?UTF-8?q?=E7=82=B9=E6=A8=A1=E5=BC=8F=E6=95=B0=E6=8D=AE=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/SampleAppService.cs | 6 +-- .../AFNEntity/SingleMeasuringAFNDataEntity.cs | 4 +- .../Attribute/SingleMeasuringAttribute.cs | 2 +- .../Provider/IoTDBProvider.cs | 42 ++++++------------- .../Provider/SessionPoolAdapter.cs | 3 +- .../Provider/TableSessionPoolAdapter.cs | 8 +++- 6 files changed, 28 insertions(+), 37 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 551dc60..f243e05 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -128,16 +128,16 @@ public class SampleAppService : CollectBusAppService, ISampleAppService /// /// [HttpGet] - public async Task TestSingleMeasuringAFNData(string measuring, object value) + public async Task TestSingleMeasuringAFNData(string measuring, string value) { - SingleMeasuringAFNDataEntity meter = new SingleMeasuringAFNDataEntity() + var meter = new SingleMeasuringAFNDataEntity() { SystemName = "energy", DeviceId = "402440506", DeviceType = "Ammeter", ProjectCode = "10059", Timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), - SingleMeasuring = new Tuple(measuring, value) + SingleMeasuring = new Tuple(measuring, value) }; await _iotDBProvider.InsertAsync(meter); } diff --git a/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs b/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs index f53ca21..751e9e3 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs @@ -10,12 +10,12 @@ namespace JiShe.CollectBus.IotSystems.AFNEntity /// /// AFN单项数据实体 /// - public class SingleMeasuringAFNDataEntity:IoTEntity + public class SingleMeasuringAFNDataEntity : IoTEntity { /// /// 单项数据对象 /// [SingleMeasuring(nameof(SingleMeasuring))] - public Tuple SingleMeasuring { get; set; } + public Tuple SingleMeasuring { get; set; } } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs b/src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs index 9a85a85..ba5af69 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.IoTDBProvider { /// - /// 用于标识当前实体为单侧点模式,单侧点模式只有一个Filed标识字段,类型是Tuple,Item1=>测点名称,Item2=>测点值 + /// 用于标识当前实体为单侧点模式,单侧点模式只有一个Filed标识字段,类型是Tuple,Item1=>测点名称,Item2=>测点值,泛型 /// [AttributeUsage(AttributeTargets.Property)] public class SingleMeasuringAttribute : Attribute diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs index 297d250..acfab09 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs @@ -150,6 +150,7 @@ namespace JiShe.CollectBus.IoTDBProvider var rowValues = new List(); foreach (var measurement in metadata.ColumnNames) { + PropertyInfo propertyInfo = typeof(T).GetProperty(measurement); if (propertyInfo == null) { @@ -157,18 +158,14 @@ namespace JiShe.CollectBus.IoTDBProvider } var value = propertyInfo.GetValue(entity); - if (propertyInfo.IsDefined(typeof(SingleMeasuringAttribute), false))//表示当前对象是单测点模式, + if (propertyInfo.IsDefined(typeof(SingleMeasuringAttribute), false) && value != null)//表示当前对象是单测点模式, { - Tuple valueTuple = (Tuple)value!; - //获得当前Field对应的是的在measurement column中的序号,然后直接更新 metadata 中datetype对应序号的数据类型 - var indexOf = metadata.ColumnNames.IndexOf(measurement); - metadata.ColumnNames[indexOf] = valueTuple.Item1; - Type item2Type = valueTuple.Item2.GetType(); - metadata.DataTypes[indexOf] = GetDataTypeFromTypeName(valueTuple.Item2.GetType().Name); + Type tupleType = value.GetType(); + Type[] tupleArgs = tupleType.GetGenericArguments(); + Type item2Type = tupleArgs[1]; // T 的实际类型 + var item2 = tupleType.GetProperty("Item2")!.GetValue(value); - rowValues.Add(valueTuple.Item2); - - //TSDataType singleMeasuringTSDataType = + rowValues.Add(item2); } else @@ -435,34 +432,21 @@ namespace JiShe.CollectBus.IoTDBProvider if (singleMeasuringAttribute != null && column == null) { //warning: 单侧点模式注意事项 - //Entity实体 字段类型是 Tuple,Item1=>测点名称,Item2=>测点值 + //Entity实体 字段类型是 Tuple,Item1=>测点名称,Item2=>测点值,泛型 //只有一个Filed字段。 //MeasuringName 默认为 SingleMeasuringAttribute.FieldName。 - //DateTye 默认为 string,在获取对用的Value值的时候在进行重置。 + + Type tupleType = prop.PropertyType; + Type[] tupleArgs = tupleType.GetGenericArguments(); column = new ColumnInfo( singleMeasuringAttribute.FieldName, ColumnCategory.FIELD, - GetDataTypeFromTypeName(singleMeasuringAttribute.FieldName), + GetDataTypeFromTypeName(tupleArgs[1].Name), true ); } - - ////按优先级顺序检查属性,避免重复反射 - //column = prop.GetCustomAttribute() is not null ? new ColumnInfo( - // name: prop.Name, //使用属性名 - // category: ColumnCategory.TAG, - // dataType: GetDataTypeFromTypeName(prop.PropertyType.Name) - //) : prop.GetCustomAttribute() is not null ? new ColumnInfo( - // prop.Name, - // ColumnCategory.ATTRIBUTE, - // GetDataTypeFromTypeName(prop.PropertyType.Name) - //) : prop.GetCustomAttribute() is not null ? new ColumnInfo( - // prop.Name, - // ColumnCategory.FIELD, - // GetDataTypeFromTypeName(prop.PropertyType.Name) - //) : null; - + if (column.HasValue) { columns.Add(column.Value); diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs index 81a69a9..006b1c3 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs @@ -53,7 +53,8 @@ namespace JiShe.CollectBus.IoTDBProvider.Provider /// public async Task InsertAsync(Tablet tablet) { - return await _sessionPool.InsertAlignedTabletAsync(tablet); + var result = await _sessionPool.InsertAlignedTabletAsync(tablet); + return result; } /// diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/TableSessionPoolAdapter.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/TableSessionPoolAdapter.cs index 3676199..22de1b0 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/TableSessionPoolAdapter.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/TableSessionPoolAdapter.cs @@ -51,7 +51,13 @@ namespace JiShe.CollectBus.IoTDBProvider.Provider /// public async Task InsertAsync(Tablet tablet) { - return await _sessionPool.InsertAsync(tablet); + var result = await _sessionPool.InsertAsync(tablet); + if (result != 0) + { + throw new Exception($"{nameof(TableSessionPoolAdapter)} "); + } + + return result; } /// -- 2.47.2 From 085a67b20a3cf25753ee00ea308331edcbb13d96 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Fri, 11 Apr 2025 15:17:16 +0800 Subject: [PATCH 069/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JiShe.CollectBus.Host/appsettings.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 4416fd2..2c72f7d 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -35,12 +35,12 @@ }, "ConnectionStrings": { "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", - "Kafka": "192.168.0.151:29092,192.168.0.151:39092,192.168.0.151:49092", + "Kafka": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092", "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" }, "Redis": { - "Configuration": "192.168.0.151:6380,password=1q2w3e!@#,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + "Configuration": "192.168.1.9:6380,password=1q2w3e!@#,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", "DefaultDB": "14", "HangfireDB": "15" }, @@ -95,7 +95,7 @@ "Kafka": { "Connections": { "Default": { - "BootstrapServers": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092" + "BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092" // "SecurityProtocol": "SASL_PLAINTEXT", // "SaslMechanism": "PLAIN", // "SaslUserName": "lixiao", @@ -121,7 +121,7 @@ "IoTDBOptions": { "UserName": "root", "Password": "root", - "ClusterList": [ "192.168.0.151:6667" ], + "ClusterList": [ "192.168.1.9:6667" ], "PoolSize": 2, "DataBaseName": "energy", "OpenDebugMode": true, -- 2.47.2 From 99f96d889e4e5c07fe2dcdd29c06160d5ac1984a Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Fri, 11 Apr 2025 17:06:30 +0800 Subject: [PATCH 070/139] =?UTF-8?q?=E8=A7=A3=E5=86=B3IoTDB=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E5=8D=95=E4=BE=A7=E7=82=B9=E8=AE=B0=E5=BD=95=E5=85=A5?= =?UTF-8?q?=E5=BA=93=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Provider/DeviceMetadata.cs | 4 +- .../Provider/IoTDBProvider.cs | 61 ++++++++++++++----- .../Provider/SessionPoolAdapter.cs | 5 ++ 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs index 50aa8f7..6bdbd56 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs @@ -20,7 +20,7 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// 测量值集合,用于构建Table的测量值,也就是columnNames参数 /// - public List ColumnNames { get; } = new(); + public List ColumnNames { get; set; } = new(); /// /// 列类型集合,用于构建Table的列类型,也就是columnCategories参数 @@ -30,6 +30,6 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// 值类型集合,用于构建Table的值类型,也就是dataTypes参数 /// - public ListDataTypes { get; } = new(); + public List DataTypes { get; } = new(); } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs index acfab09..705b2c1 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; +using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -42,7 +43,7 @@ namespace JiShe.CollectBus.IoTDBProvider _runtimeContext = runtimeContext; } - + /// /// 插入数据 /// @@ -143,29 +144,39 @@ namespace JiShe.CollectBus.IoTDBProvider var timestamps = new List(); var values = new List>(); var devicePaths = new HashSet(); + List tempColumnNames = new List(); + tempColumnNames.AddRange(metadata.ColumnNames); foreach (var entity in entities) { timestamps.Add(entity.Timestamps); var rowValues = new List(); - foreach (var measurement in metadata.ColumnNames) + foreach (var measurement in tempColumnNames) { PropertyInfo propertyInfo = typeof(T).GetProperty(measurement); if (propertyInfo == null) { - throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,没有找到{measurement}属性,属于异常情况。"); + throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,没有找到{measurement}属性,属于异常情况,-101。"); } var value = propertyInfo.GetValue(entity); - if (propertyInfo.IsDefined(typeof(SingleMeasuringAttribute), false) && value != null)//表示当前对象是单测点模式, + if (propertyInfo.IsDefined(typeof(SingleMeasuringAttribute), false) && value != null)//表示当前对象是单测点模式 { Type tupleType = value.GetType(); Type[] tupleArgs = tupleType.GetGenericArguments(); Type item2Type = tupleArgs[1]; // T 的实际类型 - var item2 = tupleType.GetProperty("Item2")!.GetValue(value); + var item1 = tupleType.GetProperty("Item1")!.GetValue(value); + var item2 = tupleType.GetProperty("Item2")!.GetValue(value); + if (item1 == null || item2 == null) + { + throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,单测点模式构建失败,没有获取测点名称或者测点值,-102。"); + } - rowValues.Add(item2); + var indexOf = metadata.ColumnNames.IndexOf(measurement); + metadata.ColumnNames[indexOf] = (string)item1!; + + rowValues.Add(item2); } else @@ -389,12 +400,34 @@ namespace JiShe.CollectBus.IoTDBProvider /// private DeviceMetadata GetMetadata() where T : IoTEntity { - return _metadataCache.GetOrAdd(typeof(T), type => - { - var columns = CollectColumnMetadata(type); - var metadata = BuildDeviceMetadata(columns); - return metadata; - }); + + var columns = CollectColumnMetadata(typeof(T)); + var metadata = BuildDeviceMetadata(columns); + + return _metadataCache.AddOrUpdate( + typeof(T), + addValueFactory: t => metadata, // 如果键不存在,用此值添加 + updateValueFactory: (t, existingValue) => + { + var columns = CollectColumnMetadata(t); + var metadata = BuildDeviceMetadata(columns); + + //对现有值 existingValue 进行修改,返回新值 + existingValue.ColumnNames = metadata.ColumnNames; + return existingValue; + } + ); + + //return _metadataCache.GetOrAdd(typeof(T), type => + //{ + // var columns = CollectColumnMetadata(type); + // var metadata = BuildDeviceMetadata(columns); + // //if (metadata.IsSingleMeasuring) + // //{ + // // _metadataCache.Remove(typeof(T)); + // //} + // return metadata; + //}); } /// @@ -434,7 +467,7 @@ namespace JiShe.CollectBus.IoTDBProvider //warning: 单侧点模式注意事项 //Entity实体 字段类型是 Tuple,Item1=>测点名称,Item2=>测点值,泛型 //只有一个Filed字段。 - //MeasuringName 默认为 SingleMeasuringAttribute.FieldName。 + //MeasuringName 默认为 SingleMeasuringAttribute.FieldName,以便于在获取对应的Value的时候重置为 Item1 的值。 Type tupleType = prop.PropertyType; Type[] tupleArgs = tupleType.GetGenericArguments(); @@ -446,7 +479,7 @@ namespace JiShe.CollectBus.IoTDBProvider true ); } - + if (column.HasValue) { columns.Add(column.Value); diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs index 006b1c3..e836672 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs @@ -54,6 +54,11 @@ namespace JiShe.CollectBus.IoTDBProvider.Provider public async Task InsertAsync(Tablet tablet) { var result = await _sessionPool.InsertAlignedTabletAsync(tablet); + if (result != 0) + { + throw new Exception($"{nameof(TableSessionPoolAdapter)} "); + } + return result; } -- 2.47.2 From 2625d076d47527041eeaa7433fbd60222b821ff3 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Fri, 11 Apr 2025 17:12:29 +0800 Subject: [PATCH 071/139] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 22 +-- .../CollectBusApplicationModule.cs | 2 +- .../Samples/SampleAppService.cs | 9 + src/JiShe.CollectBus.Host/Startup.cs | 5 +- .../JiShe.CollectBus.Protocol.Test.csproj | 28 ++++ .../JiSheCollectBusProtocolModule.cs | 21 +++ .../TestProtocolPlugin.cs | 155 ++++++++++++++++++ 7 files changed, 230 insertions(+), 12 deletions(-) create mode 100644 src/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj create mode 100644 src/JiShe.CollectBus.Protocol.Test/JiSheCollectBusProtocolModule.cs create mode 100644 src/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index c866688..c26f3da 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -35,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka", "s EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.IoTDBProvider", "src\JiShe.CollectBus.IoTDBProvider\JiShe.CollectBus.IoTDBProvider.csproj", "{A3F3C092-0A25-450B-BF6A-5983163CBEF5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.Test", "src\JiShe.CollectBus.Protocol.Test\JiShe.CollectBus.Protocol.Test.csproj", "{A377955E-7EA1-6F29-8CF7-774569E93925}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -93,18 +95,18 @@ Global {C06C4082-638F-2996-5FED-7784475766C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {C06C4082-638F-2996-5FED-7784475766C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {C06C4082-638F-2996-5FED-7784475766C1}.Release|Any CPU.Build.0 = Release|Any CPU - {919F4CDB-5C82-4371-B209-403B408DA248}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {919F4CDB-5C82-4371-B209-403B408DA248}.Debug|Any CPU.Build.0 = Debug|Any CPU - {919F4CDB-5C82-4371-B209-403B408DA248}.Release|Any CPU.ActiveCfg = Release|Any CPU - {919F4CDB-5C82-4371-B209-403B408DA248}.Release|Any CPU.Build.0 = Release|Any CPU - {A3F3C092-0A25-450B-BF6A-5983163CBEF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A3F3C092-0A25-450B-BF6A-5983163CBEF5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A3F3C092-0A25-450B-BF6A-5983163CBEF5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A3F3C092-0A25-450B-BF6A-5983163CBEF5}.Release|Any CPU.Build.0 = Release|Any CPU {F0288175-F0EC-48BD-945F-CF1512850943}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F0288175-F0EC-48BD-945F-CF1512850943}.Debug|Any CPU.Build.0 = Debug|Any CPU {F0288175-F0EC-48BD-945F-CF1512850943}.Release|Any CPU.ActiveCfg = Release|Any CPU {F0288175-F0EC-48BD-945F-CF1512850943}.Release|Any CPU.Build.0 = Release|Any CPU + {A3F3C092-0A25-450B-BF6A-5983163CBEF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3F3C092-0A25-450B-BF6A-5983163CBEF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3F3C092-0A25-450B-BF6A-5983163CBEF5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3F3C092-0A25-450B-BF6A-5983163CBEF5}.Release|Any CPU.Build.0 = Release|Any CPU + {A377955E-7EA1-6F29-8CF7-774569E93925}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A377955E-7EA1-6F29-8CF7-774569E93925}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A377955E-7EA1-6F29-8CF7-774569E93925}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A377955E-7EA1-6F29-8CF7-774569E93925}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -123,9 +125,9 @@ Global {8BA01C3D-297D-42DF-BD63-EF07202A0A67} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {FE0457D9-4038-4A17-8808-DCAD06CFC0A0} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {C06C4082-638F-2996-5FED-7784475766C1} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {919F4CDB-5C82-4371-B209-403B408DA248} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {A3F3C092-0A25-450B-BF6A-5983163CBEF5} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {F0288175-F0EC-48BD-945F-CF1512850943} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {A3F3C092-0A25-450B-BF6A-5983163CBEF5} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {A377955E-7EA1-6F29-8CF7-774569E93925} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 87d63d0..e287289 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -103,7 +103,7 @@ public class CollectBusApplicationModule : AbpModule //默认初始化表计信息 dbContext.InitAmmeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); - dbContext.InitWatermeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); + //dbContext.InitWatermeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); } } diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 2b3962d..43bf8a4 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -14,6 +14,8 @@ using Microsoft.Extensions.Options; using JiShe.CollectBus.IoTDBProvider.Context; using Microsoft.Extensions.Logging; using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using Microsoft.Extensions.DependencyInjection; namespace JiShe.CollectBus.Samples; @@ -136,4 +138,11 @@ public class SampleAppService : CollectBusAppService, ISampleAppService var ammeterList = await SqlProvider.Instance.Change(DbEnum.PrepayDB).Select().Where(d => d.TB_CustomerID == 5).Take(10).ToListAsync(); return ammeterList; } + + [AllowAnonymous] + public bool GetTestProtocol() + { + var aa = LazyServiceProvider.GetKeyedService("TestProtocolPlugin"); + return aa == null; + } } diff --git a/src/JiShe.CollectBus.Host/Startup.cs b/src/JiShe.CollectBus.Host/Startup.cs index 1f43bc8..dd6453e 100644 --- a/src/JiShe.CollectBus.Host/Startup.cs +++ b/src/JiShe.CollectBus.Host/Startup.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Modularity.PlugIns; +using TouchSocket.Core; +using Volo.Abp.Modularity.PlugIns; namespace JiShe.CollectBus.Host { @@ -40,6 +41,8 @@ namespace JiShe.CollectBus.Host { app.InitializeApplication(); //await app.InitializeApplicationAsync(); + + } } } diff --git a/src/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj b/src/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj new file mode 100644 index 0000000..d7b4af6 --- /dev/null +++ b/src/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + preview + + + + + + + + + + + + + + + + + + + + diff --git a/src/JiShe.CollectBus.Protocol.Test/JiSheCollectBusProtocolModule.cs b/src/JiShe.CollectBus.Protocol.Test/JiSheCollectBusProtocolModule.cs new file mode 100644 index 0000000..4abc95b --- /dev/null +++ b/src/JiShe.CollectBus.Protocol.Test/JiSheCollectBusProtocolModule.cs @@ -0,0 +1,21 @@ +using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Modularity; + +namespace JiShe.CollectBus.Protocol.Test +{ + public class JiSheCollectBusProtocolModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddKeyedSingleton(nameof(TestProtocolPlugin)); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var protocol = context.ServiceProvider.GetRequiredKeyedService(nameof(TestProtocolPlugin)); + protocol.AddAsync(); + } + } +} diff --git a/src/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs new file mode 100644 index 0000000..2573ab7 --- /dev/null +++ b/src/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs @@ -0,0 +1,155 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.Protocols; +using JiShe.CollectBus.Protocol.Contracts.Abstracts; + +namespace JiShe.CollectBus.Protocol.Test +{ + public class TestProtocolPlugin : BaseProtocolPlugin + { + /// + /// Initializes a new instance of the class. + /// + /// The service provider. + public TestProtocolPlugin(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + + public sealed override ProtocolInfo Info => new(nameof(TestProtocolPlugin), "Test", "TCP", "Test协议", "DTS1980-Test"); + + public override async Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) + { + throw new NotImplementedException(); + } + + #region 上行命令 + + //68 + //32 00 + //32 00 + //68 + //C9 1100'1001. 控制域C。 + // D7=1, (终端发送)上行方向。 + // D6=1, 此帧来自启动站。 + // D5=0, (上行方向)要求访问位。表示终端无事件数据等待访问。 + // D4=0, 保留 + // D3~D0=9, 功能码。链路测试 + + //20 32 行政区划码 + //90 26 终端地址 + //00 主站地址和组地址标志。终端为单地址。 //3220 09 87 2 + // 终端启动的发送帧的 MSA 应为 0, 其主站响应帧的 MSA 也应为 0. + //02 应用层功能码。AFN=2, 链路接口检测 + //70 0111'0000. 帧序列域。无时间标签、单帧、需要确认。 + //00 00 信息点。DA1和DA2全为“0”时,表示终端信息点。 + //01 00 信息类。F1, 登录。 + //44 帧尾,包含用户区数据校验和 + //16 帧结束标志 + + /// + /// 解析上行命令 + /// + /// + /// + public CommandReulst? AnalysisCmd(string cmd) + { + CommandReulst? commandReulst = null; + var hexStringList = cmd.StringToPairs(); + + if (hexStringList.Count < hearderLen) + { + return commandReulst; + } + //验证起始字符 + if (!hexStringList[0].IsStartStr() || !hexStringList[5].IsStartStr()) + { + return commandReulst; + } + + var lenHexStr = $"{hexStringList[2]}{hexStringList[1]}"; + var lenBin = lenHexStr.HexToBin(); + var len = lenBin.Remove(lenBin.Length - 2).BinToDec(); + //验证长度 + if (hexStringList.Count - 2 != hearderLen + len) + return commandReulst; + + var userDataIndex = hearderLen; + var c = hexStringList[userDataIndex];//控制域 1字节 + userDataIndex += 1; + + var aHexList = hexStringList.Skip(userDataIndex).Take(5).ToList();//地址域 5字节 + var a = AnalysisA(aHexList); + var a3Bin = aHexList[4].HexToBin().PadLeft(8, '0'); + var mSA = a3Bin.Substring(0, 7).BinToDec(); + userDataIndex += 5; + + var aFN = (AFN)hexStringList[userDataIndex].HexToDec();//1字节 + userDataIndex += 1; + + var seq = hexStringList[userDataIndex].HexToBin().PadLeft(8, '0'); + var tpV = (TpV)Convert.ToInt32(seq.Substring(0, 1)); + var fIRFIN = (FIRFIN)Convert.ToInt32(seq.Substring(1, 2)); + var cON = (CON)Convert.ToInt32(seq.Substring(3, 1)); + var prseqBin = seq.Substring(4, 4); + userDataIndex += 1; + + // (DA2 - 1) * 8 + DA1 = pn + var da1Bin = hexStringList[userDataIndex].HexToBin(); + var da1 = da1Bin == "0" ? 0 : da1Bin.Length; + userDataIndex += 1; + var da2 = hexStringList[userDataIndex].HexToDec(); + var pn = da2 == 0 ? 0 : (da2 - 1) * 8 + da1; + userDataIndex += 1; + //(DT2*8)+DT1=fn + var dt1Bin = hexStringList[userDataIndex].HexToBin(); + var dt1 = dt1Bin != "0" ? dt1Bin.Length : 0; + userDataIndex += 1; + var dt2 = hexStringList[userDataIndex].HexToDec(); + var fn = dt2 * 8 + dt1; + userDataIndex += 1; + + //数据单元 + var datas = hexStringList.Skip(userDataIndex).Take(len + hearderLen - userDataIndex).ToList(); + + //EC + //Tp + commandReulst = new CommandReulst() + { + A = a, + MSA = mSA, + AFN = aFN, + Seq = new Seq() + { + TpV = tpV, + FIRFIN = fIRFIN, + CON = cON, + PRSEQ = prseqBin.BinToDec(), + }, + CmdLength = len, + Pn = pn, + Fn = fn, + HexDatas = datas + }; + + return commandReulst; + } + + /// + /// 解析地址 + /// + /// + /// + private string AnalysisA(List aHexList) + { + var a1 = aHexList[1] + aHexList[0]; + var a2 = aHexList[3] + aHexList[2]; + var a2Dec = a2.HexToDec(); + var a3 = aHexList[4]; + var a = $"{a1}{a2Dec.ToString().PadLeft(5, '0')}"; + return a; + } + #endregion + } +} -- 2.47.2 From ad89d2716c87b85cc19ae2a533ee1ac74445fb88 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Fri, 11 Apr 2025 17:43:19 +0800 Subject: [PATCH 072/139] =?UTF-8?q?=E4=BC=98=E5=8C=96Kafka=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E4=B8=BB=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusApplicationModule.cs | 18 ++++++++++++++++-- .../JiShe.CollectBus.Application.csproj | 1 + .../Consts/CommonConst.cs | 11 +++++++++++ .../CollectBusHostModule.cs | 2 +- src/JiShe.CollectBus.Host/appsettings.json | 4 +++- 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 87d63d0..453855d 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -29,6 +29,8 @@ using Volo.Abp.EventBus.Kafka; using Volo.Abp.Kafka; using Volo.Abp.EventBus; using Confluent.Kafka; +using JiShe.CollectBus.Kafka.AdminClient; +using JiShe.CollectBus.Common.Consts; namespace JiShe.CollectBus; @@ -98,12 +100,24 @@ public class CollectBusApplicationModule : AbpModule { context.AddBackgroundWorkerAsync(type); } - - var dbContext = context.ServiceProvider.GetRequiredService(); //默认初始化表计信息 + var dbContext = context.ServiceProvider.GetRequiredService(); dbContext.InitAmmeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); dbContext.InitWatermeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); + + //初始化主题信息 + var kafkaAdminClient = context.ServiceProvider.GetRequiredService(); + var configuration = context.ServiceProvider.GetRequiredService(); + + List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); + topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); + + List topicSpecifications = new List(); + foreach (var item in topics) + { + kafkaAdminClient.CreateTopicIfNotExistAsync(item, configuration.GetValue(CommonConst.KafkaReplicationFactor), configuration.GetValue(CommonConst.NumPartitions)); + } } } diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index ef887fb..5765ecd 100644 --- a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -28,6 +28,7 @@ + diff --git a/src/JiShe.CollectBus.Common/Consts/CommonConst.cs b/src/JiShe.CollectBus.Common/Consts/CommonConst.cs index ff38f22..471c897 100644 --- a/src/JiShe.CollectBus.Common/Consts/CommonConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/CommonConst.cs @@ -20,5 +20,16 @@ namespace JiShe.CollectBus.Common.Consts /// Kafka /// public const string Kafka = "Kafka"; + + /// + /// Kafka副本数量 + /// + public const string KafkaReplicationFactor = "KafkaReplicationFactor"; + + /// + /// Kafka主题分区数量 + /// + public const string NumPartitions = "NumPartitions"; + } } diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs index 31f2651..aabc2ba 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs @@ -45,7 +45,7 @@ namespace JiShe.CollectBus.Host ConfigureHangfire(context); ConfigureCap(context, configuration); //ConfigureMassTransit(context, configuration); - ConfigureKafkaTopic(context, configuration); + //ConfigureKafkaTopic(context, configuration); ConfigureAuditLog(context); ConfigureCustom(context, configuration); } diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 2c72f7d..4327ff5 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -127,5 +127,7 @@ "OpenDebugMode": true, "UseTableSessionPoolByDefault": false }, - "ServerTagName": "JiSheCollectBus" + "ServerTagName": "JiSheCollectBus", + "KafkaReplicationFactor": 3, + "NumPartitions": 60 } \ No newline at end of file -- 2.47.2 From 0c06cc904da63c073d0be078b1de7d61ee477040 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Sat, 12 Apr 2025 15:11:18 +0800 Subject: [PATCH 073/139] =?UTF-8?q?kafka=E4=BB=A3=E7=A0=81=E8=BF=98?= =?UTF-8?q?=E5=8E=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JiShe.CollectBus.Host/appsettings.json | 65 +++++++-------- .../AdminClient/AdminClientService.cs | 56 +++++++------ .../AdminClient/IAdminClientService.cs | 26 +----- .../CollectBusKafkaModule.cs | 10 --- .../Consumer/ConsumerBackgroundService.cs | 54 ------------- .../Consumer/ConsumerService.cs | 79 ++++++++++++------- .../Consumer/IConsumerService.cs | 5 +- .../KafkaOptions.cs | 17 ++++ .../Producer/IProducerService.cs | 6 +- .../Producer/ProducerBaseService.cs | 64 --------------- .../Producer/ProducerService.cs | 60 +++++++++++--- 11 files changed, 195 insertions(+), 247 deletions(-) delete mode 100644 src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerBackgroundService.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/KafkaOptions.cs delete mode 100644 src/JiShe.CollectBus.KafkaProducer/Producer/ProducerBaseService.cs diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 2c72f7d..2cb4f66 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -81,43 +81,44 @@ "Port": 5672 } }, + "Kafka": { + "BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092", + "EnableAuthorization": false, + "SecurityProtocol": "SASL_PLAINTEXT", + "SaslMechanism": "PLAIN", + "SaslUserName": "lixiao", + "SaslPassword": "lixiao1980" + //"Topic": { + // "ReplicationFactor": 3, + // "NumPartitions": 1000 + //} + }, //"Kafka": { - // "BootstrapServers": "121.42.242.91:29092,121.42.242.91:39092,121.42.242.91:49092", - // "EnableAuthorization": false, - // "SecurityProtocol": "SASL_PLAINTEXT", - // "SaslMechanism": "PLAIN", - // "SaslUserName": "lixiao", - // "SaslPassword": "lixiao1980", + // "Connections": { + // "Default": { + // "BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092" + // // "SecurityProtocol": "SASL_PLAINTEXT", + // // "SaslMechanism": "PLAIN", + // // "SaslUserName": "lixiao", + // // "SaslPassword": "lixiao1980", + // } + // }, + // "Consumer": { + // "GroupId": "JiShe.CollectBus" + // }, + // "Producer": { + // "MessageTimeoutMs": 6000, + // "Acks": -1 + // }, // "Topic": { // "ReplicationFactor": 3, // "NumPartitions": 1000 + // }, + // "EventBus": { + // "GroupId": "JiShe.CollectBus", + // "TopicName": "DefaultTopicName" // } - "Kafka": { - "Connections": { - "Default": { - "BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092" - // "SecurityProtocol": "SASL_PLAINTEXT", - // "SaslMechanism": "PLAIN", - // "SaslUserName": "lixiao", - // "SaslPassword": "lixiao1980", - } - }, - "Consumer": { - "GroupId": "JiShe.CollectBus" - }, - "Producer": { - "MessageTimeoutMs": 6000, - "Acks": -1 - }, - "Topic": { - "ReplicationFactor": 3, - "NumPartitions": 1000 - }, - "EventBus": { - "GroupId": "JiShe.CollectBus", - "TopicName": "DefaultTopicName" - } - }, + //}, "IoTDBOptions": { "UserName": "root", "Password": "root", diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs index e4b621b..e687276 100644 --- a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs @@ -11,7 +11,7 @@ using Volo.Abp.DependencyInjection; namespace JiShe.CollectBus.Kafka.AdminClient { - public class AdminClientService : IAdminClientService, ISingletonDependency + public class AdminClientService : IAdminClientService, IDisposable,ISingletonDependency { private readonly ILogger _logger; @@ -69,43 +69,49 @@ namespace JiShe.CollectBus.Kafka.AdminClient return await Task.FromResult(metadata.Topics.Exists(a => a.Topic == topic)); } - /// - /// Creates the topic if not exist asynchronous. - /// - /// Name of the topic. - /// The factor number. - /// The partition number. - public async Task CreateTopicIfNotExistAsync(string topicName, short factorNum, int partitionNum) + public async Task CreateTopicAsync(string topic, int numPartitions, short replicationFactor) { try { - if (await CheckTopicAsync(topicName)) return; - await Instance.CreateTopicsAsync(new[] { - new TopicSpecification { Name = topicName, ReplicationFactor = factorNum, NumPartitions = partitionNum } + new TopicSpecification + { + Name = topic, + NumPartitions = numPartitions, + ReplicationFactor = replicationFactor + } }); } catch (CreateTopicsException e) { - _logger.LogError($"An error occured creating topic {e.Results[0].Topic}: {e.Results[0].Error.Reason}"); + if (e.Results[0].Error.Code != ErrorCode.TopicAlreadyExists) + { + throw; + } } } - /// - /// Deletes the topic asynchronous. - /// - /// Name of the topic. - public async Task DeleteTopicAsync(List topicName) + public async Task DeleteTopicAsync(string topic) { - try - { - await Instance.DeleteTopicsAsync(topicName, null); - } - catch (DeleteTopicsException e) - { - _logger.LogError($"An error occured creating topic {e.Results[0].Topic}: {e.Results[0].Error.Reason}"); - } + await Instance.DeleteTopicsAsync(new[] { topic }); + } + + public async Task> ListTopicsAsync() + { + var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(10)); + return new List(metadata.Topics.Select(t => t.Topic)); + } + + public async Task TopicExistsAsync(string topic) + { + var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(10)); + return metadata.Topics.Any(t => t.Topic == topic); + } + + public void Dispose() + { + Instance?.Dispose(); } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs b/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs index bdb2d07..c3d332d 100644 --- a/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs @@ -8,27 +8,9 @@ namespace JiShe.CollectBus.Kafka.AdminClient { public interface IAdminClientService { - /// - /// Checks the topic asynchronous. - /// - /// The topic. - /// - Task CheckTopicAsync(string topic); - - /// - /// Creates the topic if not exist asynchronous. - /// - /// Name of the topic. - /// The factor number. - /// The partition number. - /// - Task CreateTopicIfNotExistAsync(string topicName, short factorNum, int partitionNum); - - /// - /// Deletes the topic asynchronous. - /// - /// Name of the topic. - /// - Task DeleteTopicAsync(List topicName); + Task CreateTopicAsync(string topic, int numPartitions, short replicationFactor); + Task DeleteTopicAsync(string topic); + Task> ListTopicsAsync(); + Task TopicExistsAsync(string topic); } } diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index 44ddcee..61cc788 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -10,16 +10,6 @@ namespace JiShe.CollectBus.Kafka { public override void ConfigureServices(ServiceConfigurationContext context) { - var configuration = context.Services.GetConfiguration(); - - // 注册 Kafka 生产者 - context.Services.AddSingleton>(sp => - new ProducerBuilder( - configuration.GetSection("Kafka:ProducerConfig").Get() - ) - .Build()); - // 注册后台服务 - context.Services.AddHostedService(); } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerBackgroundService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerBackgroundService.cs deleted file mode 100644 index e49a09e..0000000 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerBackgroundService.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Confluent.Kafka; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.Kafka.Consumer -{ - public class ConsumerBackgroundService : BackgroundService - { - private readonly ConsumerService _consumerService; - private readonly ILogger _logger; - - public ConsumerBackgroundService( - ConsumerService consumerService, - ILogger logger) - { - _consumerService = consumerService; - _logger = logger; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - _consumerService.Subscribe("abp-kafka-topic"); - - while (!stoppingToken.IsCancellationRequested) - { - try - { - var result = _consumerService.Consume(stoppingToken); - await ProcessMessageAsync(result.Message.Value); - _consumerService.Commit(result); - } - catch (ConsumeException ex) - { - _logger.LogError(ex, $"Message consume error: {ex.Error.Reason}"); - } - } - } - - private async Task ProcessMessageAsync(string message) - { - // 使用 ABP 的异步处理机制 - await Task.Run(() => - { - _logger.LogInformation($"Processing message: {message}"); - // 这里可以触发 ABP 的领域事件 - }); - } - } -} diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index 391a7fc..37efe3a 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -5,54 +5,77 @@ using Microsoft.Extensions.Logging; using JiShe.CollectBus.Kafka.Attributes; using Volo.Abp.DependencyInjection; using JiShe.CollectBus.Kafka.AdminClient; +using static Confluent.Kafka.ConfigPropertyNames; namespace JiShe.CollectBus.Kafka.Consumer { - public abstract class ConsumerService : IConsumerService, IDisposable, ISingletonDependency + public abstract class ConsumerService : IConsumerService, IDisposable, ISingletonDependency { - private readonly ILogger _logger; - private readonly IConsumer _consumer; + private readonly ILogger> _logger; + private CancellationTokenSource _cancellationTokenSource; - public ConsumerService( - ILogger logger, - IConfiguration configuration) + protected ConsumerService(IConfiguration configuration, ILogger> logger) { _logger = logger; - - var consumerConfig = configuration.GetSection("Kafka:ConsumerConfig") - .Get(); - - _consumer = new ConsumerBuilder(consumerConfig) - .SetErrorHandler(OnConsumeError) - .Build(); + GetInstance(configuration); } - public void Subscribe(string topic) + + public IConsumer Instance { get; set; } = default; + + public IConsumer GetInstance(IConfiguration configuration) { - _consumer.Subscribe(topic); - _logger.LogInformation($"Subscribed to topic: {topic}"); + var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]); + var consumerConfig = new ConsumerConfig + { + BootstrapServers = configuration["Kafka:BootstrapServers"], + AutoOffsetReset = AutoOffsetReset.Earliest + }; + + if (enableAuthorization) + { + consumerConfig.SecurityProtocol = SecurityProtocol.SaslPlaintext; + consumerConfig.SaslMechanism = SaslMechanism.Plain; + consumerConfig.SaslUsername = configuration["Kafka:SaslUserName"]; + consumerConfig.SaslPassword = configuration["Kafka:SaslPassword"]; + } + Instance = new ConsumerBuilder(consumerConfig).Build(); + return Instance; } - public ConsumeResult Consume(CancellationToken cancellationToken) + public async Task SubscribeAsync(string topic, Func messageHandler) { - return _consumer.Consume(cancellationToken); + _cancellationTokenSource = new CancellationTokenSource(); + Instance.Subscribe(topic); + + try + { + while (!_cancellationTokenSource.Token.IsCancellationRequested) + { + var result = Instance.Consume(_cancellationTokenSource.Token); + if (result != null) + { + await messageHandler(result.Message.Key, result.Message.Value); + } + } + } + catch (OperationCanceledException) + { + Instance.Close(); + } } - public void Commit(ConsumeResult result) + public void Unsubscribe() { - _consumer.Commit(result); - _logger.LogDebug($"Committed offset: {result.TopicPartitionOffset}"); - } - - private void OnConsumeError(IConsumer consumer, Error error) - { - _logger.LogError($"Kafka consumer error: {error.Reason}"); + _cancellationTokenSource?.Cancel(); + Instance?.Unsubscribe(); } public void Dispose() { - _consumer?.Close(); - _consumer?.Dispose(); + Unsubscribe(); + Instance?.Dispose(); + _cancellationTokenSource?.Dispose(); } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs index 1319957..bb88038 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs @@ -6,7 +6,10 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.Kafka.Consumer { - public interface IConsumerService + public interface IConsumerService { + Task SubscribeAsync(string topic, Func messageHandler); + void Unsubscribe(); + void Dispose(); } } diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaOptions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaOptions.cs new file mode 100644 index 0000000..d946cc8 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaOptions.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka +{ + public class KafkaOptions + { + public string BootstrapServers { get; set; } + public string GroupId { get; set; } + public Dictionary ProducerConfig { get; set; } = new(); + public Dictionary ConsumerConfig { get; set; } = new(); + public Dictionary AdminConfig { get; set; } = new(); + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs index 2c46bf4..2ceaed5 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs @@ -7,8 +7,10 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.Kafka.Producer { - public interface IProducerService + public interface IProducerService { - Task ProduceAsync(string topic, string message); + Task ProduceAsync(string topic, TKey key, TValue value); + Task ProduceAsync(string topic, TValue value); + void Dispose(); } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerBaseService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerBaseService.cs deleted file mode 100644 index cbe56a2..0000000 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerBaseService.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Confluent.Kafka; -using Microsoft.Extensions.Configuration; -using Volo.Abp.DependencyInjection; - -namespace JiShe.CollectBus.Kafka.Producer -{ - public class ProducerBaseService - { - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - public ProducerBaseService(IConfiguration configuration) - { - GetInstance(configuration); - } - - /// - /// Gets or sets the instance. - /// - /// - /// The instance. - /// - public IProducer Instance { get; set; } = default; - - /// - /// Gets the instance. - /// - /// The configuration. - /// - public IProducer GetInstance(IConfiguration configuration) - { - var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]); - var producerConfig = new ProducerConfig - { - BootstrapServers = configuration["Kafka:BootstrapServers"], - AllowAutoCreateTopics = true, - }; - - if (enableAuthorization) - { - producerConfig.SecurityProtocol = SecurityProtocol.SaslPlaintext; - producerConfig.SaslMechanism = SaslMechanism.Plain; - producerConfig.SaslUsername = configuration["Kafka:SaslUserName"]; - producerConfig.SaslPassword = configuration["Kafka:SaslPassword"]; - } - Instance = new ProducerBuilder(producerConfig).Build(); - return Instance; - } - - /// - /// Produces the asynchronous. - /// - /// The topic. - /// The message. - /// The cancellation token. - public async Task ProduceAsync(string topic, Message message, CancellationToken cancellationToken = default) - { - await Instance.ProduceAsync(topic, message, cancellationToken); - } - - - } -} diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs index 95c9a15..a927716 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs @@ -4,28 +4,70 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Confluent.Kafka; +using JiShe.CollectBus.Kafka.Consumer; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using Volo.Abp.DependencyInjection; namespace JiShe.CollectBus.Kafka.Producer { - public class ProducerService: IProducerService,ITransientDependency + public class ProducerService : IProducerService, IDisposable,ITransientDependency { - private readonly IProducer _producer; + private readonly ILogger> _logger; - public ProducerService(IProducer producer) + protected ProducerService(IConfiguration configuration, ILogger> logger) { - _producer = producer; + _logger = logger; + GetInstance(configuration); } - public async Task ProduceAsync(string topic, string message) + + public IProducer Instance { get; set; } = default; + + public IProducer GetInstance(IConfiguration configuration) { - await _producer.ProduceAsync(topic, new Message + var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]); + var consumerConfig = new ProducerConfig { - Key = null, - Value = message - }); + BootstrapServers = configuration["Kafka:BootstrapServers"], + AllowAutoCreateTopics = true + }; + + if (enableAuthorization) + { + consumerConfig.SecurityProtocol = SecurityProtocol.SaslPlaintext; + consumerConfig.SaslMechanism = SaslMechanism.Plain; + consumerConfig.SaslUsername = configuration["Kafka:SaslUserName"]; + consumerConfig.SaslPassword = configuration["Kafka:SaslPassword"]; + } + Instance = new ProducerBuilder(consumerConfig).Build(); + return Instance; + } + public async Task ProduceAsync(string topic, TKey key, TValue value) + { + var message = new Message + { + Key = key, + Value = value + }; + + await Instance.ProduceAsync(topic, message); + } + + public async Task ProduceAsync(string topic, TValue value) + { + var message = new Message + { + Value = value + }; + + await Instance.ProduceAsync(topic, message); + } + + public void Dispose() + { + Instance?.Dispose(); } } } -- 2.47.2 From 608337948a0f7b4600934bfe1532d27e5b4ec41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E7=9B=8A?= Date: Sun, 13 Apr 2025 21:26:27 +0800 Subject: [PATCH 074/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 83 ++++++++++--------- .../Helpers/CommonHelper.cs | 12 +++ .../IotSystems/Devices/Device.cs | 5 ++ .../MeterReadingRecords.cs | 43 ++++++---- 4 files changed, 86 insertions(+), 57 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 7311c42..3782d7a 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -132,8 +132,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建完成"); - //删除已经处理过的缓存数据 - await FreeRedisProvider.Instance.DelAsync(item); + //根据当前的采集频率和类型,重新更新下一个任务点,把任务的创建源固定在当前逻辑,避免任务处理的逻辑异常导致任务创建失败。 + tasksToBeIssueModel.NextTask = tasksToBeIssueModel.NextTask.AddMinutes(timeDensity); + await FreeRedisProvider.Instance.SetAsync(item, tasksToBeIssueModel); } } @@ -326,9 +327,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentTime); } - //删除任务数据 - await FreeRedisProvider.Instance.DelAsync(oneMinutekeyList); - await CacheNextTaskData(timeDensity, MeterTypeEnum.Ammeter); + ////删除任务数据 + //await FreeRedisProvider.Instance.DelAsync(oneMinutekeyList); + //await CacheNextTaskData(timeDensity, MeterTypeEnum.Ammeter); _logger.LogInformation($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理完成"); @@ -387,11 +388,11 @@ namespace JiShe.CollectBus.ScheduledMeterReading await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentTime); } - //删除任务数据 - await FreeRedisProvider.Instance.DelAsync(fiveMinutekeyList); + ////删除任务数据 + //await FreeRedisProvider.Instance.DelAsync(fiveMinutekeyList); - //缓存下一个时间的任务 - await CacheNextTaskData(timeDensity, MeterTypeEnum.Ammeter); + ////缓存下一个时间的任务 + //await CacheNextTaskData(timeDensity, MeterTypeEnum.Ammeter); _logger.LogInformation($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集电表数据处理完成"); } @@ -452,11 +453,11 @@ namespace JiShe.CollectBus.ScheduledMeterReading await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentDateTime); } - //删除任务数据 + ////删除任务数据 //await FreeRedisProvider.Instance.DelAsync(fifteenMinutekeyList); - //缓存下一个时间的任务 - await CacheNextTaskData(timeDensity, MeterTypeEnum.Ammeter); + ////缓存下一个时间的任务 + //await CacheNextTaskData(timeDensity, MeterTypeEnum.Ammeter); stopwatch.Stop(); @@ -838,11 +839,11 @@ namespace JiShe.CollectBus.ScheduledMeterReading await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } - //删除任务数据 - await FreeRedisProvider.Instance.DelAsync(oneMinutekeyList); + ////删除任务数据 + //await FreeRedisProvider.Instance.DelAsync(oneMinutekeyList); - //缓存下一个时间的任务 - await CacheNextTaskData(timeDensity, MeterTypeEnum.WaterMeter); + ////缓存下一个时间的任务 + //await CacheNextTaskData(timeDensity, MeterTypeEnum.WaterMeter); _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理完成"); @@ -900,11 +901,11 @@ namespace JiShe.CollectBus.ScheduledMeterReading await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } - //删除任务数据 - await FreeRedisProvider.Instance.DelAsync(fiveMinutekeyList); + ////删除任务数据 + //await FreeRedisProvider.Instance.DelAsync(fiveMinutekeyList); - //缓存下一个时间的任务 - await CacheNextTaskData(timeDensity, MeterTypeEnum.WaterMeter); + ////缓存下一个时间的任务 + //await CacheNextTaskData(timeDensity, MeterTypeEnum.WaterMeter); _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集水表数据处理完成"); @@ -961,11 +962,11 @@ namespace JiShe.CollectBus.ScheduledMeterReading await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } - //删除任务数据 - await FreeRedisProvider.Instance.DelAsync(fifteenMinutekeyList); + ////删除任务数据 + //await FreeRedisProvider.Instance.DelAsync(fifteenMinutekeyList); - //缓存下一个时间的任务 - await CacheNextTaskData(timeDensity, MeterTypeEnum.WaterMeter); + ////缓存下一个时间的任务 + //await CacheNextTaskData(timeDensity, MeterTypeEnum.WaterMeter); _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集水表数据处理完成"); @@ -1050,24 +1051,24 @@ namespace JiShe.CollectBus.ScheduledMeterReading return true; } - /// - /// 缓存下一个时间的任务 - /// - /// 采集频率 - /// 表类型 - /// - private async Task CacheNextTaskData(int timeDensity, MeterTypeEnum meterType) - { - //缓存下一个时间的任务 - TasksToBeIssueModel nextTask = new TasksToBeIssueModel() - { - TimeDensity = timeDensity, - NextTask = DateTime.Now.AddMinutes(timeDensity) - }; + ///// + ///// 缓存下一个时间的任务 + ///// + ///// 采集频率 + ///// 表类型 + ///// + //private async Task CacheNextTaskData(int timeDensity, MeterTypeEnum meterType) + //{ + // //缓存下一个时间的任务 + // TasksToBeIssueModel nextTask = new TasksToBeIssueModel() + // { + // TimeDensity = timeDensity, + // NextTask = DateTime.Now.AddMinutes(timeDensity) + // }; - var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, meterType, timeDensity); - await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); - } + // var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, meterType, timeDensity); + // await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); + //} /// diff --git a/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs index 33a0a38..ef2ba8e 100644 --- a/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs +++ b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs @@ -760,5 +760,17 @@ namespace JiShe.CollectBus.Common.Helpers return fontValue; } + + /// + /// 获取任务标识 + /// + /// + /// + /// + /// + public static string GetTaskMark(int afn,int fn,int pn) + { + return $"{afn}{fn}{pn}"; + } } } diff --git a/src/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs b/src/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs index a18c9d8..91a5ce7 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs @@ -57,6 +57,11 @@ namespace JiShe.CollectBus.IotSystems.Devices /// public DeviceStatus Status { get; set; } + /// + /// 设备任务超时次数,超过一定次数则发出预警。 + /// + public int TaskTimeOutCounts { get; set; } = 0; + public void UpdateByLoginAndHeartbeat(string clientId) { LastOnlineTime = DateTime.Now; diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs index 153acdf..b1f1112 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs @@ -10,7 +10,7 @@ using Volo.Abp.Domain.Entities.Auditing; namespace JiShe.CollectBus.IotSystems.MeterReadingRecords { /// - /// 抄读数据记录 + /// 抄读任务数据记录 /// public class MeterReadingRecords : AggregateRoot { @@ -19,20 +19,25 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// public bool ManualOrNot { get; set; } + /// + /// 任务数据唯一标记 + /// + public string TaskMark { get; set; } + + /// + /// 时间戳标记,IoTDB时间列处理,上报通过构建标记获取唯一标记匹配时间戳。 + /// + public long Timestamps { get; set; } + + /// + /// 是否超时 + /// + public bool IsTimeout { get; set; } = false; + /// /// 待抄读时间 /// public DateTime PendingCopyReadTime { get; set; } - - /// - /// 下发消息内容 - /// - public string IssuedMessageHexString { get; set; } - - /// - /// 下发消息Id - /// - public string IssuedMessageId { get; set; } /// /// 集中器ID @@ -88,17 +93,23 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// 采集项编码 /// public string ItemCode { get; set;} - - /// - /// 是否超时 - /// - public bool IsTimeout { get; set; } = false; + /// /// 创建时间 /// public DateTime CreationTime { get; set; } + /// + /// 下发消息内容 + /// + public string IssuedMessageHexString { get; set; } + + /// + /// 下发消息Id + /// + public string IssuedMessageId { get; set; } + /// /// 消息上报内容 /// -- 2.47.2 From d25034fdddd950e9197149432a373385727d7f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E7=9B=8A?= Date: Sun, 13 Apr 2025 21:47:47 +0800 Subject: [PATCH 075/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduledMeterReading/BasicScheduledMeterReadingService.cs | 3 ++- src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 3782d7a..2e58879 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -704,6 +704,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading AFN = aFN, Fn = fn, ItemCode = tempItem, + TaskMark = CommonHelper.GetTaskMark((int)aFN, fn,ammeter.MeteringCode), ManualOrNot = false, Pn = ammeter.MeteringCode, IssuedMessageId = GuidGenerator.Create().ToString(), @@ -976,7 +977,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading #region 公共处理方法 /// - /// 批量获取缓存的表计信息 + /// Lua脚本批量获取缓存的表计信息 /// /// 表信息数据对象 /// 采集频率对应的缓存Key集合 diff --git a/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs index ef2ba8e..22ebef5 100644 --- a/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs +++ b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs @@ -770,7 +770,7 @@ namespace JiShe.CollectBus.Common.Helpers /// public static string GetTaskMark(int afn,int fn,int pn) { - return $"{afn}{fn}{pn}"; + return $"{afn.ToString().PadLeft(2,'0')}{fn}{pn}"; } } } -- 2.47.2 From 3d56d351d3298238b6f675ac25c5c301bd072bda Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 14 Apr 2025 09:29:12 +0800 Subject: [PATCH 076/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusApplicationModule.cs | 2 +- .../BasicScheduledMeterReadingService.cs | 2 ++ .../Workers/CreateToBeIssueTaskWorker.cs | 4 ++-- .../AdminClient/AdminClientService.cs | 4 ++++ .../Producer/ProducerService.cs | 4 ++-- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index e1ac44e..8544639 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -115,7 +115,7 @@ public class CollectBusApplicationModule : AbpModule foreach (var item in topics) { - kafkaAdminClient.CreateTopicIfNotExistAsync(item, configuration.GetValue(CommonConst.KafkaReplicationFactor), configuration.GetValue(CommonConst.NumPartitions)); + kafkaAdminClient.CreateTopicAsync(item, configuration.GetValue(CommonConst.NumPartitions), configuration.GetValue(CommonConst.KafkaReplicationFactor)); } } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 2e58879..6803171 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -95,6 +95,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } + //检查任务时间节点 + //item 为 CacheTasksToBeIssuedKey 对应的缓存待下发的指令生产任务数据Redis Key tempArryay[0]=>CollectBus,tempArryay[1]=>SystemTypeConst,tempArryay[2]=>TaskInfo,tempArryay[3]=>表计类别,tempArryay[4]=>采集频率 var tempArryay = item.Split(":"); string meteryType = tempArryay[3];//表计类别 diff --git a/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs b/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs index b936bbb..05fd90d 100644 --- a/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs @@ -27,14 +27,14 @@ namespace JiShe.CollectBus.Workers { _logger = logger; RecurringJobId = nameof(CreateToBeIssueTaskWorker); - CronExpression = $"*/{1} * * * *"; ; + CronExpression = $"{10}/* * * * *"; this._scheduledMeterReadingService = scheduledMeterReadingService; } public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { - //await _scheduledMeterReadingService.CreateToBeIssueTasks(); + await _scheduledMeterReadingService.CreateToBeIssueTasks(); } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs index e687276..217e35c 100644 --- a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs @@ -71,8 +71,12 @@ namespace JiShe.CollectBus.Kafka.AdminClient public async Task CreateTopicAsync(string topic, int numPartitions, short replicationFactor) { + try { + if (await CheckTopicAsync(topic)) return; + + await Instance.CreateTopicsAsync(new[] { new TopicSpecification diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs index a927716..0cfed2e 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs @@ -14,9 +14,9 @@ namespace JiShe.CollectBus.Kafka.Producer public class ProducerService : IProducerService, IDisposable,ITransientDependency { - private readonly ILogger> _logger; + private readonly ILogger> _logger; - protected ProducerService(IConfiguration configuration, ILogger> logger) + protected ProducerService(IConfiguration configuration, ILogger> logger) { _logger = logger; GetInstance(configuration); -- 2.47.2 From 4c948592864e42609a350a7c29cefdc1e3c749d2 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 14 Apr 2025 10:20:48 +0800 Subject: [PATCH 077/139] =?UTF-8?q?=E4=BC=98=E5=8C=96json=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=EF=BC=8C=E7=A7=BB=E9=99=A4EventBus=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusApplicationModule.cs | 85 ++---- .../CustomKafkaDistributedEventBus.cs | 251 ------------------ .../JiShe.CollectBus.Application.csproj | 1 - .../BasicScheduledMeterReadingService.cs | 21 +- .../Subscribers/SubscriberAppService.cs | 12 +- .../BuildSendDatas/TasksToBeIssueModel.cs | 2 +- .../BusJsonSerializer.cs} | 76 ++++-- .../FreeRedisProvider.cs | 8 +- 8 files changed, 106 insertions(+), 350 deletions(-) delete mode 100644 src/JiShe.CollectBus.Application/CustomKafkaDistributedEventBus.cs rename src/{JiShe.CollectBus.Common/Helpers/JsonHelper.cs => JiShe.CollectBus.FreeRedisProvider/BusJsonSerializer.cs} (60%) diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 8544639..1a8b326 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -1,36 +1,24 @@ -using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.AutoMapper; -using Volo.Abp.Modularity; -using Volo.Abp.Application; -using Volo.Abp.BackgroundWorkers; -using System.Threading.Tasks; -using Volo.Abp; -using System.Reflection; -using JiShe.CollectBus.FreeSql; -using System; +using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Extensions; -using Volo.Abp.AspNetCore.Mvc.AntiForgery; using JiShe.CollectBus.FreeRedisProvider; -using JiShe.CollectBus.Workers; -using Volo.Abp.BackgroundWorkers.Hangfire; -using JiShe.CollectBus.MongoDB; -using JiShe.CollectBus.ScheduledMeterReading; -using AutoMapper.Configuration.Annotations; -using JiShe.CollectBus.Common.Attributes; +using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.IoTDBProvider; -using Confluent.Kafka.Admin; -using Microsoft.Extensions.Options; -using JiShe.CollectBus.Protocol.Contracts; -using System.Collections.Generic; -using Thrift; -using Microsoft.Extensions.Configuration; -using Volo.Abp.EventBus.Kafka; -using Volo.Abp.Kafka; -using Volo.Abp.EventBus; -using Confluent.Kafka; +using JiShe.CollectBus.Kafka; using JiShe.CollectBus.Kafka.AdminClient; -using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.ScheduledMeterReading; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Volo.Abp; +using Volo.Abp.Application; +using Volo.Abp.AutoMapper; +using Volo.Abp.BackgroundWorkers; +using Volo.Abp.BackgroundWorkers.Hangfire; +using Volo.Abp.EventBus; +using Volo.Abp.Modularity; namespace JiShe.CollectBus; @@ -42,8 +30,7 @@ namespace JiShe.CollectBus; typeof(AbpBackgroundWorkersHangfireModule), typeof(CollectBusFreeRedisModule), typeof(CollectBusFreeSqlModule), - typeof(AbpEventBusModule), - typeof(AbpKafkaModule), + typeof(CollectBusKafkaModule), typeof(CollectBusIoTDBModule) )] public class CollectBusApplicationModule : AbpModule @@ -56,44 +43,12 @@ public class CollectBusApplicationModule : AbpModule Configure(options => { options.AddMaps(validate: true); - }); - - Configure(configuration.GetSection("Kafka")); - Configure(configuration.GetSection("Kafka:EventBus")); - - Configure(options => - { - options.ConfigureConsumer = config => - { - config.GroupId = configuration.GetValue("Kafka:Consumer:GroupId"); - config.EnableAutoCommit = configuration.GetValue("Kafka:Consumer:EnableAutoCommit"); - }; - }); - - Configure(options => - { - options.ConfigureProducer = config => - { - config.MessageTimeoutMs = configuration.GetValue("Kafka:Producer:MessageTimeoutMs"); - config.Acks = (Acks)configuration.GetValue("Kafka:Producer:Acks"); - }; - }); - - Configure(options => - { - options.ConfigureTopic = specification => - { - specification.ReplicationFactor = configuration.GetValue("Kafka:Topic:ReplicationFactor"); - specification.NumPartitions = configuration.GetValue("Kafka:Topic:NumPartitions"); - }; - }); + }); } public override void OnApplicationInitialization( ApplicationInitializationContext context) - { - context.ServiceProvider.GetRequiredService().Initialize(); - + { var assembly = Assembly.GetExecutingAssembly(); var types = assembly.GetTypes().Where(t => typeof(ICollectWorker).IsAssignableFrom(t) && !t.IsInterface).ToList(); foreach (var type in types) diff --git a/src/JiShe.CollectBus.Application/CustomKafkaDistributedEventBus.cs b/src/JiShe.CollectBus.Application/CustomKafkaDistributedEventBus.cs deleted file mode 100644 index a439d13..0000000 --- a/src/JiShe.CollectBus.Application/CustomKafkaDistributedEventBus.cs +++ /dev/null @@ -1,251 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Confluent.Kafka; -using JiShe.CollectBus.Common.Attributes; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Volo.Abp; -using Volo.Abp.DependencyInjection; -using Volo.Abp.EventBus; -using Volo.Abp.EventBus.Distributed; -using Volo.Abp.EventBus.Kafka; -using Volo.Abp.EventBus.Local; -using Volo.Abp.Guids; -using Volo.Abp.Kafka; -using Volo.Abp.MultiTenancy; -using Volo.Abp.Timing; -using Volo.Abp.Tracing; -using Volo.Abp.Uow; -using Volo.Abp.Threading; - - -namespace JiShe.CollectBus; - -[Dependency(ReplaceServices = true)] -[ExposeServices(typeof(IDistributedEventBus), typeof(CustomKafkaDistributedEventBus))] -public class CustomKafkaDistributedEventBus : DistributedEventBusBase, ISingletonDependency -{ - - protected AbpKafkaEventBusOptions AbpKafkaEventBusOptions { get; } - protected IKafkaMessageConsumerFactory MessageConsumerFactory { get; } - protected IKafkaSerializer Serializer { get; } - protected IProducerPool ProducerPool { get; } - protected ConcurrentDictionary> HandlerFactories { get; } - protected ConcurrentDictionary EventTypes { get; } - protected IKafkaMessageConsumer Consumer { get; private set; } = default!; - - - public CustomKafkaDistributedEventBus(IServiceScopeFactory serviceScopeFactory, - ICurrentTenant currentTenant, - IUnitOfWorkManager unitOfWorkManager, - IOptions abpDistributedEventBusOptions, - IGuidGenerator guidGenerator, - IClock clock, - IEventHandlerInvoker eventHandlerInvoker, - ILocalEventBus localEventBus, - ICorrelationIdProvider correlationIdProvider, - IOptions abpKafkaEventBusOptions, - IKafkaMessageConsumerFactory messageConsumerFactory, - IKafkaSerializer serializer, - IProducerPool producerPool) - : base(serviceScopeFactory, currentTenant, unitOfWorkManager, abpDistributedEventBusOptions, guidGenerator, clock, eventHandlerInvoker, localEventBus, correlationIdProvider) - { - AbpKafkaEventBusOptions = abpKafkaEventBusOptions.Value; - MessageConsumerFactory = messageConsumerFactory; - Serializer = serializer; - ProducerPool = producerPool; - HandlerFactories = new ConcurrentDictionary>(); - EventTypes = new ConcurrentDictionary(); - } - - - public void Initialize() - { - - SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); - } - - public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) - { - var handlerFactories = GetOrCreateHandlerFactories(eventType); - - if (factory.IsInFactories(handlerFactories)) - { - return NullDisposable.Instance; - } - - handlerFactories.Add(factory); - - return new EventHandlerFactoryUnregistrar(this, eventType, factory); - } - - public override void Unsubscribe(Func action) - { - Check.NotNull(action, nameof(action)); - - GetOrCreateHandlerFactories(typeof(TEvent)) - .Locking(factories => - { - factories.RemoveAll( - factory => - { - var singleInstanceFactory = factory as SingleInstanceHandlerFactory; - if (singleInstanceFactory == null) - { - return false; - } - - var actionHandler = singleInstanceFactory.HandlerInstance as ActionEventHandler; - if (actionHandler == null) - { - return false; - } - - return actionHandler.Action == action; - }); - }); - } - - public override void Unsubscribe(Type eventType, IEventHandler handler) - { - GetOrCreateHandlerFactories(eventType) - .Locking(factories => - { - factories.RemoveAll( - factory => - factory is SingleInstanceHandlerFactory handlerFactory && - handlerFactory.HandlerInstance == handler - ); - }); - } - - public override void Unsubscribe(Type eventType, IEventHandlerFactory factory) - { - GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Remove(factory)); - } - - public override void UnsubscribeAll(Type eventType) - { - GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); - } - - protected override async Task PublishToEventBusAsync(Type eventType, object eventData) - { - var headers = new Headers - { - { "messageId", System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")) } - }; - - if (CorrelationIdProvider.Get() != null) - { - headers.Add(EventBusConsts.CorrelationIdHeaderName, System.Text.Encoding.UTF8.GetBytes(CorrelationIdProvider.Get()!)); - } - - var topicAttribute = eventType.GetCustomAttribute(); - - await PublishAsync( - topicAttribute==null?AbpKafkaEventBusOptions.TopicName:topicAttribute.Name, - eventType, - eventData, - headers - ); - } - - protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) - { - unitOfWork.AddOrReplaceDistributedEvent(eventRecord); - } - - protected override IEnumerable GetHandlerFactories(Type eventType) - { - var handlerFactoryList = new List(); - - foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))) - { - handlerFactoryList.Add( - new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value)); - } - - return handlerFactoryList.ToArray(); - } - - public override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) - { - throw new NotImplementedException(); - } - - public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) - { - throw new NotImplementedException(); - } - - public override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) - { - throw new NotImplementedException(); - } - - protected override byte[] Serialize(object eventData) - { - return Serializer.Serialize(eventData); - } - - private List GetOrCreateHandlerFactories(Type eventType) - { - return HandlerFactories.GetOrAdd( - eventType, - type => - { - var eventName = EventNameAttribute.GetNameOrDefault(type); - EventTypes.GetOrAdd(eventName, eventType); - return new List(); - } - ); - } - - private Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers) - { - var eventName = EventNameAttribute.GetNameOrDefault(eventType); - var body = Serializer.Serialize(eventData); - - return PublishAsync(topicName, eventName, body, headers); - } - - private Task> PublishAsync( - string topicName, - string eventName, - byte[] body, - Headers headers) - { - var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); - - return producer.ProduceAsync( - topicName, - new Message - { - Key = eventName, - Value = body, - Headers = headers - }); - } - - private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType) - { - //Should trigger same type - if (handlerEventType == targetEventType) - { - return true; - } - - //Should trigger for inherited types - if (handlerEventType.IsAssignableFrom(targetEventType)) - { - return true; - } - - return false; - } -} diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index 5765ecd..1726f39 100644 --- a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -24,7 +24,6 @@ - diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 6803171..4373dd3 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -12,6 +12,7 @@ using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Repository.MeterReadingRecord; +using JiShe.CollectBus.Serializer; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -95,7 +96,12 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - //检查任务时间节点 + //检查任务时间节点,由于定时任务10秒钟运行一次,需要判定当前时间是否在任务时间节点内,不在则跳过 + if (!IsGennerateCmd(tasksToBeIssueModel.NextTaskTime)) + { + _logger.LogWarning($"{nameof(CreateToBeIssueTasks)} 构建待处理的下发指令任务处理时Key=>{item}时间节点不在当前时间范围内,103"); + continue; + } //item 为 CacheTasksToBeIssuedKey 对应的缓存待下发的指令生产任务数据Redis Key tempArryay[0]=>CollectBus,tempArryay[1]=>SystemTypeConst,tempArryay[2]=>TaskInfo,tempArryay[3]=>表计类别,tempArryay[4]=>采集频率 var tempArryay = item.Split(":"); @@ -107,7 +113,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { - _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-103"); + _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-104"); return; } @@ -117,7 +123,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading var meterInfos = await GetMeterRedisCacheData(oneMinutekeyList, $"{timeDensity}", meteryType); if (meterInfos == null || meterInfos.Count <= 0) { - _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-104"); + _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-105"); return; } await AmmerterScheduledMeterReadingIssued(timeDensity, meterInfos); @@ -129,13 +135,13 @@ namespace JiShe.CollectBus.ScheduledMeterReading } else { - _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-105"); + _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-106"); } _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建完成"); //根据当前的采集频率和类型,重新更新下一个任务点,把任务的创建源固定在当前逻辑,避免任务处理的逻辑异常导致任务创建失败。 - tasksToBeIssueModel.NextTask = tasksToBeIssueModel.NextTask.AddMinutes(timeDensity); + tasksToBeIssueModel.NextTaskTime = tasksToBeIssueModel.NextTaskTime.AddMinutes(timeDensity); await FreeRedisProvider.Instance.SetAsync(item, tasksToBeIssueModel); } } @@ -253,7 +259,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading TasksToBeIssueModel nextTask = new TasksToBeIssueModel() { TimeDensity = itemTimeDensity.Key, - NextTask = DateTime.Now.AddMinutes(1) + NextTaskTime = DateTime.Now.AddMinutes(1) }; var taskRedisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key); @@ -546,6 +552,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading , Dictionary> focusGroup) { var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; + //todo 检查需要待补抄的电表的时间点信息,保存到需要待补抄的缓存中。如果此线程异常,该如何补偿? var currentTime = DateTime.Now; var pendingCopyReadTime = currentTime.AddMinutes(timeDensity); @@ -782,7 +789,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading TasksToBeIssueModel nextTask = new TasksToBeIssueModel() { TimeDensity = itemTimeDensity.Key, - NextTask = DateTime.Now.AddMinutes(1) + NextTaskTime = DateTime.Now.AddMinutes(1) }; var taskRedisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, MeterTypeEnum.WaterMeter, itemTimeDensity.Key); diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index c22e4dc..63559de 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -1,11 +1,5 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using DeviceDetectorNET.Parser.Device; -using DotNetCore.CAP; +using DotNetCore.CAP; using JiShe.CollectBus.Common.Enums; -using JiShe.CollectBus.Common.Extensions; -using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.Devices; @@ -15,8 +9,12 @@ using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using JiShe.CollectBus.Protocol.Contracts.Models; using JiShe.CollectBus.Repository.MeterReadingRecord; +using JiShe.CollectBus.Serializer; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using System; +using System.Linq; +using System.Threading.Tasks; using TouchSocket.Sockets; using Volo.Abp.Domain.Repositories; diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs b/src/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs index 67d9abe..5184459 100644 --- a/src/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs +++ b/src/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs @@ -14,7 +14,7 @@ namespace JiShe.CollectBus.Common.BuildSendDatas /// /// 下个任务时间 /// - public DateTime NextTask { get; set; } + public DateTime NextTaskTime { get; set; } /// /// 采集时间间隔,1分钟,5分钟,15分钟 diff --git a/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs b/src/JiShe.CollectBus.FreeRedisProvider/BusJsonSerializer.cs similarity index 60% rename from src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs rename to src/JiShe.CollectBus.FreeRedisProvider/BusJsonSerializer.cs index cbdef1e..db1227b 100644 --- a/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/BusJsonSerializer.cs @@ -7,12 +7,12 @@ using System.Text.Json.Serialization; using System.Text.Json; using System.Threading.Tasks; -namespace JiShe.CollectBus.Common.Helpers +namespace JiShe.CollectBus.Serializer { /// /// json帮助类 /// - public static class JsonHelper + public static class BusJsonSerializer { /// /// json对象转换成字符串 @@ -28,18 +28,19 @@ namespace JiShe.CollectBus.Common.Helpers jsonSerializerOptions = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.Never, - WriteIndented = false, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + WriteIndented = false,// 设置格式化输出 + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,// 允许特殊字符 IgnoreReadOnlyFields = true, IgnoreReadOnlyProperties = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString, // 允许数字字符串 + AllowTrailingCommas = true, // 忽略尾随逗号 + ReadCommentHandling = JsonCommentHandling.Skip, // 忽略注释 + PropertyNameCaseInsensitive = true, // 属性名称大小写不敏感 + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // 属性名称使用驼峰命名规则 + Converters = { new DateTimeJsonConverter() } // 注册你的自定义转换器, }; } - if (jsonSerializerOptions.Converters != null) - { - jsonSerializerOptions.Converters.Add(new DateTimeJsonConverter()); - } - if (IsIgnore == true) { jsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; // 忽略循环引用 @@ -67,18 +68,19 @@ namespace JiShe.CollectBus.Common.Helpers jsonSerializerOptions = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.Never, - WriteIndented = false, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + WriteIndented = false,// 设置格式化输出 + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,// 允许特殊字符 IgnoreReadOnlyFields = true, IgnoreReadOnlyProperties = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString, // 允许数字字符串 + AllowTrailingCommas = true, // 忽略尾随逗号 + ReadCommentHandling = JsonCommentHandling.Skip, // 忽略注释 + PropertyNameCaseInsensitive = true, // 属性名称大小写不敏感 + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // 属性名称使用驼峰命名规则 + Converters = { new DateTimeJsonConverter() } // 注册你的自定义转换器, }; } - if (jsonSerializerOptions.Converters != null) - { - jsonSerializerOptions.Converters.Add(new DateTimeJsonConverter()); - } - if (IsIgnore == true) { jsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; // 忽略循环引用 @@ -91,6 +93,48 @@ namespace JiShe.CollectBus.Common.Helpers } } + /// + /// json字符串转换成json对象 + /// + /// + /// + /// 是否忽略实体中实体,不再序列化里面包含的实体 + /// 配置 + /// + public static object? Deserialize(this string json, Type type, bool IsIgnore = false, JsonSerializerOptions jsonSerializerOptions = null) + { + if (jsonSerializerOptions == null) + { + jsonSerializerOptions = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + WriteIndented = false,// 设置格式化输出 + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,// 允许特殊字符 + IgnoreReadOnlyFields = true, + IgnoreReadOnlyProperties = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString, // 允许数字字符串 + AllowTrailingCommas = true, // 忽略尾随逗号 + ReadCommentHandling = JsonCommentHandling.Skip, // 忽略注释 + PropertyNameCaseInsensitive = true, // 属性名称大小写不敏感 + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // 属性名称使用驼峰命名规则 + Converters = { new DateTimeJsonConverter() } // 注册你的自定义转换器, + }; + } + + + + if (IsIgnore == true) + { + jsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; // 忽略循环引用 + + return json == null ? null : JsonSerializer.Deserialize(json, type, jsonSerializerOptions); + } + else + { + return json == null ? null : JsonSerializer.Deserialize(json, type, jsonSerializerOptions); + } + } + /// /// list json字符串转换成list /// diff --git a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs index b7f6a03..1a3b114 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs @@ -1,13 +1,16 @@ using FreeRedis; using JetBrains.Annotations; using JiShe.CollectBus.FreeRedisProvider.Options; +using JiShe.CollectBus.Serializer; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; +using System.Text.Encodings.Web; using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; @@ -36,10 +39,11 @@ namespace JiShe.CollectBus.FreeRedisProvider /// public IRedisClient GetInstance() { + var connectionString = $"{_option.Configuration},defaultdatabase={_option.DefaultDB}"; Instance = new RedisClient(connectionString); - Instance.Serialize = obj => JsonSerializer.Serialize(obj); - Instance.Deserialize = (json, type) => JsonSerializer.Deserialize(json, type); + Instance.Serialize = obj => BusJsonSerializer.Serialize(obj); + Instance.Deserialize = (json, type) => BusJsonSerializer.Deserialize(json, type); Instance.Notice += (s, e) => Trace.WriteLine(e.Log); return Instance; } -- 2.47.2 From 1b1c4e568318f082f5e63bb4a1924d911f3c94ed Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 14 Apr 2025 10:29:08 +0800 Subject: [PATCH 078/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JiShe.CollectBus.Host/appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 9c898b4..a5b2e15 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -130,5 +130,5 @@ }, "ServerTagName": "JiSheCollectBus", "KafkaReplicationFactor": 3, - "NumPartitions": 60 + "NumPartitions": 30 } \ No newline at end of file -- 2.47.2 From 83b7de52d518b171ef107157557d868bb482415b Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Mon, 14 Apr 2025 15:31:10 +0800 Subject: [PATCH 079/139] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=BF=81=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 7 ++ .../BaseCassandraRepository.cs | 87 +++++++++++++++++++ .../CassandraConfiguration.cs | 45 ++++++++++ .../CassandraService.cs | 40 +++++++++ .../CollectBusCassandraModule.cs | 13 +++ .../ICassandraService.cs | 9 ++ .../JiShe.CollectBus.Cassandra.csproj | 19 ++++ 7 files changed, 220 insertions(+) create mode 100644 src/JiShe.CollectBus.Cassandra/BaseCassandraRepository.cs create mode 100644 src/JiShe.CollectBus.Cassandra/CassandraConfiguration.cs create mode 100644 src/JiShe.CollectBus.Cassandra/CassandraService.cs create mode 100644 src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs create mode 100644 src/JiShe.CollectBus.Cassandra/ICassandraService.cs create mode 100644 src/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index c26f3da..9c67aae 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -37,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.IoTDBProvi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.Test", "src\JiShe.CollectBus.Protocol.Test\JiShe.CollectBus.Protocol.Test.csproj", "{A377955E-7EA1-6F29-8CF7-774569E93925}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Cassandra", "src\JiShe.CollectBus.Cassandra\JiShe.CollectBus.Cassandra.csproj", "{443B4549-0AC0-4493-8F3E-49C83225DD76}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -107,6 +109,10 @@ Global {A377955E-7EA1-6F29-8CF7-774569E93925}.Debug|Any CPU.Build.0 = Debug|Any CPU {A377955E-7EA1-6F29-8CF7-774569E93925}.Release|Any CPU.ActiveCfg = Release|Any CPU {A377955E-7EA1-6F29-8CF7-774569E93925}.Release|Any CPU.Build.0 = Release|Any CPU + {443B4549-0AC0-4493-8F3E-49C83225DD76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {443B4549-0AC0-4493-8F3E-49C83225DD76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -128,6 +134,7 @@ Global {F0288175-F0EC-48BD-945F-CF1512850943} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {A3F3C092-0A25-450B-BF6A-5983163CBEF5} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {A377955E-7EA1-6F29-8CF7-774569E93925} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {443B4549-0AC0-4493-8F3E-49C83225DD76} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/src/JiShe.CollectBus.Cassandra/BaseCassandraRepository.cs b/src/JiShe.CollectBus.Cassandra/BaseCassandraRepository.cs new file mode 100644 index 0000000..67252e4 --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/BaseCassandraRepository.cs @@ -0,0 +1,87 @@ +using Cassandra.Mapping; +using Cassandra; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Cassandra +{ + /// + /// Cassandra数据库的基础仓储类 + /// 提供了通用的CRUD操作方法,所有具体的仓储类都应该继承此类 + /// + /// 实体类型 + public abstract class BaseCassandraRepository where T : class + { + /// + /// Cassandra数据库会话 + /// 用于执行CQL查询和操作 + /// + protected readonly ISession Session; + + /// + /// Cassandra映射器 + /// 用于对象关系映射(ORM),简化实体与数据库表之间的转换 + /// + protected readonly IMapper Mapper; + + /// + /// 构造函数 + /// 初始化数据库会话和映射器 + /// + /// Cassandra连接工厂 + protected BaseCassandraRepository(ICassandraService cassandraService) + { + Session = cassandraService.GetSession(); + Mapper = new Mapper(Session); + } + + /// + /// 根据ID获取单个实体 + /// + /// 实体ID + /// 返回找到的实体,如果未找到则返回null + public async Task GetByIdAsync(string id) + { + return await Mapper.SingleOrDefaultAsync($"WHERE id = ?", id); + } + + /// + /// 获取所有实体 + /// + /// 返回实体集合 + public async Task> GetAllAsync() + { + return await Mapper.FetchAsync(); + } + + /// + /// 插入新实体 + /// + /// 要插入的实体 + public async Task InsertAsync(T entity) + { + await Mapper.InsertAsync(entity); + } + + /// + /// 更新现有实体 + /// + /// 要更新的实体 + public async Task UpdateAsync(T entity) + { + await Mapper.UpdateAsync(entity); + } + + /// + /// 根据ID删除实体 + /// + /// 要删除的实体ID + public async Task DeleteAsync(string id) + { + await Mapper.DeleteAsync($"WHERE id = ?", id); + } + } +} diff --git a/src/JiShe.CollectBus.Cassandra/CassandraConfiguration.cs b/src/JiShe.CollectBus.Cassandra/CassandraConfiguration.cs new file mode 100644 index 0000000..8fea3c1 --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/CassandraConfiguration.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Cassandra +{ + /// + /// Cassandra数据库配置类 + /// 用于存储和管理Cassandra数据库的连接配置信息 + /// + public class CassandraConfiguration + { + /// + /// Cassandra集群的节点地址列表 + /// 可以配置多个节点地址,用于实现高可用和负载均衡 + /// + public string[] ContactPoints { get; set; } + + /// + /// Cassandra的键空间名称 + /// 键空间是Cassandra中数据组织的最高级别容器 + /// + public string Keyspace { get; set; } + + /// + /// Cassandra数据库的用户名 + /// 用于身份验证 + /// + public string Username { get; set; } + + /// + /// Cassandra数据库的密码 + /// 用于身份验证 + /// + public string Password { get; set; } + + /// + /// Cassandra数据库的端口号 + /// 默认值为9042,这是Cassandra的默认CQL端口 + /// + public int Port { get; set; } = 9042; + } +} diff --git a/src/JiShe.CollectBus.Cassandra/CassandraService.cs b/src/JiShe.CollectBus.Cassandra/CassandraService.cs new file mode 100644 index 0000000..8836235 --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/CassandraService.cs @@ -0,0 +1,40 @@ +using System.Diagnostics.Metrics; +using Cassandra; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace JiShe.CollectBus.Cassandra +{ + public class CassandraService : ICassandraService, ISingletonDependency + { + private readonly CassandraConfiguration _configuration; + public ISession Instance { get; set; } = default; + + /// + /// CassandraService + /// + /// + public CassandraService(IOptions configuration) + { + _configuration = configuration.Value; + GetSession(); + } + + /// + /// 获取Cassandra数据库会话 + /// + /// + public ISession GetSession() + { + var cluster = Cluster.Builder() + .AddContactPoints(_configuration.ContactPoints) + .WithPort(_configuration.Port) + .WithCredentials(_configuration.Username, _configuration.Password) + .Build(); + + Instance = cluster.Connect(_configuration.Keyspace); + + return Instance; + } + } +} diff --git a/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs b/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs new file mode 100644 index 0000000..e7c2c69 --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs @@ -0,0 +1,13 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace JiShe.CollectBus.Cassandra +{ + public class CollectBusCassandraModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.Configure(context.Services.GetConfiguration().GetSection("Cassandra")); + } + } +} diff --git a/src/JiShe.CollectBus.Cassandra/ICassandraService.cs b/src/JiShe.CollectBus.Cassandra/ICassandraService.cs new file mode 100644 index 0000000..70eb5d6 --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/ICassandraService.cs @@ -0,0 +1,9 @@ +using Cassandra; + +namespace JiShe.CollectBus.Cassandra +{ + public interface ICassandraService + { + ISession GetSession(); + } +} diff --git a/src/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj b/src/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj new file mode 100644 index 0000000..5a8b22d --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + -- 2.47.2 From 2fdf5850c8304e30b6f12cce8521883dbcde5bc6 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 14 Apr 2025 16:41:41 +0800 Subject: [PATCH 080/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E7=94=B5=E8=A1=A8=E7=9A=84=E8=8E=B7=E5=8F=96=EF=BC=8C=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E5=88=86=E7=BB=84=E5=9D=87=E8=A1=A1=E7=9A=84=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IScheduledMeterReadingService.cs | 17 +- .../IWorkerSubscriberAppService.cs | 15 +- .../CollectBusAppService.cs | 142 ++++++++- .../CollectBusApplicationModule.cs | 2 +- .../Plugins/TcpMonitor.cs | 21 +- .../Samples/SampleAppService.cs | 43 ++- .../BasicScheduledMeterReadingService.cs | 293 +++++------------- ...nergySystemScheduledMeterReadingService.cs | 70 +++-- .../Subscribers/WorkerSubscriberAppService.cs | 61 +--- .../Helpers/DeviceGroupBalanceControl.cs | 125 ++++++-- .../AdminClient/AdminClientService.cs | 38 +++ .../AdminClient/IAdminClientService.cs | 24 ++ .../Extensions/ProtocolConstExtensions.cs | 1 - .../ProtocolConst.cs | 17 +- 14 files changed, 483 insertions(+), 386 deletions(-) diff --git a/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs index 15d60d9..0f04005 100644 --- a/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs @@ -77,22 +77,11 @@ namespace JiShe.CollectBus.ScheduledMeterReading Task InitWatermeterCacheData(string gatherCode = ""); /// - /// 1分钟采集水表数据 + /// 水表数据采集 /// /// - Task WatermeterScheduledMeterOneMinuteReading(); - - /// - /// 5分钟采集水表数据,只获取任务数据下发,不构建任务 - /// - /// - Task WatermeterScheduledMeterFiveMinuteReading(); - - /// - /// 15分钟采集水表数据,只获取任务数据下发,不构建任务 - /// - /// - Task WatermeterScheduledMeterFifteenMinuteReading(); + Task WatermeterScheduledMeterAutoReading(); + #endregion diff --git a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs index 64dddf1..abba774 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs @@ -39,19 +39,8 @@ namespace JiShe.CollectBus.Subscribers /// 1分钟采集水表数据下行消息消费订阅 /// /// - Task WatermeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); - - /// - /// 5分钟采集水表数据下行消息消费订阅 - /// - /// - Task WatermeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); - - /// - /// 15分钟采集水表数据下行消息消费订阅 - /// - /// - Task WatermeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); + Task WatermeterSubscriberWorkerAutoReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); + #endregion } } diff --git a/src/JiShe.CollectBus.Application/CollectBusAppService.cs b/src/JiShe.CollectBus.Application/CollectBusAppService.cs index c634859..467b487 100644 --- a/src/JiShe.CollectBus.Application/CollectBusAppService.cs +++ b/src/JiShe.CollectBus.Application/CollectBusAppService.cs @@ -1,9 +1,13 @@ using FreeRedis; using FreeSql; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.FreeRedisProvider; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.Localization; +using JiShe.CollectBus.Serializer; using Microsoft.AspNetCore.Mvc; +using System; using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Services; @@ -20,5 +24,141 @@ public abstract class CollectBusAppService : ApplicationService { LocalizationResource = typeof(CollectBusResource); ObjectMapperContext = typeof(CollectBusApplicationModule); - } + } + + /// + /// Lua脚本批量获取缓存的表计信息 + /// + /// 表信息数据对象 + /// 采集频率对应的缓存Key集合 + /// 系统类型 + /// 服务器标识 + /// 采集频率,1分钟、5分钟、15分钟 + /// 表计类型 + /// + protected async Task>> GetMeterRedisCacheDictionaryData(string[] redisKeys, string systemType, string serverTagName, string timeDensity, MeterTypeEnum meterType) where T : class + { + if (redisKeys == null || redisKeys.Length <=0 || string.IsNullOrWhiteSpace(systemType) || string.IsNullOrWhiteSpace(serverTagName) || string.IsNullOrWhiteSpace(timeDensity)) + { + throw new Exception($"{nameof(GetMeterRedisCacheDictionaryData)} 获取缓存的表计信息失败,参数异常,-101"); + } + + //通过lua脚本一次性获取所有缓存内容 + var luaScript = @" + local results = {} + for i, key in ipairs(KEYS) do + local data = redis.call('HGETALL', key) + results[i] = {key, data} + end + return results"; + var merterResult = await FreeRedisProvider.Instance.EvalAsync(luaScript, redisKeys); //传递 KEYS + if (merterResult == null) + { + throw new Exception($"{nameof(GetMeterRedisCacheDictionaryData)} 获取缓存的表计信息失败,没有获取到数据,-102"); + } + + // 解析结果(结果为嵌套数组) + var meterInfos = new Dictionary>(); ; + if (merterResult is object[] arr) + { + foreach (object[] item in arr) + { + string key = (string)item[0];//集中器地址对应的Redis缓存Key + object[] fieldsAndValues = (object[])item[1];//缓存Key对应的Hash表数据集合 + var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, systemType, serverTagName, meterType, timeDensity)}"; + string focusAddress = key.Replace(redisCacheKey, "");//集中器地址 + + var meterHashs = new Dictionary(); + for (int i = 0; i < fieldsAndValues.Length; i += 2) + { + string meterld = (string)fieldsAndValues[i];//表ID + string meterStr = (string)fieldsAndValues[i + 1];//表详情数据 + + T meterInfo = default!; + if (!string.IsNullOrWhiteSpace(meterStr)) + { + meterInfo = meterStr.Deserialize()!; + } + if (meterInfo != null) + { + meterHashs[meterld] = meterInfo; + } + else + { + throw new Exception($"{nameof(GetMeterRedisCacheDictionaryData)} 获取缓存的表计信息集中器缓存{key}数据的{meterld}处理异常,-102"); + } + } + meterInfos[focusAddress] = meterHashs; + } + } + + return meterInfos; + } + + /// + /// Lua脚本批量获取缓存的表计信息 + /// + /// 表信息数据对象 + /// 采集频率对应的缓存Key集合 + /// 系统类型 + /// 服务器标识 + /// 采集频率,1分钟、5分钟、15分钟 + /// 表计类型 + /// + protected async Task> GetMeterRedisCacheListData(string[] redisKeys,string systemType,string serverTagName, string timeDensity, MeterTypeEnum meterType) where T : class + { + if (redisKeys == null || redisKeys.Length <= 0 || string.IsNullOrWhiteSpace(systemType) || string.IsNullOrWhiteSpace(serverTagName) || string.IsNullOrWhiteSpace(timeDensity)) + { + throw new Exception($"{nameof(GetMeterRedisCacheListData)} 获取缓存的表计信息失败,参数异常,-101"); + } + + //通过lua脚本一次性获取所有缓存内容 + var luaScript = @" + local results = {} + for i, key in ipairs(KEYS) do + local data = redis.call('HGETALL', key) + results[i] = {key, data} + end + return results"; + var merterResult = await FreeRedisProvider.Instance.EvalAsync(luaScript, redisKeys); //传递 KEYS + if (merterResult == null) + { + throw new Exception($"{nameof(GetMeterRedisCacheListData)} 获取缓存的表计信息失败,没有获取到数据,-102"); + } + + // 解析结果(结果为嵌套数组) + var meterInfos = new List(); ; + if (merterResult is object[] arr) + { + foreach (object[] item in arr) + { + string key = (string)item[0];//集中器地址对应的Redis缓存Key + object[] fieldsAndValues = (object[])item[1];//缓存Key对应的Hash表数据集合 + var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, systemType, serverTagName, meterType, timeDensity)}"; + string focusAddress = key.Replace(redisCacheKey, "");//集中器地址 + + for (int i = 0; i < fieldsAndValues.Length; i += 2) + { + string meterld = (string)fieldsAndValues[i];//表ID + string meterStr = (string)fieldsAndValues[i + 1];//表详情数据 + + T meterInfo = default!; + if (!string.IsNullOrWhiteSpace(meterStr)) + { + meterInfo = meterStr.Deserialize()!; + } + if (meterInfo != null) + { + meterInfos.Add(meterInfo); + } + else + { + throw new Exception($"{nameof(GetMeterRedisCacheListData)} 获取缓存的表计信息集中器缓存{key}数据的{meterld}处理异常,-103"); + } + } + } + } + + return meterInfos; + } } diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 1a8b326..008e736 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -59,7 +59,7 @@ public class CollectBusApplicationModule : AbpModule //默认初始化表计信息 var dbContext = context.ServiceProvider.GetRequiredService(); dbContext.InitAmmeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); - dbContext.InitWatermeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); + //dbContext.InitWatermeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); //初始化主题信息 var kafkaAdminClient = context.ServiceProvider.GetRequiredService(); diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index c348a3d..ff4de0c 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using DeviceDetectorNET.Parser.Device; @@ -141,7 +142,15 @@ namespace JiShe.CollectBus.Plugins await client.ResetIdAsync(deviceNo); - var entity = await _deviceRepository.FindAsync(a => a.Number == deviceNo); + var deviceInfoList= await _deviceRepository.GetListAsync(a => a.Number == deviceNo); + if (deviceInfoList != null && deviceInfoList.Count > 1) + { + //todo 推送集中器编号重复预警 + _logger.LogError($"集中器编号:{deviceNo},存在多个集中器,请检查集中器编号是否重复"); + return; + } + + var entity = deviceInfoList?.FirstOrDefault(a => a.Number == deviceNo); if (entity == null) { await _deviceRepository.InsertAsync(new Device(deviceNo, oldClientId, DateTime.Now, DateTime.Now, DeviceStatus.Online)); @@ -171,7 +180,15 @@ namespace JiShe.CollectBus.Plugins string clientId = deviceNo; string oldClientId = $"{client.Id}"; - var entity = await _deviceRepository.FindAsync(a => a.Number == deviceNo); + var deviceInfoList = await _deviceRepository.GetListAsync(a => a.Number == deviceNo); + if (deviceInfoList != null && deviceInfoList.Count > 1) + { + //todo 推送集中器编号重复预警 + _logger.LogError($"集中器编号:{deviceNo},存在多个集中器,请检查集中器编号是否重复"); + return; + } + + var entity = deviceInfoList?.FirstOrDefault(a => a.Number == deviceNo); if (entity == null) //没有登录帧的设备,只有心跳帧 { await client.ResetIdAsync(clientId); diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 28b6ab0..ac09237 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -17,6 +17,9 @@ using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.IotSystems.AFNEntity; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using Microsoft.Extensions.DependencyInjection; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Enums; +using System.Diagnostics.Metrics; namespace JiShe.CollectBus.Samples; @@ -108,21 +111,49 @@ public class SampleAppService : CollectBusAppService, ISampleAppService [HttpGet] public async Task TestDeviceGroupBalanceControl(int deviceCount = 200000) { - var deviceList = new List(); - for (int i = 0; i < deviceCount; i++) + //var deviceList = new List(); + //for (int i = 0; i < deviceCount; i++) + //{ + // deviceList.Add($"Device_{Guid.NewGuid()}"); + //} + + //// 初始化缓存 + //DeviceGroupBalanceControl.InitializeCache(deviceList); + + var timeDensity = "15"; + //获取缓存中的电表信息 + var redisKeyList = $"{string.Format(RedisConst.CacheMeterInfoKey, "Energy", "JiSheCollectBus", MeterTypeEnum.Ammeter.ToString(), timeDensity)}*"; + + var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + var meterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, "Energy", "JiSheCollectBus", timeDensity, MeterTypeEnum.Ammeter); + List focusAddressDataLista = new List(); + foreach (var item in meterInfos) { - deviceList.Add($"Device_{Guid.NewGuid()}"); + focusAddressDataLista.Add(item.FocusAddress); } - // 初始化缓存 - DeviceGroupBalanceControl.InitializeCache(deviceList); - + DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista); + // 打印分布统计 DeviceGroupBalanceControl.PrintDistributionStats(); await Task.CompletedTask; } + /// + /// 测试设备分组均衡控制算法获取分组Id + /// + /// + /// + [HttpGet] + public async Task TestGetDeviceGroupBalanceControl(string deviceAddress) + { + var groupId = DeviceGroupBalanceControl.GetDeviceGroupId(deviceAddress); + Console.WriteLine(groupId); + + await Task.CompletedTask; + } + /// /// 测试单个测点数据项 diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 4373dd3..d032194 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -1,4 +1,5 @@ -using DotNetCore.CAP; +using DeviceDetectorNET.Class.Client; +using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.BuildSendDatas; using JiShe.CollectBus.Common.Consts; @@ -18,6 +19,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace JiShe.CollectBus.ScheduledMeterReading @@ -32,6 +34,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading private readonly IIoTDBProvider _dbProvider; private readonly IMeterReadingRecordRepository _meterReadingRecordRepository; + public BasicScheduledMeterReadingService( ILogger logger, ICapPublisher producerBus, @@ -79,6 +82,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// public virtual async Task CreateToBeIssueTasks() { + + //创建指定数量的线程, + var redisCacheKey = $"{RedisConst.CacheBasicDirectoryKey}{SystemType}:{ServerTagName}:TaskInfo:*"; var taskInfos = await FreeRedisProvider.Instance.KeysAsync(redisCacheKey); if (taskInfos == null || taskInfos.Length <= 0) @@ -94,22 +100,24 @@ namespace JiShe.CollectBus.ScheduledMeterReading { _logger.LogWarning($"{nameof(CreateToBeIssueTasks)} 构建待处理的下发指令任务处理时Key=>{item}没有缓存数据,102"); continue; - } - - //检查任务时间节点,由于定时任务10秒钟运行一次,需要判定当前时间是否在任务时间节点内,不在则跳过 - if (!IsGennerateCmd(tasksToBeIssueModel.NextTaskTime)) - { - _logger.LogWarning($"{nameof(CreateToBeIssueTasks)} 构建待处理的下发指令任务处理时Key=>{item}时间节点不在当前时间范围内,103"); - continue; - } + } //item 为 CacheTasksToBeIssuedKey 对应的缓存待下发的指令生产任务数据Redis Key tempArryay[0]=>CollectBus,tempArryay[1]=>SystemTypeConst,tempArryay[2]=>TaskInfo,tempArryay[3]=>表计类别,tempArryay[4]=>采集频率 var tempArryay = item.Split(":"); string meteryType = tempArryay[3];//表计类别 int timeDensity = Convert.ToInt32(tempArryay[4]);//采集频率 + //检查任务时间节点,由于定时任务10秒钟运行一次,需要判定当前时间是否在任务时间节点内,不在则跳过 + if (!IsTaskTime(tasksToBeIssueModel.NextTaskTime, timeDensity)) + { + _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} 构建待处理的下发指令任务处理时Key=>{item}时间节点不在当前时间范围内,103"); + continue; + } + + + //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, meteryType, timeDensity)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, meteryType, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -117,10 +125,12 @@ namespace JiShe.CollectBus.ScheduledMeterReading return; } + var meterTypes = EnumExtensions.ToEnumDictionary(); + if (meteryType == MeterTypeEnum.Ammeter.ToString()) { // 解析结果(结果为嵌套数组) - var meterInfos = await GetMeterRedisCacheData(oneMinutekeyList, $"{timeDensity}", meteryType); + var meterInfos = await GetMeterRedisCacheDictionaryData(oneMinutekeyList, SystemType, ServerTagName, $"{timeDensity}", meterTypes[meteryType]); if (meterInfos == null || meterInfos.Count <= 0) { _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-105"); @@ -140,6 +150,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建完成"); + + //根据当前的采集频率和类型,重新更新下一个任务点,把任务的创建源固定在当前逻辑,避免任务处理的逻辑异常导致任务创建失败。 tasksToBeIssueModel.NextTaskTime = tasksToBeIssueModel.NextTaskTime.AddMinutes(timeDensity); await FreeRedisProvider.Instance.SetAsync(item, tasksToBeIssueModel); @@ -165,7 +177,26 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// public virtual async Task InitAmmeterCacheData(string gatherCode = "") { +#if DEBUG + var timeDensity = "15"; + //获取缓存中的电表信息 + var redisKeyList = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}*"; + + var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + var meterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, SystemType, ServerTagName, timeDensity, MeterTypeEnum.Ammeter); + List focusAddressDataLista = new List(); + foreach (var item in meterInfos) + { + focusAddressDataLista.Add(item.FocusAddress); + } + + DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista); + return; +#else var meterInfos = await GetAmmeterInfoList(gatherCode); +#endif + + if (meterInfos == null || meterInfos.Count <= 0) { throw new NullReferenceException($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据时,电表数据为空"); @@ -199,7 +230,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading #if DEBUG //每次缓存时,删除缓存,避免缓存数据有不准确的问题 - await FreeRedisProvider.Instance.DelAsync(redisCacheKey); + //await FreeRedisProvider.Instance.DelAsync(redisCacheKey); #else //每次缓存时,删除缓存,避免缓存数据有不准确的问题 await FreeRedisProvider.Instance.DelAsync(redisCacheKey); @@ -299,7 +330,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), MeterTypeEnum.Ammeter.ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheDictionaryData(oneMinutekeyList, SystemType, ServerTagName, timeDensity.ToString(), MeterTypeEnum.Ammeter); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); @@ -363,7 +394,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fiveMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.Ammeter).ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheDictionaryData(fiveMinutekeyList, SystemType, ServerTagName, timeDensity.ToString(), MeterTypeEnum.Ammeter); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); @@ -427,7 +458,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fifteenMinutekeyList, timeDensity.ToString(), MeterTypeEnum.Ammeter.ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheDictionaryData(fifteenMinutekeyList, SystemType, ServerTagName, timeDensity.ToString(), MeterTypeEnum.Ammeter); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); @@ -713,7 +744,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading AFN = aFN, Fn = fn, ItemCode = tempItem, - TaskMark = CommonHelper.GetTaskMark((int)aFN, fn,ammeter.MeteringCode), + TaskMark = CommonHelper.GetTaskMark((int)aFN, fn, ammeter.MeteringCode), ManualOrNot = false, Pn = ammeter.MeteringCode, IssuedMessageId = GuidGenerator.Create().ToString(), @@ -799,10 +830,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading } /// - /// 1分钟采集水表数据 + /// 水表数据采集 /// /// - public virtual async Task WatermeterScheduledMeterOneMinuteReading() + public virtual async Task WatermeterScheduledMeterAutoReading() { //获取缓存中的水表信息 int timeDensity = 1; @@ -810,15 +841,15 @@ namespace JiShe.CollectBus.ScheduledMeterReading var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { - _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-101"); + _logger.LogError($"{nameof(WatermeterScheduledMeterAutoReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-101"); return; } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(oneMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); + Dictionary> meterTaskInfos = await GetMeterRedisCacheDictionaryData(oneMinutekeyList,SystemType,ServerTagName ,timeDensity.ToString(), MeterTypeEnum.WaterMeter); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { - _logger.LogError($"{nameof(WatermeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); + _logger.LogError($"{nameof(WatermeterScheduledMeterAutoReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); return; } @@ -836,7 +867,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - await _producerBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + await _producerBus.PublishAsync(ProtocolConst.WatermeterSubscriberWorkerAutoReadingIssuedEventName, tempMsg); //_ = _producerBus.Publish(tempMsg); @@ -856,211 +887,44 @@ namespace JiShe.CollectBus.ScheduledMeterReading //await CacheNextTaskData(timeDensity, MeterTypeEnum.WaterMeter); - _logger.LogInformation($"{nameof(WatermeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理完成"); + _logger.LogInformation($"{nameof(WatermeterScheduledMeterAutoReading)} {timeDensity}分钟采集水表数据处理完成"); } - /// - /// 5分钟采集水表数据 - /// - /// - public virtual async Task WatermeterScheduledMeterFiveMinuteReading() - { - - //获取缓存中的电表信息 - int timeDensity = 5; - var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.WaterMeter); - var fiveMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - if (fiveMinutekeyList == null || fiveMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-101"); - return; - } - - //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fiveMinutekeyList, timeDensity.ToString(), ((int)MeterTypeEnum.WaterMeter).ToString()); - if (meterTaskInfos == null || meterTaskInfos.Count <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); - return; - } - - List meterTaskInfosList = new List(); - - //将取出的缓存任务数据发送到Kafka消息队列中 - foreach (var focusItem in meterTaskInfos) - { - foreach (var ammerterItem in focusItem.Value) - { - var tempMsg = new ScheduledMeterReadingIssuedEventMessage() - { - MessageHexString = ammerterItem.Value.IssuedMessageHexString, - MessageId = ammerterItem.Value.IssuedMessageId, - FocusAddress = ammerterItem.Value.FocusAddress, - TimeDensity = timeDensity.ToString(), - }; - await _producerBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); - - //_ = _producerBus.Publish(tempMsg); - - - meterTaskInfosList.Add(ammerterItem.Value); - } - } - if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) - { - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); - } - - ////删除任务数据 - //await FreeRedisProvider.Instance.DelAsync(fiveMinutekeyList); - - ////缓存下一个时间的任务 - //await CacheNextTaskData(timeDensity, MeterTypeEnum.WaterMeter); - - - _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集水表数据处理完成"); - } - - /// - /// 15分钟采集水表数据 - /// - /// - public virtual async Task WatermeterScheduledMeterFifteenMinuteReading() - { - //获取缓存中的电表信息 - int timeDensity = 15; - var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.WaterMeter); - var fifteenMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - if (fifteenMinutekeyList == null || fifteenMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-101"); - return; - } - - //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheData(fifteenMinutekeyList, timeDensity.ToString(), MeterTypeEnum.WaterMeter.ToString()); - if (meterTaskInfos == null || meterTaskInfos.Count <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); - return; - } - - List meterTaskInfosList = new List(); - - //将取出的缓存任务数据发送到Kafka消息队列中 - foreach (var focusItem in meterTaskInfos) - { - foreach (var ammerterItem in focusItem.Value) - { - var tempMsg = new ScheduledMeterReadingIssuedEventMessage() - { - MessageHexString = ammerterItem.Value.IssuedMessageHexString, - MessageId = ammerterItem.Value.IssuedMessageId, - FocusAddress = ammerterItem.Value.FocusAddress, - TimeDensity = timeDensity.ToString(), - }; - //await _producerBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); - - //_ = _producerBus.Publish(tempMsg); - - - meterTaskInfosList.Add(ammerterItem.Value); - } - } - if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) - { - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); - } - - ////删除任务数据 - //await FreeRedisProvider.Instance.DelAsync(fifteenMinutekeyList); - - ////缓存下一个时间的任务 - //await CacheNextTaskData(timeDensity, MeterTypeEnum.WaterMeter); - - - _logger.LogInformation($"{nameof(WatermeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集水表数据处理完成"); - } #endregion #region 公共处理方法 - /// - /// Lua脚本批量获取缓存的表计信息 - /// - /// 表信息数据对象 - /// 采集频率对应的缓存Key集合 - /// 采集频率,1分钟、5分钟、15分钟 - /// 表计类型 - /// - private async Task>> GetMeterRedisCacheData(string[] redisKeys, string timeDensity, string meterType) where T : class - { - //通过lua脚本一次性获取所有缓存内容 - var luaScript = @" - local results = {} - for i, key in ipairs(KEYS) do - local data = redis.call('HGETALL', key) - results[i] = {key, data} - end - return results"; - var merterResult = await FreeRedisProvider.Instance.EvalAsync(luaScript, redisKeys); //传递 KEYS - if (merterResult == null) - { - _logger.LogError($"{nameof(GetMeterRedisCacheData)} 定时任务采集表数据处理时没有获取到缓存信息,-102"); - return null; - } - - // 解析结果(结果为嵌套数组) - var meterInfos = new Dictionary>(); ; - if (merterResult is object[] arr) - { - foreach (object[] item in arr) - { - string key = (string)item[0];//集中器地址对应的Redis缓存Key - object[] fieldsAndValues = (object[])item[1];//缓存Key对应的Hash表数据集合 - var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, meterType, timeDensity)}"; - string focusAddress = key.Replace(redisCacheKey, "");//集中器地址 - - var meterHashs = new Dictionary(); - for (int i = 0; i < fieldsAndValues.Length; i += 2) - { - string meterld = (string)fieldsAndValues[i];//表ID - string meterStr = (string)fieldsAndValues[i + 1];//表详情数据 - - T meterInfo = default!; - if (!string.IsNullOrWhiteSpace(meterStr)) - { - meterInfo = meterStr.Deserialize()!; - } - if (meterInfo != null) - { - meterHashs[meterld] = meterInfo; - } - else - { - _logger.LogInformation($"{nameof(GetMeterRedisCacheData)} 定时任务采集表数据处理时集中器缓存{key}数据的{meterld}处理异常"); - } - } - meterInfos[focusAddress] = meterHashs; - } - } - - return meterInfos; - } + /// - /// 指定时间对比当前时间 + /// 判断是否需要生成采集指令 /// - /// - /// + /// + /// /// - private bool IsGennerateCmd(DateTime lastTime, int subtrahend = 0) + private bool IsTaskTime(DateTime nextTaskTime, int timeDensity = 0) { - if (DateTime.Now.AddDays(subtrahend) >= lastTime)//当前时间减去一天,大于等于最后在线时间,不再生成该集中器下表生成采集指令 - return false; - return true; + if (DateTime.Now.AddMinutes(timeDensity) >= nextTaskTime) + { + return true; + } + + return false; } + ///// + ///// 指定时间对比当前时间 + ///// + ///// + ///// + ///// + //private bool IsGennerateCmd(DateTime lastTime, int subtrahend = 0) + //{ + // if (DateTime.Now.AddDays(subtrahend) >= lastTime)//当前时间减去一天,大于等于最后在线时间,不再生成该集中器下表生成采集指令 + // return false; + // return true; + //} + ///// ///// 缓存下一个时间的任务 ///// @@ -1091,6 +955,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { return $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, ServerTagName, meterType, timeDensity)}*"; } + #endregion } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 12453bc..62ede1d 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -71,37 +71,37 @@ namespace JiShe.CollectBus.ScheduledMeterReading public override async Task> GetAmmeterInfoList(string gatherCode = "V4-Gather-8890") { - List ammeterInfos = new List(); - ammeterInfos.Add(new AmmeterInfo() - { - Baudrate = 2400, - FocusAddress = "402440506", - Name = "张家祠工务(三相电表)", - FocusID = 95780, - DatabaseBusiID = 1, - MeteringCode = 1, - AmmerterAddress = "402410040506", - ID = 127035, - TypeName = 3, - DataTypes = "449,503,581,582,583,584,585,586,587,588,589,590,591,592,593,594,597,598,599,600,601,602,603,604,605,606,607,608,661,663,677,679", - TimeDensity = 15, - }); - ammeterInfos.Add(new AmmeterInfo() - { - Baudrate = 2400, - FocusAddress = "542400504", - Name = "五号配(长芦二所四排)(单相电表)", - FocusID = 69280, - DatabaseBusiID = 1, - MeteringCode = 2, - AmmerterAddress = "542410000504", - ID = 95594, - TypeName = 1, - DataTypes = "581,589,592,597,601", - TimeDensity = 15, - }); + //List ammeterInfos = new List(); + //ammeterInfos.Add(new AmmeterInfo() + //{ + // Baudrate = 2400, + // FocusAddress = "402440506", + // Name = "张家祠工务(三相电表)", + // FocusID = 95780, + // DatabaseBusiID = 1, + // MeteringCode = 1, + // AmmerterAddress = "402410040506", + // ID = 127035, + // TypeName = 3, + // DataTypes = "449,503,581,582,583,584,585,586,587,588,589,590,591,592,593,594,597,598,599,600,601,602,603,604,605,606,607,608,661,663,677,679", + // TimeDensity = 15, + //}); + //ammeterInfos.Add(new AmmeterInfo() + //{ + // Baudrate = 2400, + // FocusAddress = "542400504", + // Name = "五号配(长芦二所四排)(单相电表)", + // FocusID = 69280, + // DatabaseBusiID = 1, + // MeteringCode = 2, + // AmmerterAddress = "542410000504", + // ID = 95594, + // TypeName = 1, + // DataTypes = "581,589,592,597,601", + // TimeDensity = 15, + //}); - return ammeterInfos; + //return ammeterInfos; string sql = $@"SELECT C.ID,C.Name,C.FocusID,C.SingleRate,C.MeteringCode,C.Code AS BrandType,C.Baudrate,C.Password,C.MeteringPort,C.[Address] AS AmmerterAddress,C.TypeName,C.Protocol,C.TripState,C.[State],B.[Address],B.AreaCode,B.AutomaticReport,D.DataTypes,B.TimeDensity,A.GatherCode,C.Special,C.[ProjectID],B.AbnormalState,B.LastTime,CONCAT(B.AreaCode, B.[Address]) AS FocusAddress,(select top 1 DatabaseBusiID from TB_Project where ID = B.ProjectID) AS DatabaseBusiID FROM TB_GatherInfo(NOLOCK) AS A @@ -111,10 +111,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading WHERE 1=1 and C.Special = 0 "; //TODO 记得移除特殊表过滤 - if (!string.IsNullOrWhiteSpace(gatherCode)) - { - sql = $@"{sql} AND A.GatherCode = '{gatherCode}'"; - } + //if (!string.IsNullOrWhiteSpace(gatherCode)) + //{ + // sql = $@"{sql} AND A.GatherCode = '{gatherCode}'"; + //} return await SqlProvider.Instance.Change(DbEnum.EnergyDB) .Ado .QueryAsync(sql); @@ -187,6 +187,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading deviceList.Add($"Device_{Guid.NewGuid()}"); } + // 初始化缓存 + DeviceGroupBalanceControl.InitializeCache(deviceList); // 打印分布统计 DeviceGroupBalanceControl.PrintDistributionStats(); diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index 4428fe4..3caba20 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -147,69 +147,16 @@ namespace JiShe.CollectBus.Subscribers #endregion #region 水表消息采集 + /// - /// 1分钟采集水表数据下行消息消费订阅 - /// - /// - /// - [HttpPost] - [Route("watermeter/oneminute/issued-event")] - [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerOneMinuteIssuedEventName)] - public async Task WatermeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) - { - _logger.LogInformation("1分钟采集水表数据下行消息消费队列开始处理"); - var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); - if (protocolPlugin == null) - { - _logger.LogError("【1分钟采集水表数据下行消息消费队列开始处理】协议不存在!"); - } - else - { - var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.FocusAddress); - if (device != null) - { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); - - } - } - } - - /// - /// 5分钟采集水表数据下行消息消费订阅 - /// - /// - /// - [HttpPost] - [Route("watermeter/fiveminute/issued-event")] - [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerFiveMinuteIssuedEventName)] - public async Task WatermeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) - { - _logger.LogInformation("5分钟采集水表数据下行消息消费队列开始处理"); - var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); - if (protocolPlugin == null) - { - _logger.LogError("【5分钟采集水表数据下行消息消费队列开始处理】协议不存在!"); - } - else - { - var device = await _deviceRepository.FindAsync(a => a.Number == receivedMessage.FocusAddress); - if (device != null) - { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); - - } - } - } - - /// - /// 15分钟采集水表数据下行消息消费订阅 + /// 水表数据下行消息消费订阅 /// /// /// [HttpPost] [Route("watermeter/fifteenminute/issued-event")] - [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerFifteenMinuteIssuedEventName)] - public async Task WatermeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) + [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerAutoReadingIssuedEventName)] + public async Task WatermeterSubscriberWorkerAutoReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("15分钟采集水表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); diff --git a/src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs b/src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs index c48bad3..657ff7c 100644 --- a/src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs +++ b/src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs @@ -1,5 +1,6 @@ using JiShe.CollectBus.FreeRedisProvider; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; @@ -12,38 +13,98 @@ namespace JiShe.CollectBus.Common.Helpers /// 设备组负载控制 /// public class DeviceGroupBalanceControl - { - /// - /// 分组集合 - /// - private static List[] _cachedGroups; + { + private static readonly object _syncRoot = new object(); + + private static volatile CacheState _currentCache; /// - /// 设备分组关系映射 + /// 使用ConcurrentDictionary保证线程安全的设备分组映射 /// - private static Dictionary _balancedMapping; - - - /// - /// 初始化缓存并强制均衡 - /// - public static void InitializeCache(List deviceList,int groupCount = 50) + private sealed class CacheState { - // 步骤1: 生成均衡映射表 - _balancedMapping = CreateBalancedMapping(deviceList, groupCount); + public readonly ConcurrentDictionary BalancedMapping; + public readonly List[] CachedGroups; - // 步骤2: 根据映射表填充分组 - _cachedGroups = new List[groupCount]; - for (int i = 0; i < groupCount; i++) + public CacheState(int groupCount) { - _cachedGroups[i] = new List(capacity: deviceList.Count / groupCount + 1); + BalancedMapping = new ConcurrentDictionary(); + CachedGroups = new List[groupCount]; + for (int i = 0; i < groupCount; i++) + { + CachedGroups[i] = new List(); + } + } + } + + /// + /// 初始化或增量更新缓存 + /// + public static void InitializeCache(List deviceList, int groupCount = 60) + { + lock (_syncRoot) + { + // 首次初始化 + if (_currentCache == null) + { + var newCache = new CacheState(groupCount); + UpdateCacheWithDevices(newCache, deviceList, groupCount); + _currentCache = newCache; + } + // 后续增量更新 + else + { + if (_currentCache.CachedGroups.Length != groupCount) + throw new ArgumentException("Group count cannot change after initial initialization"); + + var clonedCache = CloneExistingCache(); + UpdateCacheWithDevices(clonedCache, deviceList, groupCount); + _currentCache = clonedCache; + } + } + } + + /// + /// 带锁的缓存克隆(写入时复制) + /// + private static CacheState CloneExistingCache() + { + var oldCache = _currentCache; + var newCache = new CacheState(oldCache.CachedGroups.Length); + + // 复制已有映射 + foreach (var kvp in oldCache.BalancedMapping) + { + newCache.BalancedMapping.TryAdd(kvp.Key, kvp.Value); } + // 复制分组数据 + for (int i = 0; i < oldCache.CachedGroups.Length; i++) + { + newCache.CachedGroups[i].AddRange(oldCache.CachedGroups[i]); + } + + return newCache; + } + + /// + /// 更新设备到缓存 + /// + private static void UpdateCacheWithDevices(CacheState cache, List deviceList, int groupCount) + { foreach (var deviceId in deviceList) { - int groupId = _balancedMapping[deviceId]; - _cachedGroups[groupId].Add(deviceId); - } + // 原子操作:如果设备不存在则计算分组 + cache.BalancedMapping.GetOrAdd(deviceId, id => + { + int groupId = GetGroupId(id, groupCount); + lock (cache.CachedGroups[groupId]) + { + cache.CachedGroups[groupId].Add(id); + } + return groupId; + }); + } } /// @@ -51,11 +112,11 @@ namespace JiShe.CollectBus.Common.Helpers /// public static List GetGroup(string deviceId) { - if (_balancedMapping == null || _cachedGroups == null) + var cache = _currentCache; + if (cache == null) throw new InvalidOperationException("缓存未初始化"); - int groupId = _balancedMapping[deviceId]; - return _cachedGroups[groupId]; + return cache.CachedGroups[cache.BalancedMapping[deviceId]]; } /// @@ -63,10 +124,11 @@ namespace JiShe.CollectBus.Common.Helpers /// public static int GetDeviceGroupId(string deviceId) { - if (_balancedMapping == null || _cachedGroups == null) + var cache = _currentCache; + if (cache == null) throw new InvalidOperationException("缓存未初始化"); - return _balancedMapping[deviceId]; + return cache.BalancedMapping[deviceId]; } @@ -162,7 +224,14 @@ namespace JiShe.CollectBus.Common.Helpers /// public static void PrintDistributionStats() { - var stats = _cachedGroups + var cache = _currentCache; + if (cache == null) + { + Console.WriteLine("缓存未初始化"); + return; + } + + var stats = cache.CachedGroups .Select((group, idx) => new { GroupId = idx, Count = group.Count }) .OrderBy(x => x.GroupId); diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs index 217e35c..e151145 100644 --- a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs @@ -69,6 +69,30 @@ namespace JiShe.CollectBus.Kafka.AdminClient return await Task.FromResult(metadata.Topics.Exists(a => a.Topic == topic)); } + /// + /// 判断Kafka主题是否存在 + /// + /// 主题名称 + /// 副本数量,不能高于Brokers数量 + /// + public async Task CheckTopicAsync(string topic,int numPartitions) + { + var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(5)); + if(numPartitions > metadata.Brokers.Count) + { + throw new Exception($"{nameof(CheckTopicAsync)} 主题检查时,副本数量大于了节点数量。") ; + } + + return await Task.FromResult(metadata.Topics.Exists(a => a.Topic == topic)); + } + + //// + /// 创建Kafka主题 + /// + /// 主题名称 + /// 主题分区数量 + /// 副本数量,不能高于Brokers数量 + /// public async Task CreateTopicAsync(string topic, int numPartitions, short replicationFactor) { @@ -96,17 +120,31 @@ namespace JiShe.CollectBus.Kafka.AdminClient } } + /// + /// 删除Kafka主题 + /// + /// + /// public async Task DeleteTopicAsync(string topic) { await Instance.DeleteTopicsAsync(new[] { topic }); } + /// + /// 获取Kafka主题列表 + /// + /// public async Task> ListTopicsAsync() { var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(10)); return new List(metadata.Topics.Select(t => t.Topic)); } + /// + /// 判断Kafka主题是否存在 + /// + /// + /// public async Task TopicExistsAsync(string topic) { var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(10)); diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs b/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs index c3d332d..722c485 100644 --- a/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs @@ -8,9 +8,33 @@ namespace JiShe.CollectBus.Kafka.AdminClient { public interface IAdminClientService { + /// + /// 创建Kafka主题 + /// + /// 主题名称 + /// 主题分区数量 + /// 副本数量,不能高于Brokers数量 + /// Task CreateTopicAsync(string topic, int numPartitions, short replicationFactor); + + /// + /// 删除Kafka主题 + /// + /// + /// Task DeleteTopicAsync(string topic); + + /// + /// 获取Kafka主题列表 + /// + /// Task> ListTopicsAsync(); + + /// + /// 判断Kafka主题是否存在 + /// + /// + /// Task TopicExistsAsync(string topic); } } diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs b/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs index 0812aae..fc52f31 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs @@ -49,7 +49,6 @@ namespace JiShe.CollectBus.Protocol.Contracts //动态上报主题,需根据协议的AFN功能码动态获取 var afnList = EnumExtensions.ToNameValueDictionary(); - //需要排除的AFN功能码 var excludeItems = new List() { 6, 7, 8,15 }; diff --git a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs index e7caa1c..d1e5739 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs @@ -70,22 +70,9 @@ namespace JiShe.CollectBus.Protocol.Contracts #region 水表消息主题 /// - /// 1分钟采集水表数据下行消息主题 + /// 水表数据下行消息主题,由于水表采集频率不高,所以一个主题就够 /// - public const string WatermeterSubscriberWorkerOneMinuteIssuedEventName = "issued.auto.one.watermeter.event"; - /// - /// 5分钟采集水表数据下行消息主题 - /// - public const string WatermeterSubscriberWorkerFiveMinuteIssuedEventName = "issued.auto.five.watermeter.event"; - /// - /// 15分钟采集水表数据下行消息主题 - /// - public const string WatermeterSubscriberWorkerFifteenMinuteIssuedEventName = "issued.auto.fifteen.watermeter.event"; - - /// - /// 其他采集数据下行消息主题,日冻结,月冻结、集中器版本号等 - /// - public const string WatermeterSubscriberWorkerOtherIssuedEventName = "issued.auto.other.watermeter.event"; + public const string WatermeterSubscriberWorkerAutoReadingIssuedEventName = "issued.auto.reading.watermeter.event"; /// /// 水表自动阀控 -- 2.47.2 From 8b86381e7a15c70c4318dc5701dfb34bf5e75535 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 14 Apr 2025 17:38:34 +0800 Subject: [PATCH 081/139] =?UTF-8?q?=E5=B9=B6=E8=A1=8C=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=89=80=E6=9C=89=E5=88=86=E7=BB=84=E8=AE=BE=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 199 +++++++++++++++++- .../DeviceGroupBalanceControl.cs | 46 +++- .../DeviceGroupBasicModel.cs | 19 ++ 3 files changed, 257 insertions(+), 7 deletions(-) rename src/JiShe.CollectBus.Common/{Helpers => DeviceBalanceControl}/DeviceGroupBalanceControl.cs (83%) create mode 100644 src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBasicModel.cs diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index d032194..ce7b39b 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -6,6 +6,7 @@ using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.EnergySystems.Entities; using JiShe.CollectBus.GatherItem; using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.MessageIssueds; @@ -81,10 +82,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// /// public virtual async Task CreateToBeIssueTasks() - { - - //创建指定数量的线程, - + { var redisCacheKey = $"{RedisConst.CacheBasicDirectoryKey}{SystemType}:{ServerTagName}:TaskInfo:*"; var taskInfos = await FreeRedisProvider.Instance.KeysAsync(redisCacheKey); if (taskInfos == null || taskInfos.Length <= 0) @@ -116,7 +114,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading - //获取缓存中的电表信息 + //获取缓存中的表信息 var redisKeyList = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, meteryType, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) @@ -137,6 +135,12 @@ namespace JiShe.CollectBus.ScheduledMeterReading return; } await AmmerterScheduledMeterReadingIssued(timeDensity, meterInfos); + + DeviceGroupBalanceControl.ProcessAllGroups(deviceId => { + Console.WriteLine($"Processing {deviceId} on Thread {Thread.CurrentThread.ManagedThreadId}"); + }); + + await AmmerterCreatePublishTask(timeDensity, meterInfos); } else if (meteryType == MeterTypeEnum.WaterMeter.ToString()) { @@ -573,13 +577,196 @@ namespace JiShe.CollectBus.ScheduledMeterReading } } + + /// + /// 电表创建发布任务 + /// + /// 采集频率 + /// 集中器号hash分组的集中器集合数据 + /// + private async Task AmmerterCreatePublishTask(int timeDensity + , List ammeterGroup) + { + var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; + //todo 检查需要待补抄的电表的时间点信息,保存到需要待补抄的缓存中。如果此线程异常,该如何补偿? + + var currentTime = DateTime.Now; + var pendingCopyReadTime = currentTime.AddMinutes(timeDensity); + //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 + var redisCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}{ammeterInfo.Key}"; + + foreach (var ammeterInfo in ammeterGroup) + { + + if (string.IsNullOrWhiteSpace(ammeterInfo.ItemCodes)) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,采集项为空,-101"); + continue; + } + + //载波的不处理 + if (ammeterInfo.MeteringPort == (int)MeterLinkProtocolEnum.Carrierwave) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,载波不处理,-102"); + continue; + } + + if (ammeterInfo.State.Equals(2)) + { + _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} {ammeterInfo.Name} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}状态为禁用,不处理"); + continue; + } + + ////排除1天未在线的集中器生成指令 或 排除集中器配置为自动上报的集中器 + //if (!IsGennerateCmd(ammeter.LastTime, -1)) + //{ + // _logger.LogInformation($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name},采集时间:{ammeter.LastTime},已超过1天未在线,不生成指令"); + // continue; + //} + + if (string.IsNullOrWhiteSpace(ammeterInfo.AreaCode)) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信区号为空"); + continue; + } + if (string.IsNullOrWhiteSpace(ammeterInfo.Address)) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址为空"); + continue; + } + if (Convert.ToInt32(ammeterInfo.Address) > 65535) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址无效,确保大于65535"); + continue; + } + if (ammeterInfo.MeteringCode <= 0 || ammeterInfo.MeteringCode > 2033) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},非有效测量点号({ammeterInfo.MeteringCode})"); + continue; + } + + List tempCodes = ammeterInfo.ItemCodes.Deserialize>()!; + + //TODO:自动上报数据只主动采集1类数据。 + if (ammeterInfo.AutomaticReport.Equals(1)) + { + var tempSubCodes = new List(); + if (tempCodes.Contains("0C_49")) + { + tempSubCodes.Add("0C_49"); + } + + if (tempSubCodes.Contains("0C_149")) + { + tempSubCodes.Add("0C_149"); + } + + if (ammeterInfo.ItemCodes.Contains("10_97")) + { + tempSubCodes.Add("10_97"); + } + + if (tempSubCodes == null || tempSubCodes.Count <= 0) + { + _logger.LogInformation($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}自动上报数据主动采集1类数据时数据类型为空"); + continue; + } + else + { + tempCodes = tempSubCodes; + } + } + + Dictionary keyValuePairs = new Dictionary(); + + foreach (var tempItem in tempCodes) + { + //排除已发送日冻结和月冻结采集项配置 + if (DayFreezeCodes.Contains(tempItem)) + { + continue; + } + + if (MonthFreezeCodes.Contains(tempItem)) + { + continue; + } + + var itemCodeArr = tempItem.Split('_'); + var aFNStr = itemCodeArr[0]; + var aFN = (AFN)aFNStr.HexToDec(); + var fn = int.Parse(itemCodeArr[1]); + byte[] dataInfos = null; + if (ammeterInfo.AutomaticReport.Equals(1) && aFN == AFN.请求实时数据) + { + //实时数据 + dataInfos = Build3761SendData.BuildAmmeterReadRealTimeDataSendCmd(ammeterInfo.FocusAddress, ammeterInfo.MeteringCode, (ATypeOfDataItems)fn); + } + else + { + string methonCode = $"AFN{aFNStr}_Fn_Send"; + //特殊表暂不处理 + if (handlerPacketBuilder != null && handlerPacketBuilder.TryGetValue(methonCode + , out var handler)) + { + dataInfos = handler(new TelemetryPacketRequest() + { + FocusAddress = ammeterInfo.FocusAddress, + Fn = fn, + Pn = ammeterInfo.MeteringCode + }); + } + else + { + _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}采集项{tempItem}无效编码。"); + continue; + } + } + //TODO:特殊表 + + if (dataInfos == null || dataInfos.Length <= 0) + { + _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}采集项{tempItem}未能正确获取报文。"); + continue; + } + + + + var meterReadingRecords = new MeterReadingRecords() + { + ProjectID = ammeterInfo.ProjectID, + DatabaseBusiID = ammeterInfo.DatabaseBusiID, + PendingCopyReadTime = pendingCopyReadTime, + CreationTime = currentTime, + MeterAddress = ammeterInfo.AmmerterAddress, + MeterId = ammeterInfo.ID, + MeterType = MeterTypeEnum.Ammeter, + FocusAddress = ammeterInfo.FocusAddress, + FocusID = ammeterInfo.FocusID, + AFN = aFN, + Fn = fn, + ItemCode = tempItem, + TaskMark = CommonHelper.GetTaskMark((int)aFN, fn, ammeterInfo.MeteringCode), + ManualOrNot = false, + Pn = ammeterInfo.MeteringCode, + IssuedMessageId = GuidGenerator.Create().ToString(), + IssuedMessageHexString = Convert.ToHexString(dataInfos), + }; + meterReadingRecords.CreateDataId(GuidGenerator.Create()); + + keyValuePairs.TryAdd($"{ammeterInfo.ID}_{tempItem}", meterReadingRecords); + } + await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); + } + } + /// /// 电表创建发布任务 /// /// 采集频率 /// 集中器号hash分组的集中器集合数据 /// - private async Task AmmerterCreatePublishTask(int timeDensity + private async Task AmmerterCreatePublishTask2(int timeDensity , Dictionary> focusGroup) { var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; diff --git a/src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs similarity index 83% rename from src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs rename to src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs index 657ff7c..13d51a8 100644 --- a/src/JiShe.CollectBus.Common/Helpers/DeviceGroupBalanceControl.cs +++ b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs @@ -7,7 +7,7 @@ using System.Text; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; -namespace JiShe.CollectBus.Common.Helpers +namespace JiShe.CollectBus.Common.DeviceBalanceControl { /// /// 设备组负载控制 @@ -107,6 +107,50 @@ namespace JiShe.CollectBus.Common.Helpers } } + /// + /// 并行处理所有分组设备(每个分组一个处理线程) + /// + public static void ProcessAllGroups(Action> processAction) where T : DeviceGroupBasicModel + { + var cache = _currentCache; + if (cache == null) + throw new InvalidOperationException("缓存未初始化"); + + // 使用并行选项控制并发度 + var options = new ParallelOptions + { + MaxDegreeOfParallelism = cache.CachedGroups.Length // 严格匹配分组数量 + }; + + Parallel.For(0, cache.CachedGroups.Length, options, groupId => + { + // 获取当前分组的只读副本 + var groupDevices = GetGroupSnapshot(cache, groupId); + + processAction(groupDevices); + + //foreach (var deviceId in groupDevices) + //{ + // //执行处理操作 + // processAction(deviceId); + + // // 可添加取消检测 + // // if (token.IsCancellationRequested) break; + //} + }); + } + + /// + /// 获取分组数据快照(线程安全) + /// + private static IReadOnlyList GetGroupSnapshot(CacheState cache, int groupId) + { + lock (cache.CachedGroups[groupId]) + { + return cache.CachedGroups[groupId].ToList(); // 创建内存快照 + } + } + /// /// 通过 deviceId 获取所在的分组集合 /// diff --git a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBasicModel.cs b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBasicModel.cs new file mode 100644 index 0000000..f12f15e --- /dev/null +++ b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBasicModel.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.DeviceBalanceControl +{ + /// + /// 设备组基本模型 + /// + public class DeviceGroupBasicModel + { + /// + /// 设备Id + /// + public string DeviceId { get; set; } + } +} -- 2.47.2 From 25c78d784f1254e22bd787c38d2ea66c06b337d6 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Mon, 14 Apr 2025 19:10:27 +0800 Subject: [PATCH 082/139] =?UTF-8?q?sytel=EF=BC=9A=E4=BC=98=E5=8C=96kafka?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JiShe.CollectBus.Kafka.Test.csproj | 31 ++++ JiShe.CollectBus.Kafka.Test/Program.cs | 79 +++++++++++ JiShe.CollectBus.Kafka.Test/appsettings.json | 134 ++++++++++++++++++ JiShe.CollectBus.sln | 7 + .../BasicScheduledMeterReadingService.cs | 2 +- .../Pages/Monitor.cshtml | 1 + .../AdminClient/AdminClientService.cs | 34 ++++- .../AdminClient/IAdminClientService.cs | 16 +++ .../CollectBusKafkaModule.cs | 5 + .../Consumer/ConsumerService.cs | 10 +- .../Producer/ProducerService.cs | 13 +- 11 files changed, 324 insertions(+), 8 deletions(-) create mode 100644 JiShe.CollectBus.Kafka.Test/JiShe.CollectBus.Kafka.Test.csproj create mode 100644 JiShe.CollectBus.Kafka.Test/Program.cs create mode 100644 JiShe.CollectBus.Kafka.Test/appsettings.json diff --git a/JiShe.CollectBus.Kafka.Test/JiShe.CollectBus.Kafka.Test.csproj b/JiShe.CollectBus.Kafka.Test/JiShe.CollectBus.Kafka.Test.csproj new file mode 100644 index 0000000..c7e7af1 --- /dev/null +++ b/JiShe.CollectBus.Kafka.Test/JiShe.CollectBus.Kafka.Test.csproj @@ -0,0 +1,31 @@ + + + + Exe + net8.0 + enable + enable + + + + + Always + true + PreserveNewest + + + + + + + + + + + + + + + + + diff --git a/JiShe.CollectBus.Kafka.Test/Program.cs b/JiShe.CollectBus.Kafka.Test/Program.cs new file mode 100644 index 0000000..36abdb6 --- /dev/null +++ b/JiShe.CollectBus.Kafka.Test/Program.cs @@ -0,0 +1,79 @@ +// See https://aka.ms/new-console-template for more information +using Confluent.Kafka; +using JiShe.CollectBus.Kafka.AdminClient; +using JiShe.CollectBus.Kafka.Consumer; +using JiShe.CollectBus.Kafka.Producer; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Serilog; +using System.Text.Json; +using static Confluent.Kafka.ConfigPropertyNames; + + + +// 构建配置 +var config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); +// 直接读取配置项 +var greeting = config["ServerTagName"]; +Console.WriteLine(greeting); // 输出: Hello, World! + + +// 创建服务容器 +var services = new ServiceCollection(); +// 注册 IConfiguration 实例 +services.AddSingleton(config); + +// 初始化日志 +Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(config) // 从 appsettings.json 读取配置 + .CreateLogger(); + +// 配置日志系统 +services.AddLogging(logging => +{ + logging.ClearProviders(); + logging.AddSerilog(); +}); +services.AddSingleton(); +services.AddTransient(typeof(IProducerService<,>), typeof(ProducerService<,>)); +//services.AddSingleton(typeof(IConsumerService<,>), typeof(ConsumerService<,>)); + +// 构建ServiceProvider +var serviceProvider = services.BuildServiceProvider(); + +// 获取日志记录器工厂 +var loggerFactory = serviceProvider.GetRequiredService(); +var logger = loggerFactory.CreateLogger(); +logger.LogInformation("程序启动"); + +var adminClientService = serviceProvider.GetRequiredService(); + +string topic = "test-topic"; +//await adminClientService.DeleteTopicAsync(topic); +// 创建 topic +await adminClientService.CreateTopicAsync(topic, 3, 3); + + +var producerService = serviceProvider.GetRequiredService>(); +int i = 1; +while (i <= 10) +{ + await producerService.ProduceAsync(topic, JsonSerializer.Serialize(new { topic = topic, val = i })); + i++; +} + +while (true) +{ + var key = Console.ReadKey(intercept: true); // intercept:true 隐藏按键显示 + + if (key.Key == ConsoleKey.Escape) + { + Console.WriteLine("\n程序已退出"); + break; + } +} + (serviceProvider as IDisposable)?.Dispose(); diff --git a/JiShe.CollectBus.Kafka.Test/appsettings.json b/JiShe.CollectBus.Kafka.Test/appsettings.json new file mode 100644 index 0000000..55c8ef6 --- /dev/null +++ b/JiShe.CollectBus.Kafka.Test/appsettings.json @@ -0,0 +1,134 @@ +{ + "Serilog": { + "Using": [ + "Serilog.Sinks.Console", + "Serilog.Sinks.File" + ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Volo.Abp": "Warning", + "Hangfire": "Warning", + "DotNetCore.CAP": "Warning", + "Serilog.AspNetCore": "Information", + "Microsoft.EntityFrameworkCore": "Warning", + "Microsoft.AspNetCore": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console" + }, + { + "Name": "File", + "Args": { + "path": "./logs/logs-.txt", + "rollingInterval": "Day" + } + } + ] + }, + "App": { + "SelfUrl": "http://localhost:44315", + "CorsOrigins": "http://localhost:4200,http://localhost:3100" + }, + "ConnectionStrings": { + "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", + "Kafka": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092", + "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", + "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" + }, + "Redis": { + "Configuration": "192.168.1.9:6380,password=1q2w3e!@#,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + "DefaultDB": "14", + "HangfireDB": "15" + }, + "Jwt": { + "Audience": "JiShe.CollectBus", + "SecurityKey": "dzehzRz9a8asdfasfdadfasdfasdfafsdadfasbasdf=", + "Issuer": "JiShe.CollectBus", + "ExpirationTime": 2 + }, + "HealthCheck": { + "IsEnable": true, + "MySql": { + "IsEnable": true + }, + "Pings": { + "IsEnable": true, + "Host": "https://www.baidu.com/", + "TimeOut": 5000 + } + }, + "SwaggerConfig": [ + { + "GroupName": "Basic", + "Title": "【后台管理】基础模块", + "Version": "V1" + }, + { + "GroupName": "Business", + "Title": "【后台管理】业务模块", + "Version": "V1" + } + ], + "Cap": { + "RabbitMq": { + "HostName": "118.190.144.92", + "UserName": "collectbus", + "Password": "123456", + "Port": 5672 + } + }, + "Kafka": { + "BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092", + "EnableAuthorization": false, + "SecurityProtocol": "SASL_PLAINTEXT", + "SaslMechanism": "PLAIN", + "SaslUserName": "lixiao", + "SaslPassword": "lixiao1980" + //"Topic": { + // "ReplicationFactor": 3, + // "NumPartitions": 1000 + //} + }, + //"Kafka": { + // "Connections": { + // "Default": { + // "BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092" + // // "SecurityProtocol": "SASL_PLAINTEXT", + // // "SaslMechanism": "PLAIN", + // // "SaslUserName": "lixiao", + // // "SaslPassword": "lixiao1980", + // } + // }, + // "Consumer": { + // "GroupId": "JiShe.CollectBus" + // }, + // "Producer": { + // "MessageTimeoutMs": 6000, + // "Acks": -1 + // }, + // "Topic": { + // "ReplicationFactor": 3, + // "NumPartitions": 1000 + // }, + // "EventBus": { + // "GroupId": "JiShe.CollectBus", + // "TopicName": "DefaultTopicName" + // } + //}, + "IoTDBOptions": { + "UserName": "root", + "Password": "root", + "ClusterList": [ "192.168.1.9:6667" ], + "PoolSize": 2, + "DataBaseName": "energy", + "OpenDebugMode": true, + "UseTableSessionPoolByDefault": false + }, + "ServerTagName": "JiSheCollectBus", + "KafkaReplicationFactor": 3, + "NumPartitions": 30 +} \ No newline at end of file diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index c26f3da..1d185db 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -37,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.IoTDBProvi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.Test", "src\JiShe.CollectBus.Protocol.Test\JiShe.CollectBus.Protocol.Test.csproj", "{A377955E-7EA1-6F29-8CF7-774569E93925}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka.Test", "JiShe.CollectBus.Kafka.Test\JiShe.CollectBus.Kafka.Test.csproj", "{82E4562A-3A7F-4372-8D42-8AE41BA56C04}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -107,6 +109,10 @@ Global {A377955E-7EA1-6F29-8CF7-774569E93925}.Debug|Any CPU.Build.0 = Debug|Any CPU {A377955E-7EA1-6F29-8CF7-774569E93925}.Release|Any CPU.ActiveCfg = Release|Any CPU {A377955E-7EA1-6F29-8CF7-774569E93925}.Release|Any CPU.Build.0 = Release|Any CPU + {82E4562A-3A7F-4372-8D42-8AE41BA56C04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82E4562A-3A7F-4372-8D42-8AE41BA56C04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82E4562A-3A7F-4372-8D42-8AE41BA56C04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82E4562A-3A7F-4372-8D42-8AE41BA56C04}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -128,6 +134,7 @@ Global {F0288175-F0EC-48BD-945F-CF1512850943} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {A3F3C092-0A25-450B-BF6A-5983163CBEF5} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {A377955E-7EA1-6F29-8CF7-774569E93925} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {82E4562A-3A7F-4372-8D42-8AE41BA56C04} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 4373dd3..8b19e49 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -199,7 +199,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading #if DEBUG //每次缓存时,删除缓存,避免缓存数据有不准确的问题 - await FreeRedisProvider.Instance.DelAsync(redisCacheKey); + //await FreeRedisProvider.Instance.DelAsync(redisCacheKey); #else //每次缓存时,删除缓存,避免缓存数据有不准确的问题 await FreeRedisProvider.Instance.DelAsync(redisCacheKey); diff --git a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml index f5c12d6..37f84bf 100644 --- a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml +++ b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml @@ -163,6 +163,7 @@ overflow-y: hidden; color: #555; } */ + .caption { padding: 9px; overflow-y: hidden; diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs index 217e35c..74bef5f 100644 --- a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs @@ -42,7 +42,8 @@ namespace JiShe.CollectBus.Kafka.AdminClient /// public IAdminClient GetInstance(IConfiguration configuration) { - var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]); + ArgumentNullException.ThrowIfNullOrWhiteSpace(configuration["Kafka:EnableAuthorization"]); + var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]!); var adminClientConfig = new AdminClientConfig() { BootstrapServers = configuration["Kafka:BootstrapServers"], @@ -113,6 +114,37 @@ namespace JiShe.CollectBus.Kafka.AdminClient return metadata.Topics.Any(t => t.Topic == topic); } + /// + /// 检测分区是否存在 + /// + /// + /// + /// + public Dictionary CheckPartitionsExists(string topic, int[] partitions) + { + var result = new Dictionary(); + var metadata = Instance.GetMetadata(topic, TimeSpan.FromSeconds(10)); + if (metadata.Topics.Count == 0) + return partitions.ToDictionary(p => p, p => false); + var existingPartitions = metadata.Topics[0].Partitions.Select(p => p.PartitionId).ToHashSet(); + return partitions.ToDictionary(p => p, p => existingPartitions.Contains(p)); + } + + /// + /// 检测分区是否存在 + /// + /// + /// + /// + public bool CheckPartitionsExist(string topic, int targetPartition) + { + var metadata = Instance.GetMetadata(topic, TimeSpan.FromSeconds(10)); + if (metadata.Topics.Count == 0) + return false; + var partitions = metadata.Topics[0].Partitions; + return partitions.Any(p => p.PartitionId == targetPartition); + } + public void Dispose() { Instance?.Dispose(); diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs b/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs index c3d332d..00e51b3 100644 --- a/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs @@ -12,5 +12,21 @@ namespace JiShe.CollectBus.Kafka.AdminClient Task DeleteTopicAsync(string topic); Task> ListTopicsAsync(); Task TopicExistsAsync(string topic); + + /// + /// 检测分区是否存在 + /// + /// + /// + /// + Dictionary CheckPartitionsExists(string topic, int[] partitions); + + /// + /// 检测分区是否存在 + /// + /// + /// + /// + bool CheckPartitionsExist(string topic, int targetPartition); } } diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index 61cc788..a73eb79 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -1,5 +1,6 @@ using Confluent.Kafka; using JiShe.CollectBus.Kafka.Consumer; +using JiShe.CollectBus.Kafka.Producer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; @@ -10,6 +11,10 @@ namespace JiShe.CollectBus.Kafka { public override void ConfigureServices(ServiceConfigurationContext context) { + // 注册Producer + context.Services.AddTransient(typeof(IProducerService<,>), typeof(ProducerService<,>)); + // 注册Consumer + context.Services.AddTransient(typeof(IConsumerService<,>), typeof(ConsumerService<,>)); } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index 37efe3a..34d63fb 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -9,7 +9,7 @@ using static Confluent.Kafka.ConfigPropertyNames; namespace JiShe.CollectBus.Kafka.Consumer { - public abstract class ConsumerService : IConsumerService, IDisposable, ISingletonDependency + public abstract class ConsumerService : IConsumerService, IDisposable { private readonly ILogger> _logger; private CancellationTokenSource _cancellationTokenSource; @@ -25,11 +25,15 @@ namespace JiShe.CollectBus.Kafka.Consumer public IConsumer GetInstance(IConfiguration configuration) { - var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]); + + ArgumentNullException.ThrowIfNullOrWhiteSpace(configuration["Kafka:EnableAuthorization"]); + var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]!); var consumerConfig = new ConsumerConfig { BootstrapServers = configuration["Kafka:BootstrapServers"], - AutoOffsetReset = AutoOffsetReset.Earliest + AutoOffsetReset = AutoOffsetReset.Earliest, + EnableAutoCommit = false, // 禁止AutoCommit + Acks = Acks.All, // 需要所有副本响应才算消费完成 }; if (enableAuthorization) diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs index 0cfed2e..c3e65f6 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs @@ -11,7 +11,7 @@ using Volo.Abp.DependencyInjection; namespace JiShe.CollectBus.Kafka.Producer { - public class ProducerService : IProducerService, IDisposable,ITransientDependency + public class ProducerService : IProducerService, IDisposable { private readonly ILogger> _logger; @@ -27,11 +27,18 @@ namespace JiShe.CollectBus.Kafka.Producer public IProducer GetInstance(IConfiguration configuration) { - var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]); + ArgumentNullException.ThrowIfNullOrWhiteSpace(configuration["Kafka:EnableAuthorization"]); + var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]!); var consumerConfig = new ProducerConfig { BootstrapServers = configuration["Kafka:BootstrapServers"], - AllowAutoCreateTopics = true + AllowAutoCreateTopics = true, + QueueBufferingMaxKbytes = 2097151, // 修改缓冲区最大为2GB,默认为1GB + CompressionType = CompressionType.Lz4, // 配置使用压缩算法LZ4,其他:gzip/snappy/zstd + BatchSize = 32768, // 修改批次大小为32K + LingerMs = 20, // 修改等待时间为20ms + Acks = Acks.All, // 表明只有所有副本Broker都收到消息才算提交成功 + MessageSendMaxRetries = 50, // 消息发送失败最大重试50次 }; if (enableAuthorization) -- 2.47.2 From 12848983765ea4a554834293970001c90a572609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E7=9B=8A?= Date: Mon, 14 Apr 2025 21:56:24 +0800 Subject: [PATCH 083/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=EF=BC=8C=E4=BD=BF=E7=94=A8=E7=BA=BF=E7=A8=8B?= =?UTF-8?q?=E5=88=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 357 +++++++++--------- .../DeviceGroupBalanceControl.cs | 114 ++++-- 2 files changed, 264 insertions(+), 207 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index ce7b39b..7e7626c 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -3,6 +3,7 @@ using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.BuildSendDatas; using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.DeviceBalanceControl; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; @@ -82,7 +83,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// /// public virtual async Task CreateToBeIssueTasks() - { + { var redisCacheKey = $"{RedisConst.CacheBasicDirectoryKey}{SystemType}:{ServerTagName}:TaskInfo:*"; var taskInfos = await FreeRedisProvider.Instance.KeysAsync(redisCacheKey); if (taskInfos == null || taskInfos.Length <= 0) @@ -98,7 +99,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { _logger.LogWarning($"{nameof(CreateToBeIssueTasks)} 构建待处理的下发指令任务处理时Key=>{item}没有缓存数据,102"); continue; - } + } //item 为 CacheTasksToBeIssuedKey 对应的缓存待下发的指令生产任务数据Redis Key tempArryay[0]=>CollectBus,tempArryay[1]=>SystemTypeConst,tempArryay[2]=>TaskInfo,tempArryay[3]=>表计类别,tempArryay[4]=>采集频率 var tempArryay = item.Split(":"); @@ -128,19 +129,23 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (meteryType == MeterTypeEnum.Ammeter.ToString()) { // 解析结果(结果为嵌套数组) - var meterInfos = await GetMeterRedisCacheDictionaryData(oneMinutekeyList, SystemType, ServerTagName, $"{timeDensity}", meterTypes[meteryType]); + var meterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, SystemType, ServerTagName, $"{timeDensity}", meterTypes[meteryType]); if (meterInfos == null || meterInfos.Count <= 0) { _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-105"); return; } - await AmmerterScheduledMeterReadingIssued(timeDensity, meterInfos); - - DeviceGroupBalanceControl.ProcessAllGroups(deviceId => { - Console.WriteLine($"Processing {deviceId} on Thread {Thread.CurrentThread.ManagedThreadId}"); - }); - - await AmmerterCreatePublishTask(timeDensity, meterInfos); + //await AmmerterScheduledMeterReadingIssued(timeDensity, meterInfos); + + // 处理数据 + await DeviceGroupBalanceControl.ProcessGenericListAsync( + items: meterInfos, + deviceIdSelector: data => data.FocusAddress, + processor: (data, threadId) => + { + _= AmmerterCreatePublishTask(timeDensity, data); + } + ); } else if (meteryType == MeterTypeEnum.WaterMeter.ToString()) { @@ -567,7 +572,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading //根据分组创建线程批处理集中器 foreach (var group in focusHashGroups) { - await AmmerterCreatePublishTask(timeDensity, group.Value); + await AmmerterCreatePublishTask2(timeDensity, group.Value); } } catch (Exception) @@ -582,10 +587,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// 电表创建发布任务 /// /// 采集频率 - /// 集中器号hash分组的集中器集合数据 + /// 集中器号hash分组的集中器集合数据 /// private async Task AmmerterCreatePublishTask(int timeDensity - , List ammeterGroup) + , AmmeterInfo ammeterInfo) { var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; //todo 检查需要待补抄的电表的时间点信息,保存到需要待补抄的缓存中。如果此线程异常,该如何补偿? @@ -593,171 +598,167 @@ namespace JiShe.CollectBus.ScheduledMeterReading var currentTime = DateTime.Now; var pendingCopyReadTime = currentTime.AddMinutes(timeDensity); //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 - var redisCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}{ammeterInfo.Key}"; - - foreach (var ammeterInfo in ammeterGroup) + var redisCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}{ammeterInfo.FocusAddress}"; + + if (string.IsNullOrWhiteSpace(ammeterInfo.ItemCodes)) { - - if (string.IsNullOrWhiteSpace(ammeterInfo.ItemCodes)) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,采集项为空,-101"); - continue; - } - - //载波的不处理 - if (ammeterInfo.MeteringPort == (int)MeterLinkProtocolEnum.Carrierwave) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,载波不处理,-102"); - continue; - } - - if (ammeterInfo.State.Equals(2)) - { - _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} {ammeterInfo.Name} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}状态为禁用,不处理"); - continue; - } - - ////排除1天未在线的集中器生成指令 或 排除集中器配置为自动上报的集中器 - //if (!IsGennerateCmd(ammeter.LastTime, -1)) - //{ - // _logger.LogInformation($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name},采集时间:{ammeter.LastTime},已超过1天未在线,不生成指令"); - // continue; - //} - - if (string.IsNullOrWhiteSpace(ammeterInfo.AreaCode)) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信区号为空"); - continue; - } - if (string.IsNullOrWhiteSpace(ammeterInfo.Address)) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址为空"); - continue; - } - if (Convert.ToInt32(ammeterInfo.Address) > 65535) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址无效,确保大于65535"); - continue; - } - if (ammeterInfo.MeteringCode <= 0 || ammeterInfo.MeteringCode > 2033) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},非有效测量点号({ammeterInfo.MeteringCode})"); - continue; - } - - List tempCodes = ammeterInfo.ItemCodes.Deserialize>()!; - - //TODO:自动上报数据只主动采集1类数据。 - if (ammeterInfo.AutomaticReport.Equals(1)) - { - var tempSubCodes = new List(); - if (tempCodes.Contains("0C_49")) - { - tempSubCodes.Add("0C_49"); - } - - if (tempSubCodes.Contains("0C_149")) - { - tempSubCodes.Add("0C_149"); - } - - if (ammeterInfo.ItemCodes.Contains("10_97")) - { - tempSubCodes.Add("10_97"); - } - - if (tempSubCodes == null || tempSubCodes.Count <= 0) - { - _logger.LogInformation($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}自动上报数据主动采集1类数据时数据类型为空"); - continue; - } - else - { - tempCodes = tempSubCodes; - } - } - - Dictionary keyValuePairs = new Dictionary(); - - foreach (var tempItem in tempCodes) - { - //排除已发送日冻结和月冻结采集项配置 - if (DayFreezeCodes.Contains(tempItem)) - { - continue; - } - - if (MonthFreezeCodes.Contains(tempItem)) - { - continue; - } - - var itemCodeArr = tempItem.Split('_'); - var aFNStr = itemCodeArr[0]; - var aFN = (AFN)aFNStr.HexToDec(); - var fn = int.Parse(itemCodeArr[1]); - byte[] dataInfos = null; - if (ammeterInfo.AutomaticReport.Equals(1) && aFN == AFN.请求实时数据) - { - //实时数据 - dataInfos = Build3761SendData.BuildAmmeterReadRealTimeDataSendCmd(ammeterInfo.FocusAddress, ammeterInfo.MeteringCode, (ATypeOfDataItems)fn); - } - else - { - string methonCode = $"AFN{aFNStr}_Fn_Send"; - //特殊表暂不处理 - if (handlerPacketBuilder != null && handlerPacketBuilder.TryGetValue(methonCode - , out var handler)) - { - dataInfos = handler(new TelemetryPacketRequest() - { - FocusAddress = ammeterInfo.FocusAddress, - Fn = fn, - Pn = ammeterInfo.MeteringCode - }); - } - else - { - _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}采集项{tempItem}无效编码。"); - continue; - } - } - //TODO:特殊表 - - if (dataInfos == null || dataInfos.Length <= 0) - { - _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}采集项{tempItem}未能正确获取报文。"); - continue; - } - - - - var meterReadingRecords = new MeterReadingRecords() - { - ProjectID = ammeterInfo.ProjectID, - DatabaseBusiID = ammeterInfo.DatabaseBusiID, - PendingCopyReadTime = pendingCopyReadTime, - CreationTime = currentTime, - MeterAddress = ammeterInfo.AmmerterAddress, - MeterId = ammeterInfo.ID, - MeterType = MeterTypeEnum.Ammeter, - FocusAddress = ammeterInfo.FocusAddress, - FocusID = ammeterInfo.FocusID, - AFN = aFN, - Fn = fn, - ItemCode = tempItem, - TaskMark = CommonHelper.GetTaskMark((int)aFN, fn, ammeterInfo.MeteringCode), - ManualOrNot = false, - Pn = ammeterInfo.MeteringCode, - IssuedMessageId = GuidGenerator.Create().ToString(), - IssuedMessageHexString = Convert.ToHexString(dataInfos), - }; - meterReadingRecords.CreateDataId(GuidGenerator.Create()); - - keyValuePairs.TryAdd($"{ammeterInfo.ID}_{tempItem}", meterReadingRecords); - } - await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,采集项为空,-101"); + return; } + + //载波的不处理 + if (ammeterInfo.MeteringPort == (int)MeterLinkProtocolEnum.Carrierwave) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,载波不处理,-102"); + return; + } + + if (ammeterInfo.State.Equals(2)) + { + _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} {ammeterInfo.Name} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}状态为禁用,不处理"); + return; + } + + ////排除1天未在线的集中器生成指令 或 排除集中器配置为自动上报的集中器 + //if (!IsGennerateCmd(ammeter.LastTime, -1)) + //{ + // _logger.LogInformation($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name},采集时间:{ammeter.LastTime},已超过1天未在线,不生成指令"); + // continue; + //} + + if (string.IsNullOrWhiteSpace(ammeterInfo.AreaCode)) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信区号为空"); + return; + } + if (string.IsNullOrWhiteSpace(ammeterInfo.Address)) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址为空"); + return; + } + if (Convert.ToInt32(ammeterInfo.Address) > 65535) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址无效,确保大于65535"); + return; + } + if (ammeterInfo.MeteringCode <= 0 || ammeterInfo.MeteringCode > 33) + { + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},非有效测量点号({ammeterInfo.MeteringCode})"); + return; + } + + List tempCodes = ammeterInfo.ItemCodes.Deserialize>()!; + + //TODO:自动上报数据只主动采集1类数据。 + if (ammeterInfo.AutomaticReport.Equals(1)) + { + var tempSubCodes = new List(); + if (tempCodes.Contains("0C_49")) + { + tempSubCodes.Add("0C_49"); + } + + if (tempSubCodes.Contains("0C_149")) + { + tempSubCodes.Add("0C_149"); + } + + if (ammeterInfo.ItemCodes.Contains("10_97")) + { + tempSubCodes.Add("10_97"); + } + + if (tempSubCodes == null || tempSubCodes.Count <= 0) + { + _logger.LogInformation($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}自动上报数据主动采集1类数据时数据类型为空"); + return; + } + else + { + tempCodes = tempSubCodes; + } + } + + Dictionary keyValuePairs = new Dictionary(); + + foreach (var tempItem in tempCodes) + { + //排除已发送日冻结和月冻结采集项配置 + if (DayFreezeCodes.Contains(tempItem)) + { + continue; + } + + if (MonthFreezeCodes.Contains(tempItem)) + { + continue; + } + + var itemCodeArr = tempItem.Split('_'); + var aFNStr = itemCodeArr[0]; + var aFN = (AFN)aFNStr.HexToDec(); + var fn = int.Parse(itemCodeArr[1]); + byte[] dataInfos = null; + if (ammeterInfo.AutomaticReport.Equals(1) && aFN == AFN.请求实时数据) + { + //实时数据 + dataInfos = Build3761SendData.BuildAmmeterReadRealTimeDataSendCmd(ammeterInfo.FocusAddress, ammeterInfo.MeteringCode, (ATypeOfDataItems)fn); + } + else + { + string methonCode = $"AFN{aFNStr}_Fn_Send"; + //特殊表暂不处理 + if (handlerPacketBuilder != null && handlerPacketBuilder.TryGetValue(methonCode + , out var handler)) + { + dataInfos = handler(new TelemetryPacketRequest() + { + FocusAddress = ammeterInfo.FocusAddress, + Fn = fn, + Pn = ammeterInfo.MeteringCode + }); + } + else + { + _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}采集项{tempItem}无效编码。"); + continue; + } + } + //TODO:特殊表 + + if (dataInfos == null || dataInfos.Length <= 0) + { + _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}采集项{tempItem}未能正确获取报文。"); + continue; + } + + + + var meterReadingRecords = new MeterReadingRecords() + { + ProjectID = ammeterInfo.ProjectID, + DatabaseBusiID = ammeterInfo.DatabaseBusiID, + PendingCopyReadTime = pendingCopyReadTime, + CreationTime = currentTime, + MeterAddress = ammeterInfo.AmmerterAddress, + MeterId = ammeterInfo.ID, + MeterType = MeterTypeEnum.Ammeter, + FocusAddress = ammeterInfo.FocusAddress, + FocusID = ammeterInfo.FocusID, + AFN = aFN, + Fn = fn, + ItemCode = tempItem, + TaskMark = CommonHelper.GetTaskMark((int)aFN, fn, ammeterInfo.MeteringCode), + ManualOrNot = false, + Pn = ammeterInfo.MeteringCode, + IssuedMessageId = GuidGenerator.Create().ToString(), + IssuedMessageHexString = Convert.ToHexString(dataInfos), + }; + meterReadingRecords.CreateDataId(GuidGenerator.Create()); + + keyValuePairs.TryAdd($"{ammeterInfo.ID}_{tempItem}", meterReadingRecords); + } + await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); } /// @@ -1033,7 +1034,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheDictionaryData(oneMinutekeyList,SystemType,ServerTagName ,timeDensity.ToString(), MeterTypeEnum.WaterMeter); + Dictionary> meterTaskInfos = await GetMeterRedisCacheDictionaryData(oneMinutekeyList, SystemType, ServerTagName, timeDensity.ToString(), MeterTypeEnum.WaterMeter); if (meterTaskInfos == null || meterTaskInfos.Count <= 0) { _logger.LogError($"{nameof(WatermeterScheduledMeterAutoReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); @@ -1081,7 +1082,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading #region 公共处理方法 - + /// /// 判断是否需要生成采集指令 diff --git a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs index 13d51a8..c7a5acd 100644 --- a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs +++ b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs @@ -108,48 +108,104 @@ namespace JiShe.CollectBus.Common.DeviceBalanceControl } /// - /// 并行处理所有分组设备(每个分组一个处理线程) + /// 并行处理泛型数据集(支持动态线程分配) /// - public static void ProcessAllGroups(Action> processAction) where T : DeviceGroupBasicModel + /// 已经分组的设备信息 + /// 部分或者全部的已经分组的设备集合 + /// 从泛型对象提取deviceId + /// 处理委托(参数:当前对象,线程ID) + /// 可选线程限制 + /// + /// + public static async Task ProcessGenericListAsync( + List items, Func deviceIdSelector, Action processor, int? maxThreads = null) { - var cache = _currentCache; - if (cache == null) - throw new InvalidOperationException("缓存未初始化"); + var cache = _currentCache ?? throw new InvalidOperationException("缓存未初始化"); - // 使用并行选项控制并发度 + // 创建分组任务队列 + var groupQueues = new ConcurrentQueue[cache.CachedGroups.Length]; + for (int i = 0; i < groupQueues.Length; i++) + { + groupQueues[i] = new ConcurrentQueue(); + } + + // 阶段1:分发数据到分组队列 + Parallel.ForEach(items, item => + { + var deviceId = deviceIdSelector(item); + if (cache.BalancedMapping.TryGetValue(deviceId, out int groupId)) + { + groupQueues[groupId].Enqueue(item); + } + }); + + if ((maxThreads.HasValue && maxThreads.Value > cache.CachedGroups.Length) || maxThreads.HasValue == false) + { + maxThreads = cache.CachedGroups.Length; + } + + // 阶段2:并行处理队列 var options = new ParallelOptions { - MaxDegreeOfParallelism = cache.CachedGroups.Length // 严格匹配分组数量 + MaxDegreeOfParallelism = maxThreads.Value, }; - Parallel.For(0, cache.CachedGroups.Length, options, groupId => + await Task.Run(() => { - // 获取当前分组的只读副本 - var groupDevices = GetGroupSnapshot(cache, groupId); - - processAction(groupDevices); - - //foreach (var deviceId in groupDevices) - //{ - // //执行处理操作 - // processAction(deviceId); - - // // 可添加取消检测 - // // if (token.IsCancellationRequested) break; - //} + Parallel.For(0, cache.CachedGroups.Length, options, groupId => + { + var queue = groupQueues[groupId]; + while (queue.TryDequeue(out T item)) + { + processor(item, Thread.CurrentThread.ManagedThreadId); + } + }); }); } /// - /// 获取分组数据快照(线程安全) + /// 并行处理所有分组设备(每个分组一个处理线程) /// - private static IReadOnlyList GetGroupSnapshot(CacheState cache, int groupId) - { - lock (cache.CachedGroups[groupId]) - { - return cache.CachedGroups[groupId].ToList(); // 创建内存快照 - } - } + //public static void ProcessAllGroups(Action> processAction) where T : DeviceGroupBasicModel + //{ + // var cache = _currentCache; + // if (cache == null) + // throw new InvalidOperationException("缓存未初始化"); + + // // 使用并行选项控制并发度 + // var options = new ParallelOptions + // { + // MaxDegreeOfParallelism = cache.CachedGroups.Length // 严格匹配分组数量 + // }; + + // Parallel.For(0, cache.CachedGroups.Length, options, groupId => + // { + // // 获取当前分组的只读副本 + // var groupDevices = GetGroupSnapshot(cache, groupId); + + // processAction(groupDevices); + + // //foreach (var deviceId in groupDevices) + // //{ + // // //执行处理操作 + // // processAction(deviceId); + + // // // 可添加取消检测 + // // // if (token.IsCancellationRequested) break; + // //} + // }); + //} + + ///// + ///// 获取分组数据快照(线程安全) + ///// + //public static IReadOnlyList GetGroupSnapshot(CacheState cache, int groupId) + //{ + // lock (cache.CachedGroups[groupId]) + // { + // return cache.CachedGroups[groupId].ToList(); // 创建内存快照 + // } + //} /// /// 通过 deviceId 获取所在的分组集合 -- 2.47.2 From 142f864544feddd3af5327abc49291fed04ecc7f Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Mon, 14 Apr 2025 23:42:18 +0800 Subject: [PATCH 084/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E7=94=B5=E8=A1=A8=E4=BF=A1=E6=81=AF=E8=8E=B7=E5=8F=96=EF=BC=8C?= =?UTF-8?q?=E9=87=87=E7=94=A8=E5=88=86=E9=A1=B5=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusAppService.cs | 198 ++++++++++++------ .../Samples/SampleAppService.cs | 2 +- .../BasicScheduledMeterReadingService.cs | 36 ++-- ...nergySystemScheduledMeterReadingService.cs | 1 + .../Workers/CreateToBeIssueTaskWorker.cs | 4 +- 5 files changed, 156 insertions(+), 85 deletions(-) diff --git a/src/JiShe.CollectBus.Application/CollectBusAppService.cs b/src/JiShe.CollectBus.Application/CollectBusAppService.cs index 467b487..f84d82f 100644 --- a/src/JiShe.CollectBus.Application/CollectBusAppService.cs +++ b/src/JiShe.CollectBus.Application/CollectBusAppService.cs @@ -9,6 +9,7 @@ using JiShe.CollectBus.Serializer; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Volo.Abp.Application.Services; @@ -38,57 +39,87 @@ public abstract class CollectBusAppService : ApplicationService /// protected async Task>> GetMeterRedisCacheDictionaryData(string[] redisKeys, string systemType, string serverTagName, string timeDensity, MeterTypeEnum meterType) where T : class { - if (redisKeys == null || redisKeys.Length <=0 || string.IsNullOrWhiteSpace(systemType) || string.IsNullOrWhiteSpace(serverTagName) || string.IsNullOrWhiteSpace(timeDensity)) + if (redisKeys == null || redisKeys.Length <= 0 || string.IsNullOrWhiteSpace(systemType) || string.IsNullOrWhiteSpace(serverTagName) || string.IsNullOrWhiteSpace(timeDensity)) { throw new Exception($"{nameof(GetMeterRedisCacheDictionaryData)} 获取缓存的表计信息失败,参数异常,-101"); } - //通过lua脚本一次性获取所有缓存内容 + var meterInfos = new Dictionary>(); var luaScript = @" - local results = {} - for i, key in ipairs(KEYS) do - local data = redis.call('HGETALL', key) - results[i] = {key, data} - end - return results"; - var merterResult = await FreeRedisProvider.Instance.EvalAsync(luaScript, redisKeys); //传递 KEYS - if (merterResult == null) - { - throw new Exception($"{nameof(GetMeterRedisCacheDictionaryData)} 获取缓存的表计信息失败,没有获取到数据,-102"); - } + local results = {} + for i, key in ipairs(KEYS) do + local data = redis.call('HGETALL', key) + results[i] = {key, data} + end + return results"; - // 解析结果(结果为嵌套数组) - var meterInfos = new Dictionary>(); ; - if (merterResult is object[] arr) + // 分页参数:每页处理10000个键 + int pageSize = 10000; + int totalPages = (int)Math.Ceiling(redisKeys.Length / (double)pageSize); + + for (int page = 0; page < totalPages; page++) { - foreach (object[] item in arr) + // 分页获取当前批次的键 + var batchKeys = redisKeys + .Skip(page * pageSize) + .Take(pageSize) + .ToArray(); + + // 执行Lua脚本获取当前批次数据 + var merterResult = await FreeRedisProvider.Instance.EvalAsync(luaScript, batchKeys); + if (merterResult == null) { - string key = (string)item[0];//集中器地址对应的Redis缓存Key - object[] fieldsAndValues = (object[])item[1];//缓存Key对应的Hash表数据集合 - var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, systemType, serverTagName, meterType, timeDensity)}"; - string focusAddress = key.Replace(redisCacheKey, "");//集中器地址 + throw new Exception($"{nameof(GetMeterRedisCacheDictionaryData)} 获取缓存的表计信息失败,第 {page + 1} 页数据未返回,-102"); + } - var meterHashs = new Dictionary(); - for (int i = 0; i < fieldsAndValues.Length; i += 2) + // 解析当前批次的结果 + if (merterResult is object[] arr) + { + foreach (object[] item in arr) { - string meterld = (string)fieldsAndValues[i];//表ID - string meterStr = (string)fieldsAndValues[i + 1];//表详情数据 + string key = (string)item[0]; + object[] fieldsAndValues = (object[])item[1]; + var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, systemType, serverTagName, meterType, timeDensity)}"; + string focusAddress = key.Replace(redisCacheKey, ""); - T meterInfo = default!; - if (!string.IsNullOrWhiteSpace(meterStr)) + var meterHashs = new Dictionary(); + for (int i = 0; i < fieldsAndValues.Length; i += 2) { - meterInfo = meterStr.Deserialize()!; + string meterId = (string)fieldsAndValues[i]; + string meterStr = (string)fieldsAndValues[i + 1]; + + T meterInfo = default!; + if (!string.IsNullOrWhiteSpace(meterStr)) + { + meterInfo = meterStr.Deserialize()!; + } + if (meterInfo != null) + { + meterHashs[meterId] = meterInfo; + } + else + { + throw new Exception($"{nameof(GetMeterRedisCacheDictionaryData)} 缓存表计数据异常,集中器 {key} 的表计 {meterId} 解析失败,-103"); + } } - if (meterInfo != null) + + // 合并到总结果,若存在重复key则覆盖 + if (meterInfos.ContainsKey(focusAddress)) { - meterHashs[meterld] = meterInfo; + foreach (var kvp in meterHashs) + { + meterInfos[focusAddress][kvp.Key] = kvp.Value; + } } else { - throw new Exception($"{nameof(GetMeterRedisCacheDictionaryData)} 获取缓存的表计信息集中器缓存{key}数据的{meterld}处理异常,-102"); + meterInfos[focusAddress] = meterHashs; } } - meterInfos[focusAddress] = meterHashs; + } + else + { + throw new Exception($"{nameof(GetMeterRedisCacheDictionaryData)} 第 {page + 1} 页数据解析失败,返回类型不符,-104"); } } @@ -105,58 +136,87 @@ public abstract class CollectBusAppService : ApplicationService /// 采集频率,1分钟、5分钟、15分钟 /// 表计类型 /// - protected async Task> GetMeterRedisCacheListData(string[] redisKeys,string systemType,string serverTagName, string timeDensity, MeterTypeEnum meterType) where T : class + protected async Task> GetMeterRedisCacheListData(string[] redisKeys, string systemType, string serverTagName, string timeDensity, MeterTypeEnum meterType) where T : class { - if (redisKeys == null || redisKeys.Length <= 0 || string.IsNullOrWhiteSpace(systemType) || string.IsNullOrWhiteSpace(serverTagName) || string.IsNullOrWhiteSpace(timeDensity)) + if (redisKeys == null || redisKeys.Length <= 0 || + string.IsNullOrWhiteSpace(systemType) || + string.IsNullOrWhiteSpace(serverTagName) || + string.IsNullOrWhiteSpace(timeDensity)) { - throw new Exception($"{nameof(GetMeterRedisCacheListData)} 获取缓存的表计信息失败,参数异常,-101"); + throw new Exception($"{nameof(GetMeterRedisCacheListData)} 参数异常,-101"); } - //通过lua脚本一次性获取所有缓存内容 + var meterInfos = new List(); var luaScript = @" - local results = {} - for i, key in ipairs(KEYS) do - local data = redis.call('HGETALL', key) - results[i] = {key, data} - end - return results"; - var merterResult = await FreeRedisProvider.Instance.EvalAsync(luaScript, redisKeys); //传递 KEYS - if (merterResult == null) - { - throw new Exception($"{nameof(GetMeterRedisCacheListData)} 获取缓存的表计信息失败,没有获取到数据,-102"); - } + local results = {} + for i, key in ipairs(KEYS) do + local data = redis.call('HGETALL', key) + results[i] = {key, data} + end + return results"; - // 解析结果(结果为嵌套数组) - var meterInfos = new List(); ; - if (merterResult is object[] arr) + // 分页参数:每页10000个键 + int pageSize = 10000; + int totalPages = (int)Math.Ceiling(redisKeys.Length / (double)pageSize); + + for (int page = 0; page < totalPages; page++) { - foreach (object[] item in arr) + // 分页获取当前批次键 + var batchKeys = redisKeys + .Skip(page * pageSize) + .Take(pageSize) + .ToArray(); + + // 执行Lua脚本获取当前页数据 + var merterResult = await FreeRedisProvider.Instance.EvalAsync(luaScript, batchKeys); + if (merterResult == null) { - string key = (string)item[0];//集中器地址对应的Redis缓存Key - object[] fieldsAndValues = (object[])item[1];//缓存Key对应的Hash表数据集合 - var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, systemType, serverTagName, meterType, timeDensity)}"; - string focusAddress = key.Replace(redisCacheKey, "");//集中器地址 + throw new Exception($"{nameof(GetMeterRedisCacheListData)} 第 {page + 1} 页数据未返回,-102"); + } - for (int i = 0; i < fieldsAndValues.Length; i += 2) + // 解析当前页结果 + if (merterResult is object[] arr) + { + foreach (object[] item in arr) { - string meterld = (string)fieldsAndValues[i];//表ID - string meterStr = (string)fieldsAndValues[i + 1];//表详情数据 + string key = (string)item[0]; + object[] fieldsAndValues = (object[])item[1]; + var redisCacheKey = string.Format( + RedisConst.CacheMeterInfoKey, + systemType, + serverTagName, + meterType, + timeDensity + ); + string focusAddress = key.Replace(redisCacheKey, ""); - T meterInfo = default!; - if (!string.IsNullOrWhiteSpace(meterStr)) + for (int i = 0; i < fieldsAndValues.Length; i += 2) { - meterInfo = meterStr.Deserialize()!; - } - if (meterInfo != null) - { - meterInfos.Add(meterInfo); - } - else - { - throw new Exception($"{nameof(GetMeterRedisCacheListData)} 获取缓存的表计信息集中器缓存{key}数据的{meterld}处理异常,-103"); + string meterId = (string)fieldsAndValues[i]; + string meterStr = (string)fieldsAndValues[i + 1]; + + T meterInfo = default!; + if (!string.IsNullOrWhiteSpace(meterStr)) + { + meterInfo = meterStr.Deserialize()!; + } + if (meterInfo != null) + { + meterInfos.Add(meterInfo); + } + else + { + throw new Exception( + $"{nameof(GetMeterRedisCacheListData)} 表计 {meterId} 解析失败(页 {page + 1}),-103" + ); + } } } } + else + { + throw new Exception($"{nameof(GetMeterRedisCacheListData)} 第 {page + 1} 页数据格式错误,-104"); + } } return meterInfos; diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index ac09237..7013d37 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -13,13 +13,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using JiShe.CollectBus.IoTDBProvider.Context; using Microsoft.Extensions.Logging; -using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.IotSystems.AFNEntity; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using Microsoft.Extensions.DependencyInjection; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Enums; using System.Diagnostics.Metrics; +using JiShe.CollectBus.Common.DeviceBalanceControl; namespace JiShe.CollectBus.Samples; diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 7e7626c..df03914 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -103,8 +103,12 @@ namespace JiShe.CollectBus.ScheduledMeterReading //item 为 CacheTasksToBeIssuedKey 对应的缓存待下发的指令生产任务数据Redis Key tempArryay[0]=>CollectBus,tempArryay[1]=>SystemTypeConst,tempArryay[2]=>TaskInfo,tempArryay[3]=>表计类别,tempArryay[4]=>采集频率 var tempArryay = item.Split(":"); - string meteryType = tempArryay[3];//表计类别 - int timeDensity = Convert.ToInt32(tempArryay[4]);//采集频率 + string meteryType = tempArryay[4];//表计类别 + int timeDensity = Convert.ToInt32(tempArryay[5]);//采集频率 + if(timeDensity > 15) + { + timeDensity = 15; + } //检查任务时间节点,由于定时任务10秒钟运行一次,需要判定当前时间是否在任务时间节点内,不在则跳过 if (!IsTaskTime(tasksToBeIssueModel.NextTaskTime, timeDensity)) @@ -128,6 +132,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (meteryType == MeterTypeEnum.Ammeter.ToString()) { + var timer = Stopwatch.StartNew(); // 解析结果(结果为嵌套数组) var meterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, SystemType, ServerTagName, $"{timeDensity}", meterTypes[meteryType]); if (meterInfos == null || meterInfos.Count <= 0) @@ -136,16 +141,21 @@ namespace JiShe.CollectBus.ScheduledMeterReading return; } //await AmmerterScheduledMeterReadingIssued(timeDensity, meterInfos); - - // 处理数据 - await DeviceGroupBalanceControl.ProcessGenericListAsync( - items: meterInfos, - deviceIdSelector: data => data.FocusAddress, - processor: (data, threadId) => - { - _= AmmerterCreatePublishTask(timeDensity, data); - } - ); + + + //处理数据 + //await DeviceGroupBalanceControl.ProcessGenericListAsync( + // items: meterInfos, + // deviceIdSelector: data => data.FocusAddress, + // processor: (data, threadId) => + // { + // _= AmmerterCreatePublishTask(timeDensity, data); + // } + //); + + timer.Stop(); + _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建完成,{timer.ElapsedMilliseconds},{oneMinutekeyList.Length}"); + } else if (meteryType == MeterTypeEnum.WaterMeter.ToString()) { @@ -758,7 +768,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading keyValuePairs.TryAdd($"{ammeterInfo.ID}_{tempItem}", meterReadingRecords); } - await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); + // await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); } /// diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 62ede1d..cba4f41 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.DeviceBalanceControl; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.GatherItem; diff --git a/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs b/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs index 05fd90d..2857422 100644 --- a/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs @@ -27,14 +27,14 @@ namespace JiShe.CollectBus.Workers { _logger = logger; RecurringJobId = nameof(CreateToBeIssueTaskWorker); - CronExpression = $"{10}/* * * * *"; + CronExpression = $"*/{1} * * * *"; this._scheduledMeterReadingService = scheduledMeterReadingService; } public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { - await _scheduledMeterReadingService.CreateToBeIssueTasks(); + // await _scheduledMeterReadingService.CreateToBeIssueTasks(); } } } -- 2.47.2 From 5afed96fb740e89c686431ea3901b95f76d5814b Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Tue, 15 Apr 2025 09:43:51 +0800 Subject: [PATCH 085/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E5=AD=98=E5=85=A5Redis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 59 +++++++++++++++---- .../DeviceGroupBalanceControl.cs | 4 +- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index df03914..914bfa1 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -1,4 +1,5 @@ -using DeviceDetectorNET.Class.Client; +using Confluent.Kafka; +using DeviceDetectorNET.Class.Client; using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.BuildSendDatas; @@ -105,7 +106,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading var tempArryay = item.Split(":"); string meteryType = tempArryay[4];//表计类别 int timeDensity = Convert.ToInt32(tempArryay[5]);//采集频率 - if(timeDensity > 15) + if (timeDensity > 15) { timeDensity = 15; } @@ -132,7 +133,6 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (meteryType == MeterTypeEnum.Ammeter.ToString()) { - var timer = Stopwatch.StartNew(); // 解析结果(结果为嵌套数组) var meterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, SystemType, ServerTagName, $"{timeDensity}", meterTypes[meteryType]); if (meterInfos == null || meterInfos.Count <= 0) @@ -141,17 +141,40 @@ namespace JiShe.CollectBus.ScheduledMeterReading return; } //await AmmerterScheduledMeterReadingIssued(timeDensity, meterInfos); - + + var timer = Stopwatch.StartNew(); //处理数据 - //await DeviceGroupBalanceControl.ProcessGenericListAsync( - // items: meterInfos, - // deviceIdSelector: data => data.FocusAddress, - // processor: (data, threadId) => - // { - // _= AmmerterCreatePublishTask(timeDensity, data); + List>> tempDatas = new List>>(); + await DeviceGroupBalanceControl.ProcessGenericListAsync( + items: meterInfos, + deviceIdSelector: data => data.FocusAddress, + processor: (data, threadId) => + { + _ = AmmerterCreatePublishTask(timeDensity, data); + //var keyValuePairs = AmmerterCreatePublishTask(timeDensity, data); + //if (keyValuePairs != null && keyValuePairs.Keys.Count() > 0) + //{ + // //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 + // var redisDataCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}{keyValuePairs.First().Value.FocusAddress}"; + // tempDatas.Add(Tuple.Create(redisDataCacheKey, keyValuePairs)); + // //tempDatas.Add(keyValuePairs); + //} + } + ); + + //_logger.LogError("数据处理完成。"); + + //using (var pipe = FreeRedisProvider.Instance.StartPipe()) + //{ + // _logger.LogError("开始进入管道处理。"); + // foreach (var dataItem in tempDatas) + // { + // pipe.HSet(dataItem.Item1, dataItem.Item2); // } - //); + // object[] ret = pipe.EndPipe(); + //} + //_logger.LogError("管道处理完成。"); timer.Stop(); _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建完成,{timer.ElapsedMilliseconds},{oneMinutekeyList.Length}"); @@ -609,7 +632,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading var pendingCopyReadTime = currentTime.AddMinutes(timeDensity); //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 var redisCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}{ammeterInfo.FocusAddress}"; - + if (string.IsNullOrWhiteSpace(ammeterInfo.ItemCodes)) { _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,采集项为空,-101"); @@ -768,7 +791,17 @@ namespace JiShe.CollectBus.ScheduledMeterReading keyValuePairs.TryAdd($"{ammeterInfo.ID}_{tempItem}", meterReadingRecords); } - // await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); + //TimeSpan timeSpan = TimeSpan.FromMicroseconds(5); + //await Task.Delay(timeSpan); + + //return keyValuePairs; + // await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); + + using (var pipe = FreeRedisProvider.Instance.StartPipe()) + { + pipe.HSet(redisCacheKey, keyValuePairs); + object[] ret = pipe.EndPipe(); + } } /// diff --git a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs index c7a5acd..e9ac9bc 100644 --- a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs +++ b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs @@ -150,13 +150,15 @@ namespace JiShe.CollectBus.Common.DeviceBalanceControl MaxDegreeOfParallelism = maxThreads.Value, }; + TimeSpan timeSpan = TimeSpan.FromMicroseconds(5); await Task.Run(() => { - Parallel.For(0, cache.CachedGroups.Length, options, groupId => + Parallel.For(0, cache.CachedGroups.Length, options, async groupId => { var queue = groupQueues[groupId]; while (queue.TryDequeue(out T item)) { + await Task.Delay(timeSpan); processor(item, Thread.CurrentThread.ManagedThreadId); } }); -- 2.47.2 From cfa2f5b7b7f5167aa1da94b69786227f35c41ca7 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Tue, 15 Apr 2025 11:02:13 +0800 Subject: [PATCH 086/139] =?UTF-8?q?perf=EF=BC=9A=E4=BC=98=E5=8C=96kafka?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JiShe.CollectBus.Kafka.Test.csproj | 31 ---- JiShe.CollectBus.Kafka.Test/Program.cs | 79 ----------- JiShe.CollectBus.Kafka.Test/appsettings.json | 134 ------------------ 3 files changed, 244 deletions(-) delete mode 100644 JiShe.CollectBus.Kafka.Test/JiShe.CollectBus.Kafka.Test.csproj delete mode 100644 JiShe.CollectBus.Kafka.Test/Program.cs delete mode 100644 JiShe.CollectBus.Kafka.Test/appsettings.json diff --git a/JiShe.CollectBus.Kafka.Test/JiShe.CollectBus.Kafka.Test.csproj b/JiShe.CollectBus.Kafka.Test/JiShe.CollectBus.Kafka.Test.csproj deleted file mode 100644 index c7e7af1..0000000 --- a/JiShe.CollectBus.Kafka.Test/JiShe.CollectBus.Kafka.Test.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - - - Always - true - PreserveNewest - - - - - - - - - - - - - - - - - diff --git a/JiShe.CollectBus.Kafka.Test/Program.cs b/JiShe.CollectBus.Kafka.Test/Program.cs deleted file mode 100644 index 36abdb6..0000000 --- a/JiShe.CollectBus.Kafka.Test/Program.cs +++ /dev/null @@ -1,79 +0,0 @@ -// See https://aka.ms/new-console-template for more information -using Confluent.Kafka; -using JiShe.CollectBus.Kafka.AdminClient; -using JiShe.CollectBus.Kafka.Consumer; -using JiShe.CollectBus.Kafka.Producer; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Serilog; -using System.Text.Json; -using static Confluent.Kafka.ConfigPropertyNames; - - - -// 构建配置 -var config = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json") - .Build(); -// 直接读取配置项 -var greeting = config["ServerTagName"]; -Console.WriteLine(greeting); // 输出: Hello, World! - - -// 创建服务容器 -var services = new ServiceCollection(); -// 注册 IConfiguration 实例 -services.AddSingleton(config); - -// 初始化日志 -Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(config) // 从 appsettings.json 读取配置 - .CreateLogger(); - -// 配置日志系统 -services.AddLogging(logging => -{ - logging.ClearProviders(); - logging.AddSerilog(); -}); -services.AddSingleton(); -services.AddTransient(typeof(IProducerService<,>), typeof(ProducerService<,>)); -//services.AddSingleton(typeof(IConsumerService<,>), typeof(ConsumerService<,>)); - -// 构建ServiceProvider -var serviceProvider = services.BuildServiceProvider(); - -// 获取日志记录器工厂 -var loggerFactory = serviceProvider.GetRequiredService(); -var logger = loggerFactory.CreateLogger(); -logger.LogInformation("程序启动"); - -var adminClientService = serviceProvider.GetRequiredService(); - -string topic = "test-topic"; -//await adminClientService.DeleteTopicAsync(topic); -// 创建 topic -await adminClientService.CreateTopicAsync(topic, 3, 3); - - -var producerService = serviceProvider.GetRequiredService>(); -int i = 1; -while (i <= 10) -{ - await producerService.ProduceAsync(topic, JsonSerializer.Serialize(new { topic = topic, val = i })); - i++; -} - -while (true) -{ - var key = Console.ReadKey(intercept: true); // intercept:true 隐藏按键显示 - - if (key.Key == ConsoleKey.Escape) - { - Console.WriteLine("\n程序已退出"); - break; - } -} - (serviceProvider as IDisposable)?.Dispose(); diff --git a/JiShe.CollectBus.Kafka.Test/appsettings.json b/JiShe.CollectBus.Kafka.Test/appsettings.json deleted file mode 100644 index 55c8ef6..0000000 --- a/JiShe.CollectBus.Kafka.Test/appsettings.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "Serilog": { - "Using": [ - "Serilog.Sinks.Console", - "Serilog.Sinks.File" - ], - "MinimumLevel": { - "Default": "Information", - "Override": { - "Microsoft": "Warning", - "Volo.Abp": "Warning", - "Hangfire": "Warning", - "DotNetCore.CAP": "Warning", - "Serilog.AspNetCore": "Information", - "Microsoft.EntityFrameworkCore": "Warning", - "Microsoft.AspNetCore": "Warning" - } - }, - "WriteTo": [ - { - "Name": "Console" - }, - { - "Name": "File", - "Args": { - "path": "./logs/logs-.txt", - "rollingInterval": "Day" - } - } - ] - }, - "App": { - "SelfUrl": "http://localhost:44315", - "CorsOrigins": "http://localhost:4200,http://localhost:3100" - }, - "ConnectionStrings": { - "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", - "Kafka": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092", - "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", - "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" - }, - "Redis": { - "Configuration": "192.168.1.9:6380,password=1q2w3e!@#,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", - "DefaultDB": "14", - "HangfireDB": "15" - }, - "Jwt": { - "Audience": "JiShe.CollectBus", - "SecurityKey": "dzehzRz9a8asdfasfdadfasdfasdfafsdadfasbasdf=", - "Issuer": "JiShe.CollectBus", - "ExpirationTime": 2 - }, - "HealthCheck": { - "IsEnable": true, - "MySql": { - "IsEnable": true - }, - "Pings": { - "IsEnable": true, - "Host": "https://www.baidu.com/", - "TimeOut": 5000 - } - }, - "SwaggerConfig": [ - { - "GroupName": "Basic", - "Title": "【后台管理】基础模块", - "Version": "V1" - }, - { - "GroupName": "Business", - "Title": "【后台管理】业务模块", - "Version": "V1" - } - ], - "Cap": { - "RabbitMq": { - "HostName": "118.190.144.92", - "UserName": "collectbus", - "Password": "123456", - "Port": 5672 - } - }, - "Kafka": { - "BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092", - "EnableAuthorization": false, - "SecurityProtocol": "SASL_PLAINTEXT", - "SaslMechanism": "PLAIN", - "SaslUserName": "lixiao", - "SaslPassword": "lixiao1980" - //"Topic": { - // "ReplicationFactor": 3, - // "NumPartitions": 1000 - //} - }, - //"Kafka": { - // "Connections": { - // "Default": { - // "BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092" - // // "SecurityProtocol": "SASL_PLAINTEXT", - // // "SaslMechanism": "PLAIN", - // // "SaslUserName": "lixiao", - // // "SaslPassword": "lixiao1980", - // } - // }, - // "Consumer": { - // "GroupId": "JiShe.CollectBus" - // }, - // "Producer": { - // "MessageTimeoutMs": 6000, - // "Acks": -1 - // }, - // "Topic": { - // "ReplicationFactor": 3, - // "NumPartitions": 1000 - // }, - // "EventBus": { - // "GroupId": "JiShe.CollectBus", - // "TopicName": "DefaultTopicName" - // } - //}, - "IoTDBOptions": { - "UserName": "root", - "Password": "root", - "ClusterList": [ "192.168.1.9:6667" ], - "PoolSize": 2, - "DataBaseName": "energy", - "OpenDebugMode": true, - "UseTableSessionPoolByDefault": false - }, - "ServerTagName": "JiSheCollectBus", - "KafkaReplicationFactor": 3, - "NumPartitions": 30 -} \ No newline at end of file -- 2.47.2 From e672a6800d20145fa436c985c2b99698540f1ce5 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Tue, 15 Apr 2025 11:15:22 +0800 Subject: [PATCH 087/139] =?UTF-8?q?=E8=B0=83=E6=95=B4kafka=E7=BA=A6?= =?UTF-8?q?=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../Attributes/KafkaSubscribeAttribute.cs | 51 ++++++++ .../CollectBusKafkaModule.cs | 12 ++ .../Consumer/ConsumerService.cs | 28 +++++ .../Consumer/IConsumerService.cs | 8 ++ .../IKafkaSubscribe.cs | 12 ++ .../JiShe.CollectBus.Kafka.csproj | 3 +- .../KafkaSubcribesExtensions.cs | 119 ++++++++++++++++++ .../Producer/ProducerService.cs | 2 +- 9 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/IKafkaSubscribe.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs diff --git a/.gitignore b/.gitignore index daafdc8..68f674a 100644 --- a/.gitignore +++ b/.gitignore @@ -401,3 +401,4 @@ FodyWeavers.xsd # ABP Studio **/.abpstudio/ /src/JiShe.CollectBus.Host/Plugins/*.dll +JiShe.CollectBus.Kafka.Test diff --git a/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs b/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs new file mode 100644 index 0000000..5f70b77 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka.Attributes +{ + [AttributeUsage(AttributeTargets.Method)] + public class KafkaSubscribeAttribute : Attribute + { + /// + /// 订阅的主题 + /// + public string[] Topics { get; set; } + + /// + /// 分区 + /// + public int Partition { get; set; } = -1; + + /// + /// 消费者组 + /// + public string GroupId { get; set; } = "default"; + + public KafkaSubscribeAttribute(string[] topics) + { + this.Topics = topics; + } + + public KafkaSubscribeAttribute(string[] topics, string groupId) + { + this.Topics = topics; + this.GroupId = groupId; + } + + public KafkaSubscribeAttribute(string[] topics, int partition) + { + this.Topics = topics; + this.Partition = partition; + } + + public KafkaSubscribeAttribute(string[] topics, int partition, string groupId) + { + this.Topics = topics; + this.Partition = partition; + this.GroupId = groupId; + } + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index a73eb79..f0462b0 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -1,9 +1,14 @@ using Confluent.Kafka; using JiShe.CollectBus.Kafka.Consumer; using JiShe.CollectBus.Kafka.Producer; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using System.Reflection; +using Volo.Abp; +using Volo.Abp.DependencyInjection; using Volo.Abp.Modularity; +using static Confluent.Kafka.ConfigPropertyNames; namespace JiShe.CollectBus.Kafka { @@ -16,5 +21,12 @@ namespace JiShe.CollectBus.Kafka // 注册Consumer context.Services.AddTransient(typeof(IConsumerService<,>), typeof(ConsumerService<,>)); } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + app.UseKafkaSubscribers(Assembly.Load("JiShe.CollectBus.Application")); + + } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index 34d63fb..a7626dd 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -69,6 +69,34 @@ namespace JiShe.CollectBus.Kafka.Consumer } } + /// + /// 订阅多个topic + /// + /// + /// + /// + public async Task SubscribeAsync(string[] topics, Func messageHandler) + { + _cancellationTokenSource = new CancellationTokenSource(); + Instance.Subscribe(topics); + + try + { + while (!_cancellationTokenSource.Token.IsCancellationRequested) + { + var result = Instance.Consume(_cancellationTokenSource.Token); + if (result != null) + { + await messageHandler(result.Message.Key, result.Message.Value); + } + } + } + catch (OperationCanceledException) + { + Instance.Close(); + } + } + public void Unsubscribe() { _cancellationTokenSource?.Cancel(); diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs index bb88038..990bc86 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs @@ -11,5 +11,13 @@ namespace JiShe.CollectBus.Kafka.Consumer Task SubscribeAsync(string topic, Func messageHandler); void Unsubscribe(); void Dispose(); + + /// + /// 订阅多个topic + /// + /// + /// + /// + Task SubscribeAsync(string[] topics, Func messageHandler); } } diff --git a/src/JiShe.CollectBus.KafkaProducer/IKafkaSubscribe.cs b/src/JiShe.CollectBus.KafkaProducer/IKafkaSubscribe.cs new file mode 100644 index 0000000..3ccea65 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/IKafkaSubscribe.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka +{ + public interface IKafkaSubscribe + { + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj b/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj index 3175346..cef24d5 100644 --- a/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj +++ b/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj @@ -8,7 +8,8 @@ - + + diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs new file mode 100644 index 0000000..fede540 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -0,0 +1,119 @@ +using Confluent.Kafka; +using JiShe.CollectBus.Kafka.Attributes; +using JiShe.CollectBus.Kafka.Consumer; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka +{ + public static class KafkaSubcribesExtensions + { + /// + /// 添加Kafka订阅 + /// + /// + /// + public static void UseKafkaSubscribers(this IApplicationBuilder app, Assembly assembly) + { + var subscribeTypes = assembly.GetTypes() + .Where(t => typeof(IKafkaSubscribe).IsAssignableFrom(t)) + .ToList(); + + if (subscribeTypes.Count == 0) return; + + var provider = app.ApplicationServices; + var lifetime = provider.GetRequiredService(); + + lifetime.ApplicationStarted.Register(() => + { + foreach (var subscribeType in subscribeTypes) + { + var subscribes = provider.GetServices(subscribeType).ToList(); + subscribes.ForEach(subscribe => { + + if(subscribe is IKafkaSubscribe) + { + BuildKafkaSubscriber(subscribe, provider); + } + }); + } + }); + } + + /// + /// 构建Kafka订阅 + /// + /// + /// + private static void BuildKafkaSubscriber(object subscribe, IServiceProvider provider) + { + var methods = subscribe.GetType().GetMethods(); + foreach (var method in methods) + { + var attr = method.GetCustomAttribute(); + if (attr == null) continue; + + // 启动后台消费线程 + Task.Run(() => StartConsumerAsync(provider, attr, method, subscribe)); + } + } + + /// + /// 启动后台消费线程 + /// + /// + /// + /// + /// + /// + private static async Task StartConsumerAsync(IServiceProvider provider, KafkaSubscribeAttribute attr,MethodInfo method, object subscribe) + { + var consumerService = provider.GetRequiredService>(); + await consumerService.SubscribeAsync(attr.Topics, async (key, message) => + { + try + { + if (string.IsNullOrEmpty(message)) + await Task.CompletedTask ; + + // 处理消息 + await ProcessMessageAsync(message, method, subscribe); + } + catch (ConsumeException ex) + { + // 处理消费错误 + throw; + } + }); + } + + private static async Task ProcessMessageAsync(string message, MethodInfo method, object subscribe) + { + var parameters = method.GetParameters(); + if (parameters.Length != 1) return; + + var paramType = parameters[0].ParameterType; + var messageObj = paramType == typeof(string) + ? message + : JsonConvert.DeserializeObject(message, paramType); + + if (method.ReturnType == typeof(Task)) + { + await (Task)method.Invoke(subscribe, new[] { messageObj })!; + } + else + { + method.Invoke(subscribe, new[] { messageObj }); + } + } + + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs index c3e65f6..99d3dd3 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs @@ -37,7 +37,7 @@ namespace JiShe.CollectBus.Kafka.Producer CompressionType = CompressionType.Lz4, // 配置使用压缩算法LZ4,其他:gzip/snappy/zstd BatchSize = 32768, // 修改批次大小为32K LingerMs = 20, // 修改等待时间为20ms - Acks = Acks.All, // 表明只有所有副本Broker都收到消息才算提交成功 + Acks = Acks.All, // 表明只有所有副本Broker都收到消息才算提交成功, 可以 Acks.Leader MessageSendMaxRetries = 50, // 消息发送失败最大重试50次 }; -- 2.47.2 From 6409afa98e57f7c4d1654173a8a040d2d542fe33 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Tue, 15 Apr 2025 11:15:42 +0800 Subject: [PATCH 088/139] =?UTF-8?q?=E8=B0=83=E6=95=B4kafka=E7=BA=A6?= =?UTF-8?q?=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/SampleAppService.cs | 6 ++++++ .../Producer/IProducerService.cs | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 28b6ab0..8471d2d 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -180,4 +180,10 @@ public class SampleAppService : CollectBusAppService, ISampleAppService var aa = LazyServiceProvider.GetKeyedService("TestProtocolPlugin"); return aa == null; } + + //[AllowAnonymous] + //public async Task KafkaAsync() + //{ + + //} } diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs index 2ceaed5..bd9b21b 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs @@ -7,10 +7,10 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.Kafka.Producer { - public interface IProducerService + public interface IProducerService { - Task ProduceAsync(string topic, TKey key, TValue value); - Task ProduceAsync(string topic, TValue value); + Task ProduceAsync(string topic, TKey key, TValue value); + Task ProduceAsync(string topic, TValue value); void Dispose(); } } -- 2.47.2 From be076a81e5dcc8bdb3c3c9c41f967df5b316788a Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Tue, 15 Apr 2025 15:49:22 +0800 Subject: [PATCH 089/139] =?UTF-8?q?=E4=BC=98=E5=8C=96kafka=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E8=AE=A2=E9=98=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/SampleAppService.cs | 15 +- .../Attributes/KafkaSubscribeAttribute.cs | 22 +- .../CollectBusKafkaModule.cs | 4 +- .../Consumer/ConsumerService.cs | 233 +++++++++++++----- .../Consumer/IConsumerService.cs | 26 +- .../KafkaSubcribesExtensions.cs | 55 +++-- .../Producer/IProducerService.cs | 10 +- .../Producer/ProducerService.cs | 169 ++++++++++--- 8 files changed, 390 insertions(+), 144 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 8471d2d..e9ce063 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -17,10 +17,13 @@ using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.IotSystems.AFNEntity; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using Microsoft.Extensions.DependencyInjection; +using JiShe.CollectBus.Kafka.Attributes; +using System.Text.Json; +using JiShe.CollectBus.Kafka; namespace JiShe.CollectBus.Samples; -public class SampleAppService : CollectBusAppService, ISampleAppService +public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaSubscribe { private readonly ILogger _logger; private readonly IIoTDBProvider _iotDBProvider; @@ -181,9 +184,11 @@ public class SampleAppService : CollectBusAppService, ISampleAppService return aa == null; } - //[AllowAnonymous] - //public async Task KafkaAsync() - //{ + [KafkaSubscribe(["test-topic"])] - //} + public async Task KafkaSubscribeAsync(string obj) + { + _logger.LogWarning($"收到订阅消息: {obj}"); + await Task.CompletedTask; + } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs b/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs index 5f70b77..7a059e0 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs @@ -22,28 +22,30 @@ namespace JiShe.CollectBus.Kafka.Attributes /// /// 消费者组 /// - public string GroupId { get; set; } = "default"; + public string GroupId { get; set; } - public KafkaSubscribeAttribute(string[] topics) - { - this.Topics = topics; - } - - public KafkaSubscribeAttribute(string[] topics, string groupId) + public KafkaSubscribeAttribute(string[] topics, string groupId = "default") { this.Topics = topics; this.GroupId = groupId; } - public KafkaSubscribeAttribute(string[] topics, int partition) + public KafkaSubscribeAttribute(string topic, string groupId = "default") + { + this.Topics = new string[] { topic }; + this.GroupId = groupId; + } + public KafkaSubscribeAttribute(string[] topics, int partition, string groupId = "default") { this.Topics = topics; this.Partition = partition; + this.GroupId = groupId; } - public KafkaSubscribeAttribute(string[] topics, int partition, string groupId) + + public KafkaSubscribeAttribute(string topic, int partition, string groupId = "default") { - this.Topics = topics; + this.Topics = new string[] { topic }; this.Partition = partition; this.GroupId = groupId; } diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index f0462b0..153e5ef 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -17,9 +17,9 @@ namespace JiShe.CollectBus.Kafka public override void ConfigureServices(ServiceConfigurationContext context) { // 注册Producer - context.Services.AddTransient(typeof(IProducerService<,>), typeof(ProducerService<,>)); + context.Services.AddTransient(); // 注册Consumer - context.Services.AddTransient(typeof(IConsumerService<,>), typeof(ConsumerService<,>)); + context.Services.AddTransient(); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index a7626dd..08b8cb1 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -6,108 +6,205 @@ using JiShe.CollectBus.Kafka.Attributes; using Volo.Abp.DependencyInjection; using JiShe.CollectBus.Kafka.AdminClient; using static Confluent.Kafka.ConfigPropertyNames; +using System.Collections.Concurrent; +using System.Text.RegularExpressions; +using NUglify.Html; namespace JiShe.CollectBus.Kafka.Consumer { - public abstract class ConsumerService : IConsumerService, IDisposable + public class ConsumerService : IConsumerService, IDisposable { - private readonly ILogger> _logger; - private CancellationTokenSource _cancellationTokenSource; + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + private readonly ConcurrentDictionary + _consumerStore = new(); - protected ConsumerService(IConfiguration configuration, ILogger> logger) + public ConsumerService(IConfiguration configuration, ILogger logger) { + _configuration = configuration; _logger = logger; - GetInstance(configuration); } + #region private 私有方法 - public IConsumer Instance { get; set; } = default; - - public IConsumer GetInstance(IConfiguration configuration) + /// + /// 创建消费者 + /// + /// + /// + /// + private IConsumer CreateConsumer(string? groupId = null) where TKey : notnull where TValue : class { + var config = BuildConsumerConfig(groupId); + return new ConsumerBuilder(config) + .SetErrorHandler((_, e) => _logger.LogError($"消费者错误: {e.Reason}")) + .Build(); + } - ArgumentNullException.ThrowIfNullOrWhiteSpace(configuration["Kafka:EnableAuthorization"]); - var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]!); - var consumerConfig = new ConsumerConfig + private ConsumerConfig BuildConsumerConfig(string? groupId = null) + { + var enableAuth = bool.Parse(_configuration["Kafka:EnableAuthorization"]!); + + var config = new ConsumerConfig { - BootstrapServers = configuration["Kafka:BootstrapServers"], + BootstrapServers = _configuration["Kafka:BootstrapServers"], + GroupId = groupId ?? "default", AutoOffsetReset = AutoOffsetReset.Earliest, - EnableAutoCommit = false, // 禁止AutoCommit - Acks = Acks.All, // 需要所有副本响应才算消费完成 + EnableAutoCommit = false // 禁止AutoCommit }; - if (enableAuthorization) + if (enableAuth) { - consumerConfig.SecurityProtocol = SecurityProtocol.SaslPlaintext; - consumerConfig.SaslMechanism = SaslMechanism.Plain; - consumerConfig.SaslUsername = configuration["Kafka:SaslUserName"]; - consumerConfig.SaslPassword = configuration["Kafka:SaslPassword"]; + config.SecurityProtocol = SecurityProtocol.SaslPlaintext; + config.SaslMechanism = SaslMechanism.Plain; + config.SaslUsername = _configuration["Kafka:SaslUserName"]; + config.SaslPassword = _configuration["Kafka:SaslPassword"]; } - Instance = new ConsumerBuilder(consumerConfig).Build(); - return Instance; + + return config; + } + #endregion + + /// + /// 订阅消息 + /// + /// + /// + /// + /// + /// + public async Task SubscribeAsync(string topic, Func> messageHandler, string? groupId = null) where TKey : notnull where TValue : class + { + await SubscribeAsync(new[] { topic }, messageHandler, groupId); } - public async Task SubscribeAsync(string topic, Func messageHandler) - { - _cancellationTokenSource = new CancellationTokenSource(); - Instance.Subscribe(topic); - try + /// + /// 订阅消息 + /// + /// + /// + /// + /// + public async Task SubscribeAsync(string topic, Func> messageHandler, string? groupId = null) where TValue : class + { + await SubscribeAsync(new[] { topic }, messageHandler,groupId); + } + + /// + /// 订阅消息 + /// + /// + /// + /// + /// + /// + public async Task SubscribeAsync(string[] topics, Func> messageHandler, string? groupId = null) where TKey : notnull where TValue : class + { + var consumerKey = typeof((TKey, TValue)); + var cts = new CancellationTokenSource(); + + var consumer = _consumerStore.GetOrAdd(consumerKey, _ => + ( + CreateConsumer(groupId), + cts + )).Consumer as IConsumer; + + consumer!.Subscribe(topics); + + _ = Task.Run(async () => { - while (!_cancellationTokenSource.Token.IsCancellationRequested) + while (!cts.IsCancellationRequested) { - var result = Instance.Consume(_cancellationTokenSource.Token); - if (result != null) + try { - await messageHandler(result.Message.Key, result.Message.Value); + var result = consumer.Consume(cts.Token); + bool sucess= await messageHandler(result.Message.Key, result.Message.Value); + if (sucess) + { + consumer.Commit(result); // 手动提交 + } + } + catch (ConsumeException ex) + { + _logger.LogError(ex, $"消息消费失败: {ex.Error.Reason}"); } } - } - catch (OperationCanceledException) + }); + await Task.CompletedTask; + } + + + + /// + /// 订阅消息 + /// + /// + /// + /// + /// + /// + public async Task SubscribeAsync(string[] topics, Func> messageHandler, string? groupId) where TValue : class + { + var consumerKey = typeof((Null, TValue)); + var cts = new CancellationTokenSource(); + + var consumer = _consumerStore.GetOrAdd(consumerKey, _=> + ( + CreateConsumer(groupId), + cts + )).Consumer as IConsumer; + + consumer!.Subscribe(topics); + + _ = Task.Run(async () => { - Instance.Close(); + while (!cts.IsCancellationRequested) + { + try + { + var result = consumer.Consume(cts.Token); + bool sucess = await messageHandler(result.Message.Value); + if (sucess) + consumer.Commit(result); // 手动提交 + } + catch (ConsumeException ex) + { + _logger.LogError(ex, $"消息消费失败: {ex.Error.Reason}"); + } + } + }); + await Task.CompletedTask; + } + + /// + /// 取消消息订阅 + /// + /// + /// + public void Unsubscribe() where TKey : notnull where TValue : class + { + var consumerKey = typeof((TKey, TValue)); + if (_consumerStore.TryRemove(consumerKey, out var entry)) + { + entry.CTS.Cancel(); + (entry.Consumer as IDisposable)?.Dispose(); + entry.CTS.Dispose(); } } /// - /// 订阅多个topic + /// 释放资源 /// - /// - /// - /// - public async Task SubscribeAsync(string[] topics, Func messageHandler) - { - _cancellationTokenSource = new CancellationTokenSource(); - Instance.Subscribe(topics); - - try - { - while (!_cancellationTokenSource.Token.IsCancellationRequested) - { - var result = Instance.Consume(_cancellationTokenSource.Token); - if (result != null) - { - await messageHandler(result.Message.Key, result.Message.Value); - } - } - } - catch (OperationCanceledException) - { - Instance.Close(); - } - } - - public void Unsubscribe() - { - _cancellationTokenSource?.Cancel(); - Instance?.Unsubscribe(); - } - public void Dispose() { - Unsubscribe(); - Instance?.Dispose(); - _cancellationTokenSource?.Dispose(); + foreach (var entry in _consumerStore.Values) + { + entry.CTS.Cancel(); + (entry.Consumer as IDisposable)?.Dispose(); + entry.CTS.Dispose(); + } + _consumerStore.Clear(); } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs index 990bc86..5cfae2c 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs @@ -6,18 +6,32 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.Kafka.Consumer { - public interface IConsumerService + public interface IConsumerService { - Task SubscribeAsync(string topic, Func messageHandler); - void Unsubscribe(); - void Dispose(); + Task SubscribeAsync(string topic, Func> messageHandler, string? groupId=null) where TKey : notnull where TValue : class; /// - /// 订阅多个topic + /// 订阅消息 /// + /// + /// + /// + /// + Task SubscribeAsync(string topic, Func> messageHandler, string? groupId = null) where TValue : class; + + Task SubscribeAsync(string[] topics, Func> messageHandler, string? groupId) where TKey : notnull where TValue : class; + + + /// + /// 订阅消息 + /// + /// + /// /// /// /// - Task SubscribeAsync(string[] topics, Func messageHandler); + Task SubscribeAsync(string[] topics, Func> messageHandler, string? groupId = null) where TValue : class; + + void Unsubscribe() where TKey : notnull where TValue : class; } } diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs index fede540..6a5f5cb 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -4,6 +4,8 @@ using JiShe.CollectBus.Kafka.Consumer; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -11,6 +13,7 @@ using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; +using static Confluent.Kafka.ConfigPropertyNames; namespace JiShe.CollectBus.Kafka { @@ -55,14 +58,13 @@ namespace JiShe.CollectBus.Kafka /// private static void BuildKafkaSubscriber(object subscribe, IServiceProvider provider) { - var methods = subscribe.GetType().GetMethods(); - foreach (var method in methods) + var subscribedMethods = subscribe.GetType().GetMethods() + .Select(m => new { Method = m, Attribute = m.GetCustomAttribute() }) + .Where(x => x.Attribute != null) + .ToArray(); + foreach (var sub in subscribedMethods) { - var attr = method.GetCustomAttribute(); - if (attr == null) continue; - - // 启动后台消费线程 - Task.Run(() => StartConsumerAsync(provider, attr, method, subscribe)); + Task.Run(() => StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe)); } } @@ -76,43 +78,54 @@ namespace JiShe.CollectBus.Kafka /// private static async Task StartConsumerAsync(IServiceProvider provider, KafkaSubscribeAttribute attr,MethodInfo method, object subscribe) { - var consumerService = provider.GetRequiredService>(); - await consumerService.SubscribeAsync(attr.Topics, async (key, message) => + var consumerService = provider.GetRequiredService(); + var logger = provider.GetRequiredService>(); + await consumerService.SubscribeAsync(attr.Topics, async (message) => { try { - if (string.IsNullOrEmpty(message)) - await Task.CompletedTask ; - // 处理消息 - await ProcessMessageAsync(message, method, subscribe); + return await ProcessMessageAsync(message, method, subscribe); } catch (ConsumeException ex) { // 处理消费错误 - throw; + logger.LogError($"kafka消费异常:{ex.Message}"); } + return await Task.FromResult(false); }); } - private static async Task ProcessMessageAsync(string message, MethodInfo method, object subscribe) + /// + /// 处理消息 + /// + /// + /// + /// + /// + private static async Task ProcessMessageAsync(string message, MethodInfo method, object subscribe) { var parameters = method.GetParameters(); - if (parameters.Length != 1) return; + if (parameters.Length != 1) + return true; var paramType = parameters[0].ParameterType; - var messageObj = paramType == typeof(string) - ? message - : JsonConvert.DeserializeObject(message, paramType); + var messageObj = paramType == typeof(string)? message: JsonConvert.DeserializeObject(message, paramType); if (method.ReturnType == typeof(Task)) { - await (Task)method.Invoke(subscribe, new[] { messageObj })!; + object? result = await (Task)method.Invoke(subscribe, new[] { messageObj })!; + if (result is bool success) + return success; } else { - method.Invoke(subscribe, new[] { messageObj }); + object? result = method.Invoke(subscribe, new[] { messageObj }); + if (result is bool success) + return success; } + + return false; } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs index bd9b21b..becea90 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs @@ -9,8 +9,12 @@ namespace JiShe.CollectBus.Kafka.Producer { public interface IProducerService { - Task ProduceAsync(string topic, TKey key, TValue value); - Task ProduceAsync(string topic, TValue value); - void Dispose(); + Task ProduceAsync(string topic, TKey key, TValue value) where TKey : notnull where TValue : class; + + Task ProduceAsync(string topic, TValue value) where TValue : class; + + Task ProduceAsync(string topic, TKey key, TValue value, int? partition, Action>? deliveryHandler = null) where TKey : notnull where TValue : class; + + Task ProduceAsync(string topic, TValue value, int? partition = null, Action>? deliveryHandler = null) where TValue : class; } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs index 99d3dd3..ca45f8c 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; @@ -11,70 +12,180 @@ using Volo.Abp.DependencyInjection; namespace JiShe.CollectBus.Kafka.Producer { - public class ProducerService : IProducerService, IDisposable + public class ProducerService: IProducerService, IDisposable { + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + private readonly ConcurrentDictionary, object> _producerCache = new(); - private readonly ILogger> _logger; - - protected ProducerService(IConfiguration configuration, ILogger> logger) + public ProducerService(IConfiguration configuration,ILogger logger) { + _configuration = configuration; _logger = logger; - GetInstance(configuration); } - - public IProducer Instance { get; set; } = default; - - public IProducer GetInstance(IConfiguration configuration) + #region private 私有方法 + /// + /// 创建生产者实例 + /// + /// + /// + /// + private IProducer GetProducer() { - ArgumentNullException.ThrowIfNullOrWhiteSpace(configuration["Kafka:EnableAuthorization"]); - var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]!); - var consumerConfig = new ProducerConfig + var typeKey = Tuple.Create(typeof(TKey), typeof(TValue))!; + + return (IProducer)_producerCache.GetOrAdd(typeKey, _ => { - BootstrapServers = configuration["Kafka:BootstrapServers"], + var config = BuildProducerConfig(); + return new ProducerBuilder(config) + .SetLogHandler((_, msg) => _logger.Log(ConvertLogLevel(msg.Level), msg.Message)) + .Build(); + }); + } + + /// + /// 配置 + /// + /// + private ProducerConfig BuildProducerConfig() + { + var enableAuth = bool.Parse(_configuration["Kafka:EnableAuthorization"]!); + + var config = new ProducerConfig + { + BootstrapServers = _configuration["Kafka:BootstrapServers"], AllowAutoCreateTopics = true, - QueueBufferingMaxKbytes = 2097151, // 修改缓冲区最大为2GB,默认为1GB + QueueBufferingMaxKbytes = 2_097_151, // 修改缓冲区最大为2GB,默认为1GB CompressionType = CompressionType.Lz4, // 配置使用压缩算法LZ4,其他:gzip/snappy/zstd - BatchSize = 32768, // 修改批次大小为32K + BatchSize = 32_768, // 修改批次大小为32K LingerMs = 20, // 修改等待时间为20ms Acks = Acks.All, // 表明只有所有副本Broker都收到消息才算提交成功, 可以 Acks.Leader MessageSendMaxRetries = 50, // 消息发送失败最大重试50次 }; - if (enableAuthorization) + if (enableAuth) { - consumerConfig.SecurityProtocol = SecurityProtocol.SaslPlaintext; - consumerConfig.SaslMechanism = SaslMechanism.Plain; - consumerConfig.SaslUsername = configuration["Kafka:SaslUserName"]; - consumerConfig.SaslPassword = configuration["Kafka:SaslPassword"]; + config.SecurityProtocol = SecurityProtocol.SaslPlaintext; + config.SaslMechanism = SaslMechanism.Plain; + config.SaslUsername = _configuration["Kafka:SaslUserName"]; + config.SaslPassword = _configuration["Kafka:SaslPassword"]; } - Instance = new ProducerBuilder(consumerConfig).Build(); - return Instance; + + return config; } - public async Task ProduceAsync(string topic, TKey key, TValue value) + + private static LogLevel ConvertLogLevel(SyslogLevel level) => level switch + { + SyslogLevel.Emergency => LogLevel.Critical, + SyslogLevel.Alert => LogLevel.Critical, + SyslogLevel.Critical => LogLevel.Critical, + SyslogLevel.Error => LogLevel.Error, + SyslogLevel.Warning => LogLevel.Warning, + SyslogLevel.Notice => LogLevel.Information, + SyslogLevel.Info => LogLevel.Information, + SyslogLevel.Debug => LogLevel.Debug, + _ => LogLevel.None + }; + + #endregion + + /// + /// 发布消息 + /// + /// + /// + /// + /// + /// + /// + public async Task ProduceAsync(string topic, TKey key, TValue value)where TKey : notnull where TValue : class + { + var producer = GetProducer(); + await producer.ProduceAsync(topic, new Message { Key = key, Value = value }); + } + + /// + /// 发布消息 + /// + /// + /// + /// + /// + public async Task ProduceAsync(string topic, TValue value) where TValue : class + { + var producer = GetProducer(); + await producer.ProduceAsync(topic, new Message { Value = value }); + } + + /// + /// 发布消息 + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task ProduceAsync(string topic,TKey key,TValue value,int? partition=null, Action>? deliveryHandler = null)where TKey : notnull where TValue : class { var message = new Message { Key = key, Value = value }; + var producer = GetProducer(); + if (partition.HasValue) + { + var topicPartition = new TopicPartition(topic, partition.Value); + producer.Produce(topicPartition, message, deliveryHandler); + } + else + { + producer.Produce(topic, message, deliveryHandler); + } + await Task.CompletedTask; - await Instance.ProduceAsync(topic, message); } - public async Task ProduceAsync(string topic, TValue value) + /// + /// 发布消息 + /// + /// + /// + /// + /// + /// + /// + /// + public async Task ProduceAsync(string topic, TValue value, int? partition=null, Action>? deliveryHandler = null) where TValue : class { - var message = new Message + var message = new Message { Value = value }; - - await Instance.ProduceAsync(topic, message); + var producer = GetProducer(); + if (partition.HasValue) + { + var topicPartition = new TopicPartition(topic, partition.Value); + producer.Produce(topicPartition, message, deliveryHandler); + } + else + { + producer.Produce(topic, message, deliveryHandler); + } + await Task.CompletedTask; } public void Dispose() { - Instance?.Dispose(); + foreach (var producer in _producerCache.Values.OfType()) + { + producer.Dispose(); + } + _producerCache.Clear(); } } } -- 2.47.2 From 033fde66b19fbbc9bc09db74d8b7f99d5ccb1a7e Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Tue, 15 Apr 2025 15:49:51 +0800 Subject: [PATCH 090/139] =?UTF-8?q?redis=20=E5=A4=A7=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=86=99=E5=85=A5=E5=9C=BA=E6=99=AF=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusAppService.cs | 12 +- .../BasicScheduledMeterReadingService.cs | 142 ++++-- ...nergySystemScheduledMeterReadingService.cs | 4 +- .../DeviceGroupBalanceControl.cs | 153 ++++-- .../DeviceGroupBasicModel.cs | 19 - .../Extensions/DateTimeExtensions.cs | 19 + .../Extensions/EnumerableExtensions.cs | 25 + .../Extensions/BusGlobalPagedResult.cs | 31 ++ .../{ => Extensions}/BusJsonSerializer.cs | 0 .../Extensions/BusPagedResult.cs} | 16 +- .../Extensions/DeviceCacheBasicModel.cs | 24 + .../FreeRedisProvider.cs | 443 +++++++++++++++++- .../Interface/IIoTDBProvider.cs | 5 +- .../Provider/IoTDBProvider.cs | 16 +- .../CollectBusKafkaModule.cs | 5 + .../Consumer/ConsumerService.cs | 2 +- .../Producer/IProducerService.cs | 1 + .../Producer/ProducerService.cs | 13 +- 18 files changed, 808 insertions(+), 122 deletions(-) delete mode 100644 src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBasicModel.cs create mode 100644 src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusGlobalPagedResult.cs rename src/JiShe.CollectBus.FreeRedisProvider/{ => Extensions}/BusJsonSerializer.cs (100%) rename src/{JiShe.CollectBus.IoTDBProvider/Options/PagedResult.cs => JiShe.CollectBus.FreeRedisProvider/Extensions/BusPagedResult.cs} (55%) create mode 100644 src/JiShe.CollectBus.FreeRedisProvider/Extensions/DeviceCacheBasicModel.cs diff --git a/src/JiShe.CollectBus.Application/CollectBusAppService.cs b/src/JiShe.CollectBus.Application/CollectBusAppService.cs index f84d82f..e1f913e 100644 --- a/src/JiShe.CollectBus.Application/CollectBusAppService.cs +++ b/src/JiShe.CollectBus.Application/CollectBusAppService.cs @@ -1,15 +1,20 @@ -using FreeRedis; +using Confluent.Kafka; +using FreeRedis; using FreeSql; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.FreeRedisProvider; using JiShe.CollectBus.FreeSql; +using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Localization; using JiShe.CollectBus.Serializer; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.Application.Services; @@ -21,6 +26,7 @@ public abstract class CollectBusAppService : ApplicationService public IFreeSqlProvider SqlProvider => LazyServiceProvider.LazyGetRequiredService(); protected IFreeRedisProvider FreeRedisProvider => LazyServiceProvider.LazyGetService()!; + protected CollectBusAppService() { LocalizationResource = typeof(CollectBusResource); @@ -220,5 +226,5 @@ public abstract class CollectBusAppService : ApplicationService } return meterInfos; - } -} + } +} diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 914bfa1..2e5e1fb 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -14,6 +14,7 @@ using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; +using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Repository.MeterReadingRecord; using JiShe.CollectBus.Serializer; @@ -36,18 +37,21 @@ namespace JiShe.CollectBus.ScheduledMeterReading private readonly ICapPublisher _producerBus; private readonly IIoTDBProvider _dbProvider; private readonly IMeterReadingRecordRepository _meterReadingRecordRepository; + private readonly IProducerService _producerService; public BasicScheduledMeterReadingService( ILogger logger, ICapPublisher producerBus, IMeterReadingRecordRepository meterReadingRecordRepository, + IProducerService producerService, IIoTDBProvider dbProvider) { _producerBus = producerBus; _logger = logger; _dbProvider = dbProvider; _meterReadingRecordRepository = meterReadingRecordRepository; + _producerService = producerService; } /// @@ -144,38 +148,27 @@ namespace JiShe.CollectBus.ScheduledMeterReading var timer = Stopwatch.StartNew(); - //处理数据 - List>> tempDatas = new List>>(); - await DeviceGroupBalanceControl.ProcessGenericListAsync( + //处理数据 + //await DeviceGroupBalanceControl.ProcessGenericListAsync( + // items: meterInfos, + // deviceIdSelector: data => data.FocusAddress, + // processor: (data, threadId) => + // { + // _ = AmmerterCreatePublishTask(timeDensity, data); + // } + //); + + + + await DeviceGroupBalanceControl.ProcessWithThrottleAsync( items: meterInfos, deviceIdSelector: data => data.FocusAddress, - processor: (data, threadId) => + processor: data => { _ = AmmerterCreatePublishTask(timeDensity, data); - //var keyValuePairs = AmmerterCreatePublishTask(timeDensity, data); - //if (keyValuePairs != null && keyValuePairs.Keys.Count() > 0) - //{ - // //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 - // var redisDataCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}{keyValuePairs.First().Value.FocusAddress}"; - // tempDatas.Add(Tuple.Create(redisDataCacheKey, keyValuePairs)); - // //tempDatas.Add(keyValuePairs); - //} } ); - //_logger.LogError("数据处理完成。"); - - //using (var pipe = FreeRedisProvider.Instance.StartPipe()) - //{ - // _logger.LogError("开始进入管道处理。"); - // foreach (var dataItem in tempDatas) - // { - // pipe.HSet(dataItem.Item1, dataItem.Item2); - // } - // object[] ret = pipe.EndPipe(); - //} - //_logger.LogError("管道处理完成。"); - timer.Stop(); _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建完成,{timer.ElapsedMilliseconds},{oneMinutekeyList.Length}"); @@ -533,12 +526,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentDateTime); } - - ////删除任务数据 - //await FreeRedisProvider.Instance.DelAsync(fifteenMinutekeyList); - - ////缓存下一个时间的任务 - //await CacheNextTaskData(timeDensity, MeterTypeEnum.Ammeter); + stopwatch.Stop(); @@ -635,20 +623,20 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (string.IsNullOrWhiteSpace(ammeterInfo.ItemCodes)) { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,采集项为空,-101"); + // _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,采集项为空,-101"); return; } //载波的不处理 if (ammeterInfo.MeteringPort == (int)MeterLinkProtocolEnum.Carrierwave) { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,载波不处理,-102"); + //_logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,载波不处理,-102"); return; } if (ammeterInfo.State.Equals(2)) { - _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} {ammeterInfo.Name} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}状态为禁用,不处理"); + //_logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} {ammeterInfo.Name} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}状态为禁用,不处理"); return; } @@ -661,22 +649,22 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (string.IsNullOrWhiteSpace(ammeterInfo.AreaCode)) { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信区号为空"); + // _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信区号为空"); return; } if (string.IsNullOrWhiteSpace(ammeterInfo.Address)) { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址为空"); + //_logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址为空"); return; } if (Convert.ToInt32(ammeterInfo.Address) > 65535) { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址无效,确保大于65535"); + //_logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址无效,确保大于65535"); return; } if (ammeterInfo.MeteringCode <= 0 || ammeterInfo.MeteringCode > 33) { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},非有效测量点号({ammeterInfo.MeteringCode})"); + //_logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},非有效测量点号({ammeterInfo.MeteringCode})"); return; } @@ -703,7 +691,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (tempSubCodes == null || tempSubCodes.Count <= 0) { - _logger.LogInformation($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}自动上报数据主动采集1类数据时数据类型为空"); + //_logger.LogInformation($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}自动上报数据主动采集1类数据时数据类型为空"); return; } else @@ -753,7 +741,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } else { - _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}采集项{tempItem}无效编码。"); + //_logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}采集项{tempItem}无效编码。"); continue; } } @@ -761,7 +749,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (dataInfos == null || dataInfos.Length <= 0) { - _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}采集项{tempItem}未能正确获取报文。"); + //_logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}采集项{tempItem}未能正确获取报文。"); continue; } @@ -802,6 +790,78 @@ namespace JiShe.CollectBus.ScheduledMeterReading pipe.HSet(redisCacheKey, keyValuePairs); object[] ret = pipe.EndPipe(); } + + + await Task.CompletedTask; + } + + /// + /// Kafka 推送消息 + /// + /// 主题名称 + /// 任务记录 + /// + private async Task KafkaProducerIssuedMessage(string topicName, + MeterReadingRecords taskRecord) + { + if (string.IsNullOrWhiteSpace(topicName) || taskRecord == null) + { + throw new Exception($"{nameof(KafkaProducerIssuedMessage)} 推送消息失败,参数异常,-101"); + } + int partition = DeviceGroupBalanceControl.GetDeviceGroupId(taskRecord.FocusAddress); + TopicPartition topicPartition = new TopicPartition(topicName, partition); + await _producerService.ProduceAsync(topicPartition, null, taskRecord); + } + + private async Task AmmerterCreatePublishTask(int timeDensity, MeterTypeEnum meterType) + { + var currentDateTime = DateTime.Now; + + var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, meterType); + + //FreeRedisProvider.Instance.key() + + var fifteenMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + if (fifteenMinutekeyList == null || fifteenMinutekeyList.Length <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-101"); + return; + } + + //获取下发任务缓存数据 + Dictionary> meterTaskInfos = await GetMeterRedisCacheDictionaryData(fifteenMinutekeyList, SystemType, ServerTagName, timeDensity.ToString(), meterType); + if (meterTaskInfos == null || meterTaskInfos.Count <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); + return; + } + + List meterTaskInfosList = new List(); + + //将取出的缓存任务数据发送到Kafka消息队列中 + foreach (var focusItem in meterTaskInfos) + { + foreach (var ammerterItem in focusItem.Value) + { + var tempMsg = new ScheduledMeterReadingIssuedEventMessage() + { + MessageHexString = ammerterItem.Value.IssuedMessageHexString, + MessageId = ammerterItem.Value.IssuedMessageId, + FocusAddress = ammerterItem.Value.FocusAddress, + TimeDensity = timeDensity.ToString(), + }; + + _ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); + + //_ = _producerBus.Publish(tempMsg); + + meterTaskInfosList.Add(ammerterItem.Value); + } + } + if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) + { + await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentDateTime); + } } /// diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index cba4f41..32a3b9e 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Confluent.Kafka; using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; @@ -13,6 +14,7 @@ using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; +using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Repository; using JiShe.CollectBus.Repository.MeterReadingRecord; using MassTransit; @@ -34,7 +36,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { string serverTagName = string.Empty; public EnergySystemScheduledMeterReadingService(ILogger logger, - ICapPublisher producerBus, IIoTDBProvider dbProvider, IMeterReadingRecordRepository meterReadingRecordRepository,IConfiguration configuration) : base(logger, producerBus, meterReadingRecordRepository, dbProvider) + ICapPublisher producerBus, IIoTDBProvider dbProvider, IMeterReadingRecordRepository meterReadingRecordRepository,IConfiguration configuration, IProducerService producerService) : base(logger, producerBus, meterReadingRecordRepository, producerService,dbProvider) { serverTagName = configuration.GetValue(CommonConst.ServerTagName)!; } diff --git a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs index e9ac9bc..2575675 100644 --- a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs +++ b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs @@ -40,8 +40,18 @@ namespace JiShe.CollectBus.Common.DeviceBalanceControl /// /// 初始化或增量更新缓存 /// - public static void InitializeCache(List deviceList, int groupCount = 60) + public static void InitializeCache(List deviceList, int groupCount = 30) { + if (deviceList == null || deviceList.Count <= 0) + { + throw new ArgumentException($"{nameof(InitializeCache)} 设备分组初始化失败,设备数据为空"); + } + + if (groupCount > 60 || groupCount <= 0) + { + groupCount = 60; + } + lock (_syncRoot) { // 首次初始化 @@ -55,7 +65,9 @@ namespace JiShe.CollectBus.Common.DeviceBalanceControl else { if (_currentCache.CachedGroups.Length != groupCount) - throw new ArgumentException("Group count cannot change after initial initialization"); + { + throw new ArgumentException($"{nameof(InitializeCache)} 设备分组初始化完成以后,分组数量不能更改"); + } var clonedCache = CloneExistingCache(); UpdateCacheWithDevices(clonedCache, deviceList, groupCount); @@ -165,49 +177,90 @@ namespace JiShe.CollectBus.Common.DeviceBalanceControl }); } + /// - /// 并行处理所有分组设备(每个分组一个处理线程) + /// 智能节流处理(CPU友好型) /// - //public static void ProcessAllGroups(Action> processAction) where T : DeviceGroupBasicModel - //{ - // var cache = _currentCache; - // if (cache == null) - // throw new InvalidOperationException("缓存未初始化"); + /// 已经分组的设备信息 + /// 部分或者全部的已经分组的设备集合 + /// 从泛型对象提取deviceId + /// 处理委托(参数:当前对象,线程ID) + /// 可选最佳并发度 + /// + /// + public static async Task ProcessWithThrottleAsync( + List items, + Func deviceIdSelector, + Action processor, + int? maxConcurrency = null) + { + var cache = _currentCache ?? throw new InvalidOperationException("缓存未初始化"); - // // 使用并行选项控制并发度 - // var options = new ParallelOptions - // { - // MaxDegreeOfParallelism = cache.CachedGroups.Length // 严格匹配分组数量 - // }; + // 自动计算最佳并发度 + int recommendedThreads = CalculateOptimalThreadCount(); + if ((maxConcurrency.HasValue && maxConcurrency.Value > cache.CachedGroups.Length) || maxConcurrency.HasValue == false) + { + maxConcurrency = cache.CachedGroups.Length; + } - // Parallel.For(0, cache.CachedGroups.Length, options, groupId => - // { - // // 获取当前分组的只读副本 - // var groupDevices = GetGroupSnapshot(cache, groupId); + int actualThreads = maxConcurrency ?? recommendedThreads; - // processAction(groupDevices); + // 创建节流器 + using var throttler = new SemaphoreSlim(initialCount: actualThreads); - // //foreach (var deviceId in groupDevices) - // //{ - // // //执行处理操作 - // // processAction(deviceId); + // 使用LongRunning避免线程池饥饿 + var tasks = items.Select(async item => + { + await throttler.WaitAsync(); + try + { + var deviceId = deviceIdSelector(item); + if (cache.BalancedMapping.TryGetValue(deviceId, out int groupId)) + { + // 分组级处理(保持顺序性) + await ProcessItemAsync(item, processor, groupId); + } + } + finally + { + throttler.Release(); + } + }); - // // // 可添加取消检测 - // // // if (token.IsCancellationRequested) break; - // //} - // }); - //} + await Task.WhenAll(tasks); + } + + /// + /// 自动计算最优线程数 + /// + private static int CalculateOptimalThreadCount() + { + int coreCount = Environment.ProcessorCount; + return Math.Min( + coreCount * 2, // 超线程优化 + _currentCache?.CachedGroups.Length ?? 60 + ); + } + + /// + /// 分组异步处理(带节流) + /// + private static async Task ProcessItemAsync(T item, Action processor, int groupId) + { + // 使用内存缓存降低CPU负载 + await Task.Yield(); // 立即释放当前线程 + + // 分组处理上下文 + var context = ExecutionContext.Capture(); + ThreadPool.QueueUserWorkItem(_ => + { + ExecutionContext.Run(context!, state => + { + processor(item); + }, null); + }); + } - ///// - ///// 获取分组数据快照(线程安全) - ///// - //public static IReadOnlyList GetGroupSnapshot(CacheState cache, int groupId) - //{ - // lock (cache.CachedGroups[groupId]) - // { - // return cache.CachedGroups[groupId].ToList(); // 创建内存快照 - // } - //} /// /// 通过 deviceId 获取所在的分组集合 @@ -321,6 +374,32 @@ namespace JiShe.CollectBus.Common.DeviceBalanceControl return (int)hash; } + /// + /// CRC16算法实现 + /// + /// + /// + public static ushort CRC16Hash(byte[] bytes) + { + ushort crc = 0xFFFF; + for (int i = 0; i < bytes.Length; i++) + { + crc ^= bytes[i]; + for (int j = 0; j < 8; j++) + { + if ((crc & 0x0001) == 1) + { + crc = (ushort)((crc >> 1) ^ 0xA001); + } + else + { + crc >>= 1; + } + } + } + return crc; + } + /// /// 打印分组统计数据 /// diff --git a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBasicModel.cs b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBasicModel.cs deleted file mode 100644 index f12f15e..0000000 --- a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBasicModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.Common.DeviceBalanceControl -{ - /// - /// 设备组基本模型 - /// - public class DeviceGroupBasicModel - { - /// - /// 设备Id - /// - public string DeviceId { get; set; } - } -} diff --git a/src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs b/src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs index 7734288..4e3fef9 100644 --- a/src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs +++ b/src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs @@ -181,5 +181,24 @@ namespace JiShe.CollectBus.Common.Extensions return $"{dateTime:yyyyMMddHH}"; #endif } + + /// + /// 获取当前时间毫秒级时间戳 + /// + /// + public static long GetCurrentTimeMillis() + { + return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + } + + /// + /// 将Unix时间戳转换为日期时间 + /// + /// + /// + public static DateTime FromUnixMillis(long millis) + { + return DateTimeOffset.FromUnixTimeMilliseconds(millis).DateTime; + } } } diff --git a/src/JiShe.CollectBus.Common/Extensions/EnumerableExtensions.cs b/src/JiShe.CollectBus.Common/Extensions/EnumerableExtensions.cs index 94e4ad1..80d2f23 100644 --- a/src/JiShe.CollectBus.Common/Extensions/EnumerableExtensions.cs +++ b/src/JiShe.CollectBus.Common/Extensions/EnumerableExtensions.cs @@ -64,5 +64,30 @@ namespace JiShe.CollectBus.Common.Extensions ? source.Where(predicate) : source; } + + /// + /// 分批 + /// + /// + /// + /// + /// + public static IEnumerable> Batch( + this IEnumerable source, + int batchSize) + { + var buffer = new List(batchSize); + foreach (var item in source) + { + buffer.Add(item); + if (buffer.Count == batchSize) + { + yield return buffer; + buffer = new List(batchSize); + } + } + if (buffer.Count > 0) + yield return buffer; + } } } diff --git a/src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusGlobalPagedResult.cs b/src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusGlobalPagedResult.cs new file mode 100644 index 0000000..18cd089 --- /dev/null +++ b/src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusGlobalPagedResult.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.PagedResult +{ + public class GlobalPagedResult + { + /// + /// 数据集合 + /// + public List Items { get; set; } + + /// + /// 是否有下一页 + /// + public bool HasNext { get; set; } + + /// + /// 下一页的分页索引 + /// + public long? NextScore { get; set; } + + /// + /// 下一页的分页索引 + /// + public string NextMember { get; set; } + } +} diff --git a/src/JiShe.CollectBus.FreeRedisProvider/BusJsonSerializer.cs b/src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusJsonSerializer.cs similarity index 100% rename from src/JiShe.CollectBus.FreeRedisProvider/BusJsonSerializer.cs rename to src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusJsonSerializer.cs diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/PagedResult.cs b/src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusPagedResult.cs similarity index 55% rename from src/JiShe.CollectBus.IoTDBProvider/Options/PagedResult.cs rename to src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusPagedResult.cs index b707355..3ee9950 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Options/PagedResult.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusPagedResult.cs @@ -4,18 +4,28 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.IoTDBProvider +namespace JiShe.CollectBus.PagedResult { /// /// 查询结果 /// /// - public class PagedResult + public class BusPagedResult { /// /// 总条数 /// - public int TotalCount { get; set; } + public long TotalCount { get; set; } + + /// + /// 当前页码 + /// + public int PageIndex { get; set; } + + /// + /// 每页条数 + /// + public int PageSize { get; set; } /// /// 数据集合 diff --git a/src/JiShe.CollectBus.FreeRedisProvider/Extensions/DeviceCacheBasicModel.cs b/src/JiShe.CollectBus.FreeRedisProvider/Extensions/DeviceCacheBasicModel.cs new file mode 100644 index 0000000..c561df2 --- /dev/null +++ b/src/JiShe.CollectBus.FreeRedisProvider/Extensions/DeviceCacheBasicModel.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.FreeRedisProvider +{ + /// + /// 设备缓存基础模型 + /// + public class DeviceCacheBasicModel + { + /// + /// 集中器Id + /// + public int FocusId { get; set; } + + /// + /// 表Id + /// + public int MeterId { get; set; } + } +} diff --git a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs index 1a3b114..4bdbd6d 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs @@ -1,6 +1,7 @@ using FreeRedis; using JetBrains.Annotations; using JiShe.CollectBus.FreeRedisProvider.Options; +using JiShe.CollectBus.PagedResult; using JiShe.CollectBus.Serializer; using Microsoft.Extensions.Options; using System; @@ -31,7 +32,7 @@ namespace JiShe.CollectBus.FreeRedisProvider GetInstance(); } - public RedisClient Instance { get; set; } = new (string.Empty); + public RedisClient Instance { get; set; } = new(string.Empty); /// /// 获取 FreeRedis 客户端 @@ -39,7 +40,7 @@ namespace JiShe.CollectBus.FreeRedisProvider /// public IRedisClient GetInstance() { - + var connectionString = $"{_option.Configuration},defaultdatabase={_option.DefaultDB}"; Instance = new RedisClient(connectionString); Instance.Serialize = obj => BusJsonSerializer.Serialize(obj); @@ -47,5 +48,443 @@ namespace JiShe.CollectBus.FreeRedisProvider Instance.Notice += (s, e) => Trace.WriteLine(e.Log); return Instance; } + + + //public async Task AddMeterZSetCacheData(string redisCacheKey, string redisCacheIndexKey, decimal score, T data) + //{ + // if (score < 0 || data == null || string.IsNullOrWhiteSpace(redisCacheKey) || string.IsNullOrWhiteSpace(redisCacheIndexKey)) + // { + // throw new Exception($"{nameof(AddMeterZSetCacheData)} 参数异常,-101"); + // } + + // // 生成唯一member标识 + // var member = data.Serialize(); + + // // 计算score范围 + // decimal dataScore = (long)score << 32; + + // //// 事务操作 + // //using (var tran = FreeRedisProvider.Instance.Multi()) + // //{ + // // await tran.ZAddAsync(cacheKey, score,member); + // // await tran.SAddAsync($"cat_index:{categoryId}", member); + // // object[] ret = tran.Exec(); + // //} + + // using (var pipe = Instance.StartPipe()) + // { + // pipe.ZAdd(redisCacheKey, dataScore, member); + // pipe.SAdd(redisCacheIndexKey, member); + // object[] ret = pipe.EndPipe(); + // } + + // await Task.CompletedTask; + //} + + //public async Task> GetMeterZSetPagedData( + //string redisCacheKey, + //string redisCacheIndexKey, + //decimal score, + //int pageSize = 10, + //int pageIndex = 1) + //{ + // if (score < 0 || string.IsNullOrWhiteSpace(redisCacheKey) || string.IsNullOrWhiteSpace(redisCacheIndexKey)) + // { + // throw new Exception($"{nameof(GetMeterZSetPagedData)} 参数异常,-101"); + // } + + // // 计算score范围 + // decimal minScore = (long)score << 32; + // decimal maxScore = ((long)score + 1) << 32; + + // // 分页参数 + // int start = (pageIndex - 1) * pageSize; + + // // 查询主数据 + // var members = await Instance.ZRevRangeByScoreAsync( + // redisCacheKey, + // maxScore, + // minScore, + // offset: start, + // count: pageSize + // ); + + // if (members == null) + // { + // throw new Exception($"{nameof(GetMeterZSetPagedData)} 获取缓存的信息失败,第 {pageIndex + 1} 页数据未返回,-102"); + // } + + // // 查询总数 + // var total = await Instance.ZCountAsync(redisCacheKey, minScore, maxScore); + + // return new BusPagedResult + // { + // Items = members.Select(m => + // BusJsonSerializer.Deserialize(m)!).ToList(), + // TotalCount = total, + // PageIndex = pageIndex, + // PageSize = pageSize + // }; + //} + + ///// + ///// 删除数据示例 + ///// + ///// + ///// 分类 + ///// + ///// + ///// + //public async Task RemoveMeterZSetData( + //string redisCacheKey, + //string redisCacheIndexKey, + //T data) + //{ + + // // 查询需要删除的member + // var members = await Instance.SMembersAsync(redisCacheIndexKey); + // var target = members.FirstOrDefault(m => + // BusJsonSerializer.Deserialize(m) == data);//泛型此处该如何处理? + + // if (target != null) + // { + // using (var trans = Instance.Multi()) + // { + // trans.ZRem(redisCacheKey, target); + // trans.SRem(redisCacheIndexKey, target); + // trans.Exec(); + // } + // } + + // await Task.CompletedTask; + //} + + + public async Task AddMeterZSetCacheData( + string redisCacheKey, + string redisCacheIndexKey, + int categoryId, // 新增分类ID参数 + T data, + DateTimeOffset? timestamp = null) + { + // 参数校验增强 + if (data == null || string.IsNullOrWhiteSpace(redisCacheKey) + || string.IsNullOrWhiteSpace(redisCacheIndexKey)) + { + throw new ArgumentException("Invalid parameters"); + } + + // 生成唯一member标识(带数据指纹) + var member = $"{categoryId}:{Guid.NewGuid()}"; + var serializedData = data.Serialize(); + + // 计算组合score(分类ID + 时间戳) + var actualTimestamp = timestamp ?? DateTimeOffset.UtcNow; + + long scoreValue = ((long)categoryId << 32) | (uint)actualTimestamp.Ticks; + + //全局索引写入 + long globalScore = actualTimestamp.ToUnixTimeMilliseconds(); + + // 使用事务保证原子性 + using (var trans = Instance.Multi()) + { + // 主数据存储Hash + trans.HSet(redisCacheKey, member, serializedData); + + // 排序索引使用ZSET + trans.ZAdd($"{redisCacheKey}_scores", scoreValue, member); + + // 分类索引 + trans.SAdd(redisCacheIndexKey, member); + + //全局索引 + trans.ZAdd("global_data_all", globalScore, member); + + var results = trans.Exec(); + + if (results == null || results.Length <= 0) + throw new Exception("Transaction failed"); + } + + await Task.CompletedTask; + } + + public async Task BatchAddMeterData( + string redisCacheKey, + string indexKey, + IEnumerable items) where T : DeviceCacheBasicModel + { + const int BATCH_SIZE = 1000; // 每批1000条 + var semaphore = new SemaphoreSlim(Environment.ProcessorCount * 2); + + //foreach (var batch in items.Batch(BATCH_SIZE)) + //{ + // await semaphore.WaitAsync(); + + // _ = Task.Run(async () => + // { + // using (var pipe = FreeRedisProvider.Instance.StartPipe()) + // { + // foreach (var item in batch) + // { + // var member = $"{item.CategoryId}:{Guid.NewGuid()}"; + // long score = ((long)item.CategoryId << 32) | (uint)item.Timestamp.Ticks; + + // // Hash主数据 + // pipe.HSet(redisCacheKey, member, item.Data.Serialize()); + + // // 分类索引 + // pipe.ZAdd($"{redisCacheKey}_scores", score, member); + + // // 全局索引 + // pipe.ZAdd("global_data_all", item.Timestamp.ToUnixTimeMilliseconds(), member); + + // // 分类快速索引 + // pipe.SAdd(indexKey, member); + // } + // pipe.EndPipe(); + // } + // semaphore.Release(); + // }); + //} + + await Task.CompletedTask; + } + + public async Task UpdateMeterData( + string redisCacheKey, + string oldCategoryIndexKey, + string newCategoryIndexKey, + string memberId, // 唯一标识(格式:"分类ID:GUID") + T newData, + int? newCategoryId = null, + DateTimeOffset? newTimestamp = null) + { + // 参数校验 + if (string.IsNullOrWhiteSpace(memberId)) + throw new ArgumentException("Invalid member ID"); + + var luaScript = @" + local mainKey = KEYS[1] + local scoreKey = KEYS[2] + local oldIndex = KEYS[3] + local newIndex = KEYS[4] + local member = ARGV[1] + local newData = ARGV[2] + local newScore = ARGV[3] + + -- 校验旧数据是否存在 + if redis.call('HEXISTS', mainKey, member) == 0 then + return 0 + end + + -- 更新主数据 + redis.call('HSET', mainKey, member, newData) + + -- 处理分类变更 + if newScore ~= '' then + -- 删除旧索引 + redis.call('SREM', oldIndex, member) + -- 更新排序分数 + redis.call('ZADD', scoreKey, newScore, member) + -- 添加新索引 + redis.call('SADD', newIndex, member) + end + + return 1 + "; + + // 计算新score(当分类或时间变化时) + long? newScoreValue = null; + if (newCategoryId.HasValue || newTimestamp.HasValue) + { + var parts = memberId.Split(':'); + var oldCategoryId = int.Parse(parts[0]); + + var actualCategoryId = newCategoryId ?? oldCategoryId; + var actualTimestamp = newTimestamp ?? DateTimeOffset.UtcNow; + + newScoreValue = ((long)actualCategoryId << 32) | (uint)actualTimestamp.Ticks; + } + + var result = await Instance.EvalAsync(luaScript, + new[] + { + redisCacheKey, + $"{redisCacheKey}_scores", + oldCategoryIndexKey, + newCategoryIndexKey + }, + new[] + { + memberId, + newData.Serialize(), + newScoreValue?.ToString() ?? "" + }); + + // 如果时间戳变化则更新全局索引 + if (newTimestamp.HasValue) + { + long newGlobalScore = newTimestamp.Value.ToUnixTimeMilliseconds(); + await Instance.ZAddAsync("global_data_all", newGlobalScore, memberId); + } + + if ((int)result == 0) + throw new KeyNotFoundException("指定数据不存在"); + } + + + public async Task> GetMeterZSetPagedData( + string redisCacheKey, + string redisCacheIndexKey, + int categoryId, + int pageSize = 10, + int pageIndex = 1, + bool descending = true) + { + // 计算score范围 + long minScore = (long)categoryId << 32; + long maxScore = ((long)categoryId + 1) << 32; + + // 分页参数计算 + int start = (pageIndex - 1) * pageSize; + + // 获取排序后的member列表 + var members = descending + ? await Instance.ZRevRangeByScoreAsync( + $"{redisCacheKey}_scores", + maxScore, + minScore, + start, + pageSize) + : await Instance.ZRangeByScoreAsync( + $"{redisCacheKey}_scores", + minScore, + maxScore, + start, + pageSize); + + // 批量获取实际数据 + var dataTasks = members.Select(m => + Instance.HGetAsync(redisCacheKey, m)).ToArray(); + await Task.WhenAll(dataTasks); + + // 总数统计优化 + var total = await Instance.ZCountAsync( + $"{redisCacheKey}_scores", + minScore, + maxScore); + + return new BusPagedResult + { + Items = dataTasks.Select(t => t.Result).ToList(), + TotalCount = total, + PageIndex = pageIndex, + PageSize = pageSize + }; + } + + + public async Task RemoveMeterZSetData( + string redisCacheKey, + string redisCacheIndexKey, + string uniqueId) // 改为基于唯一标识删除 + { + // 原子操作 + var luaScript = @" + local mainKey = KEYS[1] + local scoreKey = KEYS[2] + local indexKey = KEYS[3] + local member = ARGV[1] + + redis.call('HDEL', mainKey, member) + redis.call('ZREM', scoreKey, member) + redis.call('SREM', indexKey, member) + return 1 + "; + + var keys = new[] + { + redisCacheKey, + $"{redisCacheKey}_scores", + redisCacheIndexKey + }; + + var result = await Instance.EvalAsync(luaScript, + keys, + new[] { uniqueId }); + + if ((int)result != 1) + throw new Exception("删除操作失败"); + } + + public async Task> GetGlobalPagedData( + string redisCacheKey, + int pageSize = 10, + long? lastScore = null, + string lastMember = null, + bool descending = true) + { + const string zsetKey = "global_data_all"; + + // 分页参数处理 + var (startScore, excludeMember) = descending + ? (lastScore ?? long.MaxValue, lastMember) + : (lastScore ?? 0, lastMember); + + // 获取成员列表 + string[] members; + if (descending) + { + members = await Instance.ZRevRangeByScoreAsync( + zsetKey, + max: startScore, + min: 0, + offset: 0, + count: pageSize + 1); + } + else + { + members = await Instance.ZRangeByScoreAsync( + zsetKey, + min: startScore, + max: long.MaxValue, + offset: 0, + count: pageSize + 1); + } + + // 处理分页结果 + bool hasNext = members.Length > pageSize; + var actualMembers = members.Take(pageSize).ToArray(); + + // 批量获取数据(优化版本) + var dataTasks = actualMembers + .Select(m => Instance.HGetAsync(redisCacheKey, m)) + .ToArray(); + await Task.WhenAll(dataTasks); + + // 获取下一页游标 + (long? nextScore, string nextMember) = actualMembers.Any() + ? await GetNextCursor(zsetKey, actualMembers.Last(), descending) + : (null, null); + + return new GlobalPagedResult + { + Items = dataTasks.Select(t => t.Result).ToList(), + HasNext = hasNext, + NextScore = nextScore, + NextMember = nextMember + }; + } + + private async Task<(long? score, string member)> GetNextCursor( + string zsetKey, + string lastMember, + bool descending) + { + var score = await Instance.ZScoreAsync(zsetKey, lastMember); + return (score.HasValue ? (long)score.Value : null, lastMember); + } } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs index 0aad03e..292549a 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs @@ -1,4 +1,5 @@ -using System; +using JiShe.CollectBus.PagedResult; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -48,6 +49,6 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// /// - Task> QueryAsync(QueryOptions options) where T : IoTEntity, new(); + Task> QueryAsync(QueryOptions options) where T : IoTEntity, new(); } } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs index 705b2c1..6ae0ef1 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs @@ -1,22 +1,14 @@ using Apache.IoTDB; using Apache.IoTDB.DataStructure; using JiShe.CollectBus.Common.Extensions; -using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.IoTDBProvider.Context; using JiShe.CollectBus.IoTDBProvider.Interface; -using JiShe.CollectBus.IoTDBProvider.Provider; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; -using System.Collections; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Text; -using System.Threading.Tasks; -using static Thrift.Protocol.Utilities.TJSONProtocolConstants; +using JiShe.CollectBus.PagedResult; + namespace JiShe.CollectBus.IoTDBProvider { @@ -118,12 +110,12 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// /// - public async Task> QueryAsync(QueryOptions options) where T : IoTEntity, new() + public async Task> QueryAsync(QueryOptions options) where T : IoTEntity, new() { var query = BuildQuerySQL(options); var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query); - var result = new PagedResult + var result = new BusPagedResult { TotalCount = await GetTotalCount(options), Items = ParseResults(sessionDataSet, options.PageSize) diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index 61cc788..a73eb79 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -1,5 +1,6 @@ using Confluent.Kafka; using JiShe.CollectBus.Kafka.Consumer; +using JiShe.CollectBus.Kafka.Producer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; @@ -10,6 +11,10 @@ namespace JiShe.CollectBus.Kafka { public override void ConfigureServices(ServiceConfigurationContext context) { + // 注册Producer + context.Services.AddTransient(typeof(IProducerService<,>), typeof(ProducerService<,>)); + // 注册Consumer + context.Services.AddTransient(typeof(IConsumerService<,>), typeof(ConsumerService<,>)); } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index 37efe3a..700d52f 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -14,7 +14,7 @@ namespace JiShe.CollectBus.Kafka.Consumer private readonly ILogger> _logger; private CancellationTokenSource _cancellationTokenSource; - protected ConsumerService(IConfiguration configuration, ILogger> logger) + public ConsumerService(IConfiguration configuration, ILogger> logger) { _logger = logger; GetInstance(configuration); diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs index 2ceaed5..587013d 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs @@ -10,6 +10,7 @@ namespace JiShe.CollectBus.Kafka.Producer public interface IProducerService { Task ProduceAsync(string topic, TKey key, TValue value); + Task ProduceAsync(TopicPartition topicPartition, TKey key, TValue value); Task ProduceAsync(string topic, TValue value); void Dispose(); } diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs index 0cfed2e..af6bc54 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs @@ -16,7 +16,7 @@ namespace JiShe.CollectBus.Kafka.Producer private readonly ILogger> _logger; - protected ProducerService(IConfiguration configuration, ILogger> logger) + public ProducerService(IConfiguration configuration, ILogger> logger) { _logger = logger; GetInstance(configuration); @@ -55,6 +55,17 @@ namespace JiShe.CollectBus.Kafka.Producer await Instance.ProduceAsync(topic, message); } + public async Task ProduceAsync(TopicPartition topicPartition, TKey key, TValue value) + { + var message = new Message + { + Key = key, + Value = value + }; + + await Instance.ProduceAsync(topicPartition, message); + } + public async Task ProduceAsync(string topic, TValue value) { var message = new Message -- 2.47.2 From f74bedd3cdfeb49dc389c3642e8b6519e3558834 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Tue, 15 Apr 2025 15:50:30 +0800 Subject: [PATCH 091/139] =?UTF-8?q?=E7=89=B9=E6=80=A7=E8=AE=A2=E9=98=85?= =?UTF-8?q?=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JiShe.CollectBus.Application/Samples/SampleAppService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index e9ce063..e8b483d 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -186,9 +186,9 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS [KafkaSubscribe(["test-topic"])] - public async Task KafkaSubscribeAsync(string obj) + public async Task KafkaSubscribeAsync(string obj) { _logger.LogWarning($"收到订阅消息: {obj}"); - await Task.CompletedTask; + return await Task.FromResult(true); } } -- 2.47.2 From c456a22bb84a81bea9693c235af4b4828f349222 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Tue, 15 Apr 2025 15:57:14 +0800 Subject: [PATCH 092/139] =?UTF-8?q?=E5=90=88=E5=B9=B6=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 10 +++++----- .../EnergySystemScheduledMeterReadingService.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 2e5e1fb..248bb92 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -37,14 +37,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading private readonly ICapPublisher _producerBus; private readonly IIoTDBProvider _dbProvider; private readonly IMeterReadingRecordRepository _meterReadingRecordRepository; - private readonly IProducerService _producerService; + private readonly IProducerService _producerService; public BasicScheduledMeterReadingService( ILogger logger, ICapPublisher producerBus, IMeterReadingRecordRepository meterReadingRecordRepository, - IProducerService producerService, + IProducerService producerService, IIoTDBProvider dbProvider) { _producerBus = producerBus; @@ -809,8 +809,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading throw new Exception($"{nameof(KafkaProducerIssuedMessage)} 推送消息失败,参数异常,-101"); } int partition = DeviceGroupBalanceControl.GetDeviceGroupId(taskRecord.FocusAddress); - TopicPartition topicPartition = new TopicPartition(topicName, partition); - await _producerService.ProduceAsync(topicPartition, null, taskRecord); + + await _producerService.ProduceAsync(topicName, partition, taskRecord); } private async Task AmmerterCreatePublishTask(int timeDensity, MeterTypeEnum meterType) @@ -1158,7 +1158,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - await _producerBus.PublishAsync(ProtocolConst.WatermeterSubscriberWorkerAutoReadingIssuedEventName, tempMsg); + //await _producerBus.PublishAsync(ProtocolConst.WatermeterSubscriberWorkerAutoReadingIssuedEventName, tempMsg); //_ = _producerBus.Publish(tempMsg); diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 32a3b9e..91f7d4a 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -36,7 +36,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { string serverTagName = string.Empty; public EnergySystemScheduledMeterReadingService(ILogger logger, - ICapPublisher producerBus, IIoTDBProvider dbProvider, IMeterReadingRecordRepository meterReadingRecordRepository,IConfiguration configuration, IProducerService producerService) : base(logger, producerBus, meterReadingRecordRepository, producerService,dbProvider) + ICapPublisher producerBus, IIoTDBProvider dbProvider, IMeterReadingRecordRepository meterReadingRecordRepository,IConfiguration configuration, IProducerService producerService) : base(logger, producerBus, meterReadingRecordRepository, producerService,dbProvider) { serverTagName = configuration.GetValue(CommonConst.ServerTagName)!; } -- 2.47.2 From f3a0c82d24a5bacc803fe4bd135476f9fc3d2e34 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Tue, 15 Apr 2025 16:05:07 +0800 Subject: [PATCH 093/139] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusAppService.cs | 10 ++-------- .../BasicScheduledMeterReadingService.cs | 7 +------ .../Subscribers/SubscriberAppService.cs | 2 +- .../DeviceGroupBalanceControl.cs | 3 +-- .../Helpers}/BusJsonSerializer.cs | 2 +- .../JiShe.CollectBus.Common.csproj | 4 ---- .../Models}/BusGlobalPagedResult.cs | 2 +- .../Models}/BusPagedResult.cs | 2 +- .../Models}/DeviceCacheBasicModel.cs | 2 +- .../FreeRedisProvider.cs | 14 +++----------- .../JiShe.CollectBus.FreeRedisProvider.csproj | 3 +++ .../Interface/IIoTDBProvider.cs | 7 +------ .../Provider/IoTDBProvider.cs | 2 +- .../JiShe.CollectBus.Kafka.csproj | 4 ++++ 14 files changed, 21 insertions(+), 43 deletions(-) rename src/{JiShe.CollectBus.FreeRedisProvider/Extensions => JiShe.CollectBus.Common/Helpers}/BusJsonSerializer.cs (99%) rename src/{JiShe.CollectBus.FreeRedisProvider/Extensions => JiShe.CollectBus.Common/Models}/BusGlobalPagedResult.cs (94%) rename src/{JiShe.CollectBus.FreeRedisProvider/Extensions => JiShe.CollectBus.Common/Models}/BusPagedResult.cs (94%) rename src/{JiShe.CollectBus.FreeRedisProvider/Extensions => JiShe.CollectBus.Common/Models}/DeviceCacheBasicModel.cs (91%) diff --git a/src/JiShe.CollectBus.Application/CollectBusAppService.cs b/src/JiShe.CollectBus.Application/CollectBusAppService.cs index e1f913e..6ecc509 100644 --- a/src/JiShe.CollectBus.Application/CollectBusAppService.cs +++ b/src/JiShe.CollectBus.Application/CollectBusAppService.cs @@ -1,20 +1,14 @@ -using Confluent.Kafka; -using FreeRedis; -using FreeSql; -using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; -using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.FreeRedisProvider; using JiShe.CollectBus.FreeSql; -using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Localization; -using JiShe.CollectBus.Serializer; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Volo.Abp.Application.Services; diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 248bb92..287158f 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -1,6 +1,4 @@ -using Confluent.Kafka; -using DeviceDetectorNET.Class.Client; -using DotNetCore.CAP; +using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.BuildSendDatas; using JiShe.CollectBus.Common.Consts; @@ -8,7 +6,6 @@ using JiShe.CollectBus.Common.DeviceBalanceControl; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; -using JiShe.CollectBus.EnergySystems.Entities; using JiShe.CollectBus.GatherItem; using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.MessageIssueds; @@ -17,13 +14,11 @@ using JiShe.CollectBus.IotSystems.Watermeter; using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Repository.MeterReadingRecord; -using JiShe.CollectBus.Serializer; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading; using System.Threading.Tasks; namespace JiShe.CollectBus.ScheduledMeterReading diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index 63559de..d5e9caf 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -1,5 +1,6 @@ using DotNetCore.CAP; using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.Devices; @@ -9,7 +10,6 @@ using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using JiShe.CollectBus.Protocol.Contracts.Models; using JiShe.CollectBus.Repository.MeterReadingRecord; -using JiShe.CollectBus.Serializer; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; diff --git a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs index 2575675..4be0f4f 100644 --- a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs +++ b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs @@ -1,5 +1,4 @@ -using JiShe.CollectBus.FreeRedisProvider; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; diff --git a/src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusJsonSerializer.cs b/src/JiShe.CollectBus.Common/Helpers/BusJsonSerializer.cs similarity index 99% rename from src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusJsonSerializer.cs rename to src/JiShe.CollectBus.Common/Helpers/BusJsonSerializer.cs index db1227b..f938fd8 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusJsonSerializer.cs +++ b/src/JiShe.CollectBus.Common/Helpers/BusJsonSerializer.cs @@ -7,7 +7,7 @@ using System.Text.Json.Serialization; using System.Text.Json; using System.Threading.Tasks; -namespace JiShe.CollectBus.Serializer +namespace JiShe.CollectBus.Common.Helpers { /// /// json帮助类 diff --git a/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj b/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj index 448bf62..b22e8ca 100644 --- a/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj +++ b/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj @@ -28,8 +28,4 @@ - - - - diff --git a/src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusGlobalPagedResult.cs b/src/JiShe.CollectBus.Common/Models/BusGlobalPagedResult.cs similarity index 94% rename from src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusGlobalPagedResult.cs rename to src/JiShe.CollectBus.Common/Models/BusGlobalPagedResult.cs index 18cd089..6ae8b91 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusGlobalPagedResult.cs +++ b/src/JiShe.CollectBus.Common/Models/BusGlobalPagedResult.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.PagedResult +namespace JiShe.CollectBus.Common.Models { public class GlobalPagedResult { diff --git a/src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusPagedResult.cs b/src/JiShe.CollectBus.Common/Models/BusPagedResult.cs similarity index 94% rename from src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusPagedResult.cs rename to src/JiShe.CollectBus.Common/Models/BusPagedResult.cs index 3ee9950..2e68b14 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/Extensions/BusPagedResult.cs +++ b/src/JiShe.CollectBus.Common/Models/BusPagedResult.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.PagedResult +namespace JiShe.CollectBus.Common.Models { /// /// 查询结果 diff --git a/src/JiShe.CollectBus.FreeRedisProvider/Extensions/DeviceCacheBasicModel.cs b/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs similarity index 91% rename from src/JiShe.CollectBus.FreeRedisProvider/Extensions/DeviceCacheBasicModel.cs rename to src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs index c561df2..05b654d 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/Extensions/DeviceCacheBasicModel.cs +++ b/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.FreeRedisProvider +namespace JiShe.CollectBus.Common.Models { /// /// 设备缓存基础模型 diff --git a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs index 4bdbd6d..c76f9e6 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs @@ -1,18 +1,10 @@ using FreeRedis; -using JetBrains.Annotations; -using JiShe.CollectBus.FreeRedisProvider.Options; -using JiShe.CollectBus.PagedResult; -using JiShe.CollectBus.Serializer; +using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.FreeRedisProvider.Options; using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Text.Encodings.Web; using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading.Tasks; using Volo.Abp.DependencyInjection; namespace JiShe.CollectBus.FreeRedisProvider diff --git a/src/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj b/src/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj index e4f3447..7db0478 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj +++ b/src/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj @@ -9,4 +9,7 @@ + + + diff --git a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs index 292549a..8fe46a9 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs @@ -1,9 +1,4 @@ -using JiShe.CollectBus.PagedResult; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using JiShe.CollectBus.Common.Models; namespace JiShe.CollectBus.IoTDBProvider { diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs index 6ae0ef1..3dd1341 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs @@ -1,13 +1,13 @@ using Apache.IoTDB; using Apache.IoTDB.DataStructure; using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IoTDBProvider.Context; using JiShe.CollectBus.IoTDBProvider.Interface; using Microsoft.Extensions.Logging; using System.Collections.Concurrent; using System.Reflection; using System.Text; -using JiShe.CollectBus.PagedResult; namespace JiShe.CollectBus.IoTDBProvider diff --git a/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj b/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj index cef24d5..9518b0b 100644 --- a/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj +++ b/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj @@ -12,4 +12,8 @@ + + + + -- 2.47.2 From 00372913500bf2c950fb5ce93dfabb7229fb12e9 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Tue, 15 Apr 2025 16:08:07 +0800 Subject: [PATCH 094/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JiShe.CollectBus.Host/Pages/Monitor.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml index 37f84bf..aaadf3f 100644 --- a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml +++ b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml @@ -17,7 +17,7 @@ 后端服务 - +
/// RedisClient Instance { get; set; } + + /// + /// 单个添加数据 + /// + /// + /// 主数据存储Hash缓存Key + /// 集中器索引Set缓存Key + /// 集中器排序索引ZSET缓存Key + /// 集中器采集频率分组全局索引ZSet缓存Key + /// 表计信息 + /// 可选时间戳 + /// + Task AddMeterCacheData( + string redisCacheKey, + string redisCacheFocusIndexKey, + string redisCacheScoresIndexKey, + string redisCacheGlobalIndexKey, + T data, + DateTimeOffset? timestamp = null) where T : DeviceCacheBasicModel; + + /// + /// 批量添加数据 + /// + /// + /// 主数据存储Hash缓存Key + /// 集中器索引Set缓存Key + /// 集中器排序索引ZSET缓存Key + /// 集中器采集频率分组全局索引ZSet缓存Key + /// 数据集合 + /// 可选时间戳 + /// + Task BatchAddMeterData( + string redisCacheKey, + string redisCacheFocusIndexKey, + string redisCacheScoresIndexKey, + string redisCacheGlobalIndexKey, + IEnumerable items, + DateTimeOffset? timestamp = null) where T : DeviceCacheBasicModel; } } diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index a5b2e15..c922f8d 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -128,7 +128,7 @@ "OpenDebugMode": true, "UseTableSessionPoolByDefault": false }, - "ServerTagName": "JiSheCollectBus", + "ServerTagName": "JiSheCollectBus2", "KafkaReplicationFactor": 3, "NumPartitions": 30 } \ No newline at end of file -- 2.47.2 From b5f1f1f50bc5f2e3266be1f1f18258f36c2076b2 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Tue, 15 Apr 2025 17:40:17 +0800 Subject: [PATCH 097/139] =?UTF-8?q?=E5=A4=A7=E6=89=B9=E9=87=8F=E6=8F=92?= =?UTF-8?q?=E5=85=A5=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 68 +++++--- ...nergySystemScheduledMeterReadingService.cs | 8 +- .../Consts/RedisConst.cs | 33 +++- .../JiShe.CollectBus.Common.csproj | 1 + .../Ammeters/AmmeterInfoTemp.cs | 152 ++++++++++++++++++ 5 files changed, 228 insertions(+), 34 deletions(-) create mode 100644 src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfoTemp.cs diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 5a665ff..33e70f9 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -14,12 +14,14 @@ using JiShe.CollectBus.IotSystems.Watermeter; using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Repository.MeterReadingRecord; +using Mapster; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using static FreeSql.Internal.GlobalFilter; namespace JiShe.CollectBus.ScheduledMeterReading { @@ -207,26 +209,30 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// public virtual async Task InitAmmeterCacheData(string gatherCode = "") { -//#if DEBUG -// var timeDensity = "15"; -// //获取缓存中的电表信息 -// var redisKeyList = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}*"; +#if DEBUG + var timeDensity = "15"; + string tempCacheMeterInfoKey = $"CollectBus:{"{0}:{1}"}:MeterInfo:{"{2}"}:{"{3}"}"; + //获取缓存中的电表信息 + var redisKeyList = $"{string.Format(tempCacheMeterInfoKey, SystemType, "JiSheCollectBus", MeterTypeEnum.Ammeter, timeDensity)}*"; -// var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); -// var meterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, SystemType, ServerTagName, timeDensity, MeterTypeEnum.Ammeter); -// List focusAddressDataLista = new List(); -// foreach (var item in meterInfos) -// { -// focusAddressDataLista.Add(item.FocusAddress); -// } - -// DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista); -// return; -//#else -// var meterInfos = await GetAmmeterInfoList(gatherCode); -//#endif + var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + var tempMeterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, SystemType, ServerTagName, timeDensity, MeterTypeEnum.Ammeter); + //List focusAddressDataLista = new List(); + List meterInfos = new List(); + foreach (var item in tempMeterInfos) + { + var tempData = item.Adapt(); + tempData.FocusId = item.FocusID; + tempData.MeterId = item.Id; + meterInfos.Add(tempData); + //focusAddressDataLista.Add(item.FocusAddress); + } + //DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista); +#else var meterInfos = await GetAmmeterInfoList(gatherCode); +#endif + if (meterInfos == null || meterInfos.Count <= 0) { throw new NullReferenceException($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据时,电表数据为空"); @@ -238,6 +244,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { throw new NullReferenceException($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据时,采集项类型数据为空"); } + var timer = Stopwatch.StartNew(); List focusAddressDataList = new List();//用于处理Kafka主题分区数据的分发和处理。 @@ -245,6 +252,12 @@ namespace JiShe.CollectBus.ScheduledMeterReading var meterInfoGroupByTimeDensity = meterInfos.GroupBy(d => d.TimeDensity); foreach (var itemTimeDensity in meterInfoGroupByTimeDensity) { + var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; + var redisCacheFocusIndexKey = $"{string.Format(RedisConst.CacheMeterInfoFocusIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; + var redisCacheScoresIndexKey = $"{string.Format(RedisConst.CacheMeterInfoScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; + var redisCacheGlobalIndexKey = $"{string.Format(RedisConst.CacheMeterInfoGlobalIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; + + List ammeterInfos = new List(); //将表计信息根据集中器分组,获得集中器号 var meterInfoGroup = itemTimeDensity.GroupBy(x => x.FocusAddress).ToList(); foreach (var item in meterInfoGroup) @@ -256,17 +269,17 @@ namespace JiShe.CollectBus.ScheduledMeterReading focusAddressDataList.Add(item.Key); - var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}{item.Key}"; + // var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}{item.Key}"; #if DEBUG //每次缓存时,删除缓存,避免缓存数据有不准确的问题 //await FreeRedisProvider.Instance.DelAsync(redisCacheKey); #else //每次缓存时,删除缓存,避免缓存数据有不准确的问题 - await FreeRedisProvider.Instance.DelAsync(redisCacheKey); + //await FreeRedisProvider.Instance.DelAsync(redisCacheKey); #endif - Dictionary keyValuePairs = new Dictionary(); + //Dictionary keyValuePairs = new Dictionary(); foreach (var ammeter in item) { //处理ItemCode @@ -311,11 +324,18 @@ namespace JiShe.CollectBus.ScheduledMeterReading } } - keyValuePairs.TryAdd($"{ammeter.MeterId}", ammeter); + ammeterInfos.Add(ammeter); + //keyValuePairs.TryAdd($"{ammeter.MeterId}", ammeter); } - await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); + //await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); } + await FreeRedisProvider.BatchAddMeterData( + redisCacheKey, + redisCacheFocusIndexKey, + redisCacheScoresIndexKey, + redisCacheGlobalIndexKey, ammeterInfos); + //在缓存表信息数据的时候,新增下一个时间的自动处理任务,1分钟后执行所有的采集频率任务 TasksToBeIssueModel nextTask = new TasksToBeIssueModel() { @@ -338,7 +358,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading DeviceGroupBalanceControl.InitializeCache(focusAddressDataList); } - _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据完成"); + timer.Stop(); + + _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据完成,耗时{timer.ElapsedMilliseconds}毫秒"); } /// diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 07f5526..d44fe56 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -114,10 +114,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading WHERE 1=1 and C.Special = 0 "; //TODO 记得移除特殊表过滤 - if (!string.IsNullOrWhiteSpace(gatherCode)) - { - sql = $@"{sql} AND A.GatherCode = '{gatherCode}'"; - } + //if (!string.IsNullOrWhiteSpace(gatherCode)) + //{ + // sql = $@"{sql} AND A.GatherCode = '{gatherCode}'"; + //} return await SqlProvider.Instance.Change(DbEnum.EnergyDB) .Ado .QueryAsync(sql); diff --git a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs index ea4323b..9377056 100644 --- a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs @@ -28,25 +28,44 @@ namespace JiShe.CollectBus.Common.Consts /// public const string FifteenMinuteAcquisitionTimeInterval = "Fifteen"; + public const string MeterInfo = "MeterInfo"; /// /// 缓存表计信息,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 /// - public const string CacheMeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:MeterInfo:{"{2}"}:{"{3}"}:"; + public const string CacheMeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{MeterInfo}:{"{2}"}:{"{3}"}"; + /// + /// 缓存表计信息集中器索引Set缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 + /// + public const string CacheMeterInfoFocusIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{MeterInfo}:{"{2}"}:FocusIndex:{"{3}"}"; + + /// + /// 缓存表计信息集中器排序索引ZSET缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 + /// + public const string CacheMeterInfoScoresIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{MeterInfo}:{"{2}"}:ScoresIndex:{"{3}"}"; + + /// + /// 缓存表计信息集中器采集频率分组全局索引ZSet缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 + /// + public const string CacheMeterInfoGlobalIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{MeterInfo}:{"{2}"}:GlobalIndex:{"{3}"}"; + + + public const string TaskInfo = "TaskInfo"; /// /// 缓存待下发的指令生产任务数据,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 /// - public const string CacheTasksToBeIssuedKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:TaskInfo:{"{2}"}:{"{3}"}"; + public const string CacheTasksToBeIssuedKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TaskInfo}:{"{2}"}{"{3}"}"; + public const string TelemetryPacket = "TelemetryPacket"; /// /// 缓存表计下发指令数据集,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 /// - public const string CacheTelemetryPacketInfoKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:TelemetryPacket:{"{2}"}:{"{3}"}:"; + public const string CacheTelemetryPacketInfoKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TelemetryPacket}:{"{2}"}:{"{3}"}"; - /// - /// 缓存设备平衡关系映射结果,{0}=>系统类型,{1}=>应用服务部署标记 - /// - public const string CacheDeviceBalanceRelationMapResultKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:RelationMap"; + ///// + ///// 缓存设备平衡关系映射结果,{0}=>系统类型,{1}=>应用服务部署标记 + ///// + //public const string CacheDeviceBalanceRelationMapResultKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:RelationMap"; public const string CacheAmmeterFocusKey = "CacheAmmeterFocusKey"; } diff --git a/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj b/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj index b22e8ca..05645ef 100644 --- a/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj +++ b/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj @@ -17,6 +17,7 @@ + diff --git a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfoTemp.cs b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfoTemp.cs new file mode 100644 index 0000000..fce33fa --- /dev/null +++ b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfoTemp.cs @@ -0,0 +1,152 @@ +using JiShe.CollectBus.Common.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Ammeters +{ + public class AmmeterInfoTemp + { + /// + /// 集中器Id + /// + public int FocusID { get; set; } + + /// + /// 表Id + /// + public int Id { get; set; } + + /// + /// 电表名称 + /// + public string Name { get; set; } + + /// + /// 集中器地址 + /// + public string FocusAddress { get; set; } + + /// + /// 集中器地址 + /// + public string Address { get; set; } + + /// + /// 集中器区域代码 + /// + public string AreaCode { get; set; } + + /// + /// 电表类别 (1单相、2三相三线、3三相四线), + /// 07协议: 开合闸指令(1A开闸断电,1C单相表合闸,1B多相表合闸) 645 2007 表 + /// 97协议://true(合闸);false(跳闸) 545 1997 没有单相多相 之分 "true" ? "9966" : "3355" + /// + public int TypeName { get; set; } + + /// + /// 跳合闸状态字段: 0 合闸,1 跳闸 + /// 电表:TripState (0 合闸-通电, 1 断开、跳闸); + /// + public int TripState { get; set; } + + /// + /// 规约 -电表default(30) 1:97协议,30:07协议 + /// + public int? Protocol { get; set; } + + /// + /// 一个集中器下的[MeteringCode]必须唯一。 PN + /// + public int MeteringCode { get; set; } + + /// + /// 电表通信地址 + /// + public string AmmerterAddress { get; set; } + + /// + /// 波特率 default(2400) + /// + public int Baudrate { get; set; } + + /// + /// MeteringPort 端口就几个可以枚举。 + /// + public int MeteringPort { get; set; } + + /// + /// 电表密码 + /// + public string Password { get; set; } + + /// + /// 采集时间间隔(分钟,如15) + /// + public int TimeDensity { get; set; } + + /// + /// 该电表方案下采集项,JSON格式,如:["0D_80","0D_80"] + /// + public string ItemCodes { get; set; } + + /// + /// State表状态: + /// 0新装(未下发),1运行(档案下发成功时设置状态值1), 2暂停, 100销表(销表后是否重新启用) + /// 特定:State -1 已删除 + /// + public int State { get; set; } + + /// + /// 是否自动采集(0:主动采集,1:自动采集) + /// + public int AutomaticReport { get; set; } + + /// + /// 该电表方案下采集项编号 + /// + public string DataTypes { get; set; } + + /// + /// 品牌型号 + /// + public string BrandType { get; set; } + + /// + /// 采集器编号 + /// + public string GatherCode { get; set; } + + /// + /// 是否特殊表,1是特殊电表 + /// + public int Special { get; set; } + + /// + /// 费率类型,单、多 (SingleRate :单费率(单相表1),多费率(其他0) ,与TypeName字段无关) + /// SingleRate ? "单" : "复" + /// [SingleRate] --0 复费率 false , 1 单费率 true (与PayPlanID保持一致) + ///对应 TB_PayPlan.Type: 1复费率,2单费率 + /// + public bool SingleRate { get; set; } + + /// + /// 项目ID + /// + public int ProjectID { get; set; } + + /// + /// 数据库业务ID + /// + public int DatabaseBusiID { get; set; } + + /// + /// 是否异常集中器 0:正常,1异常 + /// + public int AbnormalState { get; set; } + + public DateTime LastTime { get; set; } + } +} -- 2.47.2 From aa55e476c2a0066331642f86ee4cbfeef9c59fc2 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Tue, 15 Apr 2025 17:57:47 +0800 Subject: [PATCH 098/139] =?UTF-8?q?=E5=B0=81=E8=A3=85Cassandra=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusApplicationModule.cs | 17 +- .../JiShe.CollectBus.Application.csproj | 3 + .../Samples/TestAppService.cs | 51 ++++++ .../Workers/CreateToBeIssueTaskWorker.cs | 2 +- .../Workers/SubscriberFifteenMinuteWorker.cs | 2 +- .../Workers/SubscriberFiveMinuteWorker.cs | 2 +- .../Workers/SubscriberOneMinuteWorker.cs | 2 +- .../BaseCassandraRepository.cs | 87 ---------- .../CassandraConfig.cs | 64 +++++++ .../CassandraConfiguration.cs | 45 ----- .../CassandraProvider.cs | 147 +++++++++++++++++ .../CassandraQueryOptimizer.cs | 156 ++++++++++++++++++ .../CassandraRepository.cs | 69 ++++++++ .../CassandraService.cs | 40 ----- .../CollectBusCassandraModule.cs | 20 ++- .../Extensions/ServiceCollectionExtensions.cs | 35 ++++ .../Extensions/SessionExtension.cs | 83 ++++++++++ .../ICassandraProvider.cs | 24 +++ .../ICassandraRepository.cs | 20 +++ .../ICassandraService.cs | 9 - .../JiShe.CollectBus.Cassandra.csproj | 5 +- .../Mappers/CollectBusMapping.cs | 20 +++ .../Attributes/CassandraTableAttribute.cs | 32 ++++ .../MessageIssueds/MessageIssued.cs | 4 + src/JiShe.CollectBus.Host/Program.cs | 40 ++++- src/JiShe.CollectBus.Host/Startup.cs | 9 +- src/JiShe.CollectBus.Host/appsettings.json | 45 ++++- .../JiSheCollectBusProtocolModule.cs | 4 +- 28 files changed, 830 insertions(+), 207 deletions(-) create mode 100644 src/JiShe.CollectBus.Application/Samples/TestAppService.cs delete mode 100644 src/JiShe.CollectBus.Cassandra/BaseCassandraRepository.cs create mode 100644 src/JiShe.CollectBus.Cassandra/CassandraConfig.cs delete mode 100644 src/JiShe.CollectBus.Cassandra/CassandraConfiguration.cs create mode 100644 src/JiShe.CollectBus.Cassandra/CassandraProvider.cs create mode 100644 src/JiShe.CollectBus.Cassandra/CassandraQueryOptimizer.cs create mode 100644 src/JiShe.CollectBus.Cassandra/CassandraRepository.cs delete mode 100644 src/JiShe.CollectBus.Cassandra/CassandraService.cs create mode 100644 src/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs create mode 100644 src/JiShe.CollectBus.Cassandra/ICassandraProvider.cs create mode 100644 src/JiShe.CollectBus.Cassandra/ICassandraRepository.cs delete mode 100644 src/JiShe.CollectBus.Cassandra/ICassandraService.cs create mode 100644 src/JiShe.CollectBus.Cassandra/Mappers/CollectBusMapping.cs create mode 100644 src/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 1a8b326..5f80f51 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -12,8 +12,11 @@ using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Threading.Tasks; +using JiShe.CollectBus.Cassandra; using Volo.Abp; using Volo.Abp.Application; +using Volo.Abp.Autofac; using Volo.Abp.AutoMapper; using Volo.Abp.BackgroundWorkers; using Volo.Abp.BackgroundWorkers.Hangfire; @@ -27,11 +30,13 @@ namespace JiShe.CollectBus; typeof(CollectBusApplicationContractsModule), typeof(AbpDddApplicationModule), typeof(AbpAutoMapperModule), + typeof(AbpAutofacModule), typeof(AbpBackgroundWorkersHangfireModule), typeof(CollectBusFreeRedisModule), typeof(CollectBusFreeSqlModule), typeof(CollectBusKafkaModule), - typeof(CollectBusIoTDBModule) + typeof(CollectBusIoTDBModule), + typeof(CollectBusCassandraModule) )] public class CollectBusApplicationModule : AbpModule { @@ -46,20 +51,20 @@ public class CollectBusApplicationModule : AbpModule }); } - public override void OnApplicationInitialization( + public override async Task OnApplicationInitializationAsync( ApplicationInitializationContext context) { var assembly = Assembly.GetExecutingAssembly(); var types = assembly.GetTypes().Where(t => typeof(ICollectWorker).IsAssignableFrom(t) && !t.IsInterface).ToList(); foreach (var type in types) { - context.AddBackgroundWorkerAsync(type); + await context.AddBackgroundWorkerAsync(type); } //默认初始化表计信息 var dbContext = context.ServiceProvider.GetRequiredService(); - dbContext.InitAmmeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); - dbContext.InitWatermeterCacheData().ConfigureAwait(false).GetAwaiter().GetResult(); + await dbContext.InitAmmeterCacheData(); + await dbContext.InitWatermeterCacheData(); //初始化主题信息 var kafkaAdminClient = context.ServiceProvider.GetRequiredService(); @@ -70,7 +75,7 @@ public class CollectBusApplicationModule : AbpModule foreach (var item in topics) { - kafkaAdminClient.CreateTopicAsync(item, configuration.GetValue(CommonConst.NumPartitions), configuration.GetValue(CommonConst.KafkaReplicationFactor)); + await kafkaAdminClient.CreateTopicAsync(item, configuration.GetValue(CommonConst.NumPartitions), configuration.GetValue(CommonConst.KafkaReplicationFactor)); } } diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index 1726f39..a2cc613 100644 --- a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -24,8 +24,11 @@ + + + diff --git a/src/JiShe.CollectBus.Application/Samples/TestAppService.cs b/src/JiShe.CollectBus.Application/Samples/TestAppService.cs new file mode 100644 index 0000000..9c53c3a --- /dev/null +++ b/src/JiShe.CollectBus.Application/Samples/TestAppService.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Apache.IoTDB.DataStructure; +using Apache.IoTDB; +using Confluent.Kafka; +using JiShe.CollectBus.Ammeters; +using JiShe.CollectBus.FreeSql; +using JiShe.CollectBus.IoTDBProvider; +using JiShe.CollectBus.IotSystems.PrepayModel; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using JiShe.CollectBus.IoTDBProvider.Context; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.IotSystems.AFNEntity; +using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using JiShe.CollectBus.Cassandra; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.IotSystems.MessageIssueds; +using Volo.Abp.Application.Services; + +namespace JiShe.CollectBus.Samples; + +[AllowAnonymous] +public class TestAppService : CollectBusAppService, IApplicationService +{ + private readonly ILogger _logger; + private readonly ICassandraRepository _messageIssuedCassandraRepository; + public TestAppService( + ILogger logger, + ICassandraRepository messageIssuedCassandraRepository + ) + { + _logger = logger; + _messageIssuedCassandraRepository = messageIssuedCassandraRepository; + } + public async Task AddMessage() + { + await _messageIssuedCassandraRepository.InsertAsync(new MessageIssued + { + ClientId = Guid.NewGuid().ToString(), + Message = Array.Empty(), + DeviceNo = "123321312", + MessageId = Guid.NewGuid().ToString(), + Type = IssuedEventType.Data + }); + } +} diff --git a/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs b/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs index 05fd90d..e994530 100644 --- a/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs @@ -27,7 +27,7 @@ namespace JiShe.CollectBus.Workers { _logger = logger; RecurringJobId = nameof(CreateToBeIssueTaskWorker); - CronExpression = $"{10}/* * * * *"; + CronExpression = "* 10 * * * *"; this._scheduledMeterReadingService = scheduledMeterReadingService; } diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs b/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs index f1bf5a1..441b22a 100644 --- a/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs @@ -26,7 +26,7 @@ namespace JiShe.CollectBus.Workers { _logger = logger; RecurringJobId = nameof(SubscriberFifteenMinuteWorker); - CronExpression = $"*/{15} * * * *"; + CronExpression = "* 15 * * * *"; this._scheduledMeterReadingService = scheduledMeterReadingService; } diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs b/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs index 2e491d6..0a61c63 100644 --- a/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs @@ -26,7 +26,7 @@ namespace JiShe.CollectBus.Workers { _logger = logger; RecurringJobId = nameof(SubscriberFiveMinuteWorker); - CronExpression = $"*/{5} * * * *"; + CronExpression = "* 5 * * * *"; this._scheduledMeterReadingService = scheduledMeterReadingService; } diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs b/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs index 82b979b..8b7cbfd 100644 --- a/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs +++ b/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs @@ -26,7 +26,7 @@ namespace JiShe.CollectBus.Workers { _logger = logger; RecurringJobId = nameof(SubscriberOneMinuteWorker); - CronExpression = $"*/{1} * * * *"; + CronExpression = "* 1 * * * *"; this._scheduledMeterReadingService = scheduledMeterReadingService; } diff --git a/src/JiShe.CollectBus.Cassandra/BaseCassandraRepository.cs b/src/JiShe.CollectBus.Cassandra/BaseCassandraRepository.cs deleted file mode 100644 index 67252e4..0000000 --- a/src/JiShe.CollectBus.Cassandra/BaseCassandraRepository.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Cassandra.Mapping; -using Cassandra; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.Cassandra -{ - /// - /// Cassandra数据库的基础仓储类 - /// 提供了通用的CRUD操作方法,所有具体的仓储类都应该继承此类 - /// - /// 实体类型 - public abstract class BaseCassandraRepository where T : class - { - /// - /// Cassandra数据库会话 - /// 用于执行CQL查询和操作 - /// - protected readonly ISession Session; - - /// - /// Cassandra映射器 - /// 用于对象关系映射(ORM),简化实体与数据库表之间的转换 - /// - protected readonly IMapper Mapper; - - /// - /// 构造函数 - /// 初始化数据库会话和映射器 - /// - /// Cassandra连接工厂 - protected BaseCassandraRepository(ICassandraService cassandraService) - { - Session = cassandraService.GetSession(); - Mapper = new Mapper(Session); - } - - /// - /// 根据ID获取单个实体 - /// - /// 实体ID - /// 返回找到的实体,如果未找到则返回null - public async Task GetByIdAsync(string id) - { - return await Mapper.SingleOrDefaultAsync($"WHERE id = ?", id); - } - - /// - /// 获取所有实体 - /// - /// 返回实体集合 - public async Task> GetAllAsync() - { - return await Mapper.FetchAsync(); - } - - /// - /// 插入新实体 - /// - /// 要插入的实体 - public async Task InsertAsync(T entity) - { - await Mapper.InsertAsync(entity); - } - - /// - /// 更新现有实体 - /// - /// 要更新的实体 - public async Task UpdateAsync(T entity) - { - await Mapper.UpdateAsync(entity); - } - - /// - /// 根据ID删除实体 - /// - /// 要删除的实体ID - public async Task DeleteAsync(string id) - { - await Mapper.DeleteAsync($"WHERE id = ?", id); - } - } -} diff --git a/src/JiShe.CollectBus.Cassandra/CassandraConfig.cs b/src/JiShe.CollectBus.Cassandra/CassandraConfig.cs new file mode 100644 index 0000000..c14171e --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/CassandraConfig.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Cassandra +{ + public class CassandraConfig + { + public Node[] Nodes { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public string Keyspace { get; set; } + public string ConsistencyLevel { get; set; } + public Pooling PoolingOptions { get; set; } + public Socket SocketOptions { get; set; } + public Query QueryOptions { get; set; } + + public ReplicationStrategy ReplicationStrategy { get; set; } + } + + public class Pooling + { + public int CoreConnectionsPerHost { get; set; } + public int MaxConnectionsPerHost { get; set; } + public int MaxRequestsPerConnection { get; set; } + } + + public class Socket + { + public int ConnectTimeoutMillis { get; set; } + public int ReadTimeoutMillis { get; set; } + } + + public class Query + { + public string ConsistencyLevel { get; set; } + public string SerialConsistencyLevel { get; set; } + public bool DefaultIdempotence { get; set; } + } + + public class ReplicationStrategy + { + public string Class { get; set; } + public DataCenter[] DataCenters { get; set; } + } + + public class DataCenter + { + public string Name { get; set; } + public int ReplicationFactor { get; set; } + public string Strategy { get; set; } + } + + public class Node + { + public string Host { get; set; } + public int Port { get; set; } + public string DataCenter { get; set; } + public string Rack { get; set; } + } + +} diff --git a/src/JiShe.CollectBus.Cassandra/CassandraConfiguration.cs b/src/JiShe.CollectBus.Cassandra/CassandraConfiguration.cs deleted file mode 100644 index 8fea3c1..0000000 --- a/src/JiShe.CollectBus.Cassandra/CassandraConfiguration.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.Cassandra -{ - /// - /// Cassandra数据库配置类 - /// 用于存储和管理Cassandra数据库的连接配置信息 - /// - public class CassandraConfiguration - { - /// - /// Cassandra集群的节点地址列表 - /// 可以配置多个节点地址,用于实现高可用和负载均衡 - /// - public string[] ContactPoints { get; set; } - - /// - /// Cassandra的键空间名称 - /// 键空间是Cassandra中数据组织的最高级别容器 - /// - public string Keyspace { get; set; } - - /// - /// Cassandra数据库的用户名 - /// 用于身份验证 - /// - public string Username { get; set; } - - /// - /// Cassandra数据库的密码 - /// 用于身份验证 - /// - public string Password { get; set; } - - /// - /// Cassandra数据库的端口号 - /// 默认值为9042,这是Cassandra的默认CQL端口 - /// - public int Port { get; set; } = 9042; - } -} diff --git a/src/JiShe.CollectBus.Cassandra/CassandraProvider.cs b/src/JiShe.CollectBus.Cassandra/CassandraProvider.cs new file mode 100644 index 0000000..a5cd9c8 --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/CassandraProvider.cs @@ -0,0 +1,147 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Text; +using Cassandra; +using Cassandra.Mapping; +using Cassandra.Data.Linq; +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using JiShe.CollectBus.Common.Attributes; + +namespace JiShe.CollectBus.Cassandra +{ + public class CassandraProvider : IDisposable, ICassandraProvider, ISingletonDependency + { + private readonly ILogger _logger; + + public Cluster Instance { get; set; } + + public ISession Session { get; set; } + + public CassandraConfig CassandraConfig { get; set; } + /// + /// + /// + /// + /// + public CassandraProvider( + IOptions options, + ILogger logger) + { + CassandraConfig = options.Value; + _logger = logger; + } + + public void InitClusterAndSession() + { + GetCluster((keyspace) => + { + GetSession(keyspace); + }); + } + + public Cluster GetCluster(Action? callback=null) + { + var clusterBuilder = Cluster.Builder(); + + // 添加多个节点 + foreach (var node in CassandraConfig.Nodes) + { + clusterBuilder.AddContactPoint(node.Host) + .WithPort(node.Port); + } + + clusterBuilder.WithCredentials(CassandraConfig.Username, CassandraConfig.Password); + + // 优化连接池配置 + var poolingOptions = new PoolingOptions() + .SetCoreConnectionsPerHost(HostDistance.Local, CassandraConfig.PoolingOptions.CoreConnectionsPerHost) + .SetMaxConnectionsPerHost(HostDistance.Local, CassandraConfig.PoolingOptions.MaxConnectionsPerHost) + .SetMaxRequestsPerConnection(CassandraConfig.PoolingOptions.MaxRequestsPerConnection) + .SetHeartBeatInterval(30000); // 30秒心跳 + + clusterBuilder.WithPoolingOptions(poolingOptions); + + // 优化Socket配置 + var socketOptions = new SocketOptions() + .SetConnectTimeoutMillis(CassandraConfig.SocketOptions.ConnectTimeoutMillis) + .SetReadTimeoutMillis(CassandraConfig.SocketOptions.ReadTimeoutMillis) + .SetTcpNoDelay(true) // 启用Nagle算法 + .SetKeepAlive(true) // 启用TCP保活 + .SetReceiveBufferSize(32768) // 32KB接收缓冲区 + .SetSendBufferSize(32768); // 32KB发送缓冲区 + + clusterBuilder.WithSocketOptions(socketOptions); + + // 优化查询选项 + var queryOptions = new QueryOptions() + .SetConsistencyLevel((ConsistencyLevel)Enum.Parse(typeof(ConsistencyLevel), CassandraConfig.QueryOptions.ConsistencyLevel)) + .SetSerialConsistencyLevel((ConsistencyLevel)Enum.Parse(typeof(ConsistencyLevel), CassandraConfig.QueryOptions.SerialConsistencyLevel)) + .SetDefaultIdempotence(CassandraConfig.QueryOptions.DefaultIdempotence) + .SetPageSize(5000); // 增加页面大小 + + clusterBuilder.WithQueryOptions(queryOptions); + + // 启用压缩 + clusterBuilder.WithCompression(CompressionType.LZ4); + + // 配置重连策略 + clusterBuilder.WithReconnectionPolicy(new ExponentialReconnectionPolicy(1000, 10 * 60 * 1000)); + Instance = clusterBuilder.Build(); + callback?.Invoke(null); + return Instance; + } + + public ISession GetSession(string? keyspace = null) + { + if (string.IsNullOrEmpty(keyspace)) + { + keyspace = CassandraConfig.Keyspace; + } + Session = Instance.Connect(); + var replication = GetReplicationStrategy(); + Session.CreateKeyspaceIfNotExists(keyspace, replication); + Session.ChangeKeyspace(keyspace); + return Session; + } + + private Dictionary GetReplicationStrategy() + { + var strategy = CassandraConfig.ReplicationStrategy.Class; + var dataCenters = CassandraConfig.ReplicationStrategy.DataCenters; + + switch (strategy) + { + case "NetworkTopologyStrategy": + var networkDic = new Dictionary { { "class", "NetworkTopologyStrategy" } }; + foreach (var dataCenter in dataCenters) + { + networkDic.Add(dataCenter.Name, dataCenter.ReplicationFactor.ToString()); + } + return networkDic; + case "SimpleStrategy": + var dic = new Dictionary { { "class", "SimpleStrategy" } }; + if (dataCenters.Length >= 1) + { + dic.Add("replication_factor", dataCenters[0].ReplicationFactor.ToString()); + } + else + { + _logger.LogError("SimpleStrategy 不支持多个数据中心!"); + } + return dic; + default: + throw new ArgumentNullException($"Strategy", "Strategy配置错误!"); + } + } + + public void Dispose() + { + Instance.Dispose(); + Session.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/JiShe.CollectBus.Cassandra/CassandraQueryOptimizer.cs b/src/JiShe.CollectBus.Cassandra/CassandraQueryOptimizer.cs new file mode 100644 index 0000000..0ea1b56 --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/CassandraQueryOptimizer.cs @@ -0,0 +1,156 @@ +using System.Collections.Concurrent; +using Cassandra; +using Cassandra.Mapping; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Cassandra +{ + public class CassandraQueryOptimizer + { + private readonly ISession _session; + private readonly ILogger _logger; + private readonly IMemoryCache _cache; + private readonly ConcurrentDictionary _preparedStatements; + private readonly int _batchSize; + private readonly TimeSpan _cacheExpiration; + + public CassandraQueryOptimizer( + ISession session, + ILogger logger, + IMemoryCache cache, + int batchSize = 100, + TimeSpan? cacheExpiration = null) + { + _session = session; + _logger = logger; + _cache = cache; + _preparedStatements = new ConcurrentDictionary(); + _batchSize = batchSize; + _cacheExpiration = cacheExpiration ?? TimeSpan.FromMinutes(5); + } + + public async Task GetOrPrepareStatementAsync(string cql) + { + return _preparedStatements.GetOrAdd(cql, key => + { + try + { + var statement = _session.Prepare(key); + _logger.LogDebug($"Prepared statement for CQL: {key}"); + return statement; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Failed to prepare statement for CQL: {key}"); + throw; + } + }); + } + + public async Task ExecuteBatchAsync(IEnumerable statements) + { + var batch = new BatchStatement(); + var count = 0; + + foreach (var statement in statements) + { + batch.Add(statement); + count++; + + if (count >= _batchSize) + { + await ExecuteBatchAsync(batch); + batch = new BatchStatement(); + count = 0; + } + } + + if (count > 0) + { + await ExecuteBatchAsync(batch); + } + } + + private async Task ExecuteBatchAsync(BatchStatement batch) + { + try + { + await _session.ExecuteAsync(batch); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to execute batch statement"); + throw; + } + } + + public async Task GetOrSetFromCacheAsync(string cacheKey, Func> getData) + { + if (_cache.TryGetValue(cacheKey, out T cachedValue)) + { + _logger.LogDebug($"Cache hit for key: {cacheKey}"); + return cachedValue; + } + + var data = await getData(); + _cache.Set(cacheKey, data, _cacheExpiration); + _logger.LogDebug($"Cache miss for key: {cacheKey}, data cached"); + return data; + } + + public async Task> ExecutePagedQueryAsync( + string cql, + object[] parameters, + int pageSize = 100, + string pagingState = null) where T : class + { + var statement = await GetOrPrepareStatementAsync(cql); + var boundStatement = statement.Bind(parameters); + + if (!string.IsNullOrEmpty(pagingState)) + { + boundStatement.SetPagingState(Convert.FromBase64String(pagingState)); + } + + boundStatement.SetPageSize(pageSize); + + try + { + var result = await _session.ExecuteAsync(boundStatement); + //TODO: RETURN OBJECT + throw new NotImplementedException(); + //result.GetRows() + //return result.Select(row => row); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Failed to execute paged query: {cql}"); + throw; + } + } + + public async Task BulkInsertAsync(IEnumerable items, string tableName) + { + var mapper = new Mapper(_session); + var batch = new List(); + var cql = $"INSERT INTO {tableName} ({{0}}) VALUES ({{1}})"; + + foreach (var chunk in items.Chunk(_batchSize)) + { + var statements = chunk.Select(item => + { + var props = typeof(T).GetProperties(); + var columns = string.Join(", ", props.Select(p => p.Name)); + var values = string.Join(", ", props.Select(p => "?")); + var statement = _session.Prepare(string.Format(cql, columns, values)); + return statement.Bind(props.Select(p => p.GetValue(item)).ToArray()); + }); + + batch.AddRange(statements); + } + + await ExecuteBatchAsync(batch); + } + } +} \ No newline at end of file diff --git a/src/JiShe.CollectBus.Cassandra/CassandraRepository.cs b/src/JiShe.CollectBus.Cassandra/CassandraRepository.cs new file mode 100644 index 0000000..8381450 --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/CassandraRepository.cs @@ -0,0 +1,69 @@ +using Cassandra; +using Cassandra.Mapping; +using JiShe.CollectBus.Cassandra.Extensions; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Repositories; + +namespace JiShe.CollectBus.Cassandra +{ + public class CassandraRepository + : ICassandraRepository + where TEntity : class + { + + public CassandraRepository(ICassandraProvider cassandraProvider, MappingConfiguration mappingConfig) + { + Mapper = new Mapper(cassandraProvider.Session, mappingConfig); + cassandraProvider.Session.CreateTable(cassandraProvider.CassandraConfig.Keyspace); + } + + public readonly IMapper Mapper; + + public virtual async Task GetAsync(TKey id) + { + return await Mapper.SingleOrDefaultAsync("WHERE id = ?", id); + } + + public virtual async Task> GetListAsync() + { + return (await Mapper.FetchAsync()).ToList(); + } + + public virtual async Task InsertAsync(TEntity entity) + { + await Mapper.InsertAsync(entity); + return entity; + } + + public virtual async Task UpdateAsync(TEntity entity) + { + await Mapper.UpdateAsync(entity); + return entity; + } + + public virtual async Task DeleteAsync(TEntity entity) + { + await Mapper.DeleteAsync(entity); + } + + public virtual async Task DeleteAsync(TKey id) + { + await Mapper.DeleteAsync("WHERE id = ?", id); + } + + public virtual async Task> GetPagedListAsync( + int skipCount, + int maxResultCount, + string sorting) + { + var cql = $"SELECT * FROM {typeof(TEntity).Name.ToLower()}"; + if (!string.IsNullOrWhiteSpace(sorting)) + { + cql += $" ORDER BY {sorting}"; + } + cql += $" LIMIT {maxResultCount} OFFSET {skipCount}"; + + return (await Mapper.FetchAsync(cql)).ToList(); + } + } +} \ No newline at end of file diff --git a/src/JiShe.CollectBus.Cassandra/CassandraService.cs b/src/JiShe.CollectBus.Cassandra/CassandraService.cs deleted file mode 100644 index 8836235..0000000 --- a/src/JiShe.CollectBus.Cassandra/CassandraService.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Diagnostics.Metrics; -using Cassandra; -using Microsoft.Extensions.Options; -using Volo.Abp.DependencyInjection; - -namespace JiShe.CollectBus.Cassandra -{ - public class CassandraService : ICassandraService, ISingletonDependency - { - private readonly CassandraConfiguration _configuration; - public ISession Instance { get; set; } = default; - - /// - /// CassandraService - /// - /// - public CassandraService(IOptions configuration) - { - _configuration = configuration.Value; - GetSession(); - } - - /// - /// 获取Cassandra数据库会话 - /// - /// - public ISession GetSession() - { - var cluster = Cluster.Builder() - .AddContactPoints(_configuration.ContactPoints) - .WithPort(_configuration.Port) - .WithCredentials(_configuration.Username, _configuration.Password) - .Build(); - - Instance = cluster.Connect(_configuration.Keyspace); - - return Instance; - } - } -} diff --git a/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs b/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs index e7c2c69..2502420 100644 --- a/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs +++ b/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs @@ -1,13 +1,29 @@ -using Microsoft.Extensions.DependencyInjection; +using Cassandra; +using Cassandra.Mapping; +using JiShe.CollectBus.Cassandra.Mappers; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Autofac; using Volo.Abp.Modularity; namespace JiShe.CollectBus.Cassandra { + [DependsOn( + typeof(AbpAutofacModule) + )] public class CollectBusCassandraModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.Configure(context.Services.GetConfiguration().GetSection("Cassandra")); + Configure(context.Services.GetConfiguration().GetSection("Cassandra")); + + context.AddCassandra(); + + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + context.UseCassandra(); } } } diff --git a/src/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs b/src/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..9c6a044 --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,35 @@ +using Autofac.Core; +using Cassandra; +using Cassandra.Mapping; +using JiShe.CollectBus.Cassandra; +using JiShe.CollectBus.Cassandra.Mappers; +using Microsoft.Extensions.Options; +using System.Reflection; +using Volo.Abp; +using Volo.Abp.Modularity; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.DependencyInjection +{ + public static class ApplicationInitializationContextExtensions + { + public static void UseCassandra(this ApplicationInitializationContext context) + { + var service = context.ServiceProvider; + var cassandraProvider = service.GetRequiredService(); + cassandraProvider.InitClusterAndSession(); + } + } + + public static class ServiceCollectionExtensions + { + public static void AddCassandra(this ServiceConfigurationContext context) + { + context.Services.AddSingleton(typeof(ICassandraRepository<,>), typeof(CassandraRepository<,>)); + + var mappingConfig = new MappingConfiguration() + .Define(new CollectBusMapping()); + context.Services.AddSingleton(mappingConfig); + } + } +} diff --git a/src/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs b/src/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs new file mode 100644 index 0000000..f515416 --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs @@ -0,0 +1,83 @@ +using System.Reflection; +using System.Text; +using Cassandra; +using System.ComponentModel.DataAnnotations; +using JiShe.CollectBus.Common.Attributes; +using Cassandra.Mapping; + +namespace JiShe.CollectBus.Cassandra.Extensions +{ + public static class SessionExtension + { + public static void CreateTable(this ISession session,string? defaultKeyspace=null) where TEntity : class + { + var type = typeof(TEntity); + var tableAttribute = type.GetCustomAttribute(); + var tableName = tableAttribute?.Name ?? type.Name.ToLower(); + var tableKeyspace = tableAttribute?.Keyspace ?? defaultKeyspace; + + var properties = type.GetProperties(); + var primaryKey = properties.FirstOrDefault(p => p.GetCustomAttribute() != null); + + if (primaryKey == null) + { + throw new InvalidOperationException($"No primary key defined for type {type.Name}"); + } + + var cql = new StringBuilder(); + cql.Append($"CREATE TABLE IF NOT EXISTS {tableKeyspace}.{tableName} ("); + + foreach (var prop in properties) + { + var ignoreAttribute = prop.GetCustomAttribute(); + if (ignoreAttribute != null) continue; + var columnName = prop.Name.ToLower(); + var cqlType = GetCassandraType(prop.PropertyType); + + cql.Append($"{columnName} {cqlType}, "); + } + cql.Length -= 2; // Remove last comma and space + cql.Append($", PRIMARY KEY ({primaryKey.Name.ToLower()}))"); + + session.Execute(cql.ToString()); + } + + private static string GetCassandraType(Type type) + { + // 基础类型处理 + switch (Type.GetTypeCode(type)) + { + case TypeCode.String: return "text"; + case TypeCode.Int32: return "int"; + case TypeCode.Int64: return "bigint"; + case TypeCode.Boolean: return "boolean"; + case TypeCode.DateTime: return "timestamp"; + case TypeCode.Byte: return "tinyint"; + } + + if (type == typeof(Guid)) return "uuid"; + if (type == typeof(DateTimeOffset)) return "timestamp"; + if (type == typeof(Byte[])) return "blob"; + + // 处理集合类型 + if (type.IsGenericType) + { + var genericType = type.GetGenericTypeDefinition(); + var elementType = type.GetGenericArguments()[0]; + + if (genericType == typeof(List<>)) + return $"list<{GetCassandraType(elementType)}>"; + if (genericType == typeof(HashSet<>)) + return $"set<{GetCassandraType(elementType)}>"; + if (genericType == typeof(Dictionary<,>)) + { + var keyType = type.GetGenericArguments()[0]; + var valueType = type.GetGenericArguments()[1]; + return $"map<{GetCassandraType(keyType)}, {GetCassandraType(valueType)}>"; + } + } + + throw new NotSupportedException($"不支持的类型: {type.Name}"); + } + } +} diff --git a/src/JiShe.CollectBus.Cassandra/ICassandraProvider.cs b/src/JiShe.CollectBus.Cassandra/ICassandraProvider.cs new file mode 100644 index 0000000..45c6ca6 --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/ICassandraProvider.cs @@ -0,0 +1,24 @@ +using Cassandra; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Cassandra +{ + public interface ICassandraProvider + { + Cluster Instance { get;} + + ISession Session { get;} + + CassandraConfig CassandraConfig { get; } + + ISession GetSession(string? keyspace = null); + + Cluster GetCluster(Action? callback = null); + + void InitClusterAndSession(); + } +} diff --git a/src/JiShe.CollectBus.Cassandra/ICassandraRepository.cs b/src/JiShe.CollectBus.Cassandra/ICassandraRepository.cs new file mode 100644 index 0000000..6dd474f --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/ICassandraRepository.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities; + +namespace JiShe.CollectBus.Cassandra +{ + public interface ICassandraRepository where TEntity : class + { + Task GetAsync(TKey id); + Task> GetListAsync(); + Task InsertAsync(TEntity entity); + Task UpdateAsync(TEntity entity); + Task DeleteAsync(TEntity entity); + Task DeleteAsync(TKey id); + Task> GetPagedListAsync(int skipCount, int maxResultCount, string sorting); + } +} diff --git a/src/JiShe.CollectBus.Cassandra/ICassandraService.cs b/src/JiShe.CollectBus.Cassandra/ICassandraService.cs deleted file mode 100644 index 70eb5d6..0000000 --- a/src/JiShe.CollectBus.Cassandra/ICassandraService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Cassandra; - -namespace JiShe.CollectBus.Cassandra -{ - public interface ICassandraService - { - ISession GetSession(); - } -} diff --git a/src/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj b/src/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj index 5a8b22d..db7ccf3 100644 --- a/src/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj +++ b/src/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj @@ -9,11 +9,14 @@ + + - + + diff --git a/src/JiShe.CollectBus.Cassandra/Mappers/CollectBusMapping.cs b/src/JiShe.CollectBus.Cassandra/Mappers/CollectBusMapping.cs new file mode 100644 index 0000000..d07f8ea --- /dev/null +++ b/src/JiShe.CollectBus.Cassandra/Mappers/CollectBusMapping.cs @@ -0,0 +1,20 @@ +using Cassandra.Mapping; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using JiShe.CollectBus.IotSystems.MessageIssueds; +using static Cassandra.QueryTrace; + +namespace JiShe.CollectBus.Cassandra.Mappers +{ + public class CollectBusMapping: Mappings + { + public CollectBusMapping() + { + For() + .Column(e => e.Type, cm => cm.WithName("type").WithDbType()); + } + } +} diff --git a/src/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs b/src/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs new file mode 100644 index 0000000..bdd16d5 --- /dev/null +++ b/src/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.EventBus; +using Volo.Abp; + +namespace JiShe.CollectBus.Common.Attributes +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class CassandraTableAttribute : Attribute + { + public CassandraTableAttribute(string? name = null,string? keyspace =null) + { + Name = name; + Keyspace = keyspace; + } + + public virtual string? Name { get; } + + public virtual string? Keyspace { get; } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class CassandraIgnoreAttribute : Attribute + { + public CassandraIgnoreAttribute() + { + } + } +} diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs index 42d740c..5977d5e 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs @@ -1,14 +1,18 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; +using JiShe.CollectBus.Common.Attributes; using JiShe.CollectBus.Common.Enums; namespace JiShe.CollectBus.IotSystems.MessageIssueds { + [CassandraTable] public class MessageIssued { + [Key] public string ClientId { get; set; } public byte[] Message { get; set; } public string DeviceNo { get; set; } diff --git a/src/JiShe.CollectBus.Host/Program.cs b/src/JiShe.CollectBus.Host/Program.cs index 40a13cc..d75a227 100644 --- a/src/JiShe.CollectBus.Host/Program.cs +++ b/src/JiShe.CollectBus.Host/Program.cs @@ -1,12 +1,35 @@ using JiShe.CollectBus.Host; using Microsoft.AspNetCore.Hosting; using Serilog; +using Volo.Abp.Modularity.PlugIns; public class Program { - public static void Main(string[] args) + /// + /// + /// + /// + /// + public static async Task Main(string[] args) { - CreateHostBuilder(args).Build().Run(); + var builder = WebApplication.CreateBuilder(args); + builder.Host.UseContentRoot(Directory.GetCurrentDirectory()) + .UseSerilog((context, loggerConfiguration) => + { + loggerConfiguration.ReadFrom.Configuration(context.Configuration); + }) + .UseAutofac(); + await builder.AddApplicationAsync(options => + { + // ز̶ģʽȲ + options.PlugInSources.AddFolder(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins")); + }); + var app = builder.Build(); + await app.InitializeApplicationAsync(); + await app.RunAsync(); + + + //await CreateHostBuilder(args).Build().RunAsync(); } private static IHostBuilder CreateHostBuilder(string[] args) => @@ -16,13 +39,14 @@ public class Program { loggerConfiguration.ReadFrom.Configuration(context.Configuration); }) - .UseAutofac() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); - }); - - + }) + .UseAutofac(); + + + private static IHostBuilder CreateConsoleHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) @@ -34,8 +58,8 @@ public class Program }); - private static void ConfigureServices(IServiceCollection services, HostBuilderContext hostContext) + private static async Task ConfigureServices(IServiceCollection services, HostBuilderContext hostContext) { - services.AddApplication(); + await services.AddApplicationAsync(); } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.Host/Startup.cs b/src/JiShe.CollectBus.Host/Startup.cs index dd6453e..32740ee 100644 --- a/src/JiShe.CollectBus.Host/Startup.cs +++ b/src/JiShe.CollectBus.Host/Startup.cs @@ -39,10 +39,15 @@ namespace JiShe.CollectBus.Host /// The lifetime. public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime) { - app.InitializeApplication(); - //await app.InitializeApplicationAsync(); + app.Use(async (context, next) => + { + // 在请求处理之前调用 InitializeApplicationAsync + await app.InitializeApplicationAsync(); + // 继续请求管道中的下一个中间件 + await next(); + }); } } } diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index a5b2e15..a837636 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -130,5 +130,48 @@ }, "ServerTagName": "JiSheCollectBus", "KafkaReplicationFactor": 3, - "NumPartitions": 30 + "NumPartitions": 30, + "Cassandra": { + "ReplicationStrategy": { + "Class": "NetworkTopologyStrategy", //策略为NetworkTopologyStrategy时才会有多个数据中心,SimpleStrategy用在只有一个数据中心的情况下 + "DataCenters": [ + { + "Name": "dc1", + "ReplicationFactor": 3 + } + ] + }, + "Nodes": [ + { + "Host": "192.168.1.9", + "Port": 9042, + "DataCenter": "dc1", + "Rack": "RAC1" + }, + { + "Host": "192.168.1.9", + "Port": 9043, + "DataCenter": "dc1", + "Rack": "RAC2" + } + ], + "Username": "admin", + "Password": "lixiao1980", + "Keyspace": "jishecollectbus", + "ConsistencyLevel": "Quorum", + "PoolingOptions": { + "CoreConnectionsPerHost": 4, + "MaxConnectionsPerHost": 8, + "MaxRequestsPerConnection": 2000 + }, + "SocketOptions": { + "ConnectTimeoutMillis": 10000, + "ReadTimeoutMillis": 20000 + }, + "QueryOptions": { + "ConsistencyLevel": "Quorum", + "SerialConsistencyLevel": "Serial", + "DefaultIdempotence": true + } + } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs b/src/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs index 64661d1..5cda0c8 100644 --- a/src/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs +++ b/src/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs @@ -12,10 +12,10 @@ namespace JiShe.CollectBus.Protocol context.Services.AddKeyedSingleton(nameof(StandardProtocolPlugin)); } - public override void OnApplicationInitialization(ApplicationInitializationContext context) + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) { var standardProtocol = context.ServiceProvider.GetRequiredKeyedService(nameof(StandardProtocolPlugin)); - standardProtocol.AddAsync(); + await standardProtocol.AddAsync(); } } } -- 2.47.2 From 3b29e58951398c8c5681772745d587f34e937fb7 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Tue, 15 Apr 2025 18:01:50 +0800 Subject: [PATCH 099/139] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 7 ------- 1 file changed, 7 deletions(-) diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index 4b4a650..9c67aae 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -39,8 +39,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.T EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Cassandra", "src\JiShe.CollectBus.Cassandra\JiShe.CollectBus.Cassandra.csproj", "{443B4549-0AC0-4493-8F3E-49C83225DD76}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka.Test", "JiShe.CollectBus.Kafka.Test\JiShe.CollectBus.Kafka.Test.csproj", "{82E4562A-3A7F-4372-8D42-8AE41BA56C04}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -115,10 +113,6 @@ Global {443B4549-0AC0-4493-8F3E-49C83225DD76}.Debug|Any CPU.Build.0 = Debug|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.ActiveCfg = Release|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.Build.0 = Release|Any CPU - {82E4562A-3A7F-4372-8D42-8AE41BA56C04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {82E4562A-3A7F-4372-8D42-8AE41BA56C04}.Debug|Any CPU.Build.0 = Debug|Any CPU - {82E4562A-3A7F-4372-8D42-8AE41BA56C04}.Release|Any CPU.ActiveCfg = Release|Any CPU - {82E4562A-3A7F-4372-8D42-8AE41BA56C04}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -141,7 +135,6 @@ Global {A3F3C092-0A25-450B-BF6A-5983163CBEF5} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {A377955E-7EA1-6F29-8CF7-774569E93925} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {443B4549-0AC0-4493-8F3E-49C83225DD76} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {82E4562A-3A7F-4372-8D42-8AE41BA56C04} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} -- 2.47.2 From f9577156a34000f0737635ba3b4b65f49aba6c69 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Tue, 15 Apr 2025 18:02:39 +0800 Subject: [PATCH 100/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 7 ------- 1 file changed, 7 deletions(-) diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index 1d185db..c26f3da 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -37,8 +37,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.IoTDBProvi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.Test", "src\JiShe.CollectBus.Protocol.Test\JiShe.CollectBus.Protocol.Test.csproj", "{A377955E-7EA1-6F29-8CF7-774569E93925}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka.Test", "JiShe.CollectBus.Kafka.Test\JiShe.CollectBus.Kafka.Test.csproj", "{82E4562A-3A7F-4372-8D42-8AE41BA56C04}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -109,10 +107,6 @@ Global {A377955E-7EA1-6F29-8CF7-774569E93925}.Debug|Any CPU.Build.0 = Debug|Any CPU {A377955E-7EA1-6F29-8CF7-774569E93925}.Release|Any CPU.ActiveCfg = Release|Any CPU {A377955E-7EA1-6F29-8CF7-774569E93925}.Release|Any CPU.Build.0 = Release|Any CPU - {82E4562A-3A7F-4372-8D42-8AE41BA56C04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {82E4562A-3A7F-4372-8D42-8AE41BA56C04}.Debug|Any CPU.Build.0 = Debug|Any CPU - {82E4562A-3A7F-4372-8D42-8AE41BA56C04}.Release|Any CPU.ActiveCfg = Release|Any CPU - {82E4562A-3A7F-4372-8D42-8AE41BA56C04}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -134,7 +128,6 @@ Global {F0288175-F0EC-48BD-945F-CF1512850943} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {A3F3C092-0A25-450B-BF6A-5983163CBEF5} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {A377955E-7EA1-6F29-8CF7-774569E93925} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {82E4562A-3A7F-4372-8D42-8AE41BA56C04} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} -- 2.47.2 From 72d1b9f623ee392decfa8daafea9bd1f0367ee72 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Tue, 15 Apr 2025 18:03:51 +0800 Subject: [PATCH 101/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=A2=E9=98=85?= =?UTF-8?q?=E5=9B=9E=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/SampleAppService.cs | 12 +++++++-- .../Pages/Monitor.cshtml | 1 + .../KafkaSubcribesExtensions.cs | 27 ++++++++++++------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 3e1c2ea..8f5449a 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -215,11 +215,19 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS return aa == null; } - [KafkaSubscribe(["test-topic"])] + [KafkaSubscribe(["test-topic1"])] - public async Task KafkaSubscribeAsync(object obj) + public async Task KafkaSubscribeAsync() // TestSubscribe obj { + var obj=string.Empty; _logger.LogWarning($"收到订阅消息: {obj}"); return SubscribeAck.Success(); } } + +public class TestSubscribe +{ + public string Topic { get; set; } + public int Val { get; set; } +} + diff --git a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml index aaadf3f..b438e18 100644 --- a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml +++ b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml @@ -16,6 +16,7 @@ 后端服务 + diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs index 6cf2b60..d3ce904 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -1,4 +1,7 @@ using Confluent.Kafka; +using DeviceDetectorNET; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Kafka.Attributes; using JiShe.CollectBus.Kafka.Consumer; using Microsoft.AspNetCore.Builder; @@ -8,6 +11,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Newtonsoft.Json; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -96,6 +100,7 @@ namespace JiShe.CollectBus.Kafka }); } + /// /// 处理消息 /// @@ -106,15 +111,18 @@ namespace JiShe.CollectBus.Kafka private static async Task ProcessMessageAsync(string message, MethodInfo method, object subscribe) { var parameters = method.GetParameters(); - if (parameters.Length != 1) - return true; - - var paramType = parameters[0].ParameterType; - var messageObj = paramType == typeof(string)? message: JsonConvert.DeserializeObject(message, paramType); - - if (method.ReturnType == typeof(Task)) + bool isGenericTask = method.ReturnType.IsGenericType + && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>); + bool existParameters = parameters.Length > 0; + dynamic? messageObj= null; + if (existParameters) { - object? result = await (Task)method.Invoke(subscribe, new[] { messageObj })!; + var paramType = parameters[0].ParameterType; + messageObj = paramType == typeof(string) ? message : message.Deserialize(paramType); + } + if (isGenericTask) + { + object? result = await (Task)method.Invoke(subscribe, existParameters? new[] { messageObj }:null)!; if (result is ISubscribeAck ackResult) { return ackResult.Ack; @@ -122,13 +130,12 @@ namespace JiShe.CollectBus.Kafka } else { - object? result = method.Invoke(subscribe, new[] { messageObj }); + object? result = method.Invoke(subscribe, existParameters ? new[] { messageObj } : null); if (result is ISubscribeAck ackResult) { return ackResult.Ack; } } - return false; } -- 2.47.2 From 11d3fcf162ce187880a8470ad3646d37f50cc548 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Tue, 15 Apr 2025 18:58:38 +0800 Subject: [PATCH 102/139] =?UTF-8?q?=E7=A7=BB=E9=99=A4CAP=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...he.CollectBus.Application.Contracts.csproj | 1 + .../Subscribers/ISubscriberAppService.cs | 11 +++--- .../IWorkerSubscriberAppService.cs | 9 +++-- .../JiShe.CollectBus.Application.csproj | 1 - .../Plugins/TcpMonitor.cs | 18 +++++---- .../Samples/SampleAppService.cs | 11 +----- .../BasicScheduledMeterReadingService.cs | 13 +++---- ...nergySystemScheduledMeterReadingService.cs | 2 +- .../Subscribers/SubscriberAppService.cs | 39 ++++++++++++------- .../Subscribers/WorkerSubscriberAppService.cs | 24 +++++++----- .../CollectBusHostModule.cs | 2 +- .../Pages/Monitor.cshtml | 1 - .../Consumer/ConsumerService.cs | 6 +-- .../Producer/IProducerService.cs | 2 +- .../Producer/ProducerService.cs | 10 ++--- .../Abstracts/BaseProtocolPlugin.cs | 10 +++-- ...JiShe.CollectBus.Protocol.Contracts.csproj | 3 +- 17 files changed, 89 insertions(+), 74 deletions(-) diff --git a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj index de972d1..6e4fed5 100644 --- a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj +++ b/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj @@ -26,6 +26,7 @@ + diff --git a/src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs b/src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs index f9bc706..658ff29 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs @@ -1,16 +1,17 @@ using System.Threading.Tasks; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.Kafka; using Volo.Abp.Application.Services; namespace JiShe.CollectBus.Subscribers { public interface ISubscriberAppService : IApplicationService { - Task LoginIssuedEvent(IssuedEventMessage issuedEventMessage); - Task HeartbeatIssuedEvent(IssuedEventMessage issuedEventMessage); - Task ReceivedEvent(MessageReceived receivedMessage); - Task ReceivedHeartbeatEvent(MessageReceivedHeartbeat receivedHeartbeatMessage); - Task ReceivedLoginEvent(MessageReceivedLogin receivedLoginMessage); + Task LoginIssuedEvent(IssuedEventMessage issuedEventMessage); + Task HeartbeatIssuedEvent(IssuedEventMessage issuedEventMessage); + Task ReceivedEvent(MessageReceived receivedMessage); + Task ReceivedHeartbeatEvent(MessageReceivedHeartbeat receivedHeartbeatMessage); + Task ReceivedLoginEvent(MessageReceivedLogin receivedLoginMessage); } } diff --git a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs index abba774..9a37167 100644 --- a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs @@ -1,6 +1,7 @@ using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; +using JiShe.CollectBus.Kafka; using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Services; @@ -19,19 +20,19 @@ namespace JiShe.CollectBus.Subscribers /// 1分钟采集电表数据下行消息消费订阅 ///
/// - Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); + Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); /// /// 5分钟采集电表数据下行消息消费订阅 /// /// - Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); + Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); /// /// 15分钟采集电表数据下行消息消费订阅 /// /// - Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); + Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); #endregion #region 水表消息采集 @@ -39,7 +40,7 @@ namespace JiShe.CollectBus.Subscribers /// 1分钟采集水表数据下行消息消费订阅 ///
/// - Task WatermeterSubscriberWorkerAutoReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); + Task WatermeterSubscriberWorkerAutoReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); #endregion } diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index a2cc613..24db5a5 100644 --- a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -23,7 +23,6 @@ - diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index ff4de0c..29427fc 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -7,10 +7,12 @@ using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Enums; using JiShe.CollectBus.Interceptors; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Protocol.Contracts; using MassTransit; using Microsoft.Extensions.Logging; @@ -26,7 +28,7 @@ namespace JiShe.CollectBus.Plugins { public partial class TcpMonitor : PluginBase, ITransientDependency, ITcpReceivedPlugin, ITcpConnectingPlugin, ITcpConnectedPlugin, ITcpClosedPlugin { - private readonly ICapPublisher _producerBus; + private readonly IProducerService _producerService; private readonly ILogger _logger; private readonly IRepository _deviceRepository; private readonly IDistributedCache _ammeterInfoCache; @@ -34,16 +36,16 @@ namespace JiShe.CollectBus.Plugins /// /// /// - /// + /// /// /// /// - public TcpMonitor(ICapPublisher producerBus, + public TcpMonitor(IProducerService producerService, ILogger logger, IRepository deviceRepository, IDistributedCache ammeterInfoCache) { - _producerBus = producerBus; + _producerService = producerService; _logger = logger; _deviceRepository = deviceRepository; _ammeterInfoCache = ammeterInfoCache; @@ -170,7 +172,7 @@ namespace JiShe.CollectBus.Plugins DeviceNo = deviceNo, MessageId = NewId.NextGuid().ToString() }; - await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent); + await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent.Serialize()); //await _producerBus.Publish( messageReceivedLoginEvent); } @@ -217,7 +219,7 @@ namespace JiShe.CollectBus.Plugins DeviceNo = deviceNo, MessageId = NewId.NextGuid().ToString() }; - await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent); + await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent.Serialize()); //await _producerBus.Publish(messageReceivedHeartbeatEvent); } @@ -245,7 +247,7 @@ namespace JiShe.CollectBus.Plugins //string topicName = string.Format(ProtocolConst.AFNTopicNameFormat, aFn); //todo 如何确定时标?目前集中器的采集频率,都是固定,数据上报的时候,根据当前时间,往后推测出应当采集的时间点作为时标。但是如果由于网络问题,数据一直没上报的情况改怎么计算? - await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedEventName, new MessageReceived + await _producerService.ProduceAsync(ProtocolConst.SubscriberReceivedEventName, new MessageReceived { ClientId = client.Id, ClientIp = client.IP, @@ -253,7 +255,7 @@ namespace JiShe.CollectBus.Plugins MessageHexString = messageHexString, DeviceNo = deviceNo, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); } } } diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 8f5449a..78b472d 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -215,19 +215,12 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS return aa == null; } - [KafkaSubscribe(["test-topic1"])] + [KafkaSubscribe(["test-topic"])] - public async Task KafkaSubscribeAsync() // TestSubscribe obj + public async Task KafkaSubscribeAsync(object obj) { - var obj=string.Empty; _logger.LogWarning($"收到订阅消息: {obj}"); return SubscribeAck.Success(); } } -public class TestSubscribe -{ - public string Topic { get; set; } - public int Val { get; set; } -} - diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 287158f..f2a08b1 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -29,7 +29,6 @@ namespace JiShe.CollectBus.ScheduledMeterReading public abstract class BasicScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService { private readonly ILogger _logger; - private readonly ICapPublisher _producerBus; private readonly IIoTDBProvider _dbProvider; private readonly IMeterReadingRecordRepository _meterReadingRecordRepository; private readonly IProducerService _producerService; @@ -37,12 +36,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading public BasicScheduledMeterReadingService( ILogger logger, - ICapPublisher producerBus, IMeterReadingRecordRepository meterReadingRecordRepository, IProducerService producerService, IIoTDBProvider dbProvider) { - _producerBus = producerBus; _logger = logger; _dbProvider = dbProvider; _meterReadingRecordRepository = meterReadingRecordRepository; @@ -381,7 +378,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - _ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg.Serialize()); //_= _producerBus.Publish(tempMsg); @@ -445,7 +442,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - _ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg); + _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg.Serialize()); //_ = _producerBus.Publish(tempMsg); @@ -510,7 +507,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading TimeDensity = timeDensity.ToString(), }; - _ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); + _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg.Serialize()); //_ = _producerBus.Publish(tempMsg); @@ -805,7 +802,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } int partition = DeviceGroupBalanceControl.GetDeviceGroupId(taskRecord.FocusAddress); - await _producerService.ProduceAsync(topicName, partition, taskRecord); + await _producerService.ProduceAsync(topicName, partition, taskRecord.Serialize()); } private async Task AmmerterCreatePublishTask(int timeDensity, MeterTypeEnum meterType) @@ -846,7 +843,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading TimeDensity = timeDensity.ToString(), }; - _ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); + _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg.Serialize()); //_ = _producerBus.Publish(tempMsg); diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 91f7d4a..8593dfd 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -36,7 +36,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { string serverTagName = string.Empty; public EnergySystemScheduledMeterReadingService(ILogger logger, - ICapPublisher producerBus, IIoTDBProvider dbProvider, IMeterReadingRecordRepository meterReadingRecordRepository,IConfiguration configuration, IProducerService producerService) : base(logger, producerBus, meterReadingRecordRepository, producerService,dbProvider) + IIoTDBProvider dbProvider, IMeterReadingRecordRepository meterReadingRecordRepository,IConfiguration configuration, IProducerService producerService) : base(logger, meterReadingRecordRepository, producerService,dbProvider) { serverTagName = configuration.GetValue(CommonConst.ServerTagName)!; } diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index d5e9caf..9bf297f 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -6,6 +6,8 @@ using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; +using JiShe.CollectBus.Kafka; +using JiShe.CollectBus.Kafka.Attributes; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using JiShe.CollectBus.Protocol.Contracts.Models; @@ -20,7 +22,7 @@ using Volo.Abp.Domain.Repositories; namespace JiShe.CollectBus.Subscribers { - public class SubscriberAppService : CollectBusAppService, ISubscriberAppService, ICapSubscribe + public class SubscriberAppService : CollectBusAppService, ISubscriberAppService, IKafkaSubscribe { private readonly ILogger _logger; private readonly ITcpService _tcpService; @@ -63,9 +65,10 @@ namespace JiShe.CollectBus.Subscribers _dbProvider = dbProvider; } - [CapSubscribe(ProtocolConst.SubscriberLoginIssuedEventName)] - public async Task LoginIssuedEvent(IssuedEventMessage issuedEventMessage) + [KafkaSubscribe(ProtocolConst.SubscriberLoginIssuedEventName)] + public async Task LoginIssuedEvent(IssuedEventMessage issuedEventMessage) { + bool isAck = false; switch (issuedEventMessage.Type) { case IssuedEventType.Heartbeat: @@ -76,6 +79,7 @@ namespace JiShe.CollectBus.Subscribers loginEntity.AckTime = Clock.Now; loginEntity.IsAck = true; await _messageReceivedLoginEventRepository.UpdateAsync(loginEntity); + isAck = true; break; case IssuedEventType.Data: break; @@ -90,11 +94,13 @@ namespace JiShe.CollectBus.Subscribers //} await _tcpService.SendAsync(issuedEventMessage.ClientId, issuedEventMessage.Message); + return isAck? SubscribeAck.Success(): SubscribeAck.Fail(); } - [CapSubscribe(ProtocolConst.SubscriberHeartbeatIssuedEventName)] - public async Task HeartbeatIssuedEvent(IssuedEventMessage issuedEventMessage) + [KafkaSubscribe(ProtocolConst.SubscriberHeartbeatIssuedEventName)] + public async Task HeartbeatIssuedEvent(IssuedEventMessage issuedEventMessage) { + bool isAck = false; switch (issuedEventMessage.Type) { case IssuedEventType.Heartbeat: @@ -103,6 +109,7 @@ namespace JiShe.CollectBus.Subscribers heartbeatEntity.AckTime = Clock.Now; heartbeatEntity.IsAck = true; await _messageReceivedHeartbeatEventRepository.UpdateAsync(heartbeatEntity); + isAck = true; break; case IssuedEventType.Data: break; @@ -117,10 +124,11 @@ namespace JiShe.CollectBus.Subscribers //} await _tcpService.SendAsync(issuedEventMessage.ClientId, issuedEventMessage.Message); + return isAck ? SubscribeAck.Success() : SubscribeAck.Fail(); } - [CapSubscribe(ProtocolConst.SubscriberReceivedEventName)] - public async Task ReceivedEvent(MessageReceived receivedMessage) + [KafkaSubscribe(ProtocolConst.SubscriberReceivedEventName)] + public async Task ReceivedEvent(MessageReceived receivedMessage) { var currentTime = Clock.Now; @@ -137,13 +145,13 @@ namespace JiShe.CollectBus.Subscribers if(fN == null) { Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); - return; + return SubscribeAck.Success(); } var tb3761FN = fN.FnList.FirstOrDefault(); if (tb3761FN == null) { Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); - return; + return SubscribeAck.Success(); } //报文入库 @@ -169,11 +177,14 @@ namespace JiShe.CollectBus.Subscribers //todo 查找是否有下发任务 //await _messageReceivedEventRepository.InsertAsync(receivedMessage); + + } + return SubscribeAck.Success(); } - [CapSubscribe(ProtocolConst.SubscriberHeartbeatReceivedEventName)] - public async Task ReceivedHeartbeatEvent(MessageReceivedHeartbeat receivedHeartbeatMessage) + [KafkaSubscribe(ProtocolConst.SubscriberHeartbeatReceivedEventName)] + public async Task ReceivedHeartbeatEvent(MessageReceivedHeartbeat receivedHeartbeatMessage) { var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); if (protocolPlugin == null) @@ -185,10 +196,11 @@ namespace JiShe.CollectBus.Subscribers await protocolPlugin.HeartbeatAsync(receivedHeartbeatMessage); await _messageReceivedHeartbeatEventRepository.InsertAsync(receivedHeartbeatMessage); } + return SubscribeAck.Success(); } - [CapSubscribe(ProtocolConst.SubscriberLoginReceivedEventName)] - public async Task ReceivedLoginEvent(MessageReceivedLogin receivedLoginMessage) + [KafkaSubscribe(ProtocolConst.SubscriberLoginReceivedEventName)] + public async Task ReceivedLoginEvent(MessageReceivedLogin receivedLoginMessage) { var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); if (protocolPlugin == null) @@ -200,6 +212,7 @@ namespace JiShe.CollectBus.Subscribers await protocolPlugin.LoginAsync(receivedLoginMessage); await _messageReceivedLoginEventRepository.InsertAsync(receivedLoginMessage); } + return SubscribeAck.Success(); } } } diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index 3caba20..f3c485c 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -8,6 +8,8 @@ using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; +using JiShe.CollectBus.Kafka; +using JiShe.CollectBus.Kafka.Attributes; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using JiShe.CollectBus.Repository.MeterReadingRecord; @@ -24,7 +26,7 @@ namespace JiShe.CollectBus.Subscribers /// 定时抄读任务消息消费订阅 ///
[Route($"/worker/app/subscriber")] - public class WorkerSubscriberAppService : CollectBusAppService, IWorkerSubscriberAppService,ICapSubscribe + public class WorkerSubscriberAppService : CollectBusAppService, IWorkerSubscriberAppService, IKafkaSubscribe { private readonly ILogger _logger; private readonly ITcpService _tcpService; @@ -63,8 +65,8 @@ namespace JiShe.CollectBus.Subscribers /// [HttpPost] [Route("ammeter/oneminute/issued-event")] - [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] - public async Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) + [KafkaSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] + public async Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("1分钟采集电表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); @@ -81,6 +83,7 @@ namespace JiShe.CollectBus.Subscribers } } + return SubscribeAck.Success(); } /// @@ -90,8 +93,8 @@ namespace JiShe.CollectBus.Subscribers /// [HttpPost] [Route("ammeter/fiveminute/issued-event")] - [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName)] - public async Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) + [KafkaSubscribe(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName)] + public async Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("5分钟采集电表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); @@ -108,6 +111,7 @@ namespace JiShe.CollectBus.Subscribers } } + return SubscribeAck.Success(); } /// @@ -117,8 +121,8 @@ namespace JiShe.CollectBus.Subscribers /// [HttpPost] [Route("ammeter/fifteenminute/issued-event")] - [CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName)] - public async Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) + [KafkaSubscribe(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName)] + public async Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("15分钟采集电表数据下行消息消费队列开始处理"); try @@ -137,6 +141,7 @@ namespace JiShe.CollectBus.Subscribers } } + return SubscribeAck.Success(); } catch (Exception ex) { @@ -155,8 +160,8 @@ namespace JiShe.CollectBus.Subscribers /// [HttpPost] [Route("watermeter/fifteenminute/issued-event")] - [CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerAutoReadingIssuedEventName)] - public async Task WatermeterSubscriberWorkerAutoReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) + [KafkaSubscribe(ProtocolConst.WatermeterSubscriberWorkerAutoReadingIssuedEventName)] + public async Task WatermeterSubscriberWorkerAutoReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("15分钟采集水表数据下行消息消费队列开始处理"); var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); @@ -172,6 +177,7 @@ namespace JiShe.CollectBus.Subscribers await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); } } + return SubscribeAck.Success(); } #endregion } diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs index aabc2ba..377453e 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs @@ -43,7 +43,7 @@ namespace JiShe.CollectBus.Host ConfigureNetwork(context, configuration); ConfigureJwtAuthentication(context, configuration); ConfigureHangfire(context); - ConfigureCap(context, configuration); + //ConfigureCap(context, configuration); //ConfigureMassTransit(context, configuration); //ConfigureKafkaTopic(context, configuration); ConfigureAuditLog(context); diff --git a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml index b438e18..aaadf3f 100644 --- a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml +++ b/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml @@ -16,7 +16,6 @@ 后端服务 - diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index 08b8cb1..f59385f 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -146,14 +146,14 @@ namespace JiShe.CollectBus.Kafka.Consumer /// public async Task SubscribeAsync(string[] topics, Func> messageHandler, string? groupId) where TValue : class { - var consumerKey = typeof((Null, TValue)); + var consumerKey = typeof((Ignore, TValue)); var cts = new CancellationTokenSource(); var consumer = _consumerStore.GetOrAdd(consumerKey, _=> ( - CreateConsumer(groupId), + CreateConsumer(groupId), cts - )).Consumer as IConsumer; + )).Consumer as IConsumer; consumer!.Subscribe(topics); diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs index becea90..b00f5cf 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs @@ -15,6 +15,6 @@ namespace JiShe.CollectBus.Kafka.Producer Task ProduceAsync(string topic, TKey key, TValue value, int? partition, Action>? deliveryHandler = null) where TKey : notnull where TValue : class; - Task ProduceAsync(string topic, TValue value, int? partition = null, Action>? deliveryHandler = null) where TValue : class; + Task ProduceAsync(string topic, TValue value, int? partition = null, Action>? deliveryHandler = null) where TValue : class; } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs index ca45f8c..c322294 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs @@ -114,8 +114,8 @@ namespace JiShe.CollectBus.Kafka.Producer /// public async Task ProduceAsync(string topic, TValue value) where TValue : class { - var producer = GetProducer(); - await producer.ProduceAsync(topic, new Message { Value = value }); + var producer = GetProducer(); + await producer.ProduceAsync(topic, new Message { Value = value }); } /// @@ -160,13 +160,13 @@ namespace JiShe.CollectBus.Kafka.Producer /// /// /// - public async Task ProduceAsync(string topic, TValue value, int? partition=null, Action>? deliveryHandler = null) where TValue : class + public async Task ProduceAsync(string topic, TValue value, int? partition=null, Action>? deliveryHandler = null) where TValue : class { - var message = new Message + var message = new Message { Value = value }; - var producer = GetProducer(); + var producer = GetProducer(); if (partition.HasValue) { var topicPartition = new TopicPartition(topic, partition.Value); diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index 762a2ca..23d0917 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -12,12 +12,14 @@ using Microsoft.Extensions.DependencyInjection; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.Protocols; using MassTransit; +using JiShe.CollectBus.Kafka.Producer; +using JiShe.CollectBus.Common.Helpers; namespace JiShe.CollectBus.Protocol.Contracts.Abstracts { public abstract class BaseProtocolPlugin : IProtocolPlugin { - private readonly ICapPublisher _producerBus; + private readonly IProducerService _producerService; private readonly ILogger _logger; private readonly IRepository _protocolInfoRepository; @@ -37,7 +39,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts _logger = serviceProvider.GetRequiredService>(); _protocolInfoRepository = serviceProvider.GetRequiredService>(); - _producerBus = serviceProvider.GetRequiredService(); + _producerService = serviceProvider.GetRequiredService(); } public abstract ProtocolInfo Info { get; } @@ -87,7 +89,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); - await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); + await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }.Serialize()); //await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); } @@ -126,7 +128,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts Fn = 1 }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); - await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); + await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }.Serialize()); //await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); } diff --git a/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj b/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj index aa5bd73..248ee30 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj +++ b/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj @@ -7,7 +7,7 @@ - + @@ -17,6 +17,7 @@ + -- 2.47.2 From e1106b32417285a363809b1208603117739dc74f Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Tue, 15 Apr 2025 19:09:16 +0800 Subject: [PATCH 103/139] =?UTF-8?q?=E7=A7=BB=E9=99=A4CAP=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EnergySystem/EnergySystemAppService.cs | 77 ++++++++++--------- .../Plugins/TcpMonitor.cs | 1 - .../BasicScheduledMeterReadingService.cs | 3 +- ...nergySystemScheduledMeterReadingService.cs | 1 - .../Subscribers/SubscriberAppService.cs | 3 +- .../Subscribers/WorkerSubscriberAppService.cs | 1 - .../Abstracts/BaseProtocolPlugin.cs | 3 +- 7 files changed, 42 insertions(+), 47 deletions(-) diff --git a/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs b/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs index 0542ede..a210d28 100644 --- a/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs +++ b/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs @@ -5,15 +5,16 @@ using System.Net; using System.Text; using System.Threading.Tasks; using DeviceDetectorNET.Class.Device; -using DotNetCore.CAP; using JiShe.CollectBus.Common.BuildSendDatas; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.EnergySystem.Dto; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.IotSystems.PrepayModel; using JiShe.CollectBus.IotSystems.Records; +using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Protocol.Contracts; using MassTransit; using Microsoft.AspNetCore.Mvc; @@ -28,15 +29,15 @@ namespace JiShe.CollectBus.EnergySystem private readonly IRepository _focusRecordRepository; private readonly IRepository _csqRecordRepository; private readonly IRepository _conrOnlineRecordRepository; - private readonly ICapPublisher _capBus; + private readonly IProducerService _producerService; public EnergySystemAppService(IRepository focusRecordRepository, IRepository csqRecordRepository, - IRepository conrOnlineRecordRepository, ICapPublisher capBus) + IRepository conrOnlineRecordRepository, IProducerService producerService) { _focusRecordRepository = focusRecordRepository; _csqRecordRepository = csqRecordRepository; _conrOnlineRecordRepository = conrOnlineRecordRepository; - _capBus = capBus; + _producerService = producerService; } /// @@ -71,14 +72,14 @@ namespace JiShe.CollectBus.EnergySystem return result; - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); result.Status = true; result.Msg = "操作成功"; result.Data.ValidData = true; @@ -108,14 +109,14 @@ namespace JiShe.CollectBus.EnergySystem foreach (var bytes in bytesList) { - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); } return result; @@ -149,14 +150,14 @@ namespace JiShe.CollectBus.EnergySystem }).ToList(); var bytes = Build3761SendData.BuildAmmeterParameterSetSendCmd(address, meterParameters); - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); result.Status = true; result.Msg = "操作成功"; result.Data.ValidData = true; @@ -178,14 +179,14 @@ namespace JiShe.CollectBus.EnergySystem { var dataUnit = Build645SendData.BuildReadMeterAddressSendDataUnit(detail.MeterAddress); var bytes =Build3761SendData.BuildTransparentForwardingSendCmd(address, detail.Port, detail.BaudRate.ToString(), dataUnit, StopBit.Stop1, Parity.None); - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); } result.Status = true; @@ -261,14 +262,14 @@ namespace JiShe.CollectBus.EnergySystem if (bytes != null) { - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); if (isManual) { @@ -320,14 +321,14 @@ namespace JiShe.CollectBus.EnergySystem var bytes = Build3761SendData.BuildCommunicationParametersSetSendCmd(address, masterIP, materPort, backupIP, backupPort, input.Data.APN); - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); result.Status = true; result.Msg = "操作成功"; @@ -347,14 +348,14 @@ namespace JiShe.CollectBus.EnergySystem var address = $"{input.AreaCode}{input.Address}"; var bytes = Build3761SendData.BuildTerminalCalendarClockSendCmd(address); - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); result.Status = true; result.Msg = "操作成功"; @@ -375,14 +376,14 @@ namespace JiShe.CollectBus.EnergySystem bool isManual = !input.AreaCode.Equals("5110");//低功耗集中器不是长连接,在连接的那一刻再发送 var bytes = Build3761SendData.BuildConrCheckTimeSendCmd(address,DateTime.Now, isManual); - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); result.Status = true; result.Msg = "操作成功"; @@ -402,14 +403,14 @@ namespace JiShe.CollectBus.EnergySystem var address = $"{input.AreaCode}{input.Address}"; var bytes = Build3761SendData.BuildConrRebootSendCmd(address); - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); result.Status = true; result.Msg = "操作成功"; @@ -430,14 +431,14 @@ namespace JiShe.CollectBus.EnergySystem var address = $"{input.AreaCode}{input.Address}"; var pnList = input.Data.Split(',').Select(it => int.Parse(it)).ToList(); var bytes = Build3761SendData.BuildAmmeterParameterReadingSendCmd(address, pnList); - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); result.Status = true; result.Msg = "操作成功"; return result; @@ -479,14 +480,14 @@ namespace JiShe.CollectBus.EnergySystem foreach (var bytes in bytesList) { - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); } result.Status = true; @@ -548,14 +549,14 @@ namespace JiShe.CollectBus.EnergySystem foreach (var bytes in bytesList) { - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); } result.Status = true; result.Msg = "操作成功"; @@ -577,14 +578,14 @@ namespace JiShe.CollectBus.EnergySystem var address = $"{code.AreaCode}{code.Address}"; var bytes = Build3761SendData.BuildAmmeterReportCollectionItemsSetSendCmd(address,input.Detail.Pn, input.Detail.Unit,input.Detail.Cycle,input.Detail.BaseTime, input.Detail.CurveRatio,input.Detail.Details.Select(it => new PnFn(it.Pn,it.Fn)).ToList()); - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); } result.Status = true; result.Msg = "操作成功"; @@ -605,14 +606,14 @@ namespace JiShe.CollectBus.EnergySystem { var address = $"{code.AreaCode}{code.Address}"; var bytes = Build3761SendData.BuildAmmeterAutoUpSwitchSetSendCmd(address, input.Detail.Pn,input.Detail.IsOpen); - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); } result.Status = true; result.Msg = "操作成功"; @@ -631,14 +632,14 @@ namespace JiShe.CollectBus.EnergySystem var result = new BaseResultDto(); var address = $"{input.AreaCode}{input.Address}"; var bytes = Build3761SendData.BuildAmmeterReadAutoUpSwitchSendCmd(address, input.Detail.Pn); - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); result.Status = true; result.Msg = "操作成功"; return result; @@ -658,14 +659,14 @@ namespace JiShe.CollectBus.EnergySystem { var address = $"{data.AreaCode}{data.Address}"; var bytes = Build3761SendData.BuildTerminalVersionInfoReadingSendCmd(address); - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); } result.Status = true; result.Msg = "操作成功"; @@ -713,14 +714,14 @@ namespace JiShe.CollectBus.EnergySystem foreach (var bytes in bytesList) { - await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }); + }.Serialize()); } result.Status = true; diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index 29427fc..7af99bc 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using DeviceDetectorNET.Parser.Device; -using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index f2a08b1..0f601d9 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -1,5 +1,4 @@ -using DotNetCore.CAP; -using JiShe.CollectBus.Ammeters; +using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.BuildSendDatas; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.DeviceBalanceControl; diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 8593dfd..c1e45bd 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using Confluent.Kafka; -using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.DeviceBalanceControl; diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index 9bf297f..fb469ac 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -1,5 +1,4 @@ -using DotNetCore.CAP; -using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IoTDBProvider; diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index f3c485c..95b563c 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using DeviceDetectorNET.Parser.Device; -using DotNetCore.CAP; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index 23d0917..d066a28 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -1,5 +1,4 @@ -using DotNetCore.CAP; -using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.Protocol.Contracts.Interfaces; -- 2.47.2 From 57123d653c62bb8817f129435f1cefc5b774c11a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E7=9B=8A?= Date: Tue, 15 Apr 2025 23:20:46 +0800 Subject: [PATCH 104/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 1 - .../Models/BusCacheGlobalPagedResult.cs | 7 +- .../Models/BusPagedResult.cs | 15 + .../Models/DeviceCacheBasicModel.cs | 4 +- .../FreeRedisProvider.cs | 544 +++++++++++++----- .../IFreeRedisProvider.cs | 60 ++ 6 files changed, 491 insertions(+), 140 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 33e70f9..2fd9781 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -21,7 +21,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using static FreeSql.Internal.GlobalFilter; namespace JiShe.CollectBus.ScheduledMeterReading { diff --git a/src/JiShe.CollectBus.Common/Models/BusCacheGlobalPagedResult.cs b/src/JiShe.CollectBus.Common/Models/BusCacheGlobalPagedResult.cs index ed42529..303b2e4 100644 --- a/src/JiShe.CollectBus.Common/Models/BusCacheGlobalPagedResult.cs +++ b/src/JiShe.CollectBus.Common/Models/BusCacheGlobalPagedResult.cs @@ -17,6 +17,11 @@ namespace JiShe.CollectBus.Common.Models /// public List Items { get; set; } + /// + /// 总条数 + /// + public long TotalCount { get; set; } + /// /// 是否有下一页 /// @@ -25,7 +30,7 @@ namespace JiShe.CollectBus.Common.Models /// /// 下一页的分页索引 /// - public long? NextScore { get; set; } + public decimal? NextScore { get; set; } /// /// 下一页的分页索引 diff --git a/src/JiShe.CollectBus.Common/Models/BusPagedResult.cs b/src/JiShe.CollectBus.Common/Models/BusPagedResult.cs index 2e68b14..f2bd1a3 100644 --- a/src/JiShe.CollectBus.Common/Models/BusPagedResult.cs +++ b/src/JiShe.CollectBus.Common/Models/BusPagedResult.cs @@ -31,5 +31,20 @@ namespace JiShe.CollectBus.Common.Models /// 数据集合 /// public IEnumerable Items { get; set; } + + /// + /// 是否有下一页 + /// + public bool HasNext { get; set; } + + /// + /// 下一页的分页索引 + /// + public decimal? NextScore { get; set; } + + /// + /// 下一页的分页索引 + /// + public string NextMember { get; set; } } } diff --git a/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs b/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs index 06a427b..59011de 100644 --- a/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs +++ b/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs @@ -22,8 +22,8 @@ namespace JiShe.CollectBus.Common.Models public int MeterId { get; set; } /// - /// 唯一标识 + /// 唯一标识,是redis ZSet和Set memberid /// - public virtual string UniqueId => $"{FocusId}:{MeterId}"; + public virtual string MemberID => $"{FocusId}:{MeterId}"; } } diff --git a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs index 1e07e20..cebc9c4 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs @@ -2,12 +2,13 @@ using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.Common.Extensions; -using JiShe.CollectBus.FreeRedisProvider.Options; +using JiShe.CollectBus.FreeRedisProvider.Options; using Microsoft.Extensions.Options; using System.Diagnostics; using System.Text.Json; using Volo.Abp.DependencyInjection; using static System.Runtime.InteropServices.JavaScript.JSType; +using System.Collections.Concurrent; namespace JiShe.CollectBus.FreeRedisProvider { @@ -70,12 +71,12 @@ namespace JiShe.CollectBus.FreeRedisProvider { throw new ArgumentException($"{nameof(AddMeterCacheData)} 参数异常,-101"); } - + // 计算组合score(分类ID + 时间戳) var actualTimestamp = timestamp ?? DateTimeOffset.UtcNow; long scoreValue = ((long)data.FocusId << 32) | (uint)actualTimestamp.Ticks; - + //全局索引写入 long globalScore = actualTimestamp.ToUnixTimeMilliseconds(); @@ -83,16 +84,16 @@ namespace JiShe.CollectBus.FreeRedisProvider using (var trans = Instance.Multi()) { // 主数据存储Hash - trans.HSet(redisCacheKey, data.UniqueId, data.Serialize()); - + trans.HSet(redisCacheKey, data.MemberID, data.Serialize()); + // 分类索引 - trans.SAdd(redisCacheFocusIndexKey, data.UniqueId); + trans.SAdd(redisCacheFocusIndexKey, data.MemberID); // 排序索引使用ZSET - trans.ZAdd(redisCacheScoresIndexKey, scoreValue, data.UniqueId); + trans.ZAdd(redisCacheScoresIndexKey, scoreValue, data.MemberID); //全局索引 - trans.ZAdd(redisCacheGlobalIndexKey, globalScore, data.UniqueId); + trans.ZAdd(redisCacheGlobalIndexKey, globalScore, data.MemberID); var results = trans.Exec(); @@ -122,6 +123,16 @@ namespace JiShe.CollectBus.FreeRedisProvider IEnumerable items, DateTimeOffset? timestamp = null) where T : DeviceCacheBasicModel { + if (items == null + || items.Count() <=0 + || string.IsNullOrWhiteSpace(redisCacheKey) + || string.IsNullOrWhiteSpace(redisCacheFocusIndexKey) + || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey) + || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey)) + { + throw new ArgumentException($"{nameof(BatchAddMeterData)} 参数异常,-101"); + } + const int BATCH_SIZE = 1000; // 每批1000条 var semaphore = new SemaphoreSlim(Environment.ProcessorCount * 2); @@ -144,16 +155,16 @@ namespace JiShe.CollectBus.FreeRedisProvider long globalScore = actualTimestamp.ToUnixTimeMilliseconds(); // 主数据存储Hash - pipe.HSet(redisCacheKey, item.UniqueId, item.Serialize()); + pipe.HSet(redisCacheKey, item.MemberID, item.Serialize()); // 分类索引 - pipe.SAdd(redisCacheFocusIndexKey, item.UniqueId); + pipe.SAdd(redisCacheFocusIndexKey, item.MemberID); // 排序索引使用ZSET - pipe.ZAdd(redisCacheScoresIndexKey, scoreValue, item.UniqueId); + pipe.ZAdd(redisCacheScoresIndexKey, scoreValue, item.MemberID); //全局索引 - pipe.ZAdd(redisCacheGlobalIndexKey, globalScore, item.UniqueId); + pipe.ZAdd(redisCacheGlobalIndexKey, globalScore, item.MemberID); } pipe.EndPipe(); } @@ -164,29 +175,108 @@ namespace JiShe.CollectBus.FreeRedisProvider await Task.CompletedTask; } - public async Task UpdateMeterData( + /// + /// 删除指定redis缓存key的缓存数据 + /// + /// + /// 主数据存储Hash缓存Key + /// 集中器索引Set缓存Key + /// 集中器排序索引ZSET缓存Key + /// 集中器采集频率分组全局索引ZSet缓存Key + /// 表计信息 + /// + public async Task RemoveMeterData( string redisCacheKey, - string oldCategoryIndexKey, - string newCategoryIndexKey, - string memberId, // 唯一标识(格式:"分类ID:GUID") - T newData, - int? newCategoryId = null, - DateTimeOffset? newTimestamp = null) + string redisCacheFocusIndexKey, + string redisCacheScoresIndexKey, + string redisCacheGlobalIndexKey, + T data) where T : DeviceCacheBasicModel { - // 参数校验 - if (string.IsNullOrWhiteSpace(memberId)) - throw new ArgumentException("Invalid member ID"); + + if (data == null + || string.IsNullOrWhiteSpace(redisCacheKey) + || string.IsNullOrWhiteSpace(redisCacheFocusIndexKey) + || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey) + || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey)) + { + throw new ArgumentException($"{nameof(RemoveMeterData)} 参数异常,-101"); + } + + const string luaScript = @" + local mainKey = KEYS[1] + local focusIndexKey = KEYS[2] + local scoresIndexKey = KEYS[3] + local globalIndexKey = KEYS[4] + local member = ARGV[1] + + local deleted = 0 + if redis.call('HDEL', mainKey, member) > 0 then + deleted = 1 + end + + redis.call('SREM', focusIndexKey, member) + redis.call('ZREM', scoresIndexKey, member) + redis.call('ZREM', globalIndexKey, member) + return deleted + "; + + var keys = new[] + { + redisCacheKey, + redisCacheFocusIndexKey, + redisCacheScoresIndexKey, + redisCacheGlobalIndexKey + }; + + var result = await Instance.EvalAsync(luaScript, keys, new[] { data.MemberID }); + + if ((int)result == 0) + throw new KeyNotFoundException("指定数据不存在"); + } + + /// + /// 修改表计缓存信息 + /// + /// + /// 主数据存储Hash缓存Key + /// 旧集中器索引Set缓存Key + /// 新集中器索引Set缓存Key + /// 集中器排序索引ZSET缓存Key + /// 集中器采集频率分组全局索引ZSet缓存Key + /// 表计信息 + /// 可选时间戳 + /// + public async Task UpdateMeterData( + string redisCacheKey, + string oldRedisCacheFocusIndexKey, + string newRedisCacheFocusIndexKey, + string redisCacheScoresIndexKey, + string redisCacheGlobalIndexKey, + T newData, + DateTimeOffset? newTimestamp = null) where T : DeviceCacheBasicModel + { + if (newData == null + || string.IsNullOrWhiteSpace(redisCacheKey) + || string.IsNullOrWhiteSpace(oldRedisCacheFocusIndexKey) + || string.IsNullOrWhiteSpace(newRedisCacheFocusIndexKey) + || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey) + || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey)) + { + throw new ArgumentException($"{nameof(UpdateMeterData)} 参数异常,-101"); + } var luaScript = @" local mainKey = KEYS[1] - local scoreKey = KEYS[2] - local oldIndex = KEYS[3] - local newIndex = KEYS[4] + local oldFocusIndexKey = KEYS[2] + local newFocusIndexKey = KEYS[3] + local scoresIndexKey = KEYS[4] + local globalIndexKey = KEYS[5] local member = ARGV[1] local newData = ARGV[2] local newScore = ARGV[3] + local newGlobalScore = ARGV[4] - -- 校验旧数据是否存在 + -- 校验存在性 if redis.call('HEXISTS', mainKey, member) == 0 then return 0 end @@ -194,70 +284,67 @@ namespace JiShe.CollectBus.FreeRedisProvider -- 更新主数据 redis.call('HSET', mainKey, member, newData) - -- 处理分类变更 + -- 处理变更 if newScore ~= '' then -- 删除旧索引 - redis.call('SREM', oldIndex, member) - -- 更新排序分数 - redis.call('ZADD', scoreKey, newScore, member) + redis.call('SREM', oldFocusIndexKey, member) + redis.call('ZREM', scoresIndexKey, member) + -- 添加新索引 - redis.call('SADD', newIndex, member) + redis.call('SADD', newFocusIndexKey, member) + redis.call('ZADD', scoresIndexKey, newScore, member) + end + + -- 更新全局索引 + if newGlobalScore ~= '' then + -- 删除旧索引 + redis.call('ZREM', globalIndexKey, member) + + -- 添加新索引 + redis.call('ZADD', globalIndexKey, newGlobalScore, member) end return 1 "; - // 计算新score(当分类或时间变化时) - long? newScoreValue = null; - if (newCategoryId.HasValue || newTimestamp.HasValue) - { - var parts = memberId.Split(':'); - var oldCategoryId = int.Parse(parts[0]); - - var actualCategoryId = newCategoryId ?? oldCategoryId; - var actualTimestamp = newTimestamp ?? DateTimeOffset.UtcNow; - - newScoreValue = ((long)actualCategoryId << 32) | (uint)actualTimestamp.Ticks; - } + var actualTimestamp = newTimestamp ?? DateTimeOffset.UtcNow; + var newGlobalScore = actualTimestamp.ToUnixTimeMilliseconds(); + var newScoreValue = ((long)newData.FocusId << 32) | (uint)actualTimestamp.Ticks; var result = await Instance.EvalAsync(luaScript, new[] { redisCacheKey, - $"{redisCacheKey}_scores", - oldCategoryIndexKey, - newCategoryIndexKey + oldRedisCacheFocusIndexKey, + newRedisCacheFocusIndexKey, + redisCacheScoresIndexKey, + redisCacheGlobalIndexKey }, - new[] + new object[] { - memberId, + newData.MemberID, newData.Serialize(), - newScoreValue?.ToString() ?? "" + newScoreValue.ToString() ?? "", + newGlobalScore.ToString() ?? "" }); - // 如果时间戳变化则更新全局索引 - if (newTimestamp.HasValue) - { - long newGlobalScore = newTimestamp.Value.ToUnixTimeMilliseconds(); - await Instance.ZAddAsync("global_data_all", newGlobalScore, memberId); - } - if ((int)result == 0) - throw new KeyNotFoundException("指定数据不存在"); + { + throw new KeyNotFoundException($"{nameof(UpdateMeterData)}指定Key{redisCacheKey}的数据不存在"); + } } - - - public async Task> GetMeterZSetPagedData( + + public async Task> SingleGetMeterPagedData( string redisCacheKey, - string redisCacheIndexKey, - int categoryId, + string redisCacheScoresIndexKey, + int focusId, int pageSize = 10, int pageIndex = 1, bool descending = true) { // 计算score范围 - long minScore = (long)categoryId << 32; - long maxScore = ((long)categoryId + 1) << 32; + long minScore = (long)focusId << 32; + long maxScore = ((long)focusId + 1) << 32; // 分页参数计算 int start = (pageIndex - 1) * pageSize; @@ -265,13 +352,13 @@ namespace JiShe.CollectBus.FreeRedisProvider // 获取排序后的member列表 var members = descending ? await Instance.ZRevRangeByScoreAsync( - $"{redisCacheKey}_scores", + redisCacheScoresIndexKey, maxScore, minScore, start, pageSize) : await Instance.ZRangeByScoreAsync( - $"{redisCacheKey}_scores", + redisCacheScoresIndexKey, minScore, maxScore, start, @@ -284,7 +371,7 @@ namespace JiShe.CollectBus.FreeRedisProvider // 总数统计优化 var total = await Instance.ZCountAsync( - $"{redisCacheKey}_scores", + redisCacheScoresIndexKey, minScore, maxScore); @@ -298,105 +385,290 @@ namespace JiShe.CollectBus.FreeRedisProvider } - public async Task RemoveMeterZSetData( - string redisCacheKey, - string redisCacheIndexKey, - string uniqueId) // 改为基于唯一标识删除 - { - // 原子操作 - var luaScript = @" - local mainKey = KEYS[1] - local scoreKey = KEYS[2] - local indexKey = KEYS[3] - local member = ARGV[1] - - redis.call('HDEL', mainKey, member) - redis.call('ZREM', scoreKey, member) - redis.call('SREM', indexKey, member) - return 1 - "; - - var keys = new[] - { - redisCacheKey, - $"{redisCacheKey}_scores", - redisCacheIndexKey - }; - - var result = await Instance.EvalAsync(luaScript, - keys, - new[] { uniqueId }); - - if ((int)result != 1) - throw new Exception("删除操作失败"); - } - - public async Task> GetGlobalPagedData( + public async Task> GetFocusPagedData( string redisCacheKey, + string redisCacheScoresIndexKey, + int focusId, int pageSize = 10, long? lastScore = null, string lastMember = null, - bool descending = true) + bool descending = true) where T : DeviceCacheBasicModel { - const string zsetKey = "global_data_all"; - - // 分页参数处理 - var (startScore, excludeMember) = descending - ? (lastScore ?? long.MaxValue, lastMember) - : (lastScore ?? 0, lastMember); + // 计算分数范围 + long minScore = (long)focusId << 32; + long maxScore = ((long)focusId + 1) << 32; // 获取成员列表 - string[] members; - if (descending) + var members = await GetSortedMembers( + redisCacheScoresIndexKey, + minScore, + maxScore, + pageSize, + lastScore, + lastMember, + descending); + + // 批量获取数据 + var dataDict = await Instance.HMGetAsync(redisCacheKey, members.CurrentItems); + + return new BusPagedResult { - members = await Instance.ZRevRangeByScoreAsync( + Items = dataDict, + TotalCount = await GetTotalCount(redisCacheScoresIndexKey, minScore, maxScore), + HasNext = members.HasNext, + NextScore = members.NextScore, + NextMember = members.NextMember + }; + } + + private async Task<(string[] CurrentItems, bool HasNext, decimal? NextScore, string NextMember)> + GetSortedMembers( + string zsetKey, + long minScore, + long maxScore, + int pageSize, + long? lastScore, + string lastMember, + bool descending) + { + var querySize = pageSize + 1; + var (startScore, exclude) = descending + ? (lastScore ?? maxScore, lastMember) + : (lastScore ?? minScore, lastMember); + + var members = descending + ? await Instance.ZRevRangeByScoreAsync( zsetKey, max: startScore, - min: 0, + min: minScore, offset: 0, - count: pageSize + 1); - } - else - { - members = await Instance.ZRangeByScoreAsync( + count: querySize) + : await Instance.ZRangeByScoreAsync( zsetKey, min: startScore, - max: long.MaxValue, + max: maxScore, offset: 0, - count: pageSize + 1); + count: querySize); + + var hasNext = members.Length > pageSize; + var currentItems = members.Take(pageSize).ToArray(); + + var nextCursor = currentItems.Any() + ? await GetNextCursor(zsetKey, currentItems.Last(), descending) + : (null, null); + + return (currentItems, hasNext, nextCursor.score, nextCursor.member); + } + + private async Task GetTotalCount(string zsetKey, long min, long max) + { + // 缓存计数优化 + var cacheKey = $"{zsetKey}_count_{min}_{max}"; + var cached = await Instance.GetAsync(cacheKey); + + if (cached.HasValue) + return cached.Value; + + var count = await Instance.ZCountAsync(zsetKey, min, max); + await Instance.SetExAsync(cacheKey, 60, count); // 缓存60秒 + return count; + } + + + public async Task>> BatchGetMeterPagedData( + string redisCacheKey, + string redisCacheScoresIndexKey, + IEnumerable focusIds, + int pageSizePerFocus = 10) where T : DeviceCacheBasicModel + { + var results = new ConcurrentDictionary>(); + var parallelOptions = new ParallelOptions + { + MaxDegreeOfParallelism = Environment.ProcessorCount * 2 + }; + + await Parallel.ForEachAsync(focusIds, parallelOptions, async (focusId, _) => + { + var data = await SingleGetMeterPagedData( + redisCacheKey, + redisCacheScoresIndexKey, + focusId, + pageSizePerFocus); + + results.TryAdd(focusId, data); + }); + + return new Dictionary>(results); + } + + /// + /// 通过全局索引分页查询表计缓存数据 + /// + /// + /// 主数据存储Hash缓存Key + /// 集中器采集频率分组全局索引ZSet缓存Key + /// 分页尺寸 + /// 最后一个索引 + /// 最后一个唯一标识 + /// 排序方式 + /// + public async Task> GetGlobalPagedData( + string redisCacheKey, + string redisCacheGlobalIndexKey, + int pageSize = 10, + decimal? lastScore = null, + string lastMember = null, + bool descending = true) + where T : DeviceCacheBasicModel + { + // 参数校验增强 + if (string.IsNullOrWhiteSpace(redisCacheKey) || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey)) + { + throw new ArgumentException($"{nameof(GetGlobalPagedData)} 参数异常,-101"); } - // 处理分页结果 - bool hasNext = members.Length > pageSize; - var actualMembers = members.Take(pageSize).ToArray(); + if (pageSize < 1 || pageSize > 1000) + { + throw new ArgumentException($"{nameof(GetGlobalPagedData)} 分页大小应在1-1000之间,-102"); + } - // 批量获取数据(优化版本) - var dataTasks = actualMembers - .Select(m => Instance.HGetAsync(redisCacheKey, m)) - .ToArray(); - await Task.WhenAll(dataTasks); + // 分页参数解析 + var (startScore, excludeMember) = descending + ? (lastScore ?? decimal.MaxValue, lastMember) + : (lastScore ?? 0, lastMember); + + // 游标分页查询 + var (members, hasNext) = await GetPagedMembers( + redisCacheGlobalIndexKey, + pageSize, + startScore, + excludeMember, + descending); + + // 批量获取数据(优化内存分配) + var dataDict = await BatchGetData(redisCacheKey, members); // 获取下一页游标 - (long? nextScore, string nextMember) = actualMembers.Any() - ? await GetNextCursor(zsetKey, actualMembers.Last(), descending) + var nextCursor = members.Any() + ? await GetNextCursor(redisCacheGlobalIndexKey, members.Last(), descending) : (null, null); return new BusCacheGlobalPagedResult { - Items = dataTasks.Select(t => t.Result).ToList(), + Items = members.Select(m => dataDict.TryGetValue(m, out var v) ? v : default) + .Where(x => x != null).ToList(), HasNext = hasNext, - NextScore = nextScore, - NextMember = nextMember + NextScore = nextCursor.score, + NextMember = nextCursor.member }; } - private async Task<(long? score, string member)> GetNextCursor( - string zsetKey, + /// + /// 游标分页查询 + /// + /// + /// 分页数量 + /// 开始索引 + /// 开始唯一标识 + /// 排序方式 + /// + private async Task<(List Members, bool HasNext)> GetPagedMembers( + string redisCacheGlobalIndexKey, + int pageSize, + decimal? startScore, + string excludeMember, + bool descending) + { + const int bufferSize = 50; // 预读缓冲区大小 + + // 使用流式分页(避免OFFSET性能问题) + var members = new List(pageSize + 1); + decimal? currentScore = startScore; + string lastMember = excludeMember; + + while (members.Count < pageSize + 1 && currentScore.HasValue) + { + var querySize = Math.Min(bufferSize, pageSize + 1 - members.Count); + + var batch = descending + ? await Instance.ZRevRangeByScoreAsync( + redisCacheGlobalIndexKey, + max: currentScore.Value, + min: 0, + offset: 0, + count: querySize + ) + : await Instance.ZRangeByScoreAsync( + redisCacheGlobalIndexKey, + min: currentScore.Value, + max: long.MaxValue, + offset: 0, + count: querySize); + + if (!batch.Any()) break; + + members.AddRange(batch); + lastMember = batch.LastOrDefault(); + currentScore = await Instance.ZScoreAsync(redisCacheGlobalIndexKey, lastMember); + } + + return ( + members.Take(pageSize).ToList(), + members.Count > pageSize + ); + } + + /// + /// 批量获取指定分页的数据 + /// + /// + /// + /// + /// + private async Task> BatchGetData( + string hashKey, + IEnumerable members) + where T : DeviceCacheBasicModel + { + const int batchSize = 100; + var result = new Dictionary(); + + foreach (var batch in members.Batch(batchSize)) + { + var batchArray = batch.ToArray(); + var values = await Instance.HMGetAsync(hashKey, batchArray); + + for (int i = 0; i < batchArray.Length; i++) + { + if (EqualityComparer.Default.Equals(values[i], default)) continue; + result[batchArray[i]] = values[i]; + } + } + + return result; + } + + /// + /// 获取下一页游标 + /// + /// 全局索引Key + /// 最后一个唯一标识 + /// 排序方式 + /// + private async Task<(decimal? score, string member)> GetNextCursor( + string redisCacheGlobalIndexKey, string lastMember, bool descending) { - var score = await Instance.ZScoreAsync(zsetKey, lastMember); - return (score.HasValue ? (long)score.Value : null, lastMember); + if (string.IsNullOrWhiteSpace(lastMember)) + { + return (null, null); + } + + var score = await Instance.ZScoreAsync(redisCacheGlobalIndexKey, lastMember); + return score.HasValue + ? (Convert.ToInt64(score.Value), lastMember) + : (null, null); } } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs b/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs index 36f9a81..dc0aaa3 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs @@ -48,6 +48,66 @@ namespace JiShe.CollectBus.FreeRedisProvider string redisCacheGlobalIndexKey, IEnumerable items, DateTimeOffset? timestamp = null) where T : DeviceCacheBasicModel; + + /// + /// 删除指定redis缓存key的缓存数据 + /// + /// + /// 主数据存储Hash缓存Key + /// 集中器索引Set缓存Key + /// 集中器排序索引ZSET缓存Key + /// 集中器采集频率分组全局索引ZSet缓存Key + /// 表计信息 + /// + Task RemoveMeterData( + string redisCacheKey, + string redisCacheFocusIndexKey, + string redisCacheScoresIndexKey, + string redisCacheGlobalIndexKey, + T data) where T : DeviceCacheBasicModel; + + /// + /// 修改表计缓存信息 + /// + /// + /// 主数据存储Hash缓存Key + /// 旧集中器索引Set缓存Key + /// 新集中器索引Set缓存Key + /// 集中器排序索引ZSET缓存Key + /// 集中器采集频率分组全局索引ZSet缓存Key + /// 表计信息 + /// 可选时间戳 + /// + Task UpdateMeterData( + string redisCacheKey, + string oldRedisCacheFocusIndexKey, + string newRedisCacheFocusIndexKey, + string redisCacheScoresIndexKey, + string redisCacheGlobalIndexKey, + T newData, + DateTimeOffset? newTimestamp = null) where T : DeviceCacheBasicModel; + + + + /// + /// 通过全局索引分页查询表计缓存数据 + /// + /// + /// 主数据存储Hash缓存Key + /// 集中器采集频率分组全局索引ZSet缓存Key + /// 分页尺寸 + /// 最后一个索引 + /// 最后一个唯一标识 + /// 排序方式 + /// + Task> GetGlobalPagedData( + string redisCacheKey, + string redisCacheGlobalIndexKey, + int pageSize = 10, + decimal? lastScore = null, + string lastMember = null, + bool descending = true) + where T : DeviceCacheBasicModel; } } -- 2.47.2 From 0d4c7807273bb64d6ecc0ee8e4a5372c222c0fa8 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Wed, 16 Apr 2025 09:54:21 +0800 Subject: [PATCH 105/139] =?UTF-8?q?=E4=BF=9D=E7=95=99CAP=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E5=90=8E=E7=BB=AD=E5=86=B3=E5=AE=9A=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E7=A7=BB=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 7 + .../EnergySystem/EnergySystemAppService.cs | 152 +++++++++++++++++- .../JiShe.CollectBus.Application.csproj | 1 + .../Plugins/TcpMonitor.cs | 23 ++- .../Samples/SampleAppService.cs | 2 +- .../BasicScheduledMeterReadingService.cs | 14 +- ...nergySystemScheduledMeterReadingService.cs | 3 +- .../Subscribers/SubscriberAppService.cs | 10 +- .../Subscribers/WorkerSubscriberAppService.cs | 7 +- .../CollectBusHostModule.cs | 2 +- .../AdminClient/AdminClientService.cs | 13 ++ .../AdminClient/IAdminClientService.cs | 7 + .../Attributes/KafkaSubscribeAttribute.cs | 22 +-- .../CollectBusKafkaModule.cs | 4 +- .../Consumer/ConsumerService.cs | 24 ++- .../KafkaSubcribesExtensions.cs | 30 +++- .../Producer/IProducerService.cs | 2 +- .../Producer/ProducerService.cs | 12 +- .../Abstracts/BaseProtocolPlugin.cs | 8 +- ...JiShe.CollectBus.Protocol.Contracts.csproj | 1 + 20 files changed, 299 insertions(+), 45 deletions(-) diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index 9c67aae..ea5e278 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.T EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Cassandra", "src\JiShe.CollectBus.Cassandra\JiShe.CollectBus.Cassandra.csproj", "{443B4549-0AC0-4493-8F3E-49C83225DD76}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka.Test", "src\JiShe.CollectBus.Kafka.Test\JiShe.CollectBus.Kafka.Test.csproj", "{FA762E8F-659A-DECF-83D6-5F364144450E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -113,6 +115,10 @@ Global {443B4549-0AC0-4493-8F3E-49C83225DD76}.Debug|Any CPU.Build.0 = Debug|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.ActiveCfg = Release|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.Build.0 = Release|Any CPU + {FA762E8F-659A-DECF-83D6-5F364144450E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA762E8F-659A-DECF-83D6-5F364144450E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA762E8F-659A-DECF-83D6-5F364144450E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA762E8F-659A-DECF-83D6-5F364144450E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -135,6 +141,7 @@ Global {A3F3C092-0A25-450B-BF6A-5983163CBEF5} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {A377955E-7EA1-6F29-8CF7-774569E93925} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {443B4549-0AC0-4493-8F3E-49C83225DD76} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {FA762E8F-659A-DECF-83D6-5F364144450E} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs b/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs index a210d28..2a60803 100644 --- a/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs +++ b/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs @@ -5,6 +5,7 @@ using System.Net; using System.Text; using System.Threading.Tasks; using DeviceDetectorNET.Class.Device; +using DotNetCore.CAP; using JiShe.CollectBus.Common.BuildSendDatas; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; @@ -30,14 +31,16 @@ namespace JiShe.CollectBus.EnergySystem private readonly IRepository _csqRecordRepository; private readonly IRepository _conrOnlineRecordRepository; private readonly IProducerService _producerService; + private readonly ICapPublisher _capBus; public EnergySystemAppService(IRepository focusRecordRepository, IRepository csqRecordRepository, - IRepository conrOnlineRecordRepository, IProducerService producerService) + IRepository conrOnlineRecordRepository, IProducerService producerService, ICapPublisher capBus) { _focusRecordRepository = focusRecordRepository; _csqRecordRepository = csqRecordRepository; _conrOnlineRecordRepository = conrOnlineRecordRepository; _producerService = producerService; + _capBus = capBus; } /// @@ -71,7 +74,15 @@ namespace JiShe.CollectBus.EnergySystem if (bytes == null) return result; - + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -109,6 +120,15 @@ namespace JiShe.CollectBus.EnergySystem foreach (var bytes in bytesList) { + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -117,6 +137,7 @@ namespace JiShe.CollectBus.EnergySystem Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() }.Serialize()); + } return result; @@ -150,6 +171,14 @@ namespace JiShe.CollectBus.EnergySystem }).ToList(); var bytes = Build3761SendData.BuildAmmeterParameterSetSendCmd(address, meterParameters); + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -179,6 +208,15 @@ namespace JiShe.CollectBus.EnergySystem { var dataUnit = Build645SendData.BuildReadMeterAddressSendDataUnit(detail.MeterAddress); var bytes =Build3761SendData.BuildTransparentForwardingSendCmd(address, detail.Port, detail.BaudRate.ToString(), dataUnit, StopBit.Stop1, Parity.None); + + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -262,6 +300,15 @@ namespace JiShe.CollectBus.EnergySystem if (bytes != null) { + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -321,6 +368,15 @@ namespace JiShe.CollectBus.EnergySystem var bytes = Build3761SendData.BuildCommunicationParametersSetSendCmd(address, masterIP, materPort, backupIP, backupPort, input.Data.APN); + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -348,6 +404,15 @@ namespace JiShe.CollectBus.EnergySystem var address = $"{input.AreaCode}{input.Address}"; var bytes = Build3761SendData.BuildTerminalCalendarClockSendCmd(address); + + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -376,6 +441,14 @@ namespace JiShe.CollectBus.EnergySystem bool isManual = !input.AreaCode.Equals("5110");//低功耗集中器不是长连接,在连接的那一刻再发送 var bytes = Build3761SendData.BuildConrCheckTimeSendCmd(address,DateTime.Now, isManual); + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -403,6 +476,14 @@ namespace JiShe.CollectBus.EnergySystem var address = $"{input.AreaCode}{input.Address}"; var bytes = Build3761SendData.BuildConrRebootSendCmd(address); + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -431,6 +512,14 @@ namespace JiShe.CollectBus.EnergySystem var address = $"{input.AreaCode}{input.Address}"; var pnList = input.Data.Split(',').Select(it => int.Parse(it)).ToList(); var bytes = Build3761SendData.BuildAmmeterParameterReadingSendCmd(address, pnList); + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -480,6 +569,15 @@ namespace JiShe.CollectBus.EnergySystem foreach (var bytes in bytesList) { + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); + await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -488,7 +586,7 @@ namespace JiShe.CollectBus.EnergySystem Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() }.Serialize()); - + } result.Status = true; result.Msg = "操作成功"; @@ -549,6 +647,14 @@ namespace JiShe.CollectBus.EnergySystem foreach (var bytes in bytesList) { + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -578,6 +684,14 @@ namespace JiShe.CollectBus.EnergySystem var address = $"{code.AreaCode}{code.Address}"; var bytes = Build3761SendData.BuildAmmeterReportCollectionItemsSetSendCmd(address,input.Detail.Pn, input.Detail.Unit,input.Detail.Cycle,input.Detail.BaseTime, input.Detail.CurveRatio,input.Detail.Details.Select(it => new PnFn(it.Pn,it.Fn)).ToList()); + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -606,6 +720,14 @@ namespace JiShe.CollectBus.EnergySystem { var address = $"{code.AreaCode}{code.Address}"; var bytes = Build3761SendData.BuildAmmeterAutoUpSwitchSetSendCmd(address, input.Detail.Pn,input.Detail.IsOpen); + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -632,6 +754,14 @@ namespace JiShe.CollectBus.EnergySystem var result = new BaseResultDto(); var address = $"{input.AreaCode}{input.Address}"; var bytes = Build3761SendData.BuildAmmeterReadAutoUpSwitchSendCmd(address, input.Detail.Pn); + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -659,6 +789,14 @@ namespace JiShe.CollectBus.EnergySystem { var address = $"{data.AreaCode}{data.Address}"; var bytes = Build3761SendData.BuildTerminalVersionInfoReadingSendCmd(address); + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, @@ -714,6 +852,14 @@ namespace JiShe.CollectBus.EnergySystem foreach (var bytes in bytesList) { + //await _capBus.PublishAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage + //{ + // //ClientId = messageReceived.ClientId, + // DeviceNo = address, + // Message = bytes, + // Type = IssuedEventType.Data, + // MessageId = NewId.NextGuid().ToString() + //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { //ClientId = messageReceived.ClientId, diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index 24db5a5..3fa551c 100644 --- a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -15,6 +15,7 @@ + diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index 7af99bc..50b41b0 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using DeviceDetectorNET.Parser.Device; +using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; @@ -27,6 +28,7 @@ namespace JiShe.CollectBus.Plugins { public partial class TcpMonitor : PluginBase, ITransientDependency, ITcpReceivedPlugin, ITcpConnectingPlugin, ITcpConnectedPlugin, ITcpClosedPlugin { + private readonly ICapPublisher _producerBus; private readonly IProducerService _producerService; private readonly ILogger _logger; private readonly IRepository _deviceRepository; @@ -39,11 +41,12 @@ namespace JiShe.CollectBus.Plugins /// /// /// - public TcpMonitor(IProducerService producerService, + public TcpMonitor(ICapPublisher producerBus, IProducerService producerService, ILogger logger, IRepository deviceRepository, IDistributedCache ammeterInfoCache) { + _producerBus = producerBus; _producerService = producerService; _logger = logger; _deviceRepository = deviceRepository; @@ -171,6 +174,10 @@ namespace JiShe.CollectBus.Plugins DeviceNo = deviceNo, MessageId = NewId.NextGuid().ToString() }; + + //await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent); + + await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent.Serialize()); //await _producerBus.Publish( messageReceivedLoginEvent); @@ -218,6 +225,8 @@ namespace JiShe.CollectBus.Plugins DeviceNo = deviceNo, MessageId = NewId.NextGuid().ToString() }; + //await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent); + await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent.Serialize()); //await _producerBus.Publish(messageReceivedHeartbeatEvent); } @@ -242,10 +251,18 @@ namespace JiShe.CollectBus.Plugins // MessageId = NewId.NextGuid().ToString() //}); - + //string topicName = string.Format(ProtocolConst.AFNTopicNameFormat, aFn); //todo 如何确定时标?目前集中器的采集频率,都是固定,数据上报的时候,根据当前时间,往后推测出应当采集的时间点作为时标。但是如果由于网络问题,数据一直没上报的情况改怎么计算? - + //await _producerBus.PublishAsync(ProtocolConst.SubscriberReceivedEventName, new MessageReceived + //{ + // ClientId = client.Id, + // ClientIp = client.IP, + // ClientPort = client.Port, + // MessageHexString = messageHexString, + // DeviceNo = deviceNo, + // MessageId = NewId.NextGuid().ToString() + //}); await _producerService.ProduceAsync(ProtocolConst.SubscriberReceivedEventName, new MessageReceived { ClientId = client.Id, diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 78b472d..d4c9a59 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -215,7 +215,7 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS return aa == null; } - [KafkaSubscribe(["test-topic"])] + [KafkaSubscribe("test-topic1")] public async Task KafkaSubscribeAsync(object obj) { diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 0f601d9..8fa4f5e 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -1,4 +1,5 @@ -using JiShe.CollectBus.Ammeters; +using DotNetCore.CAP; +using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.BuildSendDatas; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.DeviceBalanceControl; @@ -31,14 +32,16 @@ namespace JiShe.CollectBus.ScheduledMeterReading private readonly IIoTDBProvider _dbProvider; private readonly IMeterReadingRecordRepository _meterReadingRecordRepository; private readonly IProducerService _producerService; - + private readonly ICapPublisher _producerBus; public BasicScheduledMeterReadingService( ILogger logger, + ICapPublisher producerBus, IMeterReadingRecordRepository meterReadingRecordRepository, IProducerService producerService, IIoTDBProvider dbProvider) { + _producerBus = producerBus; _logger = logger; _dbProvider = dbProvider; _meterReadingRecordRepository = meterReadingRecordRepository; @@ -377,6 +380,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; + //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); + _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg.Serialize()); //_= _producerBus.Publish(tempMsg); @@ -441,6 +446,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; + //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg); + _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg.Serialize()); //_ = _producerBus.Publish(tempMsg); @@ -505,6 +512,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; + //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg.Serialize()); @@ -841,6 +849,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; + //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg.Serialize()); @@ -1149,6 +1158,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; + //await _producerBus.PublishAsync(ProtocolConst.WatermeterSubscriberWorkerAutoReadingIssuedEventName, tempMsg); //_ = _producerBus.Publish(tempMsg); diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index c1e45bd..3b0da49 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Confluent.Kafka; +using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.DeviceBalanceControl; @@ -35,7 +36,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { string serverTagName = string.Empty; public EnergySystemScheduledMeterReadingService(ILogger logger, - IIoTDBProvider dbProvider, IMeterReadingRecordRepository meterReadingRecordRepository,IConfiguration configuration, IProducerService producerService) : base(logger, meterReadingRecordRepository, producerService,dbProvider) + ICapPublisher producerBus, IIoTDBProvider dbProvider, IMeterReadingRecordRepository meterReadingRecordRepository,IConfiguration configuration, IProducerService producerService) : base(logger, producerBus, meterReadingRecordRepository, producerService,dbProvider) { serverTagName = configuration.GetValue(CommonConst.ServerTagName)!; } diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index fb469ac..3ec6936 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -1,4 +1,5 @@ -using JiShe.CollectBus.Common.Enums; +using DotNetCore.CAP; +using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IoTDBProvider; @@ -21,7 +22,7 @@ using Volo.Abp.Domain.Repositories; namespace JiShe.CollectBus.Subscribers { - public class SubscriberAppService : CollectBusAppService, ISubscriberAppService, IKafkaSubscribe + public class SubscriberAppService : CollectBusAppService, ISubscriberAppService, ICapSubscribe, IKafkaSubscribe { private readonly ILogger _logger; private readonly ITcpService _tcpService; @@ -65,6 +66,7 @@ namespace JiShe.CollectBus.Subscribers } [KafkaSubscribe(ProtocolConst.SubscriberLoginIssuedEventName)] + //[CapSubscribe(ProtocolConst.SubscriberLoginIssuedEventName)] public async Task LoginIssuedEvent(IssuedEventMessage issuedEventMessage) { bool isAck = false; @@ -97,6 +99,7 @@ namespace JiShe.CollectBus.Subscribers } [KafkaSubscribe(ProtocolConst.SubscriberHeartbeatIssuedEventName)] + //[CapSubscribe(ProtocolConst.SubscriberHeartbeatIssuedEventName)] public async Task HeartbeatIssuedEvent(IssuedEventMessage issuedEventMessage) { bool isAck = false; @@ -127,6 +130,7 @@ namespace JiShe.CollectBus.Subscribers } [KafkaSubscribe(ProtocolConst.SubscriberReceivedEventName)] + //[CapSubscribe(ProtocolConst.SubscriberReceivedEventName)] public async Task ReceivedEvent(MessageReceived receivedMessage) { var currentTime = Clock.Now; @@ -183,6 +187,7 @@ namespace JiShe.CollectBus.Subscribers } [KafkaSubscribe(ProtocolConst.SubscriberHeartbeatReceivedEventName)] + //[CapSubscribe(ProtocolConst.SubscriberHeartbeatReceivedEventName)] public async Task ReceivedHeartbeatEvent(MessageReceivedHeartbeat receivedHeartbeatMessage) { var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); @@ -199,6 +204,7 @@ namespace JiShe.CollectBus.Subscribers } [KafkaSubscribe(ProtocolConst.SubscriberLoginReceivedEventName)] + //[CapSubscribe(ProtocolConst.SubscriberLoginReceivedEventName)] public async Task ReceivedLoginEvent(MessageReceivedLogin receivedLoginMessage) { var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index 95b563c..47cff47 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using DeviceDetectorNET.Parser.Device; +using DotNetCore.CAP; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; @@ -25,7 +26,7 @@ namespace JiShe.CollectBus.Subscribers /// 定时抄读任务消息消费订阅 /// [Route($"/worker/app/subscriber")] - public class WorkerSubscriberAppService : CollectBusAppService, IWorkerSubscriberAppService, IKafkaSubscribe + public class WorkerSubscriberAppService : CollectBusAppService, IWorkerSubscriberAppService, ICapSubscribe, IKafkaSubscribe { private readonly ILogger _logger; private readonly ITcpService _tcpService; @@ -65,6 +66,7 @@ namespace JiShe.CollectBus.Subscribers [HttpPost] [Route("ammeter/oneminute/issued-event")] [KafkaSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] + //[CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] public async Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("1分钟采集电表数据下行消息消费队列开始处理"); @@ -93,6 +95,7 @@ namespace JiShe.CollectBus.Subscribers [HttpPost] [Route("ammeter/fiveminute/issued-event")] [KafkaSubscribe(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName)] + //[CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName)] public async Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("5分钟采集电表数据下行消息消费队列开始处理"); @@ -121,6 +124,7 @@ namespace JiShe.CollectBus.Subscribers [HttpPost] [Route("ammeter/fifteenminute/issued-event")] [KafkaSubscribe(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName)] + //[CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName)] public async Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("15分钟采集电表数据下行消息消费队列开始处理"); @@ -160,6 +164,7 @@ namespace JiShe.CollectBus.Subscribers [HttpPost] [Route("watermeter/fifteenminute/issued-event")] [KafkaSubscribe(ProtocolConst.WatermeterSubscriberWorkerAutoReadingIssuedEventName)] + //[CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerAutoReadingIssuedEventName)] public async Task WatermeterSubscriberWorkerAutoReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) { _logger.LogInformation("15分钟采集水表数据下行消息消费队列开始处理"); diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs index 377453e..aabc2ba 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.cs @@ -43,7 +43,7 @@ namespace JiShe.CollectBus.Host ConfigureNetwork(context, configuration); ConfigureJwtAuthentication(context, configuration); ConfigureHangfire(context); - //ConfigureCap(context, configuration); + ConfigureCap(context, configuration); //ConfigureMassTransit(context, configuration); //ConfigureKafkaTopic(context, configuration); ConfigureAuditLog(context); diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs index 26d028e..59e34fa 100644 --- a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs @@ -183,6 +183,19 @@ namespace JiShe.CollectBus.Kafka.AdminClient return partitions.Any(p => p.PartitionId == targetPartition); } + /// + /// 获取主题的分区数量 + /// + /// + /// + public int GetTopicPartitionsNum(string topic) + { + var metadata = Instance.GetMetadata(topic, TimeSpan.FromSeconds(10)); + if (metadata.Topics.Count == 0) + return 0; + return metadata.Topics[0].Partitions.Count; + } + public void Dispose() { Instance?.Dispose(); diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs b/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs index 238a130..92121c5 100644 --- a/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs @@ -52,5 +52,12 @@ namespace JiShe.CollectBus.Kafka.AdminClient /// /// bool CheckPartitionsExist(string topic, int targetPartition); + + /// + /// 获取主题的分区数量 + /// + /// + /// + int GetTopicPartitionsNum(string topic); } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs b/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs index 7a059e0..32f652e 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs @@ -12,7 +12,7 @@ namespace JiShe.CollectBus.Kafka.Attributes /// /// 订阅的主题 /// - public string[] Topics { get; set; } + public string Topic { get; set; } /// /// 分区 @@ -24,28 +24,20 @@ namespace JiShe.CollectBus.Kafka.Attributes /// public string GroupId { get; set; } - public KafkaSubscribeAttribute(string[] topics, string groupId = "default") - { - this.Topics = topics; - this.GroupId = groupId; - } + /// + /// 任务数(默认是多少个分区多少个任务) + /// + public int TaskCount { get; set; } = -1; public KafkaSubscribeAttribute(string topic, string groupId = "default") { - this.Topics = new string[] { topic }; + this.Topic = topic; this.GroupId = groupId; } - public KafkaSubscribeAttribute(string[] topics, int partition, string groupId = "default") - { - this.Topics = topics; - this.Partition = partition; - this.GroupId = groupId; - } - public KafkaSubscribeAttribute(string topic, int partition, string groupId = "default") { - this.Topics = new string[] { topic }; + this.Topic = topic ; this.Partition = partition; this.GroupId = groupId; } diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index 153e5ef..fe0e866 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -17,9 +17,9 @@ namespace JiShe.CollectBus.Kafka public override void ConfigureServices(ServiceConfigurationContext context) { // 注册Producer - context.Services.AddTransient(); + context.Services.AddSingleton(); // 注册Consumer - context.Services.AddTransient(); + context.Services.AddSingleton(); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index f59385f..9a2142e 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -9,6 +9,7 @@ using static Confluent.Kafka.ConfigPropertyNames; using System.Collections.Concurrent; using System.Text.RegularExpressions; using NUglify.Html; +using Serilog; namespace JiShe.CollectBus.Kafka.Consumer { @@ -37,6 +38,7 @@ namespace JiShe.CollectBus.Kafka.Consumer { var config = BuildConsumerConfig(groupId); return new ConsumerBuilder(config) + .SetLogHandler((_, log) => _logger.LogInformation($"消费者Log: {log.Message}")) .SetErrorHandler((_, e) => _logger.LogError($"消费者错误: {e.Reason}")) .Build(); } @@ -50,7 +52,9 @@ namespace JiShe.CollectBus.Kafka.Consumer BootstrapServers = _configuration["Kafka:BootstrapServers"], GroupId = groupId ?? "default", AutoOffsetReset = AutoOffsetReset.Earliest, - EnableAutoCommit = false // 禁止AutoCommit + EnableAutoCommit = false, // 禁止AutoCommit + EnablePartitionEof = true, // 启用分区末尾标记 + AllowAutoCreateTopics= true // 启用自动创建 }; if (enableAuth) @@ -119,6 +123,15 @@ namespace JiShe.CollectBus.Kafka.Consumer try { var result = consumer.Consume(cts.Token); + if (result == null) continue; + if (result.Message.Value == null) continue; + if (result.IsPartitionEOF) + { + _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); + await Task.Delay(TimeSpan.FromSeconds(1)); + continue; + } + bool sucess= await messageHandler(result.Message.Key, result.Message.Value); if (sucess) { @@ -164,6 +177,15 @@ namespace JiShe.CollectBus.Kafka.Consumer try { var result = consumer.Consume(cts.Token); + if (result == null) continue; + if (result.Message == null) continue; + if (result.IsPartitionEOF) + { + _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); + await Task.Delay(TimeSpan.FromSeconds(1)); + continue; + } + bool sucess = await messageHandler(result.Message.Value); if (sucess) consumer.Commit(result); // 手动提交 diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs index d3ce904..cfe5eee 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -2,6 +2,7 @@ using DeviceDetectorNET; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.Kafka.AdminClient; using JiShe.CollectBus.Kafka.Attributes; using JiShe.CollectBus.Kafka.Consumer; using Microsoft.AspNetCore.Builder; @@ -41,6 +42,9 @@ namespace JiShe.CollectBus.Kafka lifetime.ApplicationStarted.Register(() => { + var logger = provider.GetRequiredService>(); + int threadCount = 0; + int topicCount = 0; foreach (var subscribeType in subscribeTypes) { var subscribes = provider.GetServices(subscribeType).ToList(); @@ -48,10 +52,13 @@ namespace JiShe.CollectBus.Kafka if(subscribe is IKafkaSubscribe) { - BuildKafkaSubscriber(subscribe, provider); + Tuple tuple= BuildKafkaSubscriber(subscribe, provider, logger); + threadCount+= tuple.Item1; + topicCount+= tuple.Item2; } }); } + logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); }); } @@ -60,16 +67,27 @@ namespace JiShe.CollectBus.Kafka /// /// /// - private static void BuildKafkaSubscriber(object subscribe, IServiceProvider provider) + private static Tuple BuildKafkaSubscriber(object subscribe, IServiceProvider provider,ILogger logger) { var subscribedMethods = subscribe.GetType().GetMethods() .Select(m => new { Method = m, Attribute = m.GetCustomAttribute() }) .Where(x => x.Attribute != null) .ToArray(); + + int threadCount = 0; foreach (var sub in subscribedMethods) { - Task.Run(() => StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe)); + var adminClientService = provider.GetRequiredService(); + int partitionCount = sub.Attribute!.TaskCount==-1?adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic) : sub.Attribute!.TaskCount; + if (partitionCount <= 0) + partitionCount = 1; + for (int i = 0; i < partitionCount; i++) + { + Task.Run(() => StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe, logger)); + threadCount++; + } } + return Tuple.Create(threadCount, subscribedMethods.Length); } /// @@ -80,11 +98,11 @@ namespace JiShe.CollectBus.Kafka /// /// /// - private static async Task StartConsumerAsync(IServiceProvider provider, KafkaSubscribeAttribute attr,MethodInfo method, object subscribe) + private static async Task StartConsumerAsync(IServiceProvider provider, KafkaSubscribeAttribute attr,MethodInfo method, object subscribe, ILogger logger) { var consumerService = provider.GetRequiredService(); - var logger = provider.GetRequiredService>(); - await consumerService.SubscribeAsync(attr.Topics, async (message) => + + await consumerService.SubscribeAsync(attr.Topic, async (message) => { try { diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs index b00f5cf..a401775 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs @@ -15,6 +15,6 @@ namespace JiShe.CollectBus.Kafka.Producer Task ProduceAsync(string topic, TKey key, TValue value, int? partition, Action>? deliveryHandler = null) where TKey : notnull where TValue : class; - Task ProduceAsync(string topic, TValue value, int? partition = null, Action>? deliveryHandler = null) where TValue : class; + Task ProduceAsync(string topic, TValue value, int? partition = null, Action>? deliveryHandler = null) where TValue : class; } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs index c322294..8c069a9 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs @@ -9,6 +9,7 @@ using JiShe.CollectBus.Kafka.Consumer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Volo.Abp.DependencyInjection; +using YamlDotNet.Serialization; namespace JiShe.CollectBus.Kafka.Producer { @@ -62,6 +63,7 @@ namespace JiShe.CollectBus.Kafka.Producer LingerMs = 20, // 修改等待时间为20ms Acks = Acks.All, // 表明只有所有副本Broker都收到消息才算提交成功, 可以 Acks.Leader MessageSendMaxRetries = 50, // 消息发送失败最大重试50次 + MessageTimeoutMs = 120000, // 消息发送超时时间为2分钟,设置值MessageTimeoutMs > LingerMs }; if (enableAuth) @@ -114,8 +116,8 @@ namespace JiShe.CollectBus.Kafka.Producer /// public async Task ProduceAsync(string topic, TValue value) where TValue : class { - var producer = GetProducer(); - await producer.ProduceAsync(topic, new Message { Value = value }); + var producer = GetProducer(); + await producer.ProduceAsync(topic, new Message { Value = value }); } /// @@ -160,13 +162,13 @@ namespace JiShe.CollectBus.Kafka.Producer /// /// /// - public async Task ProduceAsync(string topic, TValue value, int? partition=null, Action>? deliveryHandler = null) where TValue : class + public async Task ProduceAsync(string topic, TValue value, int? partition=null, Action>? deliveryHandler = null) where TValue : class { - var message = new Message + var message = new Message { Value = value }; - var producer = GetProducer(); + var producer = GetProducer(); if (partition.HasValue) { var topicPartition = new TopicPartition(topic, partition.Value); diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index d066a28..963d89b 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -13,11 +13,13 @@ using JiShe.CollectBus.IotSystems.Protocols; using MassTransit; using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Common.Helpers; +using DotNetCore.CAP; namespace JiShe.CollectBus.Protocol.Contracts.Abstracts { public abstract class BaseProtocolPlugin : IProtocolPlugin { + private readonly ICapPublisher _producerBus; private readonly IProducerService _producerService; private readonly ILogger _logger; private readonly IRepository _protocolInfoRepository; @@ -38,7 +40,8 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts _logger = serviceProvider.GetRequiredService>(); _protocolInfoRepository = serviceProvider.GetRequiredService>(); - _producerService = serviceProvider.GetRequiredService(); + _producerService = serviceProvider.GetRequiredService(); + _producerBus = serviceProvider.GetRequiredService(); } public abstract ProtocolInfo Info { get; } @@ -87,6 +90,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts Fn = 1 }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); + //await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }.Serialize()); //await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); @@ -127,6 +131,8 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts Fn = 1 }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); + //await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); + await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }.Serialize()); //await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); diff --git a/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj b/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj index 248ee30..2721676 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj +++ b/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj @@ -8,6 +8,7 @@ + -- 2.47.2 From 4494c92e7c699f2af491b3684c466d2b2157268a Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Wed, 16 Apr 2025 16:12:38 +0800 Subject: [PATCH 106/139] =?UTF-8?q?Cassandra=20=E6=80=A7=E8=83=BD=E5=B1=80?= =?UTF-8?q?=E9=83=A8=E8=B0=83=E4=BC=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusApplicationModule.cs | 2 +- .../Samples/TestAppService.cs | 102 +++++++++++++++--- .../CassandraProvider.cs | 7 ++ .../CassandraRepository.cs | 9 +- .../CollectBusCassandraModule.cs | 9 +- .../Extensions/ServiceCollectionExtensions.cs | 12 +-- .../Extensions/SessionExtension.cs | 7 +- .../ICassandraProvider.cs | 2 + .../Attributes/CassandraTableAttribute.cs | 12 +-- .../MessageIssueds/MessageIssued.cs | 11 +- src/JiShe.CollectBus.Host/appsettings.json | 2 +- .../MongoDB/CollectBusMongoDbContext.cs | 7 +- .../MongoDB/CollectBusMongoDbModule.cs | 2 +- 13 files changed, 141 insertions(+), 43 deletions(-) diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index f3ed978..0f7a5be 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -63,7 +63,7 @@ public class CollectBusApplicationModule : AbpModule //默认初始化表计信息 var dbContext = context.ServiceProvider.GetRequiredService(); - await dbContext.InitAmmeterCacheData(); + //await dbContext.InitAmmeterCacheData(); //await dbContext.InitWatermeterCacheData(); //初始化主题信息 diff --git a/src/JiShe.CollectBus.Application/Samples/TestAppService.cs b/src/JiShe.CollectBus.Application/Samples/TestAppService.cs index 9c53c3a..53f4b9e 100644 --- a/src/JiShe.CollectBus.Application/Samples/TestAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/TestAppService.cs @@ -19,33 +19,109 @@ using JiShe.CollectBus.Protocol.Contracts.Interfaces; using Microsoft.Extensions.DependencyInjection; using JiShe.CollectBus.Cassandra; using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.IotSystems.MessageIssueds; using Volo.Abp.Application.Services; +using JiShe.CollectBus.IotSystems.MessageReceiveds; +using Volo.Abp.Domain.Repositories; +using System.Diagnostics; +using System.Linq; +using Cassandra; namespace JiShe.CollectBus.Samples; [AllowAnonymous] -public class TestAppService : CollectBusAppService, IApplicationService +public class TestAppService : CollectBusAppService { private readonly ILogger _logger; - private readonly ICassandraRepository _messageIssuedCassandraRepository; + private readonly ICassandraRepository _messageReceivedCassandraRepository; + private readonly ICassandraProvider _cassandraProvider; + + + public TestAppService( ILogger logger, - ICassandraRepository messageIssuedCassandraRepository - ) + ICassandraRepository messageReceivedCassandraRepository, ICassandraProvider cassandraProvider) { _logger = logger; - _messageIssuedCassandraRepository = messageIssuedCassandraRepository; + _messageReceivedCassandraRepository = messageReceivedCassandraRepository; + _cassandraProvider = cassandraProvider; } - public async Task AddMessage() + public async Task AddMessageOfCassandra() { - await _messageIssuedCassandraRepository.InsertAsync(new MessageIssued + var stopwatch = Stopwatch.StartNew(); + for (int i = 1; i <= 10000; i++) { - ClientId = Guid.NewGuid().ToString(), - Message = Array.Empty(), - DeviceNo = "123321312", - MessageId = Guid.NewGuid().ToString(), - Type = IssuedEventType.Data - }); + var str = Guid.NewGuid().ToString(); + await _messageReceivedCassandraRepository.InsertAsync(new MessageIssued + { + ClientId = str, + DeviceNo = i.ToString(), + MessageId = str, + Type = IssuedEventType.Data, + Id = str, + Message = str.GetBytes() + }); + } + stopwatch.Stop(); + _logger.LogWarning($"插入 {10000} 条记录完成,耗时: {stopwatch.ElapsedMilliseconds} 毫秒"); + } + + public async Task AddMessageOfBulkInsertCassandra() + { + var records = new List(); + var prepared = await _cassandraProvider.Session.PrepareAsync( + $"INSERT INTO {_cassandraProvider.CassandraConfig.Keyspace}.{nameof(MessageIssued)} (id, clientid, message, deviceno,type,messageid) VALUES (?, ?, ?, ?, ?, ?)"); + + for (int i = 1; i <= 100000; i++) + { + var str = Guid.NewGuid().ToString(); + records.Add(new MessageIssued + { + ClientId = str, + DeviceNo = i.ToString(), + MessageId = str, + Type = IssuedEventType.Data, + Id = str, + Message = str.GetBytes() + }); + } + var stopwatch = Stopwatch.StartNew(); + await BulkInsertAsync(_cassandraProvider.Session, prepared, records); + stopwatch.Stop(); + _logger.LogWarning($"插入 {100000} 条记录完成,耗时: {stopwatch.ElapsedMilliseconds} 毫秒"); + } + + private static async Task BulkInsertAsync(ISession session, PreparedStatement prepared, List records) + { + var tasks = new List(); + var batch = new BatchStatement(); + + for (int i = 0; i < records.Count; i++) + { + var record = records[i]; + var boundStatement = prepared.Bind( + record.Id, + record.ClientId, + record.Message, + record.DeviceNo, + (int)record.Type, + record.MessageId); + + // 设置一致性级别为ONE以提高性能 + boundStatement.SetConsistencyLevel(ConsistencyLevel.One); + + batch.Add(boundStatement); + + // 当达到批处理大小时执行 + if (batch.Statements.Count() >= 1000 || i == records.Count - 1) + { + tasks.Add(session.ExecuteAsync(batch)); + batch = new BatchStatement(); + } + } + + // 等待所有批处理完成 + await Task.WhenAll(tasks); } } diff --git a/src/JiShe.CollectBus.Cassandra/CassandraProvider.cs b/src/JiShe.CollectBus.Cassandra/CassandraProvider.cs index a5cd9c8..dc3e0ee 100644 --- a/src/JiShe.CollectBus.Cassandra/CassandraProvider.cs +++ b/src/JiShe.CollectBus.Cassandra/CassandraProvider.cs @@ -6,6 +6,7 @@ using Cassandra; using Cassandra.Mapping; using Cassandra.Data.Linq; using System.ComponentModel.DataAnnotations; +using System.Diagnostics; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; @@ -35,6 +36,12 @@ namespace JiShe.CollectBus.Cassandra _logger = logger; } + public Task InitClusterAndSessionAsync() + { + InitClusterAndSession(); + return Task.CompletedTask; + } + public void InitClusterAndSession() { GetCluster((keyspace) => diff --git a/src/JiShe.CollectBus.Cassandra/CassandraRepository.cs b/src/JiShe.CollectBus.Cassandra/CassandraRepository.cs index 8381450..66ac6d7 100644 --- a/src/JiShe.CollectBus.Cassandra/CassandraRepository.cs +++ b/src/JiShe.CollectBus.Cassandra/CassandraRepository.cs @@ -1,6 +1,12 @@ using Cassandra; +using Cassandra.Data.Linq; using Cassandra.Mapping; using JiShe.CollectBus.Cassandra.Extensions; +using JiShe.CollectBus.Common.Attributes; +using JiShe.CollectBus.IoTDBProvider; +using Microsoft.AspNetCore.Http; +using System.Reflection; +using Thrift.Protocol.Entities; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; @@ -10,9 +16,10 @@ namespace JiShe.CollectBus.Cassandra : ICassandraRepository where TEntity : class { - + private readonly ICassandraProvider _cassandraProvider; public CassandraRepository(ICassandraProvider cassandraProvider, MappingConfiguration mappingConfig) { + _cassandraProvider = cassandraProvider; Mapper = new Mapper(cassandraProvider.Session, mappingConfig); cassandraProvider.Session.CreateTable(cassandraProvider.CassandraConfig.Keyspace); } diff --git a/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs b/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs index 2502420..b5274f7 100644 --- a/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs +++ b/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs @@ -13,17 +13,16 @@ namespace JiShe.CollectBus.Cassandra )] public class CollectBusCassandraModule : AbpModule { - public override void ConfigureServices(ServiceConfigurationContext context) + public override Task ConfigureServicesAsync(ServiceConfigurationContext context) { Configure(context.Services.GetConfiguration().GetSection("Cassandra")); - context.AddCassandra(); - + return Task.CompletedTask; } - public override void OnApplicationInitialization(ApplicationInitializationContext context) + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) { - context.UseCassandra(); + await context.UseCassandra(); } } } diff --git a/src/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs b/src/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs index 9c6a044..fe69268 100644 --- a/src/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs +++ b/src/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs @@ -13,11 +13,11 @@ namespace Microsoft.Extensions.DependencyInjection { public static class ApplicationInitializationContextExtensions { - public static void UseCassandra(this ApplicationInitializationContext context) + public static async Task UseCassandra(this ApplicationInitializationContext context) { var service = context.ServiceProvider; var cassandraProvider = service.GetRequiredService(); - cassandraProvider.InitClusterAndSession(); + await cassandraProvider.InitClusterAndSessionAsync(); } } @@ -25,11 +25,9 @@ namespace Microsoft.Extensions.DependencyInjection { public static void AddCassandra(this ServiceConfigurationContext context) { - context.Services.AddSingleton(typeof(ICassandraRepository<,>), typeof(CassandraRepository<,>)); - - var mappingConfig = new MappingConfiguration() - .Define(new CollectBusMapping()); - context.Services.AddSingleton(mappingConfig); + context.Services.AddTransient(typeof(ICassandraRepository<,>), typeof(CassandraRepository<,>)); + context.Services.AddSingleton(new MappingConfiguration() + .Define(new CollectBusMapping())); } } } diff --git a/src/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs b/src/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs index f515416..c313d0c 100644 --- a/src/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs +++ b/src/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs @@ -4,6 +4,8 @@ using Cassandra; using System.ComponentModel.DataAnnotations; using JiShe.CollectBus.Common.Attributes; using Cassandra.Mapping; +using Cassandra.Data.Linq; +using Thrift.Protocol.Entities; namespace JiShe.CollectBus.Cassandra.Extensions { @@ -14,7 +16,8 @@ namespace JiShe.CollectBus.Cassandra.Extensions var type = typeof(TEntity); var tableAttribute = type.GetCustomAttribute(); var tableName = tableAttribute?.Name ?? type.Name.ToLower(); - var tableKeyspace = tableAttribute?.Keyspace ?? defaultKeyspace; + //var tableKeyspace = tableAttribute?.Keyspace ?? defaultKeyspace; + var tableKeyspace = session.Keyspace; var properties = type.GetProperties(); var primaryKey = properties.FirstOrDefault(p => p.GetCustomAttribute() != null); @@ -79,5 +82,7 @@ namespace JiShe.CollectBus.Cassandra.Extensions throw new NotSupportedException($"不支持的类型: {type.Name}"); } + + } } diff --git a/src/JiShe.CollectBus.Cassandra/ICassandraProvider.cs b/src/JiShe.CollectBus.Cassandra/ICassandraProvider.cs index 45c6ca6..8b1f87a 100644 --- a/src/JiShe.CollectBus.Cassandra/ICassandraProvider.cs +++ b/src/JiShe.CollectBus.Cassandra/ICassandraProvider.cs @@ -20,5 +20,7 @@ namespace JiShe.CollectBus.Cassandra Cluster GetCluster(Action? callback = null); void InitClusterAndSession(); + + Task InitClusterAndSessionAsync(); } } diff --git a/src/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs b/src/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs index bdd16d5..5ee6d8f 100644 --- a/src/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs +++ b/src/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs @@ -9,17 +9,9 @@ using Volo.Abp; namespace JiShe.CollectBus.Common.Attributes { [AttributeUsage(AttributeTargets.Class, Inherited = false)] - public class CassandraTableAttribute : Attribute + public class CassandraTableAttribute(string? name = null) : Attribute { - public CassandraTableAttribute(string? name = null,string? keyspace =null) - { - Name = name; - Keyspace = keyspace; - } - - public virtual string? Name { get; } - - public virtual string? Keyspace { get; } + public virtual string? Name { get; } = name; } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs index 5977d5e..072abcb 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs +++ b/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs @@ -6,17 +6,24 @@ using System.Text; using System.Threading.Tasks; using JiShe.CollectBus.Common.Attributes; using JiShe.CollectBus.Common.Enums; +using Volo.Abp.Domain.Entities; namespace JiShe.CollectBus.IotSystems.MessageIssueds { [CassandraTable] - public class MessageIssued + public class MessageIssued:IEntity { - [Key] public string ClientId { get; set; } public byte[] Message { get; set; } public string DeviceNo { get; set; } public IssuedEventType Type { get; set; } public string MessageId { get; set; } + [Key] + public string Id { get; set; } + + public object?[] GetKeys() + { + return new object[] { Id }; + } } } diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index a837636..6ca53cc 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -34,7 +34,7 @@ "CorsOrigins": "http://localhost:4200,http://localhost:3100" }, "ConnectionStrings": { - "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", + "Default": "mongodb://mongo_PmEeF3:lixiao1980@192.168.1.9:27017/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", "Kafka": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092", "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs index addb7c0..ebc5ad1 100644 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs @@ -9,6 +9,7 @@ using MongoDB.Driver; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using JiShe.CollectBus.IotSystems.MessageIssueds; using Volo.Abp.Data; using Volo.Abp.MongoDB; using Volo.Abp.MultiTenancy; @@ -28,7 +29,11 @@ public class CollectBusMongoDbContext : AbpMongoDbContext, ICollectBusMongoDbCon public IMongoCollection MessageReceivedHeartbeats => Collection(); public IMongoCollection Devices => Collection(); public IMongoCollection ProtocolInfos => Collection(); - + + public IMongoCollection MessageIssueds => Collection(); + + + protected override void CreateModel(IMongoModelBuilder modelBuilder) { //modelBuilder.Entity(builder => diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs index 9a5a0f0..f427d19 100644 --- a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs +++ b/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs @@ -28,7 +28,7 @@ public class CollectBusMongoDbModule : AbpModule { context.Services.AddMongoDbContext(options => { - options.AddDefaultRepositories(); + options.AddDefaultRepositories(includeAllEntities: true); // 注册分表策略 context.Services.AddTransient( -- 2.47.2 From 1a275ec9c3ca87effa7ad64d0c640e5428f396eb Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Wed, 16 Apr 2025 17:36:46 +0800 Subject: [PATCH 107/139] =?UTF-8?q?=E6=89=B9=E9=87=8F=E5=86=99=E5=85=A5?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=B0=83=E4=BC=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RedisDataCache/IRedisDataCacheService.cs | 176 ++++ .../CollectBusAppService.cs | 4 +- .../RedisDataCache/RedisDataCacheService.cs | 745 ++++++++++++++ .../Samples/SampleAppService.cs | 57 +- .../BasicScheduledMeterReadingService.cs | 90 +- ...nergySystemScheduledMeterReadingService.cs | 16 +- .../Consts/RedisConst.cs | 29 +- .../Models/BusCacheGlobalPagedResult.cs | 16 + .../Models/DeviceCacheBasicModel.cs | 7 +- .../FreeRedisProvider.cs | 957 ++++++++---------- .../IFreeRedisProvider.cs | 100 +- .../Options/FreeRedisOptions.cs | 5 + src/JiShe.CollectBus.Host/appsettings.json | 3 +- 13 files changed, 1491 insertions(+), 714 deletions(-) create mode 100644 src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs create mode 100644 src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs b/src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs new file mode 100644 index 0000000..c6f3613 --- /dev/null +++ b/src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs @@ -0,0 +1,176 @@ +using JiShe.CollectBus.Common.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Application.Contracts +{ + /// + /// 数据缓存服务接口 + /// + public interface IRedisDataCacheService + { + /// + /// 单个添加数据 + /// + /// + /// 主数据存储Hash缓存Key + /// Set索引缓存Key + /// ZSET索引缓存Key + /// 待缓存数据 + /// + Task InsertDataAsync( + string redisHashCacheKey, + string redisSetIndexCacheKey, + string redisZSetScoresIndexCacheKey, + T data) where T : DeviceCacheBasicModel; + + /// + /// 批量添加数据 + /// + /// + /// 主数据存储Hash缓存Key + /// Set索引缓存Key + /// ZSET索引缓存Key + /// 待缓存数据集合 + /// + Task BatchInsertDataAsync( + string redisHashCacheKey, + string redisSetIndexCacheKey, + string redisZSetScoresIndexCacheKey, + IEnumerable items) where T : DeviceCacheBasicModel; + + /// + /// 删除缓存信息 + /// + /// + /// 主数据存储Hash缓存Key + /// Set索引缓存Key + /// ZSET索引缓存Key + /// 已缓存数据 + /// + Task RemoveCacheDataAsync( + string redisHashCacheKey, + string redisSetIndexCacheKey, + string redisZSetScoresIndexCacheKey, + T data) where T : DeviceCacheBasicModel; + + /// + /// 修改缓存信息,映射关系未发生改变 + /// + /// + /// 主数据存储Hash缓存Key + /// Set索引缓存Key + /// ZSET索引缓存Key + /// 待修改缓存数据 + /// + Task ModifyDataAsync( + string redisHashCacheKey, + string redisSetIndexCacheKey, + string redisZSetScoresIndexCacheKey, + T newData) where T : DeviceCacheBasicModel; + + + /// + /// 修改缓存信息,映射关系已改变 + /// + /// + /// 主数据存储Hash缓存Key + /// Set索引缓存Key + /// 旧的映射关系 + /// ZSET索引缓存Key + /// 待修改缓存数据 + /// + Task ModifyDataAsync( + string redisHashCacheKey, + string redisSetIndexCacheKey, + string oldMemberId, + string redisZSetScoresIndexCacheKey, + T newData) where T : DeviceCacheBasicModel; + + ///// + ///// 通过集中器与表计信息排序索引获取数据 + ///// + ///// + ///// 主数据存储Hash缓存Key + ///// ZSET索引缓存Key + ///// 分页尺寸 + ///// 最后一个索引 + ///// 最后一个唯一标识 + ///// 排序方式 + ///// + //Task> GetPagedData( + //string redisHashCacheKey, + //string redisZSetScoresIndexCacheKey, + //IEnumerable focusIds, + //int pageSize = 10, + //decimal? lastScore = null, + //string lastMember = null, + //bool descending = true) + //where T : DeviceCacheBasicModel; + + + /// + /// 通过ZSET索引获取数据 + /// + /// + /// 主数据存储Hash缓存Key + /// ZSET索引缓存Key + /// 分页尺寸 + /// 最后一个索引 + /// 最后一个唯一标识 + /// 排序方式 + /// + Task> GetAllPagedData( + string redisHashCacheKey, + string redisZSetScoresIndexCacheKey, + int pageSize = 1000, + decimal? lastScore = null, + string lastMember = null, + bool descending = true) + where T : DeviceCacheBasicModel; + + + ///// + ///// 游标分页查询 + ///// + ///// 排序索引ZSET缓存Key + ///// 分页数量 + ///// 开始索引 + ///// 开始唯一标识 + ///// 排序方式 + ///// + //Task<(List Members, bool HasNext)> GetPagedMembers( + // string redisZSetScoresIndexCacheKey, + // int pageSize, + // decimal? startScore, + // string excludeMember, + // bool descending); + + ///// + ///// 批量获取指定分页的数据 + ///// + ///// + ///// Hash表缓存key + ///// Hash表字段集合 + ///// + //Task> BatchGetData( + // string redisHashCacheKey, + // IEnumerable members) + // where T : DeviceCacheBasicModel; + + ///// + ///// 获取下一页游标 + ///// + ///// 排序索引ZSET缓存Key + ///// 最后一个唯一标识 + ///// 排序方式 + ///// + //Task GetNextScore( + // string redisZSetScoresIndexCacheKey, + // string lastMember, + // bool descending); + } +} diff --git a/src/JiShe.CollectBus.Application/CollectBusAppService.cs b/src/JiShe.CollectBus.Application/CollectBusAppService.cs index 6ecc509..ad5b585 100644 --- a/src/JiShe.CollectBus.Application/CollectBusAppService.cs +++ b/src/JiShe.CollectBus.Application/CollectBusAppService.cs @@ -79,7 +79,7 @@ public abstract class CollectBusAppService : ApplicationService { string key = (string)item[0]; object[] fieldsAndValues = (object[])item[1]; - var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, systemType, serverTagName, meterType, timeDensity)}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoHashKey, systemType, serverTagName, meterType, timeDensity)}"; string focusAddress = key.Replace(redisCacheKey, ""); var meterHashs = new Dictionary(); @@ -182,7 +182,7 @@ public abstract class CollectBusAppService : ApplicationService string key = (string)item[0]; object[] fieldsAndValues = (object[])item[1]; var redisCacheKey = string.Format( - RedisConst.CacheMeterInfoKey, + RedisConst.CacheMeterInfoHashKey, systemType, serverTagName, meterType, diff --git a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs new file mode 100644 index 0000000..4d78706 --- /dev/null +++ b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs @@ -0,0 +1,745 @@ +using Confluent.Kafka; +using FreeRedis; +using JiShe.CollectBus.Application.Contracts; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.FreeRedisProvider; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace JiShe.CollectBus.RedisDataCache +{ + /// + /// 数据缓存服务接口 + /// + public class RedisDataCacheService : IRedisDataCacheService, ITransientDependency + { + private readonly IFreeRedisProvider _freeRedisProvider; + private readonly ILogger _logger; + private RedisClient Instance { get; set; } + + /// + /// 数据缓存服务接口 + /// + /// + /// + public RedisDataCacheService(IFreeRedisProvider freeRedisProvider, + ILogger logger) + { + this._freeRedisProvider = freeRedisProvider; + this._logger = logger; + + Instance = _freeRedisProvider.Instance; + } + + /// + /// 单个添加数据 + /// + /// + /// 主数据存储Hash缓存Key + /// Set索引缓存Key + /// ZSET索引缓存Key + /// 待缓存数据 + /// + public async Task InsertDataAsync( + string redisHashCacheKey, + string redisSetIndexCacheKey, + string redisZSetScoresIndexCacheKey, + T data) where T : DeviceCacheBasicModel + { + // 参数校验增强 + if (data == null || string.IsNullOrWhiteSpace(redisHashCacheKey) + || string.IsNullOrWhiteSpace(redisSetIndexCacheKey) + || string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) + { + _logger.LogError($"{nameof(InsertDataAsync)} 参数异常,-101"); + return; + } + + // 使用事务保证原子性 + using (var trans = Instance.Multi()) + { + // 主数据存储Hash + trans.HSet(redisHashCacheKey, data.MemberID, data.Serialize()); + + // 集中器号分组索引Set缓存 + trans.SAdd(redisSetIndexCacheKey, data.MemberID); + + // 集中器与表计信息排序索引ZSET缓存Key + trans.ZAdd(redisZSetScoresIndexCacheKey, data.ScoreValue, data.MemberID); + + var results = trans.Exec(); + + if (results == null || results.Length <= 0) + { + _logger.LogError($"{nameof(InsertDataAsync)} 添加事务提交失败,-102"); + } + } + + await Task.CompletedTask; + } + + /// + /// 批量添加数据 + /// + /// + /// 主数据存储Hash缓存Key + /// Set索引缓存Key + /// ZSET索引缓存Key + /// 待缓存数据集合 + /// + public async Task BatchInsertDataAsync( + string redisHashCacheKey, + string redisSetIndexCacheKey, + string redisZSetScoresIndexCacheKey, + IEnumerable items) where T : DeviceCacheBasicModel + { + if (items == null + || items.Count() <= 0 + || string.IsNullOrWhiteSpace(redisHashCacheKey) + || string.IsNullOrWhiteSpace(redisSetIndexCacheKey) + || string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) + { + _logger.LogError($"{nameof(BatchInsertDataAsync)} 参数异常,-101"); + return; + } + + const int BATCH_SIZE = 1000; // 每批1000条 + var semaphore = new SemaphoreSlim(Environment.ProcessorCount * 2); + + foreach (var batch in items.Batch(BATCH_SIZE)) + { + await semaphore.WaitAsync(); + + _ = Task.Run(() => + { + using (var pipe = Instance.StartPipe()) + { + foreach (var item in batch) + { + // 主数据存储Hash + pipe.HSet(redisHashCacheKey, item.MemberID, item.Serialize()); + + // Set索引缓存 + pipe.SAdd(redisSetIndexCacheKey, item.MemberID); + + // ZSET索引缓存Key + pipe.ZAdd(redisZSetScoresIndexCacheKey, item.ScoreValue, item.MemberID); + } + pipe.EndPipe(); + } + semaphore.Release(); + }); + } + + await Task.CompletedTask; + } + + /// + /// 删除缓存信息 + /// + /// + /// 主数据存储Hash缓存Key + /// Set索引缓存Key + /// ZSET索引缓存Key + /// 已缓存数据 + /// + public async Task RemoveCacheDataAsync( + string redisHashCacheKey, + string redisSetIndexCacheKey, + string redisZSetScoresIndexCacheKey, + T data) where T : DeviceCacheBasicModel + { + if (data == null + || string.IsNullOrWhiteSpace(redisHashCacheKey) + || string.IsNullOrWhiteSpace(redisSetIndexCacheKey) + || string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) + { + _logger.LogError($"{nameof(RemoveCacheDataAsync)} 参数异常,-101"); + return; + } + + const string luaScript = @" + local hashCacheKey = KEYS[1] + local setIndexCacheKey = KEYS[2] + local zsetScoresIndexCacheKey = KEYS[3] + local member = ARGV[1] + + local deleted = 0 + if redis.call('HDEL', hashCacheKey, member) > 0 then + deleted = 1 + end + + redis.call('SREM', setIndexCacheKey, member) + redis.call('ZREM', zsetScoresIndexCacheKey, member) + return deleted + "; + + var keys = new[] + { + redisHashCacheKey, + redisSetIndexCacheKey, + redisZSetScoresIndexCacheKey + }; + + var result = await Instance.EvalAsync(luaScript, keys, new[] { data.MemberID }); + + if ((int)result == 0) + { + _logger.LogError($"{nameof(RemoveCacheDataAsync)} 删除指定Key{redisHashCacheKey}的{data.MemberID}数据失败,-102"); + } + } + + /// + /// 修改缓存信息,映射关系未发生改变 + /// + /// + /// 主数据存储Hash缓存Key + /// Set索引缓存Key + /// ZSET索引缓存Key + /// 待修改缓存数据 + /// + public async Task ModifyDataAsync( + string redisHashCacheKey, + string redisSetIndexCacheKey, + string redisZSetScoresIndexCacheKey, + T newData) where T : DeviceCacheBasicModel + { + if (newData == null + || string.IsNullOrWhiteSpace(redisHashCacheKey) + || string.IsNullOrWhiteSpace(redisSetIndexCacheKey) + || string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) + { + _logger.LogError($"{nameof(ModifyDataAsync)} 参数异常,-101"); + return; + } + + var luaScript = @" + local hashCacheKey = KEYS[1] + local member = ARGV[1] + local newData = ARGV[2] + + -- 校验存在性 + if redis.call('HEXISTS', hashCacheKey, member) == 0 then + return 0 + end + + -- 更新主数据 + redis.call('HSET', hashCacheKey, member, newData) + + return 1 + "; + + + var result = await Instance.EvalAsync(luaScript, + new[] + { + redisHashCacheKey + }, + new object[] + { + newData.MemberID, + newData.Serialize() + }); + + if ((int)result == 0) + { + _logger.LogError($"{nameof(ModifyDataAsync)} 更新指定Key{redisHashCacheKey}的{newData.MemberID}数据失败,-102"); + } + } + + /// + /// 修改缓存信息,映射关系已经改变 + /// + /// + /// 主数据存储Hash缓存Key + /// Set索引缓存Key + /// 旧的映射关系 + /// ZSET索引缓存Key + /// 待修改缓存数据 + /// + public async Task ModifyDataAsync( + string redisHashCacheKey, + string redisSetIndexCacheKey, + string oldMemberId, + string redisZSetScoresIndexCacheKey, + T newData) where T : DeviceCacheBasicModel + { + if (newData == null + || string.IsNullOrWhiteSpace(redisHashCacheKey) + || string.IsNullOrWhiteSpace(redisSetIndexCacheKey) + || string.IsNullOrWhiteSpace(oldMemberId) + || string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) + { + _logger.LogError($"{nameof(ModifyDataAsync)} 参数异常,-101"); + return; + } + + var luaScript = @" + local hashCacheKey = KEYS[1] + local setIndexCacheKey = KEYS[2] + local zsetScoresIndexCacheKey = KEYS[3] + local member = ARGV[1] + local oldMember = ARGV[2] + local newData = ARGV[3] + local newScore = ARGV[4] + + -- 校验存在性 + if redis.call('HEXISTS', hashCacheKey, oldMember) == 0 then + return 0 + end + + -- 删除旧数据 + redis.call('HDEL', hashCacheKey, oldMember) + + -- 插入新主数据 + redis.call('HSET', hashCacheKey, member, newData) + + -- 处理变更 + if newScore ~= '' then + -- 删除旧索引 + redis.call('SREM', setIndexCacheKey, oldMember) + redis.call('ZREM', zsetScoresIndexCacheKey, oldMember) + + -- 添加新索引 + redis.call('SADD', setIndexCacheKey, member) + redis.call('ZADD', zsetScoresIndexCacheKey, newScore, member) + end + + return 1 + "; + + var result = await Instance.EvalAsync(luaScript, + new[] + { + redisHashCacheKey, + redisSetIndexCacheKey, + redisZSetScoresIndexCacheKey + }, + new object[] + { + newData.MemberID, + oldMemberId, + newData.Serialize(), + newData.ScoreValue.ToString() ?? "", + }); + + if ((int)result == 0) + { + _logger.LogError($"{nameof(ModifyDataAsync)} 更新指定Key{redisHashCacheKey}的{newData.MemberID}数据失败,-102"); + } + } + + + /// + /// 通过集中器与表计信息排序索引获取指定集中器号集合数据 + /// + /// + /// 主数据存储Hash缓存Key + /// 集中器与表计信息排序索引ZSET缓存Key + /// 集中器Id + /// 分页尺寸 + /// 最后一个索引 + /// 最后一个唯一标识 + /// 排序方式 + /// + public async Task> GetPagedData( + string redisCacheKey, + string redisCacheFocusScoresIndexKey, + IEnumerable focusIds, + int pageSize = 10, + decimal? lastScore = null, + string lastMember = null, + bool descending = true) + where T : DeviceCacheBasicModel + { + throw new Exception(); + } + + /// + /// 通过ZSET索引获取数据 + /// + /// + /// 主数据存储Hash缓存Key + /// ZSET索引缓存Key + /// 分页尺寸 + /// 最后一个索引 + /// 最后一个唯一标识 + /// 排序方式 + /// + public async Task> GetAllPagedData( + string redisHashCacheKey, + string redisZSetScoresIndexCacheKey, + int pageSize = 1000, + decimal? lastScore = null, + string lastMember = null, + bool descending = true) + where T : DeviceCacheBasicModel + { + // 参数校验(保持不变) + if (string.IsNullOrWhiteSpace(redisHashCacheKey) || string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) + { + _logger.LogError($"{nameof(GetAllPagedData)} 参数异常,-101"); + return null; + } + + if (pageSize < 1 || pageSize > 10000) + { + _logger.LogError($"{nameof(GetAllPagedData)} 分页大小应在1-10000之间,-102"); + return null; + } + + var luaScript = @" + local command = ARGV[1] + local range_start = ARGV[2] + local range_end = ARGV[3] + local limit = tonumber(ARGV[4]) + local last_score = ARGV[5] + local last_member = ARGV[6] + + -- 处理相同分数下的字典序分页 + if last_score ~= '' and last_member ~= '' then + if command == 'ZRANGEBYSCORE' then + range_start = '(' .. last_score + range_end = '(' .. last_score .. ' ' .. last_member + else + range_start = '(' .. last_score .. ' ' .. last_member + range_end = '(' .. last_score + end + end + + -- 执行范围查询 + local members + if command == 'ZRANGEBYSCORE' then + members = redis.call(command, KEYS[1], range_start, range_end, + 'WITHSCORES', 'LIMIT', 0, limit) + else + members = redis.call(command, KEYS[1], range_end, range_start, + 'WITHSCORES', 'LIMIT', 0, limit) + end + + -- 提取成员和分数 + local result_members = {} + local result_scores = {} + for i = 1, #members, 2 do + table.insert(result_members, members[i]) + table.insert(result_scores, members[i+1]) + end + + -- 获取Hash数据 + local hash_data = redis.call('HMGET', KEYS[2], unpack(result_members)) + + return { + #result_members, + result_members, + result_scores, + hash_data + }"; + + //正确设置范围参数 + string rangeStart, rangeEnd; + if (descending) + { + rangeStart = lastScore.HasValue ? $"({lastScore}" : "+inf"; + rangeEnd = "-inf"; // 降序时固定为最小值 + } + else + { + rangeStart = lastScore.HasValue ? $"({lastScore}" : "-inf"; + rangeEnd = "+inf"; // 升序时固定为最大值 + } + + var result = (object[])await Instance.EvalAsync( + luaScript, + new[] { redisZSetScoresIndexCacheKey, redisHashCacheKey }, + new object[] + { + descending ? "ZREVRANGEBYSCORE" : "ZRANGEBYSCORE", + rangeStart, + rangeEnd, + (pageSize + 1).ToString(), // 多取1条用于判断hasNext + lastScore?.ToString() ?? "", + lastMember ?? "" + }); + + if ((long)result[0] == 0) + return new BusCacheGlobalPagedResult { Items = new List() }; + + // 处理结果集 + var members = ((object[])result[1]).Cast().ToList(); + var scores = ((object[])result[2]).Cast().Select(decimal.Parse).ToList(); + var hashData = ((object[])result[3]).Cast().ToList(); + + //合并有效数据并处理游标 + var validItems = members.Zip(hashData, (m, h) => + !string.IsNullOrWhiteSpace(h) ? BusJsonSerializer.Deserialize(h) : null) + .Where(x => x != null) + .Take(pageSize + 1) + .ToList(); + + var hasNext = validItems.Count > pageSize; + var actualItems = hasNext ? validItems.Take(pageSize) : validItems; + + // 计算下一页起始点 + string nextMember = null; + decimal? nextScore = null; + if (hasNext) + { + // 获取实际返回的最后一条有效数据 + var lastValidIndex = actualItems.Count() - 1; + nextMember = members[lastValidIndex]; + nextScore = scores[lastValidIndex]; + } + + return new BusCacheGlobalPagedResult + { + Items = actualItems.ToList(), + HasNext = hasNext, + NextScore = nextScore, + NextMember = nextMember, + TotalCount = await GetTotalCount(redisZSetScoresIndexCacheKey), + PageSize = pageSize, + }; + } + + ///// + ///// 通过集中器与表计信息排序索引获取数据 + ///// + ///// + ///// 主数据存储Hash缓存Key + ///// ZSET索引缓存Key + ///// 分页尺寸 + ///// 最后一个索引 + ///// 最后一个唯一标识 + ///// 排序方式 + ///// + //public async Task> GetAllPagedData( + //string redisHashCacheKey, + //string redisZSetScoresIndexCacheKey, + //int pageSize = 1000, + //decimal? lastScore = null, + //string lastMember = null, + //bool descending = true) + //where T : DeviceCacheBasicModel + //{ + // // 参数校验增强 + // if (string.IsNullOrWhiteSpace(redisHashCacheKey) || string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) + // { + // _logger.LogError($"{nameof(GetAllPagedData)} 参数异常,-101"); + // return null; + // } + + // if (pageSize < 1 || pageSize > 10000) + // { + // _logger.LogError($"{nameof(GetAllPagedData)} 分页大小应在1-10000之间,-102"); + // return null; + // } + + // //// 分页参数解析 + // //var (startScore, excludeMember) = descending + // // ? (lastScore ?? decimal.MaxValue, lastMember) + // // : (lastScore ?? 0, lastMember); + + // //执行分页查询(整合游标处理) + // var pageResult = await GetPagedMembers( + // redisZSetScoresIndexCacheKey, + // pageSize, + // lastScore, + // lastMember, + // descending); + + // // 批量获取数据(优化内存分配) + // var dataDict = await BatchGetData(redisHashCacheKey, pageResult.Members); + + // return new BusCacheGlobalPagedResult + // { + // Items = pageResult.Members.Select(m => dataDict.TryGetValue(m, out var v) ? v : default) + // .Where(x => x != null).ToList(), + // HasNext = pageResult.HasNext, + // NextScore = pageResult.NextScore, + // NextMember = pageResult.NextMember, + // TotalCount = await GetTotalCount(redisZSetScoresIndexCacheKey), + // PageSize = pageSize, + // }; + //} + + /// + /// 游标分页查询 + /// + /// 排序索引ZSET缓存Key + /// 分页数量 + /// 上一个索引 + /// 上一个标识 + /// 排序方式 + /// + private async Task<(List Members, bool HasNext, decimal? NextScore, string NextMember)> GetPagedMembers( + string redisZSetScoresIndexCacheKey, + int pageSize, + decimal? lastScore, + string lastMember, + bool descending) + { + // 根据排序方向初始化参数 + long initialScore = descending ? long.MaxValue : 0; + decimal? currentScore = lastScore ?? initialScore; + string currentMember = lastMember; + var members = new List(pageSize + 1); + + // 使用游标分页查询 + while (members.Count < pageSize + 1 && currentScore.HasValue) + { + var (batch, hasMore) = await GetNextBatch( + redisZSetScoresIndexCacheKey, + pageSize + 1 - members.Count, + currentScore.Value, + currentMember, + descending); + + if (!batch.Any()) break; + + members.AddRange(batch); + + // 更新游标 + currentMember = batch.LastOrDefault(); + currentScore = await GetNextScore(redisZSetScoresIndexCacheKey, currentMember, descending); + } + + // 处理分页结果 + bool hasNext = members.Count > pageSize; + var resultMembers = members.Take(pageSize).ToList(); + + return ( + resultMembers, + hasNext, + currentScore, + currentMember + ); + } + + /// + /// 批量获取指定分页的数据 + /// + /// + /// Hash表缓存key + /// Hash表字段集合 + /// + private async Task> BatchGetData( + string redisHashCacheKey, + IEnumerable members) + where T : DeviceCacheBasicModel + { + using var pipe = Instance.StartPipe(); + + foreach (var member in members) + { + pipe.HGet(redisHashCacheKey, member); + } + + var results = pipe.EndPipe(); + return await Task.FromResult(members.Zip(results, (k, v) => new { k, v }) + .ToDictionary(x => x.k, x => (T)x.v)); + } + + /// + /// 处理下一个分页数据 + /// + /// + /// + /// + /// + /// + /// + private async Task<(string[] Batch, bool HasMore)> GetNextBatch( + string zsetKey, + int limit, + decimal score, + string excludeMember, + bool descending) + { + var query = descending + ? await Instance.ZRevRangeByScoreAsync( + zsetKey, + max: score, + min: 0, + offset: 0, + count: limit) + : await Instance.ZRangeByScoreAsync( + zsetKey, + min: score, + max: long.MaxValue, + offset: 0, + count: limit); + + return (query, query.Length >= limit); + } + + /// + /// 获取下一页游标 + /// + /// 排序索引ZSET缓存Key + /// 最后一个唯一标识 + /// 排序方式 + /// + private async Task GetNextScore( + string redisZSetScoresIndexCacheKey, + string lastMember, + bool descending) + { + if (string.IsNullOrEmpty(lastMember)) return null; + + var score = await Instance.ZScoreAsync(redisZSetScoresIndexCacheKey, lastMember); + if (!score.HasValue) return null; + + // 根据排序方向调整score + return descending + ? score.Value - 1 // 降序时下页查询小于当前score + : score.Value + 1; // 升序时下页查询大于当前score + } + + /// + /// 获取指定ZSET区间内的总数量 + /// + /// + /// + /// + /// + public async Task GetCount(string zsetKey, long min, long max) + { + // 缓存计数优化 + var cacheKey = $"{zsetKey}_count_{min}_{max}"; + var cached = await Instance.GetAsync(cacheKey); + + if (cached.HasValue) + return cached.Value; + + var count = await Instance.ZCountAsync(zsetKey, min, max); + await Instance.SetExAsync(cacheKey, 60, count); // 缓存60秒 + return count; + } + + /// + /// 获取指定ZSET的总数量 + /// + /// + /// + private async Task GetTotalCount(string redisZSetScoresIndexCacheKey) + { + // 缓存计数优化 + var cacheKey = $"{redisZSetScoresIndexCacheKey}_total_count"; + var cached = await Instance.GetAsync(cacheKey); + + if (cached.HasValue) + return cached.Value; + + var count = await Instance.ZCountAsync(redisZSetScoresIndexCacheKey, 0, decimal.MaxValue); + await Instance.SetExAsync(cacheKey, 30, count); // 缓存30秒 + return count; + } + } +} diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index e4c078d..3af4b6a 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -23,6 +23,9 @@ using JiShe.CollectBus.Common.DeviceBalanceControl; using JiShe.CollectBus.Kafka.Attributes; using System.Text.Json; using JiShe.CollectBus.Kafka; +using JiShe.CollectBus.Application.Contracts; +using JiShe.CollectBus.Common.Models; +using System.Diagnostics; namespace JiShe.CollectBus.Samples; @@ -32,17 +35,23 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS private readonly IIoTDBProvider _iotDBProvider; private readonly IoTDBRuntimeContext _dbContext; private readonly IoTDBOptions _options; + private readonly IRedisDataCacheService _redisDataCacheService; public SampleAppService(IIoTDBProvider iotDBProvider, IOptions options, - IoTDBRuntimeContext dbContext, ILogger logger) + IoTDBRuntimeContext dbContext, ILogger logger, IRedisDataCacheService redisDataCacheService) { _iotDBProvider = iotDBProvider; _options = options.Value; _dbContext = dbContext; _logger = logger; + _redisDataCacheService = redisDataCacheService; } - + /// + /// 测试 UseSessionPool + /// + /// + /// [HttpGet] public async Task UseSessionPool(long timestamps) { @@ -72,7 +81,10 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS await _iotDBProvider.InsertAsync(meter); } - + /// + /// 测试Session切换 + /// + /// [HttpGet] public async Task UseTableSessionPool() { @@ -125,7 +137,7 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS var timeDensity = "15"; //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(RedisConst.CacheMeterInfoKey, "Energy", "JiSheCollectBus", MeterTypeEnum.Ammeter.ToString(), timeDensity)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheMeterInfoHashKey, "Energy", "JiSheCollectBus", MeterTypeEnum.Ammeter.ToString(), timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); var meterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, "Energy", "JiSheCollectBus", timeDensity, MeterTypeEnum.Ammeter); @@ -178,6 +190,43 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS await _iotDBProvider.InsertAsync(meter); } + /// + /// 测试单个测点数据项 + /// + /// + [HttpGet] + public async Task TestRedisCacheGetAllPagedData() + { + var timeDensity = "15"; + string SystemType = ""; + string ServerTagName = "JiSheCollectBus2"; + var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; + var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; + var redisCacheMeterInfoZSetScoresIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; + var timer = Stopwatch.StartNew(); + + decimal? cursor = null; + string member = null; + bool hasNext; + List meterInfos = new List(); + do + { + var page = await _redisDataCacheService + .GetAllPagedData( + redisCacheMeterInfoHashKeyTemp, + redisCacheMeterInfoZSetScoresIndexKeyTemp); + + meterInfos.AddRange(page.Items); + cursor = page.NextScore; + member = page.NextMember; + hasNext = page.HasNext; + } while (hasNext); + + timer.Stop(); + + _logger.LogInformation($"{nameof(TestRedisCacheGetAllPagedData)} 获取电表缓存数据完成,耗时{timer.ElapsedMilliseconds}毫秒"); + } + public Task GetAsync() { diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 2fd9781..ce4635b 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -1,11 +1,13 @@ using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; +using JiShe.CollectBus.Application.Contracts; using JiShe.CollectBus.Common.BuildSendDatas; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.DeviceBalanceControl; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.GatherItem; using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.MessageIssueds; @@ -34,13 +36,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading private readonly IIoTDBProvider _dbProvider; private readonly IMeterReadingRecordRepository _meterReadingRecordRepository; private readonly IProducerService _producerService; - + private readonly IRedisDataCacheService _redisDataCacheService; public BasicScheduledMeterReadingService( ILogger logger, ICapPublisher producerBus, IMeterReadingRecordRepository meterReadingRecordRepository, IProducerService producerService, + IRedisDataCacheService redisDataCacheService, IIoTDBProvider dbProvider) { _producerBus = producerBus; @@ -48,6 +51,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading _dbProvider = dbProvider; _meterReadingRecordRepository = meterReadingRecordRepository; _producerService = producerService; + _redisDataCacheService = redisDataCacheService; } /// @@ -121,7 +125,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading //获取缓存中的表信息 - var redisKeyList = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, meteryType, timeDensity)}*"; + var redisKeyList = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, ServerTagName, meteryType, timeDensity)}*"; var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) { @@ -209,25 +213,51 @@ namespace JiShe.CollectBus.ScheduledMeterReading public virtual async Task InitAmmeterCacheData(string gatherCode = "") { #if DEBUG - var timeDensity = "15"; - string tempCacheMeterInfoKey = $"CollectBus:{"{0}:{1}"}:MeterInfo:{"{2}"}:{"{3}"}"; - //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(tempCacheMeterInfoKey, SystemType, "JiSheCollectBus", MeterTypeEnum.Ammeter, timeDensity)}*"; + //var timeDensity = "15"; + //string tempCacheMeterInfoKey = $"CollectBus:{"{0}:{1}"}:MeterInfo:{"{2}"}:{"{3}"}"; + ////获取缓存中的电表信息 + //var redisKeyList = $"{string.Format(tempCacheMeterInfoKey, SystemType, "JiSheCollectBus", MeterTypeEnum.Ammeter, timeDensity)}*"; - var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - var tempMeterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, SystemType, ServerTagName, timeDensity, MeterTypeEnum.Ammeter); - //List focusAddressDataLista = new List(); - List meterInfos = new List(); - foreach (var item in tempMeterInfos) - { - var tempData = item.Adapt(); - tempData.FocusId = item.FocusID; - tempData.MeterId = item.Id; - meterInfos.Add(tempData); - //focusAddressDataLista.Add(item.FocusAddress); - } + //var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + //var tempMeterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, SystemType, ServerTagName, timeDensity, MeterTypeEnum.Ammeter); + ////List focusAddressDataLista = new List(); + //List meterInfos = new List(); + //foreach (var item in tempMeterInfos) + //{ + // var tempData = item.Adapt(); + // tempData.FocusId = item.FocusID; + // tempData.MeterId = item.Id; + // meterInfos.Add(tempData); + // //focusAddressDataLista.Add(item.FocusAddress); + //} //DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista); + + var timeDensity = "15"; + var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; + var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; + var redisCacheMeterInfoZSetScoresIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; + + + decimal? cursor = null; + string member = null; + bool hasNext; + List meterInfos = new List(); + do + { + var page = await _redisDataCacheService.GetAllPagedData( + redisCacheMeterInfoHashKeyTemp, + redisCacheMeterInfoZSetScoresIndexKeyTemp, + pageSize: 1000, + lastScore: cursor, + lastMember: member); + + meterInfos.AddRange(page.Items); + cursor = page.HasNext ? page.NextScore : null; + member = page.HasNext ? page.NextMember : null; + hasNext = page.HasNext; + } while (hasNext); + #else var meterInfos = await GetAmmeterInfoList(gatherCode); #endif @@ -251,10 +281,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading var meterInfoGroupByTimeDensity = meterInfos.GroupBy(d => d.TimeDensity); foreach (var itemTimeDensity in meterInfoGroupByTimeDensity) { - var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; - var redisCacheFocusIndexKey = $"{string.Format(RedisConst.CacheMeterInfoFocusIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; - var redisCacheScoresIndexKey = $"{string.Format(RedisConst.CacheMeterInfoScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; - var redisCacheGlobalIndexKey = $"{string.Format(RedisConst.CacheMeterInfoGlobalIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; + var redisCacheMeterInfoHashKey = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; + var redisCacheMeterInfoSetIndexKey = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; + var redisCacheMeterInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; List ammeterInfos = new List(); //将表计信息根据集中器分组,获得集中器号 @@ -329,11 +358,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading //await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); } - await FreeRedisProvider.BatchAddMeterData( - redisCacheKey, - redisCacheFocusIndexKey, - redisCacheScoresIndexKey, - redisCacheGlobalIndexKey, ammeterInfos); + await _redisDataCacheService.BatchInsertDataAsync( + redisCacheMeterInfoHashKey, + redisCacheMeterInfoSetIndexKey, + redisCacheMeterInfoZSetScoresIndexKey,ammeterInfos); //在缓存表信息数据的时候,新增下一个时间的自动处理任务,1分钟后执行所有的采集频率任务 TasksToBeIssueModel nextTask = new TasksToBeIssueModel() @@ -635,7 +663,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading var currentTime = DateTime.Now; var pendingCopyReadTime = currentTime.AddMinutes(timeDensity); //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 - var redisCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}{ammeterInfo.FocusAddress}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}{ammeterInfo.FocusAddress}"; if (string.IsNullOrWhiteSpace(ammeterInfo.ItemCodes)) { @@ -897,7 +925,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading foreach (var focusInfo in focusGroup) { //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 - var redisCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}{focusInfo.Key}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}{focusInfo.Key}"; foreach (var ammeterInfo in focusInfo.Value) { @@ -1113,7 +1141,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, MeterTypeEnum.WaterMeter, itemTimeDensity.Key)}{item.Key}"; + var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.WaterMeter, itemTimeDensity.Key)}{item.Key}"; Dictionary keyValuePairs = new Dictionary(); foreach (var subItem in item) { @@ -1260,7 +1288,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// private string GetTelemetryPacketCacheKeyPrefix(int timeDensity, MeterTypeEnum meterType) { - return $"{string.Format(RedisConst.CacheTelemetryPacketInfoKey, SystemType, ServerTagName, meterType, timeDensity)}*"; + return $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, meterType, timeDensity)}*"; } #endregion diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index d44fe56..25c5476 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Confluent.Kafka; using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; +using JiShe.CollectBus.Application.Contracts; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.DeviceBalanceControl; using JiShe.CollectBus.Common.Helpers; @@ -35,8 +36,19 @@ namespace JiShe.CollectBus.ScheduledMeterReading public class EnergySystemScheduledMeterReadingService : BasicScheduledMeterReadingService { string serverTagName = string.Empty; - public EnergySystemScheduledMeterReadingService(ILogger logger, - ICapPublisher producerBus, IIoTDBProvider dbProvider, IMeterReadingRecordRepository meterReadingRecordRepository,IConfiguration configuration, IProducerService producerService) : base(logger, producerBus, meterReadingRecordRepository, producerService,dbProvider) + public EnergySystemScheduledMeterReadingService( + ILogger logger, + ICapPublisher producerBus, IIoTDBProvider dbProvider, + IMeterReadingRecordRepository meterReadingRecordRepository, + IConfiguration configuration, + IProducerService producerService, + IRedisDataCacheService redisDataCacheService) + : base(logger, + producerBus, + meterReadingRecordRepository, + producerService, + redisDataCacheService, + dbProvider) { serverTagName = configuration.GetValue(CommonConst.ServerTagName)!; } diff --git a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs index 9377056..dce5307 100644 --- a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs @@ -32,23 +32,18 @@ namespace JiShe.CollectBus.Common.Consts /// /// 缓存表计信息,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 /// - public const string CacheMeterInfoKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{MeterInfo}:{"{2}"}:{"{3}"}"; + public const string CacheMeterInfoHashKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{MeterInfo}:{"{2}"}:{"{3}"}"; /// - /// 缓存表计信息集中器索引Set缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 + /// 缓存表计信息索引Set缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 /// - public const string CacheMeterInfoFocusIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{MeterInfo}:{"{2}"}:FocusIndex:{"{3}"}"; + public const string CacheMeterInfoSetIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{MeterInfo}:{"{2}"}:SetIndex:{"{3}"}"; /// - /// 缓存表计信息集中器排序索引ZSET缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 + /// 缓存表计信息排序索引ZSET缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 /// - public const string CacheMeterInfoScoresIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{MeterInfo}:{"{2}"}:ScoresIndex:{"{3}"}"; - - /// - /// 缓存表计信息集中器采集频率分组全局索引ZSet缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 - /// - public const string CacheMeterInfoGlobalIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{MeterInfo}:{"{2}"}:GlobalIndex:{"{3}"}"; - + public const string CacheMeterInfoZSetScoresIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{MeterInfo}:{"{2}"}:ZSetScoresIndex:{"{3}"}"; + public const string TaskInfo = "TaskInfo"; /// @@ -60,7 +55,17 @@ namespace JiShe.CollectBus.Common.Consts /// /// 缓存表计下发指令数据集,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 /// - public const string CacheTelemetryPacketInfoKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TelemetryPacket}:{"{2}"}:{"{3}"}"; + public const string CacheTelemetryPacketInfoHashKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TelemetryPacket}:{"{2}"}:{"{3}"}"; + + /// + /// 缓存表计下发指令数据集索引Set缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 + /// + public const string CacheTelemetryPacketInfoSetIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TelemetryPacket}:{"{2}"}:SetIndex:{"{3}"}"; + + /// + /// 缓存表计下发指令数据集排序索引ZSET缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 + /// + public const string CacheTelemetryPacketInfoZSetScoresIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TelemetryPacket}:{"{2}"}:ZSetScoresIndex:{"{3}"}"; ///// ///// 缓存设备平衡关系映射结果,{0}=>系统类型,{1}=>应用服务部署标记 diff --git a/src/JiShe.CollectBus.Common/Models/BusCacheGlobalPagedResult.cs b/src/JiShe.CollectBus.Common/Models/BusCacheGlobalPagedResult.cs index 303b2e4..465bd15 100644 --- a/src/JiShe.CollectBus.Common/Models/BusCacheGlobalPagedResult.cs +++ b/src/JiShe.CollectBus.Common/Models/BusCacheGlobalPagedResult.cs @@ -22,6 +22,22 @@ namespace JiShe.CollectBus.Common.Models /// public long TotalCount { get; set; } + /// + /// 每页条数 + /// + public int PageSize { get; set; } + + /// + /// 总页数 + /// + public int PageCount + { + get + { + return (int)Math.Ceiling((double)TotalCount / PageSize); + } + } + /// /// 是否有下一页 /// diff --git a/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs b/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs index 59011de..335c17c 100644 --- a/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs +++ b/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs @@ -22,8 +22,13 @@ namespace JiShe.CollectBus.Common.Models public int MeterId { get; set; } /// - /// 唯一标识,是redis ZSet和Set memberid + /// 关系映射标识,用于ZSet的Member字段和Set的Value字段,具体值可以根据不同业务场景进行定义 /// public virtual string MemberID => $"{FocusId}:{MeterId}"; + + /// + /// ZSet排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳 + /// + public virtual long ScoreValue=> ((long)FocusId << 32) | (uint)MeterId; } } diff --git a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs index cebc9c4..d3a9bff 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs @@ -36,639 +36,472 @@ namespace JiShe.CollectBus.FreeRedisProvider public IRedisClient GetInstance() { - var connectionString = $"{_option.Configuration},defaultdatabase={_option.DefaultDB}"; + var connectionString = $"{_option.Configuration},defaultdatabase={_option.DefaultDB},MaxPoolSize={_option.MaxPoolSize}"; Instance = new RedisClient(connectionString); Instance.Serialize = obj => BusJsonSerializer.Serialize(obj); Instance.Deserialize = (json, type) => BusJsonSerializer.Deserialize(json, type); - Instance.Notice += (s, e) => Trace.WriteLine(e.Log); + Instance.Notice += (s, e) => Trace.WriteLine(e.Log); return Instance; } - /// - /// 单个添加数据 - /// - /// - /// 主数据存储Hash缓存Key - /// 集中器索引Set缓存Key - /// 集中器排序索引ZSET缓存Key - /// 集中器采集频率分组全局索引ZSet缓存Key - /// 表计信息 - /// 可选时间戳 - /// - public async Task AddMeterCacheData( - string redisCacheKey, - string redisCacheFocusIndexKey, - string redisCacheScoresIndexKey, - string redisCacheGlobalIndexKey, - T data, - DateTimeOffset? timestamp = null) where T : DeviceCacheBasicModel - { - // 参数校验增强 - if (data == null || string.IsNullOrWhiteSpace(redisCacheKey) - || string.IsNullOrWhiteSpace(redisCacheFocusIndexKey) - || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey) - || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey)) - { - throw new ArgumentException($"{nameof(AddMeterCacheData)} 参数异常,-101"); - } + ///// + ///// 单个添加数据 + ///// + ///// + ///// 主数据存储Hash缓存Key + ///// 集中器索引Set缓存Key + ///// 集中器排序索引ZSET缓存Key + ///// 集中器采集频率分组全局索引ZSet缓存Key + ///// 表计信息 + ///// 可选时间戳 + ///// + //public async Task AddMeterCacheData( + //string redisCacheKey, + //string redisCacheFocusIndexKey, + //string redisCacheScoresIndexKey, + //string redisCacheGlobalIndexKey, + //T data, + //DateTimeOffset? timestamp = null) where T : DeviceCacheBasicModel + //{ + // // 参数校验增强 + // if (data == null || string.IsNullOrWhiteSpace(redisCacheKey) + // || string.IsNullOrWhiteSpace(redisCacheFocusIndexKey) + // || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey) + // || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey)) + // { + // throw new ArgumentException($"{nameof(AddMeterCacheData)} 参数异常,-101"); + // } - // 计算组合score(分类ID + 时间戳) - var actualTimestamp = timestamp ?? DateTimeOffset.UtcNow; + // // 计算组合score(分类ID + 时间戳) + // var actualTimestamp = timestamp ?? DateTimeOffset.UtcNow; - long scoreValue = ((long)data.FocusId << 32) | (uint)actualTimestamp.Ticks; + // long scoreValue = ((long)data.FocusId << 32) | (uint)actualTimestamp.Ticks; - //全局索引写入 - long globalScore = actualTimestamp.ToUnixTimeMilliseconds(); + // //全局索引写入 + // long globalScore = actualTimestamp.ToUnixTimeMilliseconds(); - // 使用事务保证原子性 - using (var trans = Instance.Multi()) - { - // 主数据存储Hash - trans.HSet(redisCacheKey, data.MemberID, data.Serialize()); + // // 使用事务保证原子性 + // using (var trans = Instance.Multi()) + // { + // // 主数据存储Hash + // trans.HSet(redisCacheKey, data.MemberID, data.Serialize()); - // 分类索引 - trans.SAdd(redisCacheFocusIndexKey, data.MemberID); + // // 分类索引 + // trans.SAdd(redisCacheFocusIndexKey, data.MemberID); - // 排序索引使用ZSET - trans.ZAdd(redisCacheScoresIndexKey, scoreValue, data.MemberID); + // // 排序索引使用ZSET + // trans.ZAdd(redisCacheScoresIndexKey, scoreValue, data.MemberID); - //全局索引 - trans.ZAdd(redisCacheGlobalIndexKey, globalScore, data.MemberID); + // //全局索引 + // trans.ZAdd(redisCacheGlobalIndexKey, globalScore, data.MemberID); - var results = trans.Exec(); + // var results = trans.Exec(); - if (results == null || results.Length <= 0) - throw new Exception($"{nameof(AddMeterCacheData)} 事务提交失败,-102"); - } + // if (results == null || results.Length <= 0) + // throw new Exception($"{nameof(AddMeterCacheData)} 事务提交失败,-102"); + // } - await Task.CompletedTask; - } + // await Task.CompletedTask; + //} - /// - /// 批量添加数据 - /// - /// - /// 主数据存储Hash缓存Key - /// 集中器索引Set缓存Key - /// 集中器排序索引ZSET缓存Key - /// 集中器采集频率分组全局索引ZSet缓存Key - /// 数据集合 - /// 可选时间戳 - /// - public async Task BatchAddMeterData( - string redisCacheKey, - string redisCacheFocusIndexKey, - string redisCacheScoresIndexKey, - string redisCacheGlobalIndexKey, - IEnumerable items, - DateTimeOffset? timestamp = null) where T : DeviceCacheBasicModel - { - if (items == null - || items.Count() <=0 - || string.IsNullOrWhiteSpace(redisCacheKey) - || string.IsNullOrWhiteSpace(redisCacheFocusIndexKey) - || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey) - || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey)) - { - throw new ArgumentException($"{nameof(BatchAddMeterData)} 参数异常,-101"); - } + ///// + ///// 批量添加数据 + ///// + ///// + ///// 主数据存储Hash缓存Key + ///// 集中器索引Set缓存Key + ///// 集中器排序索引ZSET缓存Key + ///// 集中器采集频率分组全局索引ZSet缓存Key + ///// 数据集合 + ///// 可选时间戳 + ///// + //public async Task BatchAddMeterData( + //string redisCacheKey, + //string redisCacheFocusIndexKey, + //string redisCacheScoresIndexKey, + //string redisCacheGlobalIndexKey, + //IEnumerable items, + //DateTimeOffset? timestamp = null) where T : DeviceCacheBasicModel + //{ + // if (items == null + // || items.Count() <=0 + // || string.IsNullOrWhiteSpace(redisCacheKey) + // || string.IsNullOrWhiteSpace(redisCacheFocusIndexKey) + // || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey) + // || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey)) + // { + // throw new ArgumentException($"{nameof(BatchAddMeterData)} 参数异常,-101"); + // } - const int BATCH_SIZE = 1000; // 每批1000条 - var semaphore = new SemaphoreSlim(Environment.ProcessorCount * 2); + // const int BATCH_SIZE = 1000; // 每批1000条 + // var semaphore = new SemaphoreSlim(Environment.ProcessorCount * 2); - foreach (var batch in items.Batch(BATCH_SIZE)) - { - await semaphore.WaitAsync(); + // foreach (var batch in items.Batch(BATCH_SIZE)) + // { + // await semaphore.WaitAsync(); - _ = Task.Run(() => - { - using (var pipe = Instance.StartPipe()) - { - foreach (var item in batch) - { - // 计算组合score(分类ID + 时间戳) - var actualTimestamp = timestamp ?? DateTimeOffset.UtcNow; + // _ = Task.Run(() => + // { + // using (var pipe = Instance.StartPipe()) + // { + // foreach (var item in batch) + // { + // // 计算组合score(分类ID + 时间戳) + // var actualTimestamp = timestamp ?? DateTimeOffset.UtcNow; - long scoreValue = ((long)item.FocusId << 32) | (uint)actualTimestamp.Ticks; + // long scoreValue = ((long)item.FocusId << 32) | (uint)actualTimestamp.Ticks; - //全局索引写入 - long globalScore = actualTimestamp.ToUnixTimeMilliseconds(); + // //全局索引写入 + // long globalScore = actualTimestamp.ToUnixTimeMilliseconds(); - // 主数据存储Hash - pipe.HSet(redisCacheKey, item.MemberID, item.Serialize()); + // // 主数据存储Hash + // pipe.HSet(redisCacheKey, item.MemberID, item.Serialize()); - // 分类索引 - pipe.SAdd(redisCacheFocusIndexKey, item.MemberID); + // // 分类索引Set + // pipe.SAdd(redisCacheFocusIndexKey, item.MemberID); - // 排序索引使用ZSET - pipe.ZAdd(redisCacheScoresIndexKey, scoreValue, item.MemberID); + // // 排序索引使用ZSET + // pipe.ZAdd(redisCacheScoresIndexKey, scoreValue, item.MemberID); - //全局索引 - pipe.ZAdd(redisCacheGlobalIndexKey, globalScore, item.MemberID); - } - pipe.EndPipe(); - } - semaphore.Release(); - }); - } + // //全局索引 + // pipe.ZAdd(redisCacheGlobalIndexKey, globalScore, item.MemberID); + // } + // pipe.EndPipe(); + // } + // semaphore.Release(); + // }); + // } - await Task.CompletedTask; - } + // await Task.CompletedTask; + //} - /// - /// 删除指定redis缓存key的缓存数据 - /// - /// - /// 主数据存储Hash缓存Key - /// 集中器索引Set缓存Key - /// 集中器排序索引ZSET缓存Key - /// 集中器采集频率分组全局索引ZSet缓存Key - /// 表计信息 - /// - public async Task RemoveMeterData( - string redisCacheKey, - string redisCacheFocusIndexKey, - string redisCacheScoresIndexKey, - string redisCacheGlobalIndexKey, - T data) where T : DeviceCacheBasicModel - { + ///// + ///// 删除指定redis缓存key的缓存数据 + ///// + ///// + ///// 主数据存储Hash缓存Key + ///// 集中器索引Set缓存Key + ///// 集中器排序索引ZSET缓存Key + ///// 集中器采集频率分组全局索引ZSet缓存Key + ///// 表计信息 + ///// + //public async Task RemoveMeterData( + //string redisCacheKey, + //string redisCacheFocusIndexKey, + //string redisCacheScoresIndexKey, + //string redisCacheGlobalIndexKey, + //T data) where T : DeviceCacheBasicModel + //{ - if (data == null - || string.IsNullOrWhiteSpace(redisCacheKey) - || string.IsNullOrWhiteSpace(redisCacheFocusIndexKey) - || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey) - || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey)) - { - throw new ArgumentException($"{nameof(RemoveMeterData)} 参数异常,-101"); - } + // if (data == null + // || string.IsNullOrWhiteSpace(redisCacheKey) + // || string.IsNullOrWhiteSpace(redisCacheFocusIndexKey) + // || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey) + // || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey)) + // { + // throw new ArgumentException($"{nameof(RemoveMeterData)} 参数异常,-101"); + // } - const string luaScript = @" - local mainKey = KEYS[1] - local focusIndexKey = KEYS[2] - local scoresIndexKey = KEYS[3] - local globalIndexKey = KEYS[4] - local member = ARGV[1] + // const string luaScript = @" + // local mainKey = KEYS[1] + // local focusIndexKey = KEYS[2] + // local scoresIndexKey = KEYS[3] + // local globalIndexKey = KEYS[4] + // local member = ARGV[1] - local deleted = 0 - if redis.call('HDEL', mainKey, member) > 0 then - deleted = 1 - end + // local deleted = 0 + // if redis.call('HDEL', mainKey, member) > 0 then + // deleted = 1 + // end - redis.call('SREM', focusIndexKey, member) - redis.call('ZREM', scoresIndexKey, member) - redis.call('ZREM', globalIndexKey, member) - return deleted - "; + // redis.call('SREM', focusIndexKey, member) + // redis.call('ZREM', scoresIndexKey, member) + // redis.call('ZREM', globalIndexKey, member) + // return deleted + // "; - var keys = new[] - { - redisCacheKey, - redisCacheFocusIndexKey, - redisCacheScoresIndexKey, - redisCacheGlobalIndexKey - }; + // var keys = new[] + // { + // redisCacheKey, + // redisCacheFocusIndexKey, + // redisCacheScoresIndexKey, + // redisCacheGlobalIndexKey + // }; - var result = await Instance.EvalAsync(luaScript, keys, new[] { data.MemberID }); + // var result = await Instance.EvalAsync(luaScript, keys, new[] { data.MemberID }); - if ((int)result == 0) - throw new KeyNotFoundException("指定数据不存在"); - } + // if ((int)result == 0) + // throw new KeyNotFoundException("指定数据不存在"); + //} - /// - /// 修改表计缓存信息 - /// - /// - /// 主数据存储Hash缓存Key - /// 旧集中器索引Set缓存Key - /// 新集中器索引Set缓存Key - /// 集中器排序索引ZSET缓存Key - /// 集中器采集频率分组全局索引ZSet缓存Key - /// 表计信息 - /// 可选时间戳 - /// - public async Task UpdateMeterData( - string redisCacheKey, - string oldRedisCacheFocusIndexKey, - string newRedisCacheFocusIndexKey, - string redisCacheScoresIndexKey, - string redisCacheGlobalIndexKey, - T newData, - DateTimeOffset? newTimestamp = null) where T : DeviceCacheBasicModel - { - if (newData == null - || string.IsNullOrWhiteSpace(redisCacheKey) - || string.IsNullOrWhiteSpace(oldRedisCacheFocusIndexKey) - || string.IsNullOrWhiteSpace(newRedisCacheFocusIndexKey) - || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey) - || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey)) - { - throw new ArgumentException($"{nameof(UpdateMeterData)} 参数异常,-101"); - } + ///// + ///// 修改表计缓存信息 + ///// + ///// + ///// 主数据存储Hash缓存Key + ///// 旧集中器索引Set缓存Key + ///// 新集中器索引Set缓存Key + ///// 集中器排序索引ZSET缓存Key + ///// 集中器采集频率分组全局索引ZSet缓存Key + ///// 表计信息 + ///// 可选时间戳 + ///// + //public async Task UpdateMeterData( + //string redisCacheKey, + //string oldRedisCacheFocusIndexKey, + //string newRedisCacheFocusIndexKey, + //string redisCacheScoresIndexKey, + //string redisCacheGlobalIndexKey, + //T newData, + //DateTimeOffset? newTimestamp = null) where T : DeviceCacheBasicModel + //{ + // if (newData == null + // || string.IsNullOrWhiteSpace(redisCacheKey) + // || string.IsNullOrWhiteSpace(oldRedisCacheFocusIndexKey) + // || string.IsNullOrWhiteSpace(newRedisCacheFocusIndexKey) + // || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey) + // || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey)) + // { + // throw new ArgumentException($"{nameof(UpdateMeterData)} 参数异常,-101"); + // } - var luaScript = @" - local mainKey = KEYS[1] - local oldFocusIndexKey = KEYS[2] - local newFocusIndexKey = KEYS[3] - local scoresIndexKey = KEYS[4] - local globalIndexKey = KEYS[5] - local member = ARGV[1] - local newData = ARGV[2] - local newScore = ARGV[3] - local newGlobalScore = ARGV[4] + // var luaScript = @" + // local mainKey = KEYS[1] + // local oldFocusIndexKey = KEYS[2] + // local newFocusIndexKey = KEYS[3] + // local scoresIndexKey = KEYS[4] + // local globalIndexKey = KEYS[5] + // local member = ARGV[1] + // local newData = ARGV[2] + // local newScore = ARGV[3] + // local newGlobalScore = ARGV[4] - -- 校验存在性 - if redis.call('HEXISTS', mainKey, member) == 0 then - return 0 - end + // -- 校验存在性 + // if redis.call('HEXISTS', mainKey, member) == 0 then + // return 0 + // end - -- 更新主数据 - redis.call('HSET', mainKey, member, newData) + // -- 更新主数据 + // redis.call('HSET', mainKey, member, newData) - -- 处理变更 - if newScore ~= '' then - -- 删除旧索引 - redis.call('SREM', oldFocusIndexKey, member) - redis.call('ZREM', scoresIndexKey, member) + // -- 处理变更 + // if newScore ~= '' then + // -- 删除旧索引 + // redis.call('SREM', oldFocusIndexKey, member) + // redis.call('ZREM', scoresIndexKey, member) - -- 添加新索引 - redis.call('SADD', newFocusIndexKey, member) - redis.call('ZADD', scoresIndexKey, newScore, member) - end + // -- 添加新索引 + // redis.call('SADD', newFocusIndexKey, member) + // redis.call('ZADD', scoresIndexKey, newScore, member) + // end - -- 更新全局索引 - if newGlobalScore ~= '' then - -- 删除旧索引 - redis.call('ZREM', globalIndexKey, member) + // -- 更新全局索引 + // if newGlobalScore ~= '' then + // -- 删除旧索引 + // redis.call('ZREM', globalIndexKey, member) - -- 添加新索引 - redis.call('ZADD', globalIndexKey, newGlobalScore, member) - end + // -- 添加新索引 + // redis.call('ZADD', globalIndexKey, newGlobalScore, member) + // end - return 1 - "; + // return 1 + // "; - var actualTimestamp = newTimestamp ?? DateTimeOffset.UtcNow; - var newGlobalScore = actualTimestamp.ToUnixTimeMilliseconds(); - var newScoreValue = ((long)newData.FocusId << 32) | (uint)actualTimestamp.Ticks; + // var actualTimestamp = newTimestamp ?? DateTimeOffset.UtcNow; + // var newGlobalScore = actualTimestamp.ToUnixTimeMilliseconds(); + // var newScoreValue = ((long)newData.FocusId << 32) | (uint)actualTimestamp.Ticks; - var result = await Instance.EvalAsync(luaScript, - new[] - { - redisCacheKey, - oldRedisCacheFocusIndexKey, - newRedisCacheFocusIndexKey, - redisCacheScoresIndexKey, - redisCacheGlobalIndexKey - }, - new object[] - { - newData.MemberID, - newData.Serialize(), - newScoreValue.ToString() ?? "", - newGlobalScore.ToString() ?? "" - }); + // var result = await Instance.EvalAsync(luaScript, + // new[] + // { + // redisCacheKey, + // oldRedisCacheFocusIndexKey, + // newRedisCacheFocusIndexKey, + // redisCacheScoresIndexKey, + // redisCacheGlobalIndexKey + // }, + // new object[] + // { + // newData.MemberID, + // newData.Serialize(), + // newScoreValue.ToString() ?? "", + // newGlobalScore.ToString() ?? "" + // }); - if ((int)result == 0) - { - throw new KeyNotFoundException($"{nameof(UpdateMeterData)}指定Key{redisCacheKey}的数据不存在"); - } - } + // if ((int)result == 0) + // { + // throw new KeyNotFoundException($"{nameof(UpdateMeterData)}指定Key{redisCacheKey}的数据不存在"); + // } + //} - public async Task> SingleGetMeterPagedData( - string redisCacheKey, - string redisCacheScoresIndexKey, - int focusId, - int pageSize = 10, - int pageIndex = 1, - bool descending = true) - { - // 计算score范围 - long minScore = (long)focusId << 32; - long maxScore = ((long)focusId + 1) << 32; + //public async Task> SingleGetMeterPagedData( + //string redisCacheKey, + //string redisCacheScoresIndexKey, + //int focusId, + //int pageSize = 10, + //int pageIndex = 1, + //bool descending = true) + //{ + // // 计算score范围 + // long minScore = (long)focusId << 32; + // long maxScore = ((long)focusId + 1) << 32; - // 分页参数计算 - int start = (pageIndex - 1) * pageSize; + // // 分页参数计算 + // int start = (pageIndex - 1) * pageSize; - // 获取排序后的member列表 - var members = descending - ? await Instance.ZRevRangeByScoreAsync( - redisCacheScoresIndexKey, - maxScore, - minScore, - start, - pageSize) - : await Instance.ZRangeByScoreAsync( - redisCacheScoresIndexKey, - minScore, - maxScore, - start, - pageSize); + // // 获取排序后的member列表 + // var members = descending + // ? await Instance.ZRevRangeByScoreAsync( + // redisCacheScoresIndexKey, + // maxScore, + // minScore, + // start, + // pageSize) + // : await Instance.ZRangeByScoreAsync( + // redisCacheScoresIndexKey, + // minScore, + // maxScore, + // start, + // pageSize); - // 批量获取实际数据 - var dataTasks = members.Select(m => - Instance.HGetAsync(redisCacheKey, m)).ToArray(); - await Task.WhenAll(dataTasks); + // // 批量获取实际数据 + // var dataTasks = members.Select(m => + // Instance.HGetAsync(redisCacheKey, m)).ToArray(); + // await Task.WhenAll(dataTasks); - // 总数统计优化 - var total = await Instance.ZCountAsync( - redisCacheScoresIndexKey, - minScore, - maxScore); + // // 总数统计优化 + // var total = await Instance.ZCountAsync( + // redisCacheScoresIndexKey, + // minScore, + // maxScore); - return new BusPagedResult - { - Items = dataTasks.Select(t => t.Result).ToList(), - TotalCount = total, - PageIndex = pageIndex, - PageSize = pageSize - }; - } + // return new BusPagedResult + // { + // Items = dataTasks.Select(t => t.Result).ToList(), + // TotalCount = total, + // PageIndex = pageIndex, + // PageSize = pageSize + // }; + //} - public async Task> GetFocusPagedData( - string redisCacheKey, - string redisCacheScoresIndexKey, - int focusId, - int pageSize = 10, - long? lastScore = null, - string lastMember = null, - bool descending = true) where T : DeviceCacheBasicModel - { - // 计算分数范围 - long minScore = (long)focusId << 32; - long maxScore = ((long)focusId + 1) << 32; + //public async Task> GetFocusPagedData( + //string redisCacheKey, + //string redisCacheScoresIndexKey, + //int focusId, + //int pageSize = 10, + //long? lastScore = null, + //string lastMember = null, + //bool descending = true) where T : DeviceCacheBasicModel + //{ + // // 计算分数范围 + // long minScore = (long)focusId << 32; + // long maxScore = ((long)focusId + 1) << 32; - // 获取成员列表 - var members = await GetSortedMembers( - redisCacheScoresIndexKey, - minScore, - maxScore, - pageSize, - lastScore, - lastMember, - descending); + // // 获取成员列表 + // var members = await GetSortedMembers( + // redisCacheScoresIndexKey, + // minScore, + // maxScore, + // pageSize, + // lastScore, + // lastMember, + // descending); - // 批量获取数据 - var dataDict = await Instance.HMGetAsync(redisCacheKey, members.CurrentItems); + // // 批量获取数据 + // var dataDict = await Instance.HMGetAsync(redisCacheKey, members.CurrentItems); - return new BusPagedResult - { - Items = dataDict, - TotalCount = await GetTotalCount(redisCacheScoresIndexKey, minScore, maxScore), - HasNext = members.HasNext, - NextScore = members.NextScore, - NextMember = members.NextMember - }; - } + // return new BusPagedResult + // { + // Items = dataDict, + // TotalCount = await GetTotalCount(redisCacheScoresIndexKey, minScore, maxScore), + // HasNext = members.HasNext, + // NextScore = members.NextScore, + // NextMember = members.NextMember + // }; + //} - private async Task<(string[] CurrentItems, bool HasNext, decimal? NextScore, string NextMember)> - GetSortedMembers( - string zsetKey, - long minScore, - long maxScore, - int pageSize, - long? lastScore, - string lastMember, - bool descending) - { - var querySize = pageSize + 1; - var (startScore, exclude) = descending - ? (lastScore ?? maxScore, lastMember) - : (lastScore ?? minScore, lastMember); + //private async Task<(string[] CurrentItems, bool HasNext, decimal? NextScore, string NextMember)> + // GetSortedMembers( + // string zsetKey, + // long minScore, + // long maxScore, + // int pageSize, + // long? lastScore, + // string lastMember, + // bool descending) + //{ + // var querySize = pageSize + 1; + // var (startScore, exclude) = descending + // ? (lastScore ?? maxScore, lastMember) + // : (lastScore ?? minScore, lastMember); - var members = descending - ? await Instance.ZRevRangeByScoreAsync( - zsetKey, - max: startScore, - min: minScore, - offset: 0, - count: querySize) - : await Instance.ZRangeByScoreAsync( - zsetKey, - min: startScore, - max: maxScore, - offset: 0, - count: querySize); + // var members = descending + // ? await Instance.ZRevRangeByScoreAsync( + // zsetKey, + // max: startScore, + // min: minScore, + // offset: 0, + // count: querySize) + // : await Instance.ZRangeByScoreAsync( + // zsetKey, + // min: startScore, + // max: maxScore, + // offset: 0, + // count: querySize); - var hasNext = members.Length > pageSize; - var currentItems = members.Take(pageSize).ToArray(); + // var hasNext = members.Length > pageSize; + // var currentItems = members.Take(pageSize).ToArray(); - var nextCursor = currentItems.Any() - ? await GetNextCursor(zsetKey, currentItems.Last(), descending) - : (null, null); + // var nextCursor = currentItems.Any() + // ? await GetNextCursor(zsetKey, currentItems.Last(), descending) + // : (null, null); - return (currentItems, hasNext, nextCursor.score, nextCursor.member); - } + // return (currentItems, hasNext, nextCursor.score, nextCursor.member); + //} - private async Task GetTotalCount(string zsetKey, long min, long max) - { - // 缓存计数优化 - var cacheKey = $"{zsetKey}_count_{min}_{max}"; - var cached = await Instance.GetAsync(cacheKey); + //private async Task GetTotalCount(string zsetKey, long min, long max) + //{ + // // 缓存计数优化 + // var cacheKey = $"{zsetKey}_count_{min}_{max}"; + // var cached = await Instance.GetAsync(cacheKey); - if (cached.HasValue) - return cached.Value; + // if (cached.HasValue) + // return cached.Value; - var count = await Instance.ZCountAsync(zsetKey, min, max); - await Instance.SetExAsync(cacheKey, 60, count); // 缓存60秒 - return count; - } + // var count = await Instance.ZCountAsync(zsetKey, min, max); + // await Instance.SetExAsync(cacheKey, 60, count); // 缓存60秒 + // return count; + //} - public async Task>> BatchGetMeterPagedData( - string redisCacheKey, - string redisCacheScoresIndexKey, - IEnumerable focusIds, - int pageSizePerFocus = 10) where T : DeviceCacheBasicModel - { - var results = new ConcurrentDictionary>(); - var parallelOptions = new ParallelOptions - { - MaxDegreeOfParallelism = Environment.ProcessorCount * 2 - }; + //public async Task>> BatchGetMeterPagedData( + //string redisCacheKey, + //string redisCacheScoresIndexKey, + //IEnumerable focusIds, + //int pageSizePerFocus = 10) where T : DeviceCacheBasicModel + //{ + // var results = new ConcurrentDictionary>(); + // var parallelOptions = new ParallelOptions + // { + // MaxDegreeOfParallelism = Environment.ProcessorCount * 2 + // }; - await Parallel.ForEachAsync(focusIds, parallelOptions, async (focusId, _) => - { - var data = await SingleGetMeterPagedData( - redisCacheKey, - redisCacheScoresIndexKey, - focusId, - pageSizePerFocus); + // await Parallel.ForEachAsync(focusIds, parallelOptions, async (focusId, _) => + // { + // var data = await SingleGetMeterPagedData( + // redisCacheKey, + // redisCacheScoresIndexKey, + // focusId, + // pageSizePerFocus); - results.TryAdd(focusId, data); - }); + // results.TryAdd(focusId, data); + // }); + + // return new Dictionary>(results); + //} + - return new Dictionary>(results); - } - /// - /// 通过全局索引分页查询表计缓存数据 - /// - /// - /// 主数据存储Hash缓存Key - /// 集中器采集频率分组全局索引ZSet缓存Key - /// 分页尺寸 - /// 最后一个索引 - /// 最后一个唯一标识 - /// 排序方式 - /// - public async Task> GetGlobalPagedData( - string redisCacheKey, - string redisCacheGlobalIndexKey, - int pageSize = 10, - decimal? lastScore = null, - string lastMember = null, - bool descending = true) - where T : DeviceCacheBasicModel - { - // 参数校验增强 - if (string.IsNullOrWhiteSpace(redisCacheKey) || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey)) - { - throw new ArgumentException($"{nameof(GetGlobalPagedData)} 参数异常,-101"); - } - - if (pageSize < 1 || pageSize > 1000) - { - throw new ArgumentException($"{nameof(GetGlobalPagedData)} 分页大小应在1-1000之间,-102"); - } - - // 分页参数解析 - var (startScore, excludeMember) = descending - ? (lastScore ?? decimal.MaxValue, lastMember) - : (lastScore ?? 0, lastMember); - - // 游标分页查询 - var (members, hasNext) = await GetPagedMembers( - redisCacheGlobalIndexKey, - pageSize, - startScore, - excludeMember, - descending); - - // 批量获取数据(优化内存分配) - var dataDict = await BatchGetData(redisCacheKey, members); - - // 获取下一页游标 - var nextCursor = members.Any() - ? await GetNextCursor(redisCacheGlobalIndexKey, members.Last(), descending) - : (null, null); - - return new BusCacheGlobalPagedResult - { - Items = members.Select(m => dataDict.TryGetValue(m, out var v) ? v : default) - .Where(x => x != null).ToList(), - HasNext = hasNext, - NextScore = nextCursor.score, - NextMember = nextCursor.member - }; - } - - /// - /// 游标分页查询 - /// - /// - /// 分页数量 - /// 开始索引 - /// 开始唯一标识 - /// 排序方式 - /// - private async Task<(List Members, bool HasNext)> GetPagedMembers( - string redisCacheGlobalIndexKey, - int pageSize, - decimal? startScore, - string excludeMember, - bool descending) - { - const int bufferSize = 50; // 预读缓冲区大小 - - // 使用流式分页(避免OFFSET性能问题) - var members = new List(pageSize + 1); - decimal? currentScore = startScore; - string lastMember = excludeMember; - - while (members.Count < pageSize + 1 && currentScore.HasValue) - { - var querySize = Math.Min(bufferSize, pageSize + 1 - members.Count); - - var batch = descending - ? await Instance.ZRevRangeByScoreAsync( - redisCacheGlobalIndexKey, - max: currentScore.Value, - min: 0, - offset: 0, - count: querySize - ) - : await Instance.ZRangeByScoreAsync( - redisCacheGlobalIndexKey, - min: currentScore.Value, - max: long.MaxValue, - offset: 0, - count: querySize); - - if (!batch.Any()) break; - - members.AddRange(batch); - lastMember = batch.LastOrDefault(); - currentScore = await Instance.ZScoreAsync(redisCacheGlobalIndexKey, lastMember); - } - - return ( - members.Take(pageSize).ToList(), - members.Count > pageSize - ); - } - - /// - /// 批量获取指定分页的数据 - /// - /// - /// - /// - /// - private async Task> BatchGetData( - string hashKey, - IEnumerable members) - where T : DeviceCacheBasicModel - { - const int batchSize = 100; - var result = new Dictionary(); - - foreach (var batch in members.Batch(batchSize)) - { - var batchArray = batch.ToArray(); - var values = await Instance.HMGetAsync(hashKey, batchArray); - - for (int i = 0; i < batchArray.Length; i++) - { - if (EqualityComparer.Default.Equals(values[i], default)) continue; - result[batchArray[i]] = values[i]; - } - } - - return result; - } - - /// - /// 获取下一页游标 - /// - /// 全局索引Key - /// 最后一个唯一标识 - /// 排序方式 - /// - private async Task<(decimal? score, string member)> GetNextCursor( - string redisCacheGlobalIndexKey, - string lastMember, - bool descending) - { - if (string.IsNullOrWhiteSpace(lastMember)) - { - return (null, null); - } - - var score = await Instance.ZScoreAsync(redisCacheGlobalIndexKey, lastMember); - return score.HasValue - ? (Convert.ToInt64(score.Value), lastMember) - : (null, null); - } } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs b/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs index dc0aaa3..8fc6861 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs @@ -9,105 +9,7 @@ namespace JiShe.CollectBus.FreeRedisProvider /// 获取客户端 /// /// - RedisClient Instance { get; set; } - - /// - /// 单个添加数据 - /// - /// - /// 主数据存储Hash缓存Key - /// 集中器索引Set缓存Key - /// 集中器排序索引ZSET缓存Key - /// 集中器采集频率分组全局索引ZSet缓存Key - /// 表计信息 - /// 可选时间戳 - /// - Task AddMeterCacheData( - string redisCacheKey, - string redisCacheFocusIndexKey, - string redisCacheScoresIndexKey, - string redisCacheGlobalIndexKey, - T data, - DateTimeOffset? timestamp = null) where T : DeviceCacheBasicModel; - - /// - /// 批量添加数据 - /// - /// - /// 主数据存储Hash缓存Key - /// 集中器索引Set缓存Key - /// 集中器排序索引ZSET缓存Key - /// 集中器采集频率分组全局索引ZSet缓存Key - /// 数据集合 - /// 可选时间戳 - /// - Task BatchAddMeterData( - string redisCacheKey, - string redisCacheFocusIndexKey, - string redisCacheScoresIndexKey, - string redisCacheGlobalIndexKey, - IEnumerable items, - DateTimeOffset? timestamp = null) where T : DeviceCacheBasicModel; - - /// - /// 删除指定redis缓存key的缓存数据 - /// - /// - /// 主数据存储Hash缓存Key - /// 集中器索引Set缓存Key - /// 集中器排序索引ZSET缓存Key - /// 集中器采集频率分组全局索引ZSet缓存Key - /// 表计信息 - /// - Task RemoveMeterData( - string redisCacheKey, - string redisCacheFocusIndexKey, - string redisCacheScoresIndexKey, - string redisCacheGlobalIndexKey, - T data) where T : DeviceCacheBasicModel; - - /// - /// 修改表计缓存信息 - /// - /// - /// 主数据存储Hash缓存Key - /// 旧集中器索引Set缓存Key - /// 新集中器索引Set缓存Key - /// 集中器排序索引ZSET缓存Key - /// 集中器采集频率分组全局索引ZSet缓存Key - /// 表计信息 - /// 可选时间戳 - /// - Task UpdateMeterData( - string redisCacheKey, - string oldRedisCacheFocusIndexKey, - string newRedisCacheFocusIndexKey, - string redisCacheScoresIndexKey, - string redisCacheGlobalIndexKey, - T newData, - DateTimeOffset? newTimestamp = null) where T : DeviceCacheBasicModel; - - - - /// - /// 通过全局索引分页查询表计缓存数据 - /// - /// - /// 主数据存储Hash缓存Key - /// 集中器采集频率分组全局索引ZSet缓存Key - /// 分页尺寸 - /// 最后一个索引 - /// 最后一个唯一标识 - /// 排序方式 - /// - Task> GetGlobalPagedData( - string redisCacheKey, - string redisCacheGlobalIndexKey, - int pageSize = 10, - decimal? lastScore = null, - string lastMember = null, - bool descending = true) - where T : DeviceCacheBasicModel; + RedisClient Instance { get; set; } } } diff --git a/src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs b/src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs index 75d92a2..2ed7e49 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs +++ b/src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs @@ -13,6 +13,11 @@ namespace JiShe.CollectBus.FreeRedisProvider.Options /// public string? Configuration { get; set; } + /// + /// 最大连接数 + /// + public string? MaxPoolSize { get; set; } + /// /// 默认数据库 /// diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index c922f8d..bb09204 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -41,6 +41,7 @@ }, "Redis": { "Configuration": "192.168.1.9:6380,password=1q2w3e!@#,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + "MaxPoolSize": "50", "DefaultDB": "14", "HangfireDB": "15" }, @@ -128,7 +129,7 @@ "OpenDebugMode": true, "UseTableSessionPoolByDefault": false }, - "ServerTagName": "JiSheCollectBus2", + "ServerTagName": "JiSheCollectBus3", "KafkaReplicationFactor": 3, "NumPartitions": 30 } \ No newline at end of file -- 2.47.2 From eed68d0fe007b7a2d7558e76fb7df227f77742b2 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Wed, 16 Apr 2025 18:26:25 +0800 Subject: [PATCH 108/139] =?UTF-8?q?kafka=E5=A2=9E=E5=8A=A0=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=B6=88=E8=B4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EnergySystem/EnergySystemAppService.cs | 34 +- .../Plugins/TcpMonitor.cs | 6 +- .../BasicScheduledMeterReadingService.cs | 10 +- .../Attributes/KafkaSubscribeAttribute.cs | 37 ++- .../Consumer/ConsumerService.cs | 313 +++++++++++++++++- .../Consumer/IConsumerService.cs | 8 +- .../HeadersFilter.cs | 30 ++ .../JsonSerializer.cs | 88 +++++ .../KafkaSubcribesExtensions.cs | 20 +- .../Producer/ProducerService.cs | 49 ++- .../Abstracts/BaseProtocolPlugin.cs | 4 +- 11 files changed, 535 insertions(+), 64 deletions(-) create mode 100644 src/JiShe.CollectBus.KafkaProducer/HeadersFilter.cs create mode 100644 src/JiShe.CollectBus.KafkaProducer/JsonSerializer.cs diff --git a/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs b/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs index 2a60803..1ca731b 100644 --- a/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs +++ b/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs @@ -90,7 +90,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); result.Status = true; result.Msg = "操作成功"; result.Data.ValidData = true; @@ -136,7 +136,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); } @@ -186,7 +186,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); result.Status = true; result.Msg = "操作成功"; result.Data.ValidData = true; @@ -224,7 +224,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); } result.Status = true; @@ -316,7 +316,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); if (isManual) { @@ -384,7 +384,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); result.Status = true; result.Msg = "操作成功"; @@ -420,7 +420,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); result.Status = true; result.Msg = "操作成功"; @@ -456,7 +456,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); result.Status = true; result.Msg = "操作成功"; @@ -491,7 +491,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); result.Status = true; result.Msg = "操作成功"; @@ -527,7 +527,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); result.Status = true; result.Msg = "操作成功"; return result; @@ -585,7 +585,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); } result.Status = true; @@ -662,7 +662,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); } result.Status = true; result.Msg = "操作成功"; @@ -699,7 +699,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); } result.Status = true; result.Msg = "操作成功"; @@ -735,7 +735,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); } result.Status = true; result.Msg = "操作成功"; @@ -769,7 +769,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); result.Status = true; result.Msg = "操作成功"; return result; @@ -804,7 +804,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); } result.Status = true; result.Msg = "操作成功"; @@ -867,7 +867,7 @@ namespace JiShe.CollectBus.EnergySystem Message = bytes, Type = IssuedEventType.Data, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); } result.Status = true; diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index 50b41b0..6cbc8c4 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -178,7 +178,7 @@ namespace JiShe.CollectBus.Plugins //await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent); - await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent.Serialize()); + await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent); //await _producerBus.Publish( messageReceivedLoginEvent); } @@ -227,7 +227,7 @@ namespace JiShe.CollectBus.Plugins }; //await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent); - await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent.Serialize()); + await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent); //await _producerBus.Publish(messageReceivedHeartbeatEvent); } @@ -271,7 +271,7 @@ namespace JiShe.CollectBus.Plugins MessageHexString = messageHexString, DeviceNo = deviceNo, MessageId = NewId.NextGuid().ToString() - }.Serialize()); + }); } } } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 8fa4f5e..d830707 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -382,7 +382,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading }; //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); - _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg.Serialize()); + _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); //_= _producerBus.Publish(tempMsg); @@ -448,7 +448,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading }; //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg); - _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg.Serialize()); + _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg); //_ = _producerBus.Publish(tempMsg); @@ -514,7 +514,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading }; //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); - _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg.Serialize()); + _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); //_ = _producerBus.Publish(tempMsg); @@ -809,7 +809,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } int partition = DeviceGroupBalanceControl.GetDeviceGroupId(taskRecord.FocusAddress); - await _producerService.ProduceAsync(topicName, partition, taskRecord.Serialize()); + await _producerService.ProduceAsync(topicName, partition, taskRecord); } private async Task AmmerterCreatePublishTask(int timeDensity, MeterTypeEnum meterType) @@ -851,7 +851,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading }; //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); - _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg.Serialize()); + _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); //_ = _producerBus.Publish(tempMsg); diff --git a/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs b/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs index 32f652e..df75b89 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs @@ -26,20 +26,51 @@ namespace JiShe.CollectBus.Kafka.Attributes /// /// 任务数(默认是多少个分区多少个任务) + /// 如设置订阅指定Partition则任务数始终为1 /// public int TaskCount { get; set; } = -1; - public KafkaSubscribeAttribute(string topic, string groupId = "default") + /// + /// 批量处理数量 + /// + public int BatchSize { get; set; } = 100; + + /// + /// 是否启用批量处理 + /// + public bool EnableBatch { get; set; } = false; + + /// + /// 批次超时时间 + /// + public TimeSpan? BatchTimeout { get; set; }=null; + + /// + /// 订阅主题 + /// + /// batchTimeout格式:("00:05:00") + public KafkaSubscribeAttribute(string topic, string groupId = "default", bool enableBatch = false, int batchSize = 100, string? batchTimeout = null) { this.Topic = topic; this.GroupId = groupId; + this.EnableBatch = enableBatch; + this.BatchSize = batchSize; + this.BatchTimeout = batchTimeout != null? TimeSpan.Parse(batchTimeout): null; } - public KafkaSubscribeAttribute(string topic, int partition, string groupId = "default") + /// + /// 订阅主题 + /// + /// batchTimeout格式:("00:05:00") + public KafkaSubscribeAttribute(string topic, int partition, string groupId = "default", bool enableBatch = false, int batchSize = 100, string? batchTimeout = null) { - this.Topic = topic ; + this.Topic = topic; this.Partition = partition; this.GroupId = groupId; + this.TaskCount = 1; + this.EnableBatch = enableBatch; + this.BatchSize = batchSize; + this.BatchTimeout = batchTimeout != null ? TimeSpan.Parse(batchTimeout) : null; } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index 9a2142e..5547919 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -10,6 +10,8 @@ using System.Collections.Concurrent; using System.Text.RegularExpressions; using NUglify.Html; using Serilog; +using System; +using System.Text; namespace JiShe.CollectBus.Kafka.Consumer { @@ -19,6 +21,7 @@ namespace JiShe.CollectBus.Kafka.Consumer private readonly IConfiguration _configuration; private readonly ConcurrentDictionary _consumerStore = new(); + private class KafkaConsumer where TKey : notnull where TValue : class { } public ConsumerService(IConfiguration configuration, ILogger logger) { @@ -38,6 +41,7 @@ namespace JiShe.CollectBus.Kafka.Consumer { var config = BuildConsumerConfig(groupId); return new ConsumerBuilder(config) + .SetValueDeserializer(new JsonSerializer()) .SetLogHandler((_, log) => _logger.LogInformation($"消费者Log: {log.Message}")) .SetErrorHandler((_, e) => _logger.LogError($"消费者错误: {e.Reason}")) .Build(); @@ -54,7 +58,8 @@ namespace JiShe.CollectBus.Kafka.Consumer AutoOffsetReset = AutoOffsetReset.Earliest, EnableAutoCommit = false, // 禁止AutoCommit EnablePartitionEof = true, // 启用分区末尾标记 - AllowAutoCreateTopics= true // 启用自动创建 + AllowAutoCreateTopics= true, // 启用自动创建 + FetchMaxBytes = 1024 * 1024 * 50 // 增加拉取大小(50MB) }; if (enableAuth) @@ -105,7 +110,7 @@ namespace JiShe.CollectBus.Kafka.Consumer /// public async Task SubscribeAsync(string[] topics, Func> messageHandler, string? groupId = null) where TKey : notnull where TValue : class { - var consumerKey = typeof((TKey, TValue)); + var consumerKey = typeof(KafkaConsumer); var cts = new CancellationTokenSource(); var consumer = _consumerStore.GetOrAdd(consumerKey, _ => @@ -123,15 +128,28 @@ namespace JiShe.CollectBus.Kafka.Consumer try { var result = consumer.Consume(cts.Token); - if (result == null) continue; - if (result.Message.Value == null) continue; + if (result == null || result.Message==null || result.Message.Value == null) + { + _logger.LogWarning($"Kafka消费: {result?.Topic} 分区 {result?.Partition} 值为NULL"); + consumer.Commit(result); // 手动提交 + continue; + } if (result.IsPartitionEOF) { _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); - await Task.Delay(TimeSpan.FromSeconds(1)); + await Task.Delay(TimeSpan.FromSeconds(1),cts.Token); + continue; + } + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; + // 检查 Header 是否符合条件 + if (!headersFilter.Match(result.Message.Headers)) + { + _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} Header未匹配", result.Topic, result.Partition); + + //consumer.Commit(result); // 提交偏移量 + // 跳过消息 continue; } - bool sucess= await messageHandler(result.Message.Key, result.Message.Value); if (sucess) { @@ -159,7 +177,7 @@ namespace JiShe.CollectBus.Kafka.Consumer /// public async Task SubscribeAsync(string[] topics, Func> messageHandler, string? groupId) where TValue : class { - var consumerKey = typeof((Ignore, TValue)); + var consumerKey = typeof(KafkaConsumer); var cts = new CancellationTokenSource(); var consumer = _consumerStore.GetOrAdd(consumerKey, _=> @@ -177,15 +195,27 @@ namespace JiShe.CollectBus.Kafka.Consumer try { var result = consumer.Consume(cts.Token); - if (result == null) continue; - if (result.Message == null) continue; + if (result == null || result.Message==null || result.Message.Value == null) + { + _logger.LogWarning($"Kafka消费: {result?.Topic} 分区 {result?.Partition} 值为NULL"); + consumer.Commit(result); // 手动提交 + continue; + } if (result.IsPartitionEOF) { _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); - await Task.Delay(TimeSpan.FromSeconds(1)); + await Task.Delay(100, cts.Token); + continue; + } + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; + // 检查 Header 是否符合条件 + if (!headersFilter.Match(result.Message.Headers)) + { + _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} Header值未匹配", result.Topic, result.Partition); + //consumer.Commit(result); // 提交偏移量 + // 跳过消息 continue; } - bool sucess = await messageHandler(result.Message.Value); if (sucess) consumer.Commit(result); // 手动提交 @@ -199,6 +229,267 @@ namespace JiShe.CollectBus.Kafka.Consumer await Task.CompletedTask; } + + /// + /// 批量订阅消息 + /// + /// 消息Key类型 + /// 消息Value类型 + /// 主题 + /// 批量消息处理函数 + /// 消费组ID + /// 批次大小 + /// 批次超时时间 + public async Task SubscribeBatchAsync(string topic, Func, Task> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null) where TKey : notnull where TValue : class + { + await SubscribeBatchAsync(new[] { topic }, messageBatchHandler, groupId, batchSize, batchTimeout); + } + + /// + /// 批量订阅消息 + /// + /// 消息Key类型 + /// 消息Value类型 + /// 主题列表 + /// 批量消息处理函数 + /// 消费组ID + /// 批次大小 + /// 批次超时时间 + public async Task SubscribeBatchAsync(string[] topics,Func, Task> messageBatchHandler, string? groupId = null,int batchSize = 100, TimeSpan? batchTimeout = null) where TKey : notnull where TValue : class + { + var consumerKey = typeof(KafkaConsumer); + var cts = new CancellationTokenSource(); + + var consumer = _consumerStore.GetOrAdd(consumerKey, _ => + ( + CreateConsumer(groupId), + cts + )).Consumer as IConsumer; + + consumer!.Subscribe(topics); + + var timeout = batchTimeout ?? TimeSpan.FromSeconds(5); // 默认超时时间调整为5秒 + + _ = Task.Run(async () => + { + var messages = new List<(TValue Value, TopicPartitionOffset Offset)>(); + var startTime = DateTime.UtcNow; + + while (!cts.IsCancellationRequested) + { + try + { + // 非阻塞快速累积消息 + while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout) + { + var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用 + + if (result != null) + { + if (result.IsPartitionEOF) + { + _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); + await Task.Delay(TimeSpan.FromSeconds(1), cts.Token); + } + else if (result.Message.Value != null) + { + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; + // 检查 Header 是否符合条件 + if (!headersFilter.Match(result.Message.Headers)) + { + _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} Header未匹配", result.Topic, result.Partition); + //consumer.Commit(result); // 提交偏移量 + // 跳过消息 + continue; + } + messages.Add((result.Message.Value, result.TopicPartitionOffset)); + //messages.Add(result.Message.Value); + } + } + else + { + // 无消息时短暂等待 + await Task.Delay(10, cts.Token); + } + } + + // 处理批次 + if (messages.Count > 0) + { + bool success = await messageBatchHandler(messages.Select(m => m.Value)); + if (success) + { + var offsetsByPartition = new Dictionary(); + foreach (var msg in messages) + { + var tp = msg.Offset.TopicPartition; + var offset = msg.Offset.Offset; + if (!offsetsByPartition.TryGetValue(tp, out var currentMax) || offset > currentMax) + { + offsetsByPartition[tp] = offset; + } + } + + var offsetsToCommit = offsetsByPartition + .Select(kv => new TopicPartitionOffset(kv.Key, new Offset(kv.Value + 1))) + .ToList(); + consumer.Commit(offsetsToCommit); + } + messages.Clear(); + } + + startTime = DateTime.UtcNow; + } + catch (ConsumeException ex) + { + _logger.LogError(ex, $"消息消费失败: {ex.Error.Reason}"); + } + catch (OperationCanceledException) + { + // 任务取消,正常退出 + } + catch (Exception ex) + { + _logger.LogError(ex, "处理批量消息时发生未知错误"); + } + } + }, cts.Token); + + await Task.CompletedTask; + } + + + /// + /// 批量订阅消息 + /// + /// 消息Value类型 + /// 主题列表 + /// 批量消息处理函数 + /// 消费组ID + /// 批次大小 + /// 批次超时时间 + /// 消费等待时间 + public async Task SubscribeBatchAsync(string topic, Func, Task> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null, TimeSpan? consumeTimeout = null) where TValue : class + { + await SubscribeBatchAsync(new[] { topic }, messageBatchHandler, groupId, batchSize, batchTimeout, consumeTimeout); + + } + + + /// + /// 批量订阅消息 + /// + /// 消息Value类型 + /// 主题列表 + /// 批量消息处理函数 + /// 消费组ID + /// 批次大小 + /// 批次超时时间 + /// 消费等待时间 + public async Task SubscribeBatchAsync(string[] topics,Func, Task> messageBatchHandler, string? groupId = null, int batchSize = 100,TimeSpan? batchTimeout = null,TimeSpan? consumeTimeout = null)where TValue : class + { + var consumerKey = typeof(KafkaConsumer); + var cts = new CancellationTokenSource(); + + var consumer = _consumerStore.GetOrAdd(consumerKey, _ => + ( + CreateConsumer(groupId), + cts + )).Consumer as IConsumer; + + consumer!.Subscribe(topics); + + var timeout = batchTimeout ?? TimeSpan.FromSeconds(5); // 默认超时时间调整为5秒 + + _ = Task.Run(async () => + { + var messages = new List<(TValue Value, TopicPartitionOffset Offset)>(); + //var messages = new List>(); + var startTime = DateTime.UtcNow; + + while (!cts.IsCancellationRequested) + { + try + { + // 非阻塞快速累积消息 + while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout) + { + var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用 + + if (result != null) + { + if (result.IsPartitionEOF) + { + _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); + await Task.Delay(TimeSpan.FromSeconds(1), cts.Token); + } + else if (result.Message.Value != null) + { + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; + // 检查 Header 是否符合条件 + if (!headersFilter.Match(result.Message.Headers)) + { + //consumer.Commit(result); // 提交偏移量 + // 跳过消息 + continue; + } + messages.Add((result.Message.Value, result.TopicPartitionOffset)); + //messages.Add(result.Message.Value); + } + } + else + { + // 无消息时短暂等待 + await Task.Delay(10, cts.Token); + } + } + + // 处理批次 + if (messages.Count > 0) + { + bool success = await messageBatchHandler(messages.Select(m => m.Value)); + if (success) + { + var offsetsByPartition = new Dictionary(); + foreach (var msg in messages) + { + var tp = msg.Offset.TopicPartition; + var offset = msg.Offset.Offset; + if (!offsetsByPartition.TryGetValue(tp, out var currentMax) || offset > currentMax) + { + offsetsByPartition[tp] = offset; + } + } + + var offsetsToCommit = offsetsByPartition + .Select(kv => new TopicPartitionOffset(kv.Key, new Offset(kv.Value + 1))) + .ToList(); + consumer.Commit(offsetsToCommit); + } + messages.Clear(); + } + + startTime = DateTime.UtcNow; + } + catch (ConsumeException ex) + { + _logger.LogError(ex, $"消息消费失败: {ex.Error.Reason}"); + } + catch (OperationCanceledException) + { + // 任务取消,正常退出 + } + catch (Exception ex) + { + _logger.LogError(ex, "处理批量消息时发生未知错误"); + } + } + }, cts.Token); + + await Task.CompletedTask; + } + + /// /// 取消消息订阅 /// diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs index 5cfae2c..3925014 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs @@ -1,4 +1,5 @@ -using System; +using Confluent.Kafka; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -32,6 +33,11 @@ namespace JiShe.CollectBus.Kafka.Consumer /// Task SubscribeAsync(string[] topics, Func> messageHandler, string? groupId = null) where TValue : class; + + Task SubscribeBatchAsync(string topic, Func, Task> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null) where TKey : notnull where TValue : class; + + Task SubscribeBatchAsync(string[] topics, Func, Task> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null, TimeSpan? consumeTimeout = null) where TValue : class; + void Unsubscribe() where TKey : notnull where TValue : class; } } diff --git a/src/JiShe.CollectBus.KafkaProducer/HeadersFilter.cs b/src/JiShe.CollectBus.KafkaProducer/HeadersFilter.cs new file mode 100644 index 0000000..0790f9f --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/HeadersFilter.cs @@ -0,0 +1,30 @@ +using Confluent.Kafka; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka +{ + /// + /// 消息头过滤器 + /// + public class HeadersFilter : Dictionary + { + /// + /// 判断Headers是否匹配 + /// + /// + /// + public bool Match(Headers headers) + { + foreach (var kvp in this) + { + if (!headers.TryGetLastBytes(kvp.Key, out var value) || !value.SequenceEqual(kvp.Value)) + return false; + } + return true; + } + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/JsonSerializer.cs b/src/JiShe.CollectBus.KafkaProducer/JsonSerializer.cs new file mode 100644 index 0000000..83f58a3 --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/JsonSerializer.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.Json; +using Confluent.Kafka; +using System.Text.Json.Serialization; +using System.Text.Encodings.Web; + +namespace JiShe.CollectBus.Kafka +{ + /// + /// JSON 序列化器(支持泛型) + /// + public class JsonSerializer : ISerializer, IDeserializer + { + private static readonly JsonSerializerOptions _options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + WriteIndented = false,// 设置格式化输出 + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,// 允许特殊字符 + IgnoreReadOnlyFields = true, + IgnoreReadOnlyProperties = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString, // 允许数字字符串 + AllowTrailingCommas = true, // 忽略尾随逗号 + ReadCommentHandling = JsonCommentHandling.Skip, // 忽略注释 + PropertyNameCaseInsensitive = true, // 属性名称大小写不敏感 + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // 属性名称使用驼峰命名规则 + Converters = { new DateTimeJsonConverter() } // 注册你的自定义转换器, + }; + + public byte[] Serialize(T data, SerializationContext context) + { + if (data == null) + return null; + + try + { + return JsonSerializer.SerializeToUtf8Bytes(data, _options); + } + catch (Exception ex) + { + throw new InvalidOperationException("Kafka序列化失败", ex); + } + } + + public T Deserialize(ReadOnlySpan data, bool isNull, SerializationContext context) + { + if (isNull) + return default; + + try + { + return JsonSerializer.Deserialize(data, _options); + } + catch (Exception ex) + { + throw new InvalidOperationException("Kafka反序列化失败", ex); + } + } + } + + + public class DateTimeJsonConverter : JsonConverter + { + private readonly string _dateFormatString; + public DateTimeJsonConverter() + { + _dateFormatString = "yyyy-MM-dd HH:mm:ss"; + } + + public DateTimeJsonConverter(string dateFormatString) + { + _dateFormatString = dateFormatString; + } + + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(_dateFormatString)); + } + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs index cfe5eee..8860061 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -102,7 +102,7 @@ namespace JiShe.CollectBus.Kafka { var consumerService = provider.GetRequiredService(); - await consumerService.SubscribeAsync(attr.Topic, async (message) => + await consumerService.SubscribeAsync(attr.Topic, async (message) => { try { @@ -126,21 +126,21 @@ namespace JiShe.CollectBus.Kafka /// /// /// - private static async Task ProcessMessageAsync(string message, MethodInfo method, object subscribe) + private static async Task ProcessMessageAsync(dynamic message, MethodInfo method, object subscribe) { var parameters = method.GetParameters(); bool isGenericTask = method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>); bool existParameters = parameters.Length > 0; - dynamic? messageObj= null; - if (existParameters) - { - var paramType = parameters[0].ParameterType; - messageObj = paramType == typeof(string) ? message : message.Deserialize(paramType); - } + //dynamic? messageObj= null; + //if (existParameters) + //{ + // var paramType = parameters[0].ParameterType; + // messageObj = paramType == typeof(string) ? message : message.Deserialize(paramType); + //} if (isGenericTask) { - object? result = await (Task)method.Invoke(subscribe, existParameters? new[] { messageObj }:null)!; + object? result = await (Task)method.Invoke(subscribe, existParameters? new[] { message } :null)!; if (result is ISubscribeAck ackResult) { return ackResult.Ack; @@ -148,7 +148,7 @@ namespace JiShe.CollectBus.Kafka } else { - object? result = method.Invoke(subscribe, existParameters ? new[] { messageObj } : null); + object? result = method.Invoke(subscribe, existParameters ? new[] { message } : null); if (result is ISubscribeAck ackResult) { return ackResult.Ack; diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs index 8c069a9..0ddf36b 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs @@ -17,7 +17,8 @@ namespace JiShe.CollectBus.Kafka.Producer { private readonly ILogger _logger; private readonly IConfiguration _configuration; - private readonly ConcurrentDictionary, object> _producerCache = new(); + private readonly ConcurrentDictionary _producerCache = new(); + private class KafkaProducer where TKey : notnull where TValue : class { } public ProducerService(IConfiguration configuration,ILogger logger) { @@ -32,14 +33,13 @@ namespace JiShe.CollectBus.Kafka.Producer /// /// /// - private IProducer GetProducer() + private IProducer GetProducer(Type typeKey) { - var typeKey = Tuple.Create(typeof(TKey), typeof(TValue))!; - return (IProducer)_producerCache.GetOrAdd(typeKey, _ => { var config = BuildProducerConfig(); return new ProducerBuilder(config) + .SetValueSerializer(new JsonSerializer()) // Value 使用自定义 JSON 序列化 .SetLogHandler((_, msg) => _logger.Log(ConvertLogLevel(msg.Level), msg.Message)) .Build(); }); @@ -103,8 +103,17 @@ namespace JiShe.CollectBus.Kafka.Producer /// public async Task ProduceAsync(string topic, TKey key, TValue value)where TKey : notnull where TValue : class { - var producer = GetProducer(); - await producer.ProduceAsync(topic, new Message { Key = key, Value = value }); + var typeKey = typeof(KafkaProducer); + var producer = GetProducer(typeKey); + var message = new Message + { + Key = key, + Value = value, + Headers = new Headers{ + { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } + } + }; + await producer.ProduceAsync(topic, message); } /// @@ -116,8 +125,16 @@ namespace JiShe.CollectBus.Kafka.Producer /// public async Task ProduceAsync(string topic, TValue value) where TValue : class { - var producer = GetProducer(); - await producer.ProduceAsync(topic, new Message { Value = value }); + var typeKey = typeof(KafkaProducer); + var producer = GetProducer(typeKey); + var message = new Message + { + Value = value, + Headers = new Headers{ + { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } + } + }; + await producer.ProduceAsync(topic, message); } /// @@ -136,9 +153,13 @@ namespace JiShe.CollectBus.Kafka.Producer var message = new Message { Key = key, - Value = value + Value = value, + Headers = new Headers{ + { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } + } }; - var producer = GetProducer(); + var typeKey = typeof(KafkaProducer); + var producer = GetProducer(typeKey); if (partition.HasValue) { var topicPartition = new TopicPartition(topic, partition.Value); @@ -166,9 +187,13 @@ namespace JiShe.CollectBus.Kafka.Producer { var message = new Message { - Value = value + Value = value, + Headers = new Headers{ + { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } + } }; - var producer = GetProducer(); + var typeKey = typeof(KafkaProducer); + var producer = GetProducer(typeKey); if (partition.HasValue) { var topicPartition = new TopicPartition(topic, partition.Value); diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index 963d89b..c43fa70 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -92,7 +92,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); //await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); - await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }.Serialize()); + await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); //await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); } @@ -133,7 +133,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); //await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); - await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }.Serialize()); + await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); //await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); } -- 2.47.2 From 78f9ef349adb24c57086345672c0a63de9442cf7 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Wed, 16 Apr 2025 18:46:51 +0800 Subject: [PATCH 109/139] =?UTF-8?q?=E4=BC=98=E5=8C=96kafka=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E9=BB=98=E8=AE=A4=E5=BA=8F=E5=88=97=E5=8C=96=E5=92=8C?= =?UTF-8?q?=E5=8F=8D=E5=BA=8F=E5=88=97=E5=8C=96=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=B6=88=E8=B4=B9=20=E5=A2=9E=E5=8A=A0Header?= =?UTF-8?q?=E6=B6=88=E8=B4=B9=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Consumer/ConsumerService.cs | 4 ++-- .../Consumer/IConsumerService.cs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index 5547919..0625304 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -131,7 +131,7 @@ namespace JiShe.CollectBus.Kafka.Consumer if (result == null || result.Message==null || result.Message.Value == null) { _logger.LogWarning($"Kafka消费: {result?.Topic} 分区 {result?.Partition} 值为NULL"); - consumer.Commit(result); // 手动提交 + //consumer.Commit(result); // 手动提交 continue; } if (result.IsPartitionEOF) @@ -198,7 +198,7 @@ namespace JiShe.CollectBus.Kafka.Consumer if (result == null || result.Message==null || result.Message.Value == null) { _logger.LogWarning($"Kafka消费: {result?.Topic} 分区 {result?.Partition} 值为NULL"); - consumer.Commit(result); // 手动提交 + //consumer.Commit(result); // 手动提交 continue; } if (result.IsPartitionEOF) diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs index 3925014..d86dba8 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs @@ -33,10 +33,13 @@ namespace JiShe.CollectBus.Kafka.Consumer /// Task SubscribeAsync(string[] topics, Func> messageHandler, string? groupId = null) where TValue : class; + Task SubscribeBatchAsync(string[] topics, Func, Task> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null) where TKey : notnull where TValue : class; Task SubscribeBatchAsync(string topic, Func, Task> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null) where TKey : notnull where TValue : class; - Task SubscribeBatchAsync(string[] topics, Func, Task> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null, TimeSpan? consumeTimeout = null) where TValue : class; + Task SubscribeBatchAsync(string topic, Func, Task> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null, TimeSpan? consumeTimeout = null) where TValue : class; + + Task SubscribeBatchAsync(string[] topics, Func, Task> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null, TimeSpan? consumeTimeout = null) where TValue : class; void Unsubscribe() where TKey : notnull where TValue : class; } -- 2.47.2 From 3790f3918e4bb9cc4511e1fb7ef1bf4875b17a31 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Wed, 16 Apr 2025 20:41:52 +0800 Subject: [PATCH 110/139] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Header=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E8=BF=87=E6=BB=A4=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JiShe.CollectBus.Host/appsettings.json | 1 + .../Consumer/ConsumerService.cs | 64 +++++++++++-------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index a837636..91ff4ea 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -83,6 +83,7 @@ }, "Kafka": { "BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092", + "EnableFilter": true, "EnableAuthorization": false, "SecurityProtocol": "SASL_PLAINTEXT", "SaslMechanism": "PLAIN", diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index 0625304..da9258c 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -140,15 +140,16 @@ namespace JiShe.CollectBus.Kafka.Consumer await Task.Delay(TimeSpan.FromSeconds(1),cts.Token); continue; } - var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; - // 检查 Header 是否符合条件 - if (!headersFilter.Match(result.Message.Headers)) + if (bool.Parse(_configuration["KafkaConsumer:EnableFilter"]!)) { - _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} Header未匹配", result.Topic, result.Partition); - - //consumer.Commit(result); // 提交偏移量 - // 跳过消息 - continue; + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; + // 检查 Header 是否符合条件 + if (!headersFilter.Match(result.Message.Headers)) + { + //consumer.Commit(result); // 提交偏移量 + // 跳过消息 + continue; + } } bool sucess= await messageHandler(result.Message.Key, result.Message.Value); if (sucess) @@ -207,14 +208,16 @@ namespace JiShe.CollectBus.Kafka.Consumer await Task.Delay(100, cts.Token); continue; } - var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; - // 检查 Header 是否符合条件 - if (!headersFilter.Match(result.Message.Headers)) + if (bool.Parse(_configuration["KafkaConsumer:EnableFilter"]!)) { - _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} Header值未匹配", result.Topic, result.Partition); - //consumer.Commit(result); // 提交偏移量 - // 跳过消息 - continue; + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; + // 检查 Header 是否符合条件 + if (!headersFilter.Match(result.Message.Headers)) + { + //consumer.Commit(result); // 提交偏移量 + // 跳过消息 + continue; + } } bool sucess = await messageHandler(result.Message.Value); if (sucess) @@ -293,14 +296,16 @@ namespace JiShe.CollectBus.Kafka.Consumer } else if (result.Message.Value != null) { - var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; - // 检查 Header 是否符合条件 - if (!headersFilter.Match(result.Message.Headers)) + if (bool.Parse(_configuration["KafkaConsumer:EnableFilter"]!)) { - _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} Header未匹配", result.Topic, result.Partition); - //consumer.Commit(result); // 提交偏移量 - // 跳过消息 - continue; + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; + // 检查 Header 是否符合条件 + if (!headersFilter.Match(result.Message.Headers)) + { + //consumer.Commit(result); // 提交偏移量 + // 跳过消息 + continue; + } } messages.Add((result.Message.Value, result.TopicPartitionOffset)); //messages.Add(result.Message.Value); @@ -425,13 +430,16 @@ namespace JiShe.CollectBus.Kafka.Consumer } else if (result.Message.Value != null) { - var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; - // 检查 Header 是否符合条件 - if (!headersFilter.Match(result.Message.Headers)) + if (bool.Parse(_configuration["KafkaConsumer:EnableFilter"]!)) { - //consumer.Commit(result); // 提交偏移量 - // 跳过消息 - continue; + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; + // 检查 Header 是否符合条件 + if (!headersFilter.Match(result.Message.Headers)) + { + //consumer.Commit(result); // 提交偏移量 + // 跳过消息 + continue; + } } messages.Add((result.Message.Value, result.TopicPartitionOffset)); //messages.Add(result.Message.Value); -- 2.47.2 From 849e0f9ac21c9531aae6a6d175c9fefdbcec71a2 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Wed, 16 Apr 2025 23:51:27 +0800 Subject: [PATCH 111/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RedisDataCache/RedisDataCacheService.cs | 208 +++++++++++------- .../BasicScheduledMeterReadingService.cs | 74 +++---- .../Extensions/EnumerableExtensions.cs | 21 ++ .../Ammeters/AmmeterInfo.cs | 17 +- src/JiShe.CollectBus.Host/appsettings.json | 2 +- 5 files changed, 201 insertions(+), 121 deletions(-) diff --git a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs index 4d78706..8f50017 100644 --- a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs +++ b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs @@ -12,6 +12,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; +using static FreeSql.Internal.GlobalFilter; +using static System.Runtime.InteropServices.JavaScript.JSType; +using static Volo.Abp.UI.Navigation.DefaultMenuNames.Application; namespace JiShe.CollectBus.RedisDataCache { @@ -118,7 +121,7 @@ namespace JiShe.CollectBus.RedisDataCache await semaphore.WaitAsync(); _ = Task.Run(() => - { + { using (var pipe = Instance.StartPipe()) { foreach (var item in batch) @@ -372,129 +375,172 @@ namespace JiShe.CollectBus.RedisDataCache /// 最后一个索引 /// 最后一个唯一标识 /// 排序方式 - /// + /// public async Task> GetAllPagedData( - string redisHashCacheKey, - string redisZSetScoresIndexCacheKey, - int pageSize = 1000, - decimal? lastScore = null, - string lastMember = null, - bool descending = true) - where T : DeviceCacheBasicModel + string redisHashCacheKey, + string redisZSetScoresIndexCacheKey, + int pageSize = 1000, + decimal? lastScore = null, + string lastMember = null, + bool descending = true) + where T : DeviceCacheBasicModel { // 参数校验(保持不变) - if (string.IsNullOrWhiteSpace(redisHashCacheKey) || string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) + if (string.IsNullOrWhiteSpace(redisHashCacheKey) || + string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) { - _logger.LogError($"{nameof(GetAllPagedData)} 参数异常,-101"); + _logger.LogError("参数异常: HashKey或ZSetKey为空"); return null; } if (pageSize < 1 || pageSize > 10000) - { - _logger.LogError($"{nameof(GetAllPagedData)} 分页大小应在1-10000之间,-102"); - return null; - } + throw new ArgumentException("分页大小应在1-10000之间"); + // 更新后的Lua脚本 var luaScript = @" - local command = ARGV[1] - local range_start = ARGV[2] - local range_end = ARGV[3] - local limit = tonumber(ARGV[4]) - local last_score = ARGV[5] - local last_member = ARGV[6] +local command = ARGV[1] +local range_start = ARGV[2] +local range_end = ARGV[3] +local limit = tonumber(ARGV[4]) +local last_score = ARGV[5] +local last_member = ARGV[6] - -- 处理相同分数下的字典序分页 - if last_score ~= '' and last_member ~= '' then +-- 调整range_start,当有last_score且没有last_member时 +if last_score ~= '' and last_member == '' then + if command == 'ZRANGEBYSCORE' then + range_start = '('..last_score + else + range_start = '('..last_score + end +end + +local members +if command == 'ZRANGEBYSCORE' then + members = redis.call(command, KEYS[1], range_start, range_end, 'WITHSCORES', 'LIMIT', 0, limit) +else + members = redis.call('ZREVRANGEBYSCORE', KEYS[1], range_start, range_end, 'WITHSCORES', 'LIMIT', 0, limit) +end + +if #members == 0 then return {0,{},{},{}} end + +local result_members = {} +local result_scores = {} + +-- 如果有last_member,进行过滤 +if last_member ~= '' and last_score ~= '' then + for i = 1, #members, 2 do + local member = members[i] + local score = members[i+1] + local include = true + + if score == last_score then if command == 'ZRANGEBYSCORE' then - range_start = '(' .. last_score - range_end = '(' .. last_score .. ' ' .. last_member + -- 升序:member必须 > last_member + if member <= last_member then + include = false + end else - range_start = '(' .. last_score .. ' ' .. last_member - range_end = '(' .. last_score + -- 降序:member必须 < last_member + if member >= last_member then + include = false + end end end - -- 执行范围查询 - local members - if command == 'ZRANGEBYSCORE' then - members = redis.call(command, KEYS[1], range_start, range_end, - 'WITHSCORES', 'LIMIT', 0, limit) - else - members = redis.call(command, KEYS[1], range_end, range_start, - 'WITHSCORES', 'LIMIT', 0, limit) + if include then + table.insert(result_members, member) + table.insert(result_scores, score) end + end +else + for i = 1, #members, 2 do + table.insert(result_members, members[i]) + table.insert(result_scores, members[i+1]) + end +end - -- 提取成员和分数 - local result_members = {} - local result_scores = {} - for i = 1, #members, 2 do - table.insert(result_members, members[i]) - table.insert(result_scores, members[i+1]) - end +-- 截取前limit条 +local count = #result_members +if count > limit then + result_members = {unpack(result_members, 1, limit)} + result_scores = {unpack(result_scores, 1, limit)} +end - -- 获取Hash数据 - local hash_data = redis.call('HMGET', KEYS[2], unpack(result_members)) +if #result_members == 0 then + return {0, {}, {}, {}} +end - return { - #result_members, - result_members, - result_scores, - hash_data - }"; +-- 获取Hash数据 +local hash_data = redis.call('HMGET', KEYS[2], unpack(result_members)) +return {#result_members, result_members, result_scores, hash_data}"; - //正确设置范围参数 + // 修复点:根据是否传递lastMember决定rangeStart是否排他 string rangeStart, rangeEnd; if (descending) { - rangeStart = lastScore.HasValue ? $"({lastScore}" : "+inf"; - rangeEnd = "-inf"; // 降序时固定为最小值 + rangeStart = lastScore.HasValue + ? (string.IsNullOrEmpty(lastMember) ? "(" + lastScore.Value.ToString() : lastScore.Value.ToString()) + : "+inf"; + rangeEnd = "-inf"; } else { - rangeStart = lastScore.HasValue ? $"({lastScore}" : "-inf"; - rangeEnd = "+inf"; // 升序时固定为最大值 + rangeStart = lastScore.HasValue + ? (string.IsNullOrEmpty(lastMember) ? "(" + lastScore.Value.ToString() : lastScore.Value.ToString()) + : "-inf"; + rangeEnd = "+inf"; } - var result = (object[])await Instance.EvalAsync( - luaScript, + // 执行Lua脚本(保持不变) + var scriptResult = (object[])await Instance.EvalAsync(luaScript, new[] { redisZSetScoresIndexCacheKey, redisHashCacheKey }, new object[] { - descending ? "ZREVRANGEBYSCORE" : "ZRANGEBYSCORE", - rangeStart, - rangeEnd, - (pageSize + 1).ToString(), // 多取1条用于判断hasNext - lastScore?.ToString() ?? "", - lastMember ?? "" + descending ? "ZREVRANGEBYSCORE" : "ZRANGEBYSCORE", + rangeStart, + rangeEnd, + (pageSize + 1).ToString(), + lastScore?.ToString() ?? "", + lastMember ?? "" }); - if ((long)result[0] == 0) + // 处理空结果(保持不变) + if ((long)scriptResult[0] == 0) return new BusCacheGlobalPagedResult { Items = new List() }; - // 处理结果集 - var members = ((object[])result[1]).Cast().ToList(); - var scores = ((object[])result[2]).Cast().Select(decimal.Parse).ToList(); - var hashData = ((object[])result[3]).Cast().ToList(); + // 数据提取(保持不变) + var members = ((object[])scriptResult[1]).Cast().ToList(); + var scores = ((object[])scriptResult[2]).Cast().Select(decimal.Parse).ToList(); + var hashData = ((object[])scriptResult[3]).Cast().ToList(); - //合并有效数据并处理游标 - var validItems = members.Zip(hashData, (m, h) => - !string.IsNullOrWhiteSpace(h) ? BusJsonSerializer.Deserialize(h) : null) - .Where(x => x != null) - .Take(pageSize + 1) - .ToList(); + // 反序列化处理(保持不变) + var validItems = members.Select((m, i) => + { + try + { + return !string.IsNullOrEmpty(hashData[i]) + ? BusJsonSerializer.Deserialize(hashData[i]) + : null; + } + catch (Exception ex) + { + _logger.LogError($"反序列化失败: {m} - {ex.Message}"); + return null; + } + }).Where(x => x != null).Take(pageSize + 1).ToList(); + // 分页逻辑(保持不变) var hasNext = validItems.Count > pageSize; var actualItems = hasNext ? validItems.Take(pageSize) : validItems; - // 计算下一页起始点 - string nextMember = null; + // 计算下一页锚点(保持不变) decimal? nextScore = null; - if (hasNext) + string nextMember = null; + if (hasNext && actualItems.Any()) { - // 获取实际返回的最后一条有效数据 - var lastValidIndex = actualItems.Count() - 1; - nextMember = members[lastValidIndex]; - nextScore = scores[lastValidIndex]; + var lastIndex = Math.Min(members.Count - 1, pageSize); + nextScore = scores[lastIndex]; + nextMember = members[lastIndex]; } return new BusCacheGlobalPagedResult diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index ce4635b..4f8ae1f 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -213,50 +213,50 @@ namespace JiShe.CollectBus.ScheduledMeterReading public virtual async Task InitAmmeterCacheData(string gatherCode = "") { #if DEBUG - //var timeDensity = "15"; - //string tempCacheMeterInfoKey = $"CollectBus:{"{0}:{1}"}:MeterInfo:{"{2}"}:{"{3}"}"; - ////获取缓存中的电表信息 - //var redisKeyList = $"{string.Format(tempCacheMeterInfoKey, SystemType, "JiSheCollectBus", MeterTypeEnum.Ammeter, timeDensity)}*"; + var timeDensity = "15"; + string tempCacheMeterInfoKey = $"CollectBus:{"{0}:{1}"}:MeterInfo:{"{2}"}:{"{3}"}"; + //获取缓存中的电表信息 + var redisKeyList = $"{string.Format(tempCacheMeterInfoKey, SystemType, "JiSheCollectBus", MeterTypeEnum.Ammeter, timeDensity)}*"; - //var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - //var tempMeterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, SystemType, ServerTagName, timeDensity, MeterTypeEnum.Ammeter); - ////List focusAddressDataLista = new List(); - //List meterInfos = new List(); - //foreach (var item in tempMeterInfos) - //{ - // var tempData = item.Adapt(); - // tempData.FocusId = item.FocusID; - // tempData.MeterId = item.Id; - // meterInfos.Add(tempData); - // //focusAddressDataLista.Add(item.FocusAddress); - //} + var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + var tempMeterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, SystemType, ServerTagName, timeDensity, MeterTypeEnum.Ammeter); + //List focusAddressDataLista = new List(); + List meterInfos = new List(); + foreach (var item in tempMeterInfos) + { + var tempData = item.Adapt(); + tempData.FocusId = item.FocusID; + tempData.MeterId = item.Id; + meterInfos.Add(tempData); + //focusAddressDataLista.Add(item.FocusAddress); + } //DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista); - var timeDensity = "15"; - var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; - var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; - var redisCacheMeterInfoZSetScoresIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; + //var timeDensity = "15"; + //var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; + //var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; + //var redisCacheMeterInfoZSetScoresIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; - decimal? cursor = null; - string member = null; - bool hasNext; - List meterInfos = new List(); - do - { - var page = await _redisDataCacheService.GetAllPagedData( - redisCacheMeterInfoHashKeyTemp, - redisCacheMeterInfoZSetScoresIndexKeyTemp, - pageSize: 1000, - lastScore: cursor, - lastMember: member); + //decimal? cursor = null; + //string member = null; + //bool hasNext; + //List meterInfos = new List(); + //do + //{ + // var page = await _redisDataCacheService.GetAllPagedData( + // redisCacheMeterInfoHashKeyTemp, + // redisCacheMeterInfoZSetScoresIndexKeyTemp, + // pageSize: 1000, + // lastScore: cursor, + // lastMember: member); - meterInfos.AddRange(page.Items); - cursor = page.HasNext ? page.NextScore : null; - member = page.HasNext ? page.NextMember : null; - hasNext = page.HasNext; - } while (hasNext); + // meterInfos.AddRange(page.Items); + // cursor = page.HasNext ? page.NextScore : null; + // member = page.HasNext ? page.NextMember : null; + // hasNext = page.HasNext; + //} while (hasNext); #else var meterInfos = await GetAmmeterInfoList(gatherCode); diff --git a/src/JiShe.CollectBus.Common/Extensions/EnumerableExtensions.cs b/src/JiShe.CollectBus.Common/Extensions/EnumerableExtensions.cs index 80d2f23..b17e9c2 100644 --- a/src/JiShe.CollectBus.Common/Extensions/EnumerableExtensions.cs +++ b/src/JiShe.CollectBus.Common/Extensions/EnumerableExtensions.cs @@ -89,5 +89,26 @@ namespace JiShe.CollectBus.Common.Extensions if (buffer.Count > 0) yield return buffer; } + + //public static IEnumerable> Batch(this IEnumerable source, int batchSize) + //{ + // if (batchSize <= 0) + // throw new ArgumentOutOfRangeException(nameof(batchSize)); + + // using var enumerator = source.GetEnumerator(); + // while (enumerator.MoveNext()) + // { + // yield return GetBatch(enumerator, batchSize); + // } + //} + + //private static IEnumerable GetBatch(IEnumerator enumerator, int batchSize) + //{ + // do + // { + // yield return enumerator.Current; + // batchSize--; + // } while (batchSize > 0 && enumerator.MoveNext()); + //} } } diff --git a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs index 4ac667d..3df01b5 100644 --- a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs +++ b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs @@ -1,4 +1,5 @@ -using JiShe.CollectBus.Common.Models; +using FreeSql.DataAnnotations; +using JiShe.CollectBus.Common.Models; using System; using System.Collections.Generic; using System.Linq; @@ -8,7 +9,19 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.Ammeters { public class AmmeterInfo: DeviceCacheBasicModel - { + { + /// + /// 关系映射标识,用于ZSet的Member字段和Set的Value字段,具体值可以根据不同业务场景进行定义 + /// + [Column(IsIgnore = true)] + public override string MemberID => $"{FocusId}:{MeterId}"; + + /// + /// ZSet排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳 + /// + [Column(IsIgnore = true)] + public override long ScoreValue => ((long)FocusId << 32) | (uint)DateTime.Now.Ticks; + /// /// 电表名称 /// diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index bb09204..ae2025b 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -129,7 +129,7 @@ "OpenDebugMode": true, "UseTableSessionPoolByDefault": false }, - "ServerTagName": "JiSheCollectBus3", + "ServerTagName": "JiSheCollectBus2", "KafkaReplicationFactor": 3, "NumPartitions": 30 } \ No newline at end of file -- 2.47.2 From 78127f7cea9975aa131ba007cf6b3d46ef6b9494 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 17 Apr 2025 08:20:54 +0800 Subject: [PATCH 112/139] =?UTF-8?q?=E8=A7=A3=E5=86=B3Redis=E5=A4=A7?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=95=B0=E6=8D=AE=E8=AF=BB=E5=8F=96=E6=80=BB?= =?UTF-8?q?=E6=95=B0=E4=B8=8D=E5=87=86=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E5=B0=8610=E4=B8=87=E8=AE=B0=E5=BD=95=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E6=97=B6=E9=97=B4=E6=8E=A7=E5=88=B6=E5=9C=A823?= =?UTF-8?q?=E7=A7=92=E4=BB=A5=E5=86=85=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RedisDataCache/RedisDataCacheService.cs | 115 +++++++----------- .../BasicScheduledMeterReadingService.cs | 77 ++++++------ src/JiShe.CollectBus.Host/appsettings.json | 2 +- 3 files changed, 87 insertions(+), 107 deletions(-) diff --git a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs index 8f50017..e846ada 100644 --- a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs +++ b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs @@ -385,7 +385,7 @@ namespace JiShe.CollectBus.RedisDataCache bool descending = true) where T : DeviceCacheBasicModel { - // 参数校验(保持不变) + // 参数校验 if (string.IsNullOrWhiteSpace(redisHashCacheKey) || string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) { @@ -396,7 +396,6 @@ namespace JiShe.CollectBus.RedisDataCache if (pageSize < 1 || pageSize > 10000) throw new ArgumentException("分页大小应在1-10000之间"); - // 更新后的Lua脚本 var luaScript = @" local command = ARGV[1] local range_start = ARGV[2] @@ -405,93 +404,74 @@ local limit = tonumber(ARGV[4]) local last_score = ARGV[5] local last_member = ARGV[6] --- 调整range_start,当有last_score且没有last_member时 -if last_score ~= '' and last_member == '' then - if command == 'ZRANGEBYSCORE' then - range_start = '('..last_score - else - range_start = '('..last_score - end -end - +-- 获取原始数据 local members if command == 'ZRANGEBYSCORE' then - members = redis.call(command, KEYS[1], range_start, range_end, 'WITHSCORES', 'LIMIT', 0, limit) + members = redis.call(command, KEYS[1], range_start, range_end, 'WITHSCORES', 'LIMIT', 0, limit * 2) else - members = redis.call('ZREVRANGEBYSCORE', KEYS[1], range_start, range_end, 'WITHSCORES', 'LIMIT', 0, limit) + members = redis.call('ZREVRANGEBYSCORE', KEYS[1], range_start, range_end, 'WITHSCORES', 'LIMIT', 0, limit * 2) end -if #members == 0 then return {0,{},{},{}} end - -local result_members = {} -local result_scores = {} - --- 如果有last_member,进行过滤 -if last_member ~= '' and last_score ~= '' then - for i = 1, #members, 2 do - local member = members[i] - local score = members[i+1] - local include = true - - if score == last_score then - if command == 'ZRANGEBYSCORE' then - -- 升序:member必须 > last_member - if member <= last_member then - include = false - end +-- 过滤数据 +local filtered_members = {} +local count = 0 +for i = 1, #members, 2 do + local member = members[i] + local score = members[i+1] + local include = true + if last_score ~= '' and last_member ~= '' then + if command == 'ZRANGEBYSCORE' then + -- 升序:score > last_score 或 (score == last_score 且 member > last_member) + if score == last_score then + include = member > last_member else - -- 降序:member必须 < last_member - if member >= last_member then - include = false - end + include = tonumber(score) > tonumber(last_score) + end + else + -- 降序:score < last_score 或 (score == last_score 且 member < last_member) + if score == last_score then + include = member < last_member + else + include = tonumber(score) < tonumber(last_score) end end - - if include then - table.insert(result_members, member) - table.insert(result_scores, score) + end + if include then + table.insert(filtered_members, member) + table.insert(filtered_members, score) + count = count + 1 + if count >= limit then + break end end -else - for i = 1, #members, 2 do - table.insert(result_members, members[i]) - table.insert(result_scores, members[i+1]) - end end --- 截取前limit条 -local count = #result_members -if count > limit then - result_members = {unpack(result_members, 1, limit)} - result_scores = {unpack(result_scores, 1, limit)} +-- 提取有效数据 +local result_members, result_scores = {}, {} +for i=1,#filtered_members,2 do + table.insert(result_members, filtered_members[i]) + table.insert(result_scores, filtered_members[i+1]) end -if #result_members == 0 then - return {0, {}, {}, {}} -end +if #result_members == 0 then return {0,{},{},{}} end -- 获取Hash数据 local hash_data = redis.call('HMGET', KEYS[2], unpack(result_members)) return {#result_members, result_members, result_scores, hash_data}"; - // 修复点:根据是否传递lastMember决定rangeStart是否排他 + // 调整范围构造逻辑(移除排他符号) string rangeStart, rangeEnd; if (descending) { - rangeStart = lastScore.HasValue - ? (string.IsNullOrEmpty(lastMember) ? "(" + lastScore.Value.ToString() : lastScore.Value.ToString()) - : "+inf"; + rangeStart = lastScore.HasValue ? lastScore.Value.ToString() : "+inf"; rangeEnd = "-inf"; } else { - rangeStart = lastScore.HasValue - ? (string.IsNullOrEmpty(lastMember) ? "(" + lastScore.Value.ToString() : lastScore.Value.ToString()) - : "-inf"; + rangeStart = lastScore.HasValue ? lastScore.Value.ToString() : "-inf"; rangeEnd = "+inf"; } - // 执行Lua脚本(保持不变) var scriptResult = (object[])await Instance.EvalAsync(luaScript, new[] { redisZSetScoresIndexCacheKey, redisHashCacheKey }, new object[] @@ -499,21 +479,19 @@ return {#result_members, result_members, result_scores, hash_data}"; descending ? "ZREVRANGEBYSCORE" : "ZRANGEBYSCORE", rangeStart, rangeEnd, - (pageSize + 1).ToString(), + (pageSize + 1).ToString(), // 获取pageSize+1条以判断是否有下一页 lastScore?.ToString() ?? "", lastMember ?? "" }); - // 处理空结果(保持不变) if ((long)scriptResult[0] == 0) return new BusCacheGlobalPagedResult { Items = new List() }; - // 数据提取(保持不变) + // 处理结果集 var members = ((object[])scriptResult[1]).Cast().ToList(); var scores = ((object[])scriptResult[2]).Cast().Select(decimal.Parse).ToList(); var hashData = ((object[])scriptResult[3]).Cast().ToList(); - // 反序列化处理(保持不变) var validItems = members.Select((m, i) => { try @@ -527,18 +505,17 @@ return {#result_members, result_members, result_scores, hash_data}"; _logger.LogError($"反序列化失败: {m} - {ex.Message}"); return null; } - }).Where(x => x != null).Take(pageSize + 1).ToList(); + }).Where(x => x != null).ToList(); - // 分页逻辑(保持不变) var hasNext = validItems.Count > pageSize; var actualItems = hasNext ? validItems.Take(pageSize) : validItems; - // 计算下一页锚点(保持不变) + // 修正分页锚点索引 decimal? nextScore = null; string nextMember = null; if (hasNext && actualItems.Any()) { - var lastIndex = Math.Min(members.Count - 1, pageSize); + var lastIndex = actualItems.Count() - 1; // 使用actualItems的最后一个索引 nextScore = scores[lastIndex]; nextMember = members[lastIndex]; } @@ -551,7 +528,7 @@ return {#result_members, result_members, result_scores, hash_data}"; NextMember = nextMember, TotalCount = await GetTotalCount(redisZSetScoresIndexCacheKey), PageSize = pageSize, - }; + }; } ///// diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 4f8ae1f..d32cdb2 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -213,50 +213,53 @@ namespace JiShe.CollectBus.ScheduledMeterReading public virtual async Task InitAmmeterCacheData(string gatherCode = "") { #if DEBUG - var timeDensity = "15"; - string tempCacheMeterInfoKey = $"CollectBus:{"{0}:{1}"}:MeterInfo:{"{2}"}:{"{3}"}"; - //获取缓存中的电表信息 - var redisKeyList = $"{string.Format(tempCacheMeterInfoKey, SystemType, "JiSheCollectBus", MeterTypeEnum.Ammeter, timeDensity)}*"; + //var timeDensity = "15"; + //string tempCacheMeterInfoKey = $"CollectBus:{"{0}:{1}"}:MeterInfo:{"{2}"}:{"{3}"}"; + ////获取缓存中的电表信息 + //var redisKeyList = $"{string.Format(tempCacheMeterInfoKey, SystemType, "JiSheCollectBus", MeterTypeEnum.Ammeter, timeDensity)}*"; - var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - var tempMeterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, SystemType, ServerTagName, timeDensity, MeterTypeEnum.Ammeter); - //List focusAddressDataLista = new List(); - List meterInfos = new List(); - foreach (var item in tempMeterInfos) - { - var tempData = item.Adapt(); - tempData.FocusId = item.FocusID; - tempData.MeterId = item.Id; - meterInfos.Add(tempData); - //focusAddressDataLista.Add(item.FocusAddress); - } + //var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + //var tempMeterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, SystemType, ServerTagName, timeDensity, MeterTypeEnum.Ammeter); + ////List focusAddressDataLista = new List(); + //List meterInfos = new List(); + //foreach (var item in tempMeterInfos) + //{ + // var tempData = item.Adapt(); + // tempData.FocusId = item.FocusID; + // tempData.MeterId = item.Id; + // meterInfos.Add(tempData); + // //focusAddressDataLista.Add(item.FocusAddress); + //} //DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista); - //var timeDensity = "15"; - //var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; - //var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; - //var redisCacheMeterInfoZSetScoresIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; + var timeDensity = "15"; + var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; + var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; + var redisCacheMeterInfoZSetScoresIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; + var timer1 = Stopwatch.StartNew(); + decimal? cursor = null; + string member = null; + bool hasNext; + List meterInfos = new List(); + do + { + var page = await _redisDataCacheService.GetAllPagedData( + redisCacheMeterInfoHashKeyTemp, + redisCacheMeterInfoZSetScoresIndexKeyTemp, + pageSize: 1000, + lastScore: cursor, + lastMember: member); - //decimal? cursor = null; - //string member = null; - //bool hasNext; - //List meterInfos = new List(); - //do - //{ - // var page = await _redisDataCacheService.GetAllPagedData( - // redisCacheMeterInfoHashKeyTemp, - // redisCacheMeterInfoZSetScoresIndexKeyTemp, - // pageSize: 1000, - // lastScore: cursor, - // lastMember: member); + meterInfos.AddRange(page.Items); + cursor = page.HasNext ? page.NextScore : null; + member = page.HasNext ? page.NextMember : null; + hasNext = page.HasNext; + } while (hasNext); - // meterInfos.AddRange(page.Items); - // cursor = page.HasNext ? page.NextScore : null; - // member = page.HasNext ? page.NextMember : null; - // hasNext = page.HasNext; - //} while (hasNext); + timer1.Stop(); + _logger.LogError($"读取数据更花费时间{timer1.ElapsedMilliseconds}毫秒"); #else var meterInfos = await GetAmmeterInfoList(gatherCode); diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index ae2025b..bb09204 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -129,7 +129,7 @@ "OpenDebugMode": true, "UseTableSessionPoolByDefault": false }, - "ServerTagName": "JiSheCollectBus2", + "ServerTagName": "JiSheCollectBus3", "KafkaReplicationFactor": 3, "NumPartitions": 30 } \ No newline at end of file -- 2.47.2 From cadb69f4b97ed27c46890319904789705431fd5d Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 17 Apr 2025 08:29:32 +0800 Subject: [PATCH 113/139] =?UTF-8?q?=E5=B0=8610=E4=B8=87Redis=E7=9A=84Hash?= =?UTF-8?q?=E8=A1=A8=E6=95=B0=E6=8D=AE=E8=AF=BB=E5=8F=96=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=88=B013=E7=A7=92=E4=BB=A5=E5=86=85=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RedisDataCache/RedisDataCacheService.cs | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs index e846ada..67a435d 100644 --- a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs +++ b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs @@ -492,20 +492,14 @@ return {#result_members, result_members, result_scores, hash_data}"; var scores = ((object[])scriptResult[2]).Cast().Select(decimal.Parse).ToList(); var hashData = ((object[])scriptResult[3]).Cast().ToList(); - var validItems = members.Select((m, i) => + var validItems = members.AsParallel() + .Select((m, i) => { - try - { - return !string.IsNullOrEmpty(hashData[i]) - ? BusJsonSerializer.Deserialize(hashData[i]) - : null; - } - catch (Exception ex) - { - _logger.LogError($"反序列化失败: {m} - {ex.Message}"); - return null; - } - }).Where(x => x != null).ToList(); + try { return BusJsonSerializer.Deserialize(hashData[i]); } + catch { return null; } + }) + .Where(x => x != null) + .ToList(); var hasNext = validItems.Count > pageSize; var actualItems = hasNext ? validItems.Take(pageSize) : validItems; -- 2.47.2 From 4737c5b53d2a23240c73d9591a0e6fc4b26cd9ba Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 17 Apr 2025 09:10:01 +0800 Subject: [PATCH 114/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RedisDataCache/RedisDataCacheService.cs | 133 +++++++++--------- 1 file changed, 68 insertions(+), 65 deletions(-) diff --git a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs index 67a435d..a634789 100644 --- a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs +++ b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs @@ -7,6 +7,7 @@ using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.FreeRedisProvider; using Microsoft.Extensions.Logging; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -121,7 +122,7 @@ namespace JiShe.CollectBus.RedisDataCache await semaphore.WaitAsync(); _ = Task.Run(() => - { + { using (var pipe = Instance.StartPipe()) { foreach (var item in batch) @@ -363,7 +364,7 @@ namespace JiShe.CollectBus.RedisDataCache where T : DeviceCacheBasicModel { throw new Exception(); - } + } /// /// 通过ZSET索引获取数据 @@ -385,79 +386,81 @@ namespace JiShe.CollectBus.RedisDataCache bool descending = true) where T : DeviceCacheBasicModel { - // 参数校验 - if (string.IsNullOrWhiteSpace(redisHashCacheKey) || - string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) + // 参数校验增强 + if (string.IsNullOrWhiteSpace(redisHashCacheKey) || string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) { - _logger.LogError("参数异常: HashKey或ZSetKey为空"); + _logger.LogError($"{nameof(GetAllPagedData)} 参数异常,-101"); return null; } if (pageSize < 1 || pageSize > 10000) - throw new ArgumentException("分页大小应在1-10000之间"); + { + _logger.LogError($"{nameof(GetAllPagedData)} 分页大小应在1-10000之间,-102"); + return null; + } var luaScript = @" -local command = ARGV[1] -local range_start = ARGV[2] -local range_end = ARGV[3] -local limit = tonumber(ARGV[4]) -local last_score = ARGV[5] -local last_member = ARGV[6] + local command = ARGV[1] + local range_start = ARGV[2] + local range_end = ARGV[3] + local limit = tonumber(ARGV[4]) + local last_score = ARGV[5] + local last_member = ARGV[6] --- 获取原始数据 -local members -if command == 'ZRANGEBYSCORE' then - members = redis.call(command, KEYS[1], range_start, range_end, 'WITHSCORES', 'LIMIT', 0, limit * 2) -else - members = redis.call('ZREVRANGEBYSCORE', KEYS[1], range_start, range_end, 'WITHSCORES', 'LIMIT', 0, limit * 2) -end + -- 获取原始数据 + local members + if command == 'ZRANGEBYSCORE' then + members = redis.call(command, KEYS[1], range_start, range_end, 'WITHSCORES', 'LIMIT', 0, limit * 2) + else + members = redis.call('ZREVRANGEBYSCORE', KEYS[1], range_start, range_end, 'WITHSCORES', 'LIMIT', 0, limit * 2) + end --- 过滤数据 -local filtered_members = {} -local count = 0 -for i = 1, #members, 2 do - local member = members[i] - local score = members[i+1] - local include = true - if last_score ~= '' and last_member ~= '' then - if command == 'ZRANGEBYSCORE' then - -- 升序:score > last_score 或 (score == last_score 且 member > last_member) - if score == last_score then - include = member > last_member - else - include = tonumber(score) > tonumber(last_score) - end - else - -- 降序:score < last_score 或 (score == last_score 且 member < last_member) - if score == last_score then - include = member < last_member - else - include = tonumber(score) < tonumber(last_score) - end - end - end - if include then - table.insert(filtered_members, member) - table.insert(filtered_members, score) - count = count + 1 - if count >= limit then - break - end - end -end + -- 过滤数据 + local filtered_members = {} + local count = 0 + for i = 1, #members, 2 do + local member = members[i] + local score = members[i+1] + local include = true + if last_score ~= '' and last_member ~= '' then + if command == 'ZRANGEBYSCORE' then + -- 升序:score > last_score 或 (score == last_score 且 member > last_member) + if score == last_score then + include = member > last_member + else + include = tonumber(score) > tonumber(last_score) + end + else + -- 降序:score < last_score 或 (score == last_score 且 member < last_member) + if score == last_score then + include = member < last_member + else + include = tonumber(score) < tonumber(last_score) + end + end + end + if include then + table.insert(filtered_members, member) + table.insert(filtered_members, score) + count = count + 1 + if count >= limit then + break + end + end + end --- 提取有效数据 -local result_members, result_scores = {}, {} -for i=1,#filtered_members,2 do - table.insert(result_members, filtered_members[i]) - table.insert(result_scores, filtered_members[i+1]) -end + -- 提取有效数据 + local result_members, result_scores = {}, {} + for i=1,#filtered_members,2 do + table.insert(result_members, filtered_members[i]) + table.insert(result_scores, filtered_members[i+1]) + end -if #result_members == 0 then return {0,{},{},{}} end + if #result_members == 0 then return {0,{},{},{}} end --- 获取Hash数据 -local hash_data = redis.call('HMGET', KEYS[2], unpack(result_members)) -return {#result_members, result_members, result_scores, hash_data}"; + -- 获取Hash数据 + local hash_data = redis.call('HMGET', KEYS[2], unpack(result_members)) + return {#result_members, result_members, result_scores, hash_data}"; // 调整范围构造逻辑(移除排他符号) string rangeStart, rangeEnd; @@ -504,7 +507,7 @@ return {#result_members, result_members, result_scores, hash_data}"; var hasNext = validItems.Count > pageSize; var actualItems = hasNext ? validItems.Take(pageSize) : validItems; - // 修正分页锚点索引 + //分页锚点索引 decimal? nextScore = null; string nextMember = null; if (hasNext && actualItems.Any()) @@ -522,7 +525,7 @@ return {#result_members, result_members, result_scores, hash_data}"; NextMember = nextMember, TotalCount = await GetTotalCount(redisZSetScoresIndexCacheKey), PageSize = pageSize, - }; + }; } ///// -- 2.47.2 From 13e986168e5dd00e04270ba6b94062bbad744cfa Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 17 Apr 2025 11:29:26 +0800 Subject: [PATCH 115/139] =?UTF-8?q?=E7=99=BE=E4=B8=87=E7=BA=A7=E7=9A=84?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=95=B0=E6=8D=AE=E9=9B=86=E4=B8=AD=E5=99=A8?= =?UTF-8?q?=E6=89=80=E5=9C=A8=E5=88=86=E7=BB=84=E5=8D=95=E7=8B=AC=E4=BF=9D?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RedisDataCache/IRedisDataCacheService.cs | 57 ++-- .../RedisDataCache/RedisDataCacheService.cs | 263 +++++++++++++++++- .../Samples/SampleAppService.cs | 25 +- .../BasicScheduledMeterReadingService.cs | 154 ++++++---- .../Consts/RedisConst.cs | 14 +- .../DeviceGroupBalanceControl.cs | 12 +- .../Models/DeviceCacheBasicModel.cs | 2 +- .../Ammeters/AmmeterInfo.cs | 2 +- .../MeterReadingTelemetryPacketInfo.cs | 141 ++++++++++ 9 files changed, 558 insertions(+), 112 deletions(-) create mode 100644 src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs b/src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs index c6f3613..5f0e8f8 100644 --- a/src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs @@ -113,7 +113,7 @@ namespace JiShe.CollectBus.Application.Contracts /// - /// 通过ZSET索引获取数据 + /// 通过ZSET索引获取数据,支持10万级别数据处理,控制在13秒以内。 /// /// /// 主数据存储Hash缓存Key @@ -133,6 +133,17 @@ namespace JiShe.CollectBus.Application.Contracts where T : DeviceCacheBasicModel; + /// + /// 优化后的分页获取方法(支持百万级数据) + /// + Task> GetAllPagedDataOptimized( + string redisHashCacheKey, + string redisZSetScoresIndexCacheKey, + int pageSize = 1000, + decimal? lastScore = null, + string lastMember = null, + bool descending = true) where T : DeviceCacheBasicModel; + ///// ///// 游标分页查询 ///// @@ -149,28 +160,28 @@ namespace JiShe.CollectBus.Application.Contracts // string excludeMember, // bool descending); - ///// - ///// 批量获取指定分页的数据 - ///// - ///// - ///// Hash表缓存key - ///// Hash表字段集合 - ///// - //Task> BatchGetData( - // string redisHashCacheKey, - // IEnumerable members) - // where T : DeviceCacheBasicModel; + ///// + ///// 批量获取指定分页的数据 + ///// + ///// + ///// Hash表缓存key + ///// Hash表字段集合 + ///// + //Task> BatchGetData( + // string redisHashCacheKey, + // IEnumerable members) + // where T : DeviceCacheBasicModel; - ///// - ///// 获取下一页游标 - ///// - ///// 排序索引ZSET缓存Key - ///// 最后一个唯一标识 - ///// 排序方式 - ///// - //Task GetNextScore( - // string redisZSetScoresIndexCacheKey, - // string lastMember, - // bool descending); + ///// + ///// 获取下一页游标 + ///// + ///// 排序索引ZSET缓存Key + ///// 最后一个唯一标识 + ///// 排序方式 + ///// + //Task GetNextScore( + // string redisZSetScoresIndexCacheKey, + // string lastMember, + // bool descending); } } diff --git a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs index a634789..3c96410 100644 --- a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs +++ b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs @@ -70,13 +70,13 @@ namespace JiShe.CollectBus.RedisDataCache using (var trans = Instance.Multi()) { // 主数据存储Hash - trans.HSet(redisHashCacheKey, data.MemberID, data.Serialize()); + trans.HSet(redisHashCacheKey, data.MemberId, data.Serialize()); // 集中器号分组索引Set缓存 - trans.SAdd(redisSetIndexCacheKey, data.MemberID); + trans.SAdd(redisSetIndexCacheKey, data.MemberId); // 集中器与表计信息排序索引ZSET缓存Key - trans.ZAdd(redisZSetScoresIndexCacheKey, data.ScoreValue, data.MemberID); + trans.ZAdd(redisZSetScoresIndexCacheKey, data.ScoreValue, data.MemberId); var results = trans.Exec(); @@ -128,13 +128,13 @@ namespace JiShe.CollectBus.RedisDataCache foreach (var item in batch) { // 主数据存储Hash - pipe.HSet(redisHashCacheKey, item.MemberID, item.Serialize()); + pipe.HSet(redisHashCacheKey, item.MemberId, item.Serialize()); // Set索引缓存 - pipe.SAdd(redisSetIndexCacheKey, item.MemberID); + pipe.SAdd(redisSetIndexCacheKey, item.MemberId); // ZSET索引缓存Key - pipe.ZAdd(redisZSetScoresIndexCacheKey, item.ScoreValue, item.MemberID); + pipe.ZAdd(redisZSetScoresIndexCacheKey, item.ScoreValue, item.MemberId); } pipe.EndPipe(); } @@ -192,11 +192,11 @@ namespace JiShe.CollectBus.RedisDataCache redisZSetScoresIndexCacheKey }; - var result = await Instance.EvalAsync(luaScript, keys, new[] { data.MemberID }); + var result = await Instance.EvalAsync(luaScript, keys, new[] { data.MemberId }); if ((int)result == 0) { - _logger.LogError($"{nameof(RemoveCacheDataAsync)} 删除指定Key{redisHashCacheKey}的{data.MemberID}数据失败,-102"); + _logger.LogError($"{nameof(RemoveCacheDataAsync)} 删除指定Key{redisHashCacheKey}的{data.MemberId}数据失败,-102"); } } @@ -248,13 +248,13 @@ namespace JiShe.CollectBus.RedisDataCache }, new object[] { - newData.MemberID, + newData.MemberId, newData.Serialize() }); if ((int)result == 0) { - _logger.LogError($"{nameof(ModifyDataAsync)} 更新指定Key{redisHashCacheKey}的{newData.MemberID}数据失败,-102"); + _logger.LogError($"{nameof(ModifyDataAsync)} 更新指定Key{redisHashCacheKey}的{newData.MemberId}数据失败,-102"); } } @@ -328,7 +328,7 @@ namespace JiShe.CollectBus.RedisDataCache }, new object[] { - newData.MemberID, + newData.MemberId, oldMemberId, newData.Serialize(), newData.ScoreValue.ToString() ?? "", @@ -336,7 +336,7 @@ namespace JiShe.CollectBus.RedisDataCache if ((int)result == 0) { - _logger.LogError($"{nameof(ModifyDataAsync)} 更新指定Key{redisHashCacheKey}的{newData.MemberID}数据失败,-102"); + _logger.LogError($"{nameof(ModifyDataAsync)} 更新指定Key{redisHashCacheKey}的{newData.MemberId}数据失败,-102"); } } @@ -364,10 +364,245 @@ namespace JiShe.CollectBus.RedisDataCache where T : DeviceCacheBasicModel { throw new Exception(); - } + } + /// - /// 通过ZSET索引获取数据 + /// 优化后的分页获取方法(支持百万级数据) + /// + public async Task> GetAllPagedDataOptimized( + string redisHashCacheKey, + string redisZSetScoresIndexCacheKey, + int pageSize = 1000, + decimal? lastScore = null, + string lastMember = null, + bool descending = true) where T : DeviceCacheBasicModel + { + // 参数校验 + if (string.IsNullOrWhiteSpace(redisHashCacheKey) || + string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) + { + _logger.LogError("Invalid parameters in {Method}", nameof(GetAllPagedDataOptimized)); + return new BusCacheGlobalPagedResult { Items = new List() }; + } + + pageSize = Math.Clamp(pageSize, 1, 10000); + + const string luaScript = @" + local command = ARGV[1] + local range_start = ARGV[2] + local range_end = ARGV[3] + local limit = tonumber(ARGV[4]) + local last_score = ARGV[5] + local last_member = ARGV[6] + + -- 获取扩展数据(3倍分页大小) + local members + if command == 'ZRANGEBYSCORE' then + members = redis.call('ZRANGEBYSCORE', KEYS[1], range_start, range_end, + 'WITHSCORES', 'LIMIT', 0, limit * 5) + else + members = redis.call('ZREVRANGEBYSCORE', KEYS[1], range_start, range_end, + 'WITHSCORES', 'LIMIT', 0, limit * 5) + end + + -- 精确分页过滤 + local filtered = {} + local count = 0 + local start_index = 1 + + -- 存在锚点时寻找起始位置 + if last_score ~= '' and last_member ~= '' then + for i=1,#members,2 do + local score = members[i+1] + local member = members[i] + + if command == 'ZRANGEBYSCORE' then + if tonumber(score) > tonumber(last_score) then + start_index = i + break + elseif tonumber(score) == tonumber(last_score) then + if member > last_member then + start_index = i + break + end + end + else + if tonumber(score) < tonumber(last_score) then + start_index = i + break + elseif tonumber(score) == tonumber(last_score) then + if member < last_member then + start_index = i + break + end + end + end + end + end + + -- 收集有效数据 + for i=start_index,#members,2 do + if count >= limit then break end + table.insert(filtered, members[i]) + table.insert(filtered, members[i+1]) + count = count + 1 + end + + -- 提取有效数据 + local result_members = {} + local result_scores = {} + for i=1,#filtered,2 do + table.insert(result_members, filtered[i]) + table.insert(result_scores, filtered[i+1]) + end + + if #result_members == 0 then return {0,{},{},{}} end + + -- 获取Hash数据 + local hash_data = redis.call('HMGET', KEYS[2], unpack(result_members)) + return {#result_members, result_members, result_scores, hash_data}"; + + // 构造查询范围(包含等于) + string rangeStart, rangeEnd; + if (descending) + { + rangeStart = lastScore.HasValue ? lastScore.Value.ToString() : "+inf"; + rangeEnd = "-inf"; + } + else + { + rangeStart = lastScore.HasValue ? lastScore.Value.ToString() : "-inf"; + rangeEnd = "+inf"; + } + + try + { + var scriptResult = (object[])await Instance.EvalAsync( + luaScript, + new[] { redisZSetScoresIndexCacheKey, redisHashCacheKey }, + new object[] + { + descending ? "ZREVRANGEBYSCORE" : "ZRANGEBYSCORE", + rangeStart, + rangeEnd, + pageSize, + lastScore?.ToString() ?? "", + lastMember ?? "" + }); + + var itemCount = (long)scriptResult[0]; + if (itemCount == 0) + return new BusCacheGlobalPagedResult { Items = new List() }; + + // 处理结果集 + var members = ((object[])scriptResult[1]).Cast().ToList(); + var scores = ((object[])scriptResult[2]).Cast() + .Select(decimal.Parse).ToList(); + var hashData = ((object[])scriptResult[3]).Cast().ToList(); + + var validItems = members.AsParallel() + .Select((m, i) => + { + try { return BusJsonSerializer.Deserialize(hashData[i]); } + catch { return null; } + }) + .Where(x => x != null) + .ToList(); + + // 精确分页控制 + var hasNext = validItems.Count >= pageSize; + var actualItems = validItems.Take(pageSize).ToList(); + + // 计算下一页锚点(必须基于原始排序) + decimal? nextScore = null; + string nextMember = null; + if (hasNext && actualItems.Count > 0) + { + var lastValidIndex = Math.Min(pageSize - 1, members.Count - 1); + nextScore = scores[lastValidIndex]; + nextMember = members[lastValidIndex]; + } + + return new BusCacheGlobalPagedResult + { + Items = actualItems, + HasNext = hasNext, + NextScore = nextScore, + NextMember = nextMember, + TotalCount = await GetTotalCount(redisZSetScoresIndexCacheKey), + PageSize = pageSize + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "分页查询异常"); + return new BusCacheGlobalPagedResult { Items = new List() }; + } + } + + /// + /// 并行分页导出方法(百万级数据支持) + /// + public async Task> FullExportParallel( + string hashKey, + string zsetKey, + int parallelDegree = 10, + int pageSize = 5000) where T : DeviceCacheBasicModel + { + var result = new ConcurrentBag(); + var totalCount = await GetTotalCount(zsetKey); + var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize); + + var semaphore = new SemaphoreSlim(parallelDegree); + var tasks = new List(); + + decimal? lastScore = null; + string lastMember = null; + var isDescending = true; + + for (int page = 0; page < totalPages; page++) + { + await semaphore.WaitAsync(); + + tasks.Add(Task.Run(async () => + { + try + { + var pageResult = await GetAllPagedData( + hashKey, + zsetKey, + pageSize, + lastScore, + lastMember, + isDescending); + + foreach (var item in pageResult.Items) + { + result.Add(item); + } + + // 更新分页锚点 + if (pageResult.HasNext) + { + lastScore = pageResult.NextScore; + lastMember = pageResult.NextMember; + } + } + finally + { + semaphore.Release(); + } + })); + } + + await Task.WhenAll(tasks); + return result.ToList(); + } + + + /// + /// 通过ZSET索引获取数据,支持10万级别数据处理,控制在13秒以内。 /// /// /// 主数据存储Hash缓存Key diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 3af4b6a..3658557 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -191,40 +191,41 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS } /// - /// 测试单个测点数据项 + /// 测试Redis批量读取10万条数据性能 /// /// [HttpGet] public async Task TestRedisCacheGetAllPagedData() { var timeDensity = "15"; - string SystemType = ""; + string SystemType = "Energy"; string ServerTagName = "JiSheCollectBus2"; var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; var redisCacheMeterInfoZSetScoresIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; - var timer = Stopwatch.StartNew(); + var timer1 = Stopwatch.StartNew(); decimal? cursor = null; string member = null; bool hasNext; List meterInfos = new List(); do { - var page = await _redisDataCacheService - .GetAllPagedData( - redisCacheMeterInfoHashKeyTemp, - redisCacheMeterInfoZSetScoresIndexKeyTemp); + var page = await _redisDataCacheService.GetAllPagedData( + redisCacheMeterInfoHashKeyTemp, + redisCacheMeterInfoZSetScoresIndexKeyTemp, + pageSize: 1000, + lastScore: cursor, + lastMember: member); meterInfos.AddRange(page.Items); - cursor = page.NextScore; - member = page.NextMember; + cursor = page.HasNext ? page.NextScore : null; + member = page.HasNext ? page.NextMember : null; hasNext = page.HasNext; } while (hasNext); - timer.Stop(); - - _logger.LogInformation($"{nameof(TestRedisCacheGetAllPagedData)} 获取电表缓存数据完成,耗时{timer.ElapsedMilliseconds}毫秒"); + timer1.Stop(); + _logger.LogError($"读取数据更花费时间{timer1.ElapsedMilliseconds}毫秒"); } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index d32cdb2..27de191 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -15,6 +15,7 @@ using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.RedisDataCache; using JiShe.CollectBus.Repository.MeterReadingRecord; using Mapster; using Microsoft.Extensions.Logging; @@ -23,6 +24,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using static FreeSql.Internal.GlobalFilter; namespace JiShe.CollectBus.ScheduledMeterReading { @@ -121,32 +123,45 @@ namespace JiShe.CollectBus.ScheduledMeterReading _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} 构建待处理的下发指令任务处理时Key=>{item}时间节点不在当前时间范围内,103"); continue; } - - - - //获取缓存中的表信息 - var redisKeyList = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, ServerTagName, meteryType, timeDensity)}*"; - var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-104"); - return; - } - + var meterTypes = EnumExtensions.ToEnumDictionary(); if (meteryType == MeterTypeEnum.Ammeter.ToString()) { - // 解析结果(结果为嵌套数组) - var meterInfos = await GetMeterRedisCacheListData(oneMinutekeyList, SystemType, ServerTagName, $"{timeDensity}", meterTypes[meteryType]); + var timer = Stopwatch.StartNew(); + + //获取对应频率中的所有电表信息 + var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; + var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; + var redisCacheMeterInfoZSetScoresIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; + + List meterInfos = new List(); + decimal? cursor = null; + string member = null; + bool hasNext; + do + { + var page = await _redisDataCacheService.GetAllPagedData( + redisCacheMeterInfoHashKeyTemp, + redisCacheMeterInfoZSetScoresIndexKeyTemp, + pageSize: 1000, + lastScore: cursor, + lastMember: member); + + meterInfos.AddRange(page.Items); + cursor = page.HasNext ? page.NextScore : null; + member = page.HasNext ? page.NextMember : null; + hasNext = page.HasNext; + } while (hasNext); + if (meterInfos == null || meterInfos.Count <= 0) { + timer.Stop(); _logger.LogError($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-105"); return; } //await AmmerterScheduledMeterReadingIssued(timeDensity, meterInfos); - var timer = Stopwatch.StartNew(); //处理数据 //await DeviceGroupBalanceControl.ProcessGenericListAsync( @@ -163,14 +178,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading await DeviceGroupBalanceControl.ProcessWithThrottleAsync( items: meterInfos, deviceIdSelector: data => data.FocusAddress, - processor: data => + processor: (data,groupIndex) => { - _ = AmmerterCreatePublishTask(timeDensity, data); + _ = AmmerterCreatePublishTask(timeDensity, data, groupIndex,tasksToBeIssueModel.NextTaskTime.ToString("yyyyMMddHHmmss")); } ); timer.Stop(); - _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建完成,{timer.ElapsedMilliseconds},{oneMinutekeyList.Length}"); + _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建完成,{timer.ElapsedMilliseconds},总共{meterInfos.Count}表计信息"); } else if (meteryType == MeterTypeEnum.WaterMeter.ToString()) @@ -230,37 +245,65 @@ namespace JiShe.CollectBus.ScheduledMeterReading // meterInfos.Add(tempData); // //focusAddressDataLista.Add(item.FocusAddress); //} - - //DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista); - + + + var timeDensity = "15"; var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; var redisCacheMeterInfoZSetScoresIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; - var timer1 = Stopwatch.StartNew(); - decimal? cursor = null; - string member = null; - bool hasNext; List meterInfos = new List(); - do + List focusAddressDataLista = new List(); + var timer1 = Stopwatch.StartNew(); + //decimal? cursor = null; + //string member = null; + //bool hasNext; + //do + //{ + // var page = await _redisDataCacheService.GetAllPagedDataOptimized( + // redisCacheMeterInfoHashKeyTemp, + // redisCacheMeterInfoZSetScoresIndexKeyTemp, + // pageSize: 1000, + // lastScore: cursor, + // lastMember: member); + + // meterInfos.AddRange(page.Items); + // cursor = page.HasNext ? page.NextScore : null; + // member = page.HasNext ? page.NextMember : null; + // hasNext = page.HasNext; + //} while (hasNext); + + var allIds = new HashSet(); + decimal? score = null; + string member = null; + + while (true) { - var page = await _redisDataCacheService.GetAllPagedData( + var page = await _redisDataCacheService.GetAllPagedDataOptimized( redisCacheMeterInfoHashKeyTemp, redisCacheMeterInfoZSetScoresIndexKeyTemp, pageSize: 1000, - lastScore: cursor, + lastScore: score, lastMember: member); meterInfos.AddRange(page.Items); - cursor = page.HasNext ? page.NextScore : null; - member = page.HasNext ? page.NextMember : null; - hasNext = page.HasNext; - } while (hasNext); + focusAddressDataLista.AddRange(page.Items.Select(d=>d.FocusAddress)); + foreach (var item in page.Items) + { + if (!allIds.Add(item.MemberId)) + throw new Exception("Duplicate data found!"); + } + if (!page.HasNext) break; + score = page.NextScore; + member = page.NextMember; + } + timer1.Stop(); _logger.LogError($"读取数据更花费时间{timer1.ElapsedMilliseconds}毫秒"); - + //DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista); + //return; #else var meterInfos = await GetAmmeterInfoList(gatherCode); #endif @@ -656,9 +699,11 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// /// 采集频率 /// 集中器号hash分组的集中器集合数据 + /// 集中器所在分组 + /// 时间格式的任务批次名称 /// private async Task AmmerterCreatePublishTask(int timeDensity - , AmmeterInfo ammeterInfo) + , AmmeterInfo ammeterInfo,int groupIndex,string taskBatch) { var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; //todo 检查需要待补抄的电表的时间点信息,保存到需要待补抄的缓存中。如果此线程异常,该如何补偿? @@ -666,7 +711,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading var currentTime = DateTime.Now; var pendingCopyReadTime = currentTime.AddMinutes(timeDensity); //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 - var redisCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}{ammeterInfo.FocusAddress}"; + var redisCacheTelemetryPacketInfoHashKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; + var redisCacheTelemetryPacketInfoSetIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoSetIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; + var redisCacheTelemetryPacketInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; if (string.IsNullOrWhiteSpace(ammeterInfo.ItemCodes)) { @@ -747,7 +794,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading } } - Dictionary keyValuePairs = new Dictionary(); + //Dictionary keyValuePairs = new Dictionary(); + List taskList = new List(); foreach (var tempItem in tempCodes) { @@ -802,7 +850,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading - var meterReadingRecords = new MeterReadingRecords() + var meterReadingRecords = new MeterReadingTelemetryPacketInfo() { ProjectID = ammeterInfo.ProjectID, DatabaseBusiID = ammeterInfo.DatabaseBusiID, @@ -812,7 +860,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading MeterId = ammeterInfo.MeterId, MeterType = MeterTypeEnum.Ammeter, FocusAddress = ammeterInfo.FocusAddress, - FocusID = ammeterInfo.FocusId, + FocusId = ammeterInfo.FocusId, AFN = aFN, Fn = fn, ItemCode = tempItem, @@ -822,9 +870,10 @@ namespace JiShe.CollectBus.ScheduledMeterReading IssuedMessageId = GuidGenerator.Create().ToString(), IssuedMessageHexString = Convert.ToHexString(dataInfos), }; - meterReadingRecords.CreateDataId(GuidGenerator.Create()); - keyValuePairs.TryAdd($"{ammeterInfo.MeterId}_{tempItem}", meterReadingRecords); + //meterReadingRecords.CreateDataId(GuidGenerator.Create()); + + taskList.Add(meterReadingRecords); } //TimeSpan timeSpan = TimeSpan.FromMicroseconds(5); //await Task.Delay(timeSpan); @@ -832,14 +881,25 @@ namespace JiShe.CollectBus.ScheduledMeterReading //return keyValuePairs; // await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); - using (var pipe = FreeRedisProvider.Instance.StartPipe()) + //using (var pipe = FreeRedisProvider.Instance.StartPipe()) + //{ + // pipe.HSet(redisCacheKey, keyValuePairs); + // object[] ret = pipe.EndPipe(); + //} + if (taskList == null + || taskList.Count() <= 0 + || string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoHashKey) + || string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoSetIndexKey) + || string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoZSetScoresIndexKey)) { - pipe.HSet(redisCacheKey, keyValuePairs); - object[] ret = pipe.EndPipe(); + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 写入参数异常,{redisCacheTelemetryPacketInfoHashKey}:{redisCacheTelemetryPacketInfoSetIndexKey}:{redisCacheTelemetryPacketInfoZSetScoresIndexKey},-101"); + return; } - - - await Task.CompletedTask; + await _redisDataCacheService.BatchInsertDataAsync( + redisCacheTelemetryPacketInfoHashKey, + redisCacheTelemetryPacketInfoSetIndexKey, + redisCacheTelemetryPacketInfoZSetScoresIndexKey, + taskList); } /// @@ -1088,7 +1148,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading IssuedMessageId = GuidGenerator.Create().ToString(), IssuedMessageHexString = Convert.ToHexString(dataInfos), }; - meterReadingRecords.CreateDataId(GuidGenerator.Create()); + //meterReadingRecords.CreateDataId(GuidGenerator.Create()); keyValuePairs.TryAdd($"{ammeter.MeterId}_{tempItem}", meterReadingRecords); } diff --git a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs index dce5307..7ac170b 100644 --- a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/RedisConst.cs @@ -49,23 +49,23 @@ namespace JiShe.CollectBus.Common.Consts /// /// 缓存待下发的指令生产任务数据,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 /// - public const string CacheTasksToBeIssuedKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TaskInfo}:{"{2}"}{"{3}"}"; + public const string CacheTasksToBeIssuedKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TaskInfo}:{"{2}"}:{"{3}"}"; public const string TelemetryPacket = "TelemetryPacket"; /// - /// 缓存表计下发指令数据集,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 + /// 缓存表计下发指令数据集,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率,{4}=>集中器所在分组,{5}=>时间格式的任务批次 /// - public const string CacheTelemetryPacketInfoHashKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TelemetryPacket}:{"{2}"}:{"{3}"}"; + public const string CacheTelemetryPacketInfoHashKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TelemetryPacket}:{"{2}"}:{"{3}"}:{"{4}"}:{"{5}"}"; /// - /// 缓存表计下发指令数据集索引Set缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 + /// 缓存表计下发指令数据集索引Set缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率,{4}=>集中器所在分组,{5}=>时间格式的任务批次 /// - public const string CacheTelemetryPacketInfoSetIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TelemetryPacket}:{"{2}"}:SetIndex:{"{3}"}"; + public const string CacheTelemetryPacketInfoSetIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TelemetryPacket}:{"{2}"}:SetIndex:{"{3}"}:{"{4}"}:{"{5}"}"; /// - /// 缓存表计下发指令数据集排序索引ZSET缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率 + /// 缓存表计下发指令数据集排序索引ZSET缓存Key,{0}=>系统类型,{1}=>应用服务部署标记,{2}=>表计类别,{3}=>采集频率,{4}=>集中器所在分组,{5}=>时间格式的任务批次 /// - public const string CacheTelemetryPacketInfoZSetScoresIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TelemetryPacket}:{"{2}"}:ZSetScoresIndex:{"{3}"}"; + public const string CacheTelemetryPacketInfoZSetScoresIndexKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:{TelemetryPacket}:{"{2}"}:ZSetScoresIndex:{"{3}"}:{"{4}"}:{"{5}"}"; ///// ///// 缓存设备平衡关系映射结果,{0}=>系统类型,{1}=>应用服务部署标记 diff --git a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs index 4be0f4f..06b7d70 100644 --- a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs +++ b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs @@ -161,7 +161,6 @@ namespace JiShe.CollectBus.Common.DeviceBalanceControl MaxDegreeOfParallelism = maxThreads.Value, }; - TimeSpan timeSpan = TimeSpan.FromMicroseconds(5); await Task.Run(() => { Parallel.For(0, cache.CachedGroups.Length, options, async groupId => @@ -169,8 +168,7 @@ namespace JiShe.CollectBus.Common.DeviceBalanceControl var queue = groupQueues[groupId]; while (queue.TryDequeue(out T item)) { - await Task.Delay(timeSpan); - processor(item, Thread.CurrentThread.ManagedThreadId); + processor(item, groupId); } }); }); @@ -183,14 +181,14 @@ namespace JiShe.CollectBus.Common.DeviceBalanceControl /// 已经分组的设备信息 /// 部分或者全部的已经分组的设备集合 /// 从泛型对象提取deviceId - /// 处理委托(参数:当前对象,线程ID) + /// 处理委托(参数:当前对象,分组ID) /// 可选最佳并发度 /// /// public static async Task ProcessWithThrottleAsync( List items, Func deviceIdSelector, - Action processor, + Action processor, int? maxConcurrency = null) { var cache = _currentCache ?? throw new InvalidOperationException("缓存未初始化"); @@ -244,7 +242,7 @@ namespace JiShe.CollectBus.Common.DeviceBalanceControl /// /// 分组异步处理(带节流) /// - private static async Task ProcessItemAsync(T item, Action processor, int groupId) + private static async Task ProcessItemAsync(T item, Action processor, int groupId) { // 使用内存缓存降低CPU负载 await Task.Yield(); // 立即释放当前线程 @@ -255,7 +253,7 @@ namespace JiShe.CollectBus.Common.DeviceBalanceControl { ExecutionContext.Run(context!, state => { - processor(item); + processor(item,groupId); }, null); }); } diff --git a/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs b/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs index 335c17c..d397151 100644 --- a/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs +++ b/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs @@ -24,7 +24,7 @@ namespace JiShe.CollectBus.Common.Models /// /// 关系映射标识,用于ZSet的Member字段和Set的Value字段,具体值可以根据不同业务场景进行定义 /// - public virtual string MemberID => $"{FocusId}:{MeterId}"; + public virtual string MemberId => $"{FocusId}:{MeterId}"; /// /// ZSet排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳 diff --git a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs index 3df01b5..8b082bb 100644 --- a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs +++ b/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs @@ -14,7 +14,7 @@ namespace JiShe.CollectBus.Ammeters /// 关系映射标识,用于ZSet的Member字段和Set的Value字段,具体值可以根据不同业务场景进行定义 /// [Column(IsIgnore = true)] - public override string MemberID => $"{FocusId}:{MeterId}"; + public override string MemberId => $"{FocusId}:{MeterId}"; /// /// ZSet排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳 diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs new file mode 100644 index 0000000..c3f75d3 --- /dev/null +++ b/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs @@ -0,0 +1,141 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Entities.Auditing; + +namespace JiShe.CollectBus.IotSystems.MeterReadingRecords +{ + /// + /// 抄读任务Redis缓存数据记录 + /// + public class MeterReadingTelemetryPacketInfo : DeviceCacheBasicModel + { + /// + /// 关系映射标识,用于ZSet的Member字段和Set的Value字段,具体值可以根据不同业务场景进行定义 + /// + public override string MemberId => $"{FocusId}:{MeterId}:{ItemCode}"; + + /// + /// ZSet排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳 + /// + public override long ScoreValue => ((long)FocusId << 32) | (uint)DateTime.Now.Ticks; + + + /// + /// 是否手动操作 + /// + public bool ManualOrNot { get; set; } + + /// + /// 任务数据唯一标记 + /// + public string TaskMark { get; set; } + + /// + /// 时间戳标记,IoTDB时间列处理,上报通过构建标记获取唯一标记匹配时间戳。 + /// + public long Timestamps { get; set; } + + /// + /// 是否超时 + /// + public bool IsTimeout { get; set; } = false; + + /// + /// 待抄读时间 + /// + public DateTime PendingCopyReadTime { get; set; } + + + /// + /// 集中器地址 + /// + public string FocusAddress { get; set; } + + /// + /// 表地址 + /// + public string MeterAddress { get; set; } + + /// + /// 表类型 + /// + public MeterTypeEnum MeterType { get; set; } + + /// + /// 项目ID + /// + public int ProjectID { get; set; } + + /// + /// 数据库业务ID + /// + public int DatabaseBusiID { get; set; } + + /// + /// AFN功能码 + /// + public AFN AFN { get; set; } + + /// + /// 抄读功能码 + /// + public int Fn { get; set; } + + /// + /// 抄读计量点 + /// + public int Pn { get; set; } + + /// + /// 采集项编码 + /// + public string ItemCode { get; set;} + + + /// + /// 创建时间 + /// + public DateTime CreationTime { get; set; } + + /// + /// 下发消息内容 + /// + public string IssuedMessageHexString { get; set; } + + /// + /// 下发消息Id + /// + public string IssuedMessageId { get; set; } + + /// + /// 消息上报内容 + /// + public string? ReceivedMessageHexString { get; set; } + + /// + /// 消息上报时间 + /// + public DateTime? ReceivedTime { get; set; } + + /// + /// 上报消息Id + /// + public string ReceivedMessageId { get; set; } + + /// + /// 上报报文解析备注,异常情况下才有 + /// + public string ReceivedRemark { get; set; } + + //public void CreateDataId(Guid Id) + //{ + // this.Id = Id; + //} + } +} -- 2.47.2 From 8fd5f985ab23cd794438dfa3ebfd554726996b0e Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Thu, 17 Apr 2025 11:42:35 +0800 Subject: [PATCH 116/139] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusKafkaModule.cs | 42 ++++++++++++++- .../Consumer/ConsumerService.cs | 43 ++++++--------- .../JiShe.CollectBus.Kafka.csproj | 4 -- .../KafkaOptionConfig.cs | 53 +++++++++++++++++++ .../KafkaSubcribesExtensions.cs | 50 +++++++---------- .../Producer/ProducerService.cs | 27 +++++----- 6 files changed, 144 insertions(+), 75 deletions(-) create mode 100644 src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index fe0e866..48a8c35 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -16,6 +16,15 @@ namespace JiShe.CollectBus.Kafka { public override void ConfigureServices(ServiceConfigurationContext context) { + var configuration = context.Services.GetConfiguration(); + var kafkaSection = configuration.GetSection("Kafka"); + KafkaOptionConfig kafkaOptionConfig = new KafkaOptionConfig (); + kafkaSection.Bind(kafkaOptionConfig); + if (configuration["ServerTagName"] != null) + { + kafkaOptionConfig.ServerTagName = configuration["ServerTagName"]!; + } + context.Services.AddSingleton(kafkaOptionConfig); // 注册Producer context.Services.AddSingleton(); // 注册Consumer @@ -25,8 +34,39 @@ namespace JiShe.CollectBus.Kafka public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); - app.UseKafkaSubscribers(Assembly.Load("JiShe.CollectBus.Application")); + // 程序运行目录 + var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); + if (!string.IsNullOrWhiteSpace(assemblyPath)) + { + var dllFiles = Directory.GetFiles(assemblyPath, "*.dll"); + var kafkaSubscriberAssemblies = new List(); + foreach (var file in dllFiles) + { + try + { + // 跳过已加载的程序集 + var assemblyName = AssemblyName.GetAssemblyName(file); + var existingAssembly = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().FullName == assemblyName.FullName); + var assembly = existingAssembly ?? Assembly.LoadFrom(file); + // 检查程序集是否包含 IKafkaSubscribe 的实现类 + var hasSubscriber = assembly.GetTypes() + .Any(type => + typeof(IKafkaSubscribe).IsAssignableFrom(type) && // 实现接口 + !type.IsAbstract && !type.IsInterface); // 排除抽象类和接口本身 + + if (hasSubscriber) + { + kafkaSubscriberAssemblies.Add(assembly); + } + } + catch{} + app.UseKafkaSubscribers(kafkaSubscriberAssemblies.ToArray()); + } + } + // 获取程序集 + //app.UseKafkaSubscribers(new[] { Assembly.Load("JiShe.CollectBus.Application")}); } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index da9258c..eeb5661 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -1,16 +1,7 @@ using Confluent.Kafka; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using JiShe.CollectBus.Kafka.Attributes; -using Volo.Abp.DependencyInjection; -using JiShe.CollectBus.Kafka.AdminClient; -using static Confluent.Kafka.ConfigPropertyNames; using System.Collections.Concurrent; -using System.Text.RegularExpressions; -using NUglify.Html; -using Serilog; -using System; using System.Text; namespace JiShe.CollectBus.Kafka.Consumer @@ -21,12 +12,14 @@ namespace JiShe.CollectBus.Kafka.Consumer private readonly IConfiguration _configuration; private readonly ConcurrentDictionary _consumerStore = new(); + private readonly KafkaOptionConfig _kafkaOptionConfig; private class KafkaConsumer where TKey : notnull where TValue : class { } - public ConsumerService(IConfiguration configuration, ILogger logger) + public ConsumerService(IConfiguration configuration, ILogger logger, KafkaOptionConfig kafkaOptionConfig) { _configuration = configuration; _logger = logger; + _kafkaOptionConfig = kafkaOptionConfig; } #region private 私有方法 @@ -49,11 +42,9 @@ namespace JiShe.CollectBus.Kafka.Consumer private ConsumerConfig BuildConsumerConfig(string? groupId = null) { - var enableAuth = bool.Parse(_configuration["Kafka:EnableAuthorization"]!); - var config = new ConsumerConfig { - BootstrapServers = _configuration["Kafka:BootstrapServers"], + BootstrapServers = _kafkaOptionConfig.BootstrapServers, GroupId = groupId ?? "default", AutoOffsetReset = AutoOffsetReset.Earliest, EnableAutoCommit = false, // 禁止AutoCommit @@ -62,12 +53,12 @@ namespace JiShe.CollectBus.Kafka.Consumer FetchMaxBytes = 1024 * 1024 * 50 // 增加拉取大小(50MB) }; - if (enableAuth) + if (_kafkaOptionConfig.EnableAuthorization) { - config.SecurityProtocol = SecurityProtocol.SaslPlaintext; - config.SaslMechanism = SaslMechanism.Plain; - config.SaslUsername = _configuration["Kafka:SaslUserName"]; - config.SaslPassword = _configuration["Kafka:SaslPassword"]; + config.SecurityProtocol = _kafkaOptionConfig.SecurityProtocol; + config.SaslMechanism = _kafkaOptionConfig.SaslMechanism; + config.SaslUsername = _kafkaOptionConfig.SaslUserName; + config.SaslPassword = _kafkaOptionConfig.SaslPassword; } return config; @@ -140,9 +131,9 @@ namespace JiShe.CollectBus.Kafka.Consumer await Task.Delay(TimeSpan.FromSeconds(1),cts.Token); continue; } - if (bool.Parse(_configuration["KafkaConsumer:EnableFilter"]!)) + if (_kafkaOptionConfig.EnableFilter) { - var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } }; // 检查 Header 是否符合条件 if (!headersFilter.Match(result.Message.Headers)) { @@ -208,9 +199,9 @@ namespace JiShe.CollectBus.Kafka.Consumer await Task.Delay(100, cts.Token); continue; } - if (bool.Parse(_configuration["KafkaConsumer:EnableFilter"]!)) + if (_kafkaOptionConfig.EnableFilter) { - var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } }; // 检查 Header 是否符合条件 if (!headersFilter.Match(result.Message.Headers)) { @@ -296,9 +287,9 @@ namespace JiShe.CollectBus.Kafka.Consumer } else if (result.Message.Value != null) { - if (bool.Parse(_configuration["KafkaConsumer:EnableFilter"]!)) + if (_kafkaOptionConfig.EnableFilter) { - var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } }; // 检查 Header 是否符合条件 if (!headersFilter.Match(result.Message.Headers)) { @@ -430,9 +421,9 @@ namespace JiShe.CollectBus.Kafka.Consumer } else if (result.Message.Value != null) { - if (bool.Parse(_configuration["KafkaConsumer:EnableFilter"]!)) + if (_kafkaOptionConfig.EnableFilter) { - var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } }; + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } }; // 检查 Header 是否符合条件 if (!headersFilter.Match(result.Message.Headers)) { diff --git a/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj b/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj index 9518b0b..cef24d5 100644 --- a/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj +++ b/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj @@ -12,8 +12,4 @@ - - - - diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs new file mode 100644 index 0000000..50ae47e --- /dev/null +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs @@ -0,0 +1,53 @@ +using Confluent.Kafka; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka +{ + public class KafkaOptionConfig + { + /// + /// kafka地址 + /// + public string BootstrapServers { get; set; } = null!; + + /// + /// 服务器标识 + /// + public string ServerTagName { get; set; }= "KafkaFilterKey"; + + /// + /// 是否开启过滤器 + /// + public bool EnableFilter { get; set; }= true; + + /// + /// 是否开启认证 + /// + public bool EnableAuthorization { get; set; } = false; + + /// + /// 安全协议 + /// + public SecurityProtocol SecurityProtocol { get; set; } = SecurityProtocol.SaslPlaintext; + + /// + /// 认证方式 + /// + public SaslMechanism SaslMechanism { get; set; }= SaslMechanism.Plain; + + /// + /// 用户名 + /// + public string? SaslUserName { get; set; } + + /// + /// 密码 + /// + public string? SaslPassword { get; set; } + + } +} diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs index 8860061..1f83540 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -1,7 +1,4 @@ using Confluent.Kafka; -using DeviceDetectorNET; -using JiShe.CollectBus.Common.Enums; -using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Kafka.AdminClient; using JiShe.CollectBus.Kafka.Attributes; using JiShe.CollectBus.Kafka.Consumer; @@ -9,16 +6,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Primitives; -using Newtonsoft.Json; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using static Confluent.Kafka.ConfigPropertyNames; namespace JiShe.CollectBus.Kafka { @@ -29,14 +17,8 @@ namespace JiShe.CollectBus.Kafka /// /// /// - public static void UseKafkaSubscribers(this IApplicationBuilder app, Assembly assembly) + public static void UseKafkaSubscribers(this IApplicationBuilder app, Assembly[] assemblys) { - var subscribeTypes = assembly.GetTypes() - .Where(t => typeof(IKafkaSubscribe).IsAssignableFrom(t)) - .ToList(); - - if (subscribeTypes.Count == 0) return; - var provider = app.ApplicationServices; var lifetime = provider.GetRequiredService(); @@ -45,18 +27,26 @@ namespace JiShe.CollectBus.Kafka var logger = provider.GetRequiredService>(); int threadCount = 0; int topicCount = 0; - foreach (var subscribeType in subscribeTypes) + foreach (Assembly assembly in assemblys) { - var subscribes = provider.GetServices(subscribeType).ToList(); - subscribes.ForEach(subscribe => { - - if(subscribe is IKafkaSubscribe) - { - Tuple tuple= BuildKafkaSubscriber(subscribe, provider, logger); - threadCount+= tuple.Item1; - topicCount+= tuple.Item2; - } - }); + var subscribeTypes = assembly.GetTypes() + .Where(t => typeof(IKafkaSubscribe).IsAssignableFrom(t)) + .ToList(); + + if (subscribeTypes.Count == 0) return; + foreach (var subscribeType in subscribeTypes) + { + var subscribes = provider.GetServices(subscribeType).ToList(); + subscribes.ForEach(subscribe => { + + if (subscribe is IKafkaSubscribe) + { + Tuple tuple = BuildKafkaSubscriber(subscribe, provider, logger); + threadCount += tuple.Item1; + topicCount += tuple.Item2; + } + }); + } } logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); }); diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs index 0ddf36b..27702e0 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs @@ -19,11 +19,12 @@ namespace JiShe.CollectBus.Kafka.Producer private readonly IConfiguration _configuration; private readonly ConcurrentDictionary _producerCache = new(); private class KafkaProducer where TKey : notnull where TValue : class { } - - public ProducerService(IConfiguration configuration,ILogger logger) + private readonly KafkaOptionConfig _kafkaOptionConfig; + public ProducerService(IConfiguration configuration,ILogger logger, KafkaOptionConfig kafkaOptionConfig) { _configuration = configuration; _logger = logger; + _kafkaOptionConfig = kafkaOptionConfig; } #region private 私有方法 @@ -51,11 +52,9 @@ namespace JiShe.CollectBus.Kafka.Producer /// private ProducerConfig BuildProducerConfig() { - var enableAuth = bool.Parse(_configuration["Kafka:EnableAuthorization"]!); - var config = new ProducerConfig { - BootstrapServers = _configuration["Kafka:BootstrapServers"], + BootstrapServers = _kafkaOptionConfig.BootstrapServers, AllowAutoCreateTopics = true, QueueBufferingMaxKbytes = 2_097_151, // 修改缓冲区最大为2GB,默认为1GB CompressionType = CompressionType.Lz4, // 配置使用压缩算法LZ4,其他:gzip/snappy/zstd @@ -66,12 +65,12 @@ namespace JiShe.CollectBus.Kafka.Producer MessageTimeoutMs = 120000, // 消息发送超时时间为2分钟,设置值MessageTimeoutMs > LingerMs }; - if (enableAuth) + if (_kafkaOptionConfig.EnableAuthorization) { - config.SecurityProtocol = SecurityProtocol.SaslPlaintext; - config.SaslMechanism = SaslMechanism.Plain; - config.SaslUsername = _configuration["Kafka:SaslUserName"]; - config.SaslPassword = _configuration["Kafka:SaslPassword"]; + config.SecurityProtocol = _kafkaOptionConfig.SecurityProtocol; + config.SaslMechanism = _kafkaOptionConfig.SaslMechanism; + config.SaslUsername = _kafkaOptionConfig.SaslUserName; + config.SaslPassword = _kafkaOptionConfig.SaslPassword; } return config; @@ -110,7 +109,7 @@ namespace JiShe.CollectBus.Kafka.Producer Key = key, Value = value, Headers = new Headers{ - { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } + { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } } }; await producer.ProduceAsync(topic, message); @@ -131,7 +130,7 @@ namespace JiShe.CollectBus.Kafka.Producer { Value = value, Headers = new Headers{ - { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } + { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } } }; await producer.ProduceAsync(topic, message); @@ -155,7 +154,7 @@ namespace JiShe.CollectBus.Kafka.Producer Key = key, Value = value, Headers = new Headers{ - { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } + { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } } }; var typeKey = typeof(KafkaProducer); @@ -189,7 +188,7 @@ namespace JiShe.CollectBus.Kafka.Producer { Value = value, Headers = new Headers{ - { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } + { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } } }; var typeKey = typeof(KafkaProducer); -- 2.47.2 From fa18377f107288f31731531a19fbd6d62ccda845 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Thu, 17 Apr 2025 11:53:29 +0800 Subject: [PATCH 117/139] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JiShe.CollectBus.Host/appsettings.json | 4 +- .../CollectBusKafkaModule.cs | 58 +++++++------- .../KafkaSubcribesExtensions.cs | 76 +++++++++++++------ .../Abstracts/BaseProtocolPlugin.cs | 1 + 4 files changed, 86 insertions(+), 53 deletions(-) diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index f830481..7aa3947 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -86,8 +86,8 @@ "BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092", "EnableFilter": true, "EnableAuthorization": false, - "SecurityProtocol": "SASL_PLAINTEXT", - "SaslMechanism": "PLAIN", + "SecurityProtocol": "SaslPlaintext", + "SaslMechanism": "Plain", "SaslUserName": "lixiao", "SaslPassword": "lixiao1980" //"Topic": { diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index 48a8c35..80739a6 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -35,38 +35,38 @@ namespace JiShe.CollectBus.Kafka { var app = context.GetApplicationBuilder(); // 程序运行目录 - var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); - if (!string.IsNullOrWhiteSpace(assemblyPath)) - { - var dllFiles = Directory.GetFiles(assemblyPath, "*.dll"); - var kafkaSubscriberAssemblies = new List(); - foreach (var file in dllFiles) - { - try - { - // 跳过已加载的程序集 - var assemblyName = AssemblyName.GetAssemblyName(file); - var existingAssembly = AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(a => a.GetName().FullName == assemblyName.FullName); - var assembly = existingAssembly ?? Assembly.LoadFrom(file); + //var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); + //if (!string.IsNullOrWhiteSpace(assemblyPath)) + //{ + // var dllFiles = Directory.GetFiles(assemblyPath, "*.dll"); + // var kafkaSubscriberAssemblies = new List(); + // foreach (var file in dllFiles) + // { + // try + // { + // // 跳过已加载的程序集 + // var assemblyName = AssemblyName.GetAssemblyName(file); + // var existingAssembly = AppDomain.CurrentDomain.GetAssemblies() + // .FirstOrDefault(a => a.GetName().FullName == assemblyName.FullName); + // var assembly = existingAssembly ?? Assembly.LoadFrom(file); - // 检查程序集是否包含 IKafkaSubscribe 的实现类 - var hasSubscriber = assembly.GetTypes() - .Any(type => - typeof(IKafkaSubscribe).IsAssignableFrom(type) && // 实现接口 - !type.IsAbstract && !type.IsInterface); // 排除抽象类和接口本身 + // // 检查程序集是否包含 IKafkaSubscribe 的实现类 + // var hasSubscriber = assembly.GetTypes() + // .Any(type => + // typeof(IKafkaSubscribe).IsAssignableFrom(type) && // 实现接口 + // !type.IsAbstract && !type.IsInterface); // 排除抽象类和接口本身 - if (hasSubscriber) - { - kafkaSubscriberAssemblies.Add(assembly); - } - } - catch{} - app.UseKafkaSubscribers(kafkaSubscriberAssemblies.ToArray()); - } - } + // if (hasSubscriber) + // { + // kafkaSubscriberAssemblies.Add(assembly); + // } + // } + // catch{} + // app.UseKafkaSubscribers(kafkaSubscriberAssemblies.ToArray()); + // } + //} // 获取程序集 - //app.UseKafkaSubscribers(new[] { Assembly.Load("JiShe.CollectBus.Application")}); + app.UseKafkaSubscribers(Assembly.Load("JiShe.CollectBus.Application")); } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs index 1f83540..4c69b6a 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -12,12 +12,47 @@ namespace JiShe.CollectBus.Kafka { public static class KafkaSubcribesExtensions { - /// - /// 添加Kafka订阅 - /// - /// - /// - public static void UseKafkaSubscribers(this IApplicationBuilder app, Assembly[] assemblys) + ///// + ///// 添加Kafka订阅 + ///// + ///// + ///// + //public static void UseKafkaSubscribers(this IApplicationBuilder app, Assembly[] assemblys) + //{ + // var provider = app.ApplicationServices; + // var lifetime = provider.GetRequiredService(); + + // lifetime.ApplicationStarted.Register(() => + // { + // var logger = provider.GetRequiredService>(); + // int threadCount = 0; + // int topicCount = 0; + // foreach (Assembly assembly in assemblys) + // { + // var subscribeTypes = assembly.GetTypes() + // .Where(t => typeof(IKafkaSubscribe).IsAssignableFrom(t)) + // .ToList(); + + // if (subscribeTypes.Count == 0) return; + // foreach (var subscribeType in subscribeTypes) + // { + // var subscribes = provider.GetServices(subscribeType).ToList(); + // subscribes.ForEach(subscribe => { + + // if (subscribe is IKafkaSubscribe) + // { + // Tuple tuple = BuildKafkaSubscriber(subscribe, provider, logger); + // threadCount += tuple.Item1; + // topicCount += tuple.Item2; + // } + // }); + // } + // } + // logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); + // }); + //} + + public static void UseKafkaSubscribers(this IApplicationBuilder app, Assembly assembly) { var provider = app.ApplicationServices; var lifetime = provider.GetRequiredService(); @@ -27,26 +62,23 @@ namespace JiShe.CollectBus.Kafka var logger = provider.GetRequiredService>(); int threadCount = 0; int topicCount = 0; - foreach (Assembly assembly in assemblys) - { - var subscribeTypes = assembly.GetTypes() + var subscribeTypes = assembly.GetTypes() .Where(t => typeof(IKafkaSubscribe).IsAssignableFrom(t)) .ToList(); - if (subscribeTypes.Count == 0) return; - foreach (var subscribeType in subscribeTypes) - { - var subscribes = provider.GetServices(subscribeType).ToList(); - subscribes.ForEach(subscribe => { + if (subscribeTypes.Count == 0) return; + foreach (var subscribeType in subscribeTypes) + { + var subscribes = provider.GetServices(subscribeType).ToList(); + subscribes.ForEach(subscribe => { - if (subscribe is IKafkaSubscribe) - { - Tuple tuple = BuildKafkaSubscriber(subscribe, provider, logger); - threadCount += tuple.Item1; - topicCount += tuple.Item2; - } - }); - } + if (subscribe is IKafkaSubscribe) + { + Tuple tuple = BuildKafkaSubscriber(subscribe, provider, logger); + threadCount += tuple.Item1; + topicCount += tuple.Item2; + } + }); } logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); }); diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index 159f0fc..52ef129 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -12,6 +12,7 @@ using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.Protocols; using MassTransit; using DotNetCore.CAP; +using JiShe.CollectBus.Kafka.Producer; namespace JiShe.CollectBus.Protocol.Contracts.Abstracts { -- 2.47.2 From 96e066376f9b08f00e35d119ac1df61f7894a0c6 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Thu, 17 Apr 2025 13:01:26 +0800 Subject: [PATCH 118/139] =?UTF-8?q?=E5=A2=9E=E5=8A=A0kafka=E8=AE=A2?= =?UTF-8?q?=E9=98=85=E8=87=AA=E5=8A=A8=E5=8F=96=E7=A8=8B=E5=BA=8F=E6=89=80?= =?UTF-8?q?=E6=9C=89=E7=9A=84=E8=AE=A2=E9=98=85=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusKafkaModule.cs | 37 +------- .../IKafkaSubscribe.cs | 6 ++ .../KafkaSubcribesExtensions.cs | 92 +++++++++++-------- 3 files changed, 64 insertions(+), 71 deletions(-) diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index 80739a6..0444412 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -34,39 +34,12 @@ namespace JiShe.CollectBus.Kafka public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); - // 程序运行目录 - //var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); - //if (!string.IsNullOrWhiteSpace(assemblyPath)) - //{ - // var dllFiles = Directory.GetFiles(assemblyPath, "*.dll"); - // var kafkaSubscriberAssemblies = new List(); - // foreach (var file in dllFiles) - // { - // try - // { - // // 跳过已加载的程序集 - // var assemblyName = AssemblyName.GetAssemblyName(file); - // var existingAssembly = AppDomain.CurrentDomain.GetAssemblies() - // .FirstOrDefault(a => a.GetName().FullName == assemblyName.FullName); - // var assembly = existingAssembly ?? Assembly.LoadFrom(file); - - // // 检查程序集是否包含 IKafkaSubscribe 的实现类 - // var hasSubscriber = assembly.GetTypes() - // .Any(type => - // typeof(IKafkaSubscribe).IsAssignableFrom(type) && // 实现接口 - // !type.IsAbstract && !type.IsInterface); // 排除抽象类和接口本身 - - // if (hasSubscriber) - // { - // kafkaSubscriberAssemblies.Add(assembly); - // } - // } - // catch{} - // app.UseKafkaSubscribers(kafkaSubscriberAssemblies.ToArray()); - // } - //} + + // 注册Subscriber + app.ApplicationServices.UseKafkaSubscribers(); + // 获取程序集 - app.UseKafkaSubscribers(Assembly.Load("JiShe.CollectBus.Application")); + //app.UseKafkaSubscribers(Assembly.Load("JiShe.CollectBus.Application")); } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/IKafkaSubscribe.cs b/src/JiShe.CollectBus.KafkaProducer/IKafkaSubscribe.cs index 3ccea65..39e5789 100644 --- a/src/JiShe.CollectBus.KafkaProducer/IKafkaSubscribe.cs +++ b/src/JiShe.CollectBus.KafkaProducer/IKafkaSubscribe.cs @@ -6,6 +6,12 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.Kafka { + /// + /// Kafka订阅者 + /// + /// 订阅者需要继承此接口并需要依赖注入,并使用标记 + /// + /// public interface IKafkaSubscribe { } diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs index 4c69b6a..8f2974a 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -1,8 +1,10 @@ using Confluent.Kafka; +using JiShe.CollectBus.Kafka; using JiShe.CollectBus.Kafka.AdminClient; using JiShe.CollectBus.Kafka.Attributes; using JiShe.CollectBus.Kafka.Consumer; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -12,45 +14,57 @@ namespace JiShe.CollectBus.Kafka { public static class KafkaSubcribesExtensions { - ///// - ///// 添加Kafka订阅 - ///// - ///// - ///// - //public static void UseKafkaSubscribers(this IApplicationBuilder app, Assembly[] assemblys) - //{ - // var provider = app.ApplicationServices; - // var lifetime = provider.GetRequiredService(); + /// + /// 添加Kafka订阅 + /// + /// + /// + public static void UseKafkaSubscribers(this IServiceProvider provider) + { + var lifetime = provider.GetRequiredService(); - // lifetime.ApplicationStarted.Register(() => - // { - // var logger = provider.GetRequiredService>(); - // int threadCount = 0; - // int topicCount = 0; - // foreach (Assembly assembly in assemblys) - // { - // var subscribeTypes = assembly.GetTypes() - // .Where(t => typeof(IKafkaSubscribe).IsAssignableFrom(t)) - // .ToList(); - - // if (subscribeTypes.Count == 0) return; - // foreach (var subscribeType in subscribeTypes) - // { - // var subscribes = provider.GetServices(subscribeType).ToList(); - // subscribes.ForEach(subscribe => { - - // if (subscribe is IKafkaSubscribe) - // { - // Tuple tuple = BuildKafkaSubscriber(subscribe, provider, logger); - // threadCount += tuple.Item1; - // topicCount += tuple.Item2; - // } - // }); - // } - // } - // logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); - // }); - //} + lifetime.ApplicationStarted.Register(() => + { + var logger = provider.GetRequiredService>(); + int threadCount = 0; + int topicCount = 0; + var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); + if (string.IsNullOrWhiteSpace(assemblyPath)) + { + logger.LogInformation($"kafka订阅未能找到程序路径"); + return; + } + var dllFiles = Directory.GetFiles(assemblyPath, "*.dll"); + foreach (var file in dllFiles) + { + // 跳过已加载的程序集 + var assemblyName = AssemblyName.GetAssemblyName(file); + var existingAssembly = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().FullName == assemblyName.FullName); + var assembly = existingAssembly ?? Assembly.LoadFrom(file); + // 实现IKafkaSubscribe接口 + var subscribeTypes = assembly.GetTypes().Where(type => + typeof(IKafkaSubscribe).IsAssignableFrom(type) && + !type.IsAbstract && !type.IsInterface).ToList(); ; + if (subscribeTypes.Count == 0) + continue; + foreach (var subscribeType in subscribeTypes) + { + var subscribes = provider.GetServices(subscribeType).ToList(); + subscribes.ForEach(subscribe => + { + if (subscribe!=null) + { + Tuple tuple = BuildKafkaSubscriber(subscribe, provider, logger); + threadCount += tuple.Item1; + topicCount += tuple.Item2; + } + }); + } + } + logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); + }); + } public static void UseKafkaSubscribers(this IApplicationBuilder app, Assembly assembly) { @@ -72,7 +86,7 @@ namespace JiShe.CollectBus.Kafka var subscribes = provider.GetServices(subscribeType).ToList(); subscribes.ForEach(subscribe => { - if (subscribe is IKafkaSubscribe) + if (subscribe != null) { Tuple tuple = BuildKafkaSubscriber(subscribe, provider, logger); threadCount += tuple.Item1; -- 2.47.2 From 6c8ffb3ae5429f18d925dda5b04fea80610fc7d6 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 17 Apr 2025 13:35:08 +0800 Subject: [PATCH 119/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JiShe.CollectBus.Host/appsettings.json | 8 +++---- .../CollectBusIoTDBModule.cs | 9 ++++++- .../CollectBusKafkaModule.cs | 24 +++++++++++++------ .../JiShe.CollectBus.Kafka.csproj | 4 ++++ .../KafkaOptionConfig.cs | 10 ++++++++ .../KafkaSubcribesExtensions.cs | 9 ++++--- 6 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 7aa3947..735ce0d 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -89,7 +89,9 @@ "SecurityProtocol": "SaslPlaintext", "SaslMechanism": "Plain", "SaslUserName": "lixiao", - "SaslPassword": "lixiao1980" + "SaslPassword": "lixiao1980", + "KafkaReplicationFactor": 3, + "NumPartitions": 30 //"Topic": { // "ReplicationFactor": 3, // "NumPartitions": 1000 @@ -130,9 +132,7 @@ "OpenDebugMode": true, "UseTableSessionPoolByDefault": false }, - "ServerTagName": "JiSheCollectBus3", - "KafkaReplicationFactor": 3, - "NumPartitions": 30, + "ServerTagName": "JiSheCollectBus3", "Cassandra": { "ReplicationStrategy": { "Class": "NetworkTopologyStrategy", //策略为NetworkTopologyStrategy时才会有多个数据中心,SimpleStrategy用在只有一个数据中心的情况下 diff --git a/src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs b/src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs index 444ab4e..62c63c4 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs @@ -1,6 +1,7 @@ using JiShe.CollectBus.IoTDBProvider.Context; using JiShe.CollectBus.IoTDBProvider.Interface; using JiShe.CollectBus.IoTDBProvider.Provider; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; @@ -8,6 +9,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Volo.Abp.Modularity; +using static Thrift.Server.TThreadPoolAsyncServer; namespace JiShe.CollectBus.IoTDBProvider { @@ -15,7 +17,12 @@ namespace JiShe.CollectBus.IoTDBProvider { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.Configure(context.Services.GetConfiguration().GetSection(nameof(IoTDBOptions))); + + var configuration = context.Services.GetConfiguration(); + Configure(options => + { + configuration.GetSection(nameof(IoTDBOptions)).Bind(options); + }); // 注册上下文为Scoped context.Services.AddScoped(); diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index 80739a6..5740c7f 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -1,4 +1,5 @@ using Confluent.Kafka; +using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Kafka.Consumer; using JiShe.CollectBus.Kafka.Producer; using Microsoft.AspNetCore.Builder; @@ -17,14 +18,23 @@ namespace JiShe.CollectBus.Kafka public override void ConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); - var kafkaSection = configuration.GetSection("Kafka"); - KafkaOptionConfig kafkaOptionConfig = new KafkaOptionConfig (); - kafkaSection.Bind(kafkaOptionConfig); - if (configuration["ServerTagName"] != null) + //var kafkaSection = configuration.GetSection(CommonConst.Kafka); + //KafkaOptionConfig kafkaOptionConfig = new KafkaOptionConfig (); + //kafkaSection.Bind(kafkaOptionConfig); + //if (configuration[CommonConst.ServerTagName] != null) + //{ + // kafkaOptionConfig.ServerTagName = configuration[CommonConst.ServerTagName]!; + //} + //context.Services.AddSingleton(kafkaOptionConfig); + + context.Services.Configure(context.Services.GetConfiguration().GetSection(CommonConst.Kafka)); + + Configure(options => { - kafkaOptionConfig.ServerTagName = configuration["ServerTagName"]!; - } - context.Services.AddSingleton(kafkaOptionConfig); + configuration.GetSection(CommonConst.Kafka).Bind(options); + }); + + // 注册Producer context.Services.AddSingleton(); // 注册Consumer diff --git a/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj b/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj index cef24d5..9518b0b 100644 --- a/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj +++ b/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj @@ -12,4 +12,8 @@ + + + + diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs index 50ae47e..28b80a5 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs @@ -19,6 +19,16 @@ namespace JiShe.CollectBus.Kafka /// public string ServerTagName { get; set; }= "KafkaFilterKey"; + /// + /// kafka主题副本数量 + /// + public int KafkaReplicationFactor { get; set; } + + /// + /// kafka主题分区数量 + /// + public int NumPartitions { get; set; } + /// /// 是否开启过滤器 /// diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs index 4c69b6a..d04ad4f 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -1,8 +1,10 @@ using Confluent.Kafka; +using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Kafka.AdminClient; using JiShe.CollectBus.Kafka.Attributes; using JiShe.CollectBus.Kafka.Consumer; using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -95,12 +97,13 @@ namespace JiShe.CollectBus.Kafka .Select(m => new { Method = m, Attribute = m.GetCustomAttribute() }) .Where(x => x.Attribute != null) .ToArray(); - + var configuration = provider.GetRequiredService(); int threadCount = 0; foreach (var sub in subscribedMethods) { - var adminClientService = provider.GetRequiredService(); - int partitionCount = sub.Attribute!.TaskCount==-1?adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic) : sub.Attribute!.TaskCount; + int partitionCount = configuration.GetValue(CommonConst.NumPartitions); + //var adminClientService = provider.GetRequiredService(); + //int partitionCount = sub.Attribute!.TaskCount==-1?adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic) : sub.Attribute!.TaskCount; if (partitionCount <= 0) partitionCount = 1; for (int i = 0; i < partitionCount; i++) -- 2.47.2 From 4c5f7231bf7dcd267ab30ac8c4c214c40630a653 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Thu, 17 Apr 2025 13:41:53 +0800 Subject: [PATCH 120/139] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=9C=AA=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E7=9A=84=E9=85=8D=E7=BD=AE=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../KafkaOptions.cs | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 src/JiShe.CollectBus.KafkaProducer/KafkaOptions.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaOptions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaOptions.cs deleted file mode 100644 index d946cc8..0000000 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.Kafka -{ - public class KafkaOptions - { - public string BootstrapServers { get; set; } - public string GroupId { get; set; } - public Dictionary ProducerConfig { get; set; } = new(); - public Dictionary ConsumerConfig { get; set; } = new(); - public Dictionary AdminConfig { get; set; } = new(); - } -} -- 2.47.2 From 6c0ce01634bcb6ee49fe6759c7406b1be486f4b4 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 17 Apr 2025 13:54:18 +0800 Subject: [PATCH 121/139] =?UTF-8?q?=E4=BC=98=E5=8C=96Kafka=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 14 ++++++++------ .../EnergySystemScheduledMeterReadingService.cs | 12 +++++++----- .../CollectBusCassandraModule.cs | 4 ++-- .../Helpers/BusJsonSerializer.cs | 6 ++++-- src/JiShe.CollectBus.Host/appsettings.json | 3 ++- .../CollectBusKafkaModule.cs | 2 +- .../Consumer/ConsumerService.cs | 5 +++-- .../Producer/ProducerService.cs | 5 +++-- 8 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index eac2a92..9184a62 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -13,12 +13,14 @@ using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; +using JiShe.CollectBus.Kafka; using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.RedisDataCache; using JiShe.CollectBus.Repository.MeterReadingRecord; using Mapster; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Diagnostics; @@ -38,22 +40,22 @@ namespace JiShe.CollectBus.ScheduledMeterReading private readonly IMeterReadingRecordRepository _meterReadingRecordRepository; private readonly IProducerService _producerService; private readonly IRedisDataCacheService _redisDataCacheService; - private readonly ICapPublisher _producerBus; + private readonly KafkaOptionConfig _kafkaOptions; public BasicScheduledMeterReadingService( ILogger logger, - ICapPublisher producerBus, IMeterReadingRecordRepository meterReadingRecordRepository, IProducerService producerService, IRedisDataCacheService redisDataCacheService, - IIoTDBProvider dbProvider) + IIoTDBProvider dbProvider, + IOptions kafkaOptions) { - _producerBus = producerBus; _logger = logger; _dbProvider = dbProvider; _meterReadingRecordRepository = meterReadingRecordRepository; _producerService = producerService; _redisDataCacheService = redisDataCacheService; + _kafkaOptions = kafkaOptions.Value; } /// @@ -302,7 +304,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading timer1.Stop(); _logger.LogError($"读取数据更花费时间{timer1.ElapsedMilliseconds}毫秒"); - //DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista); + //DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista, _kafkaOptions.NumPartitions); //return; #else var meterInfos = await GetAmmeterInfoList(gatherCode); @@ -428,7 +430,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } else { - DeviceGroupBalanceControl.InitializeCache(focusAddressDataList); + DeviceGroupBalanceControl.InitializeCache(focusAddressDataList, _kafkaOptions.NumPartitions); } timer.Stop(); diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 25c5476..cfb0193 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -15,6 +15,7 @@ using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; +using JiShe.CollectBus.Kafka; using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Repository; using JiShe.CollectBus.Repository.MeterReadingRecord; @@ -23,6 +24,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Volo.Abp.Domain.Repositories; using Volo.Abp.Uow; @@ -38,19 +40,19 @@ namespace JiShe.CollectBus.ScheduledMeterReading string serverTagName = string.Empty; public EnergySystemScheduledMeterReadingService( ILogger logger, - ICapPublisher producerBus, IIoTDBProvider dbProvider, + IIoTDBProvider dbProvider, IMeterReadingRecordRepository meterReadingRecordRepository, - IConfiguration configuration, + IOptions kafkaOptions, IProducerService producerService, IRedisDataCacheService redisDataCacheService) : base(logger, - producerBus, meterReadingRecordRepository, producerService, redisDataCacheService, - dbProvider) + dbProvider, + kafkaOptions) { - serverTagName = configuration.GetValue(CommonConst.ServerTagName)!; + serverTagName = kafkaOptions.Value.ServerTagName; } public sealed override string SystemType => SystemTypeConst.Energy; diff --git a/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs b/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs index 2502420..2d70f07 100644 --- a/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs +++ b/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs @@ -17,13 +17,13 @@ namespace JiShe.CollectBus.Cassandra { Configure(context.Services.GetConfiguration().GetSection("Cassandra")); - context.AddCassandra(); + // context.AddCassandra(); } public override void OnApplicationInitialization(ApplicationInitializationContext context) { - context.UseCassandra(); + // context.UseCassandra(); } } } diff --git a/src/JiShe.CollectBus.Common/Helpers/BusJsonSerializer.cs b/src/JiShe.CollectBus.Common/Helpers/BusJsonSerializer.cs index f938fd8..713501e 100644 --- a/src/JiShe.CollectBus.Common/Helpers/BusJsonSerializer.cs +++ b/src/JiShe.CollectBus.Common/Helpers/BusJsonSerializer.cs @@ -37,7 +37,8 @@ namespace JiShe.CollectBus.Common.Helpers ReadCommentHandling = JsonCommentHandling.Skip, // 忽略注释 PropertyNameCaseInsensitive = true, // 属性名称大小写不敏感 PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // 属性名称使用驼峰命名规则 - Converters = { new DateTimeJsonConverter() } // 注册你的自定义转换器, + Converters = { new DateTimeJsonConverter() }, // 注册你的自定义转换器, + DefaultBufferSize = 4096, }; } @@ -77,7 +78,8 @@ namespace JiShe.CollectBus.Common.Helpers ReadCommentHandling = JsonCommentHandling.Skip, // 忽略注释 PropertyNameCaseInsensitive = true, // 属性名称大小写不敏感 PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // 属性名称使用驼峰命名规则 - Converters = { new DateTimeJsonConverter() } // 注册你的自定义转换器, + Converters = { new DateTimeJsonConverter() }, // 注册你的自定义转换器, + DefaultBufferSize = 4096, }; } diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 735ce0d..501a2a0 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -91,7 +91,8 @@ "SaslUserName": "lixiao", "SaslPassword": "lixiao1980", "KafkaReplicationFactor": 3, - "NumPartitions": 30 + "NumPartitions": 30, + "ServerTagName": "JiSheCollectBus3" //"Topic": { // "ReplicationFactor": 3, // "NumPartitions": 1000 diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index 5740c7f..20c01fd 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -27,7 +27,7 @@ namespace JiShe.CollectBus.Kafka //} //context.Services.AddSingleton(kafkaOptionConfig); - context.Services.Configure(context.Services.GetConfiguration().GetSection(CommonConst.Kafka)); + //context.Services.Configure(context.Services.GetConfiguration().GetSection(CommonConst.Kafka)); Configure(options => { diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index eeb5661..24f2029 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -1,6 +1,7 @@ using Confluent.Kafka; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using System.Collections.Concurrent; using System.Text; @@ -15,11 +16,11 @@ namespace JiShe.CollectBus.Kafka.Consumer private readonly KafkaOptionConfig _kafkaOptionConfig; private class KafkaConsumer where TKey : notnull where TValue : class { } - public ConsumerService(IConfiguration configuration, ILogger logger, KafkaOptionConfig kafkaOptionConfig) + public ConsumerService(IConfiguration configuration, ILogger logger, IOptions kafkaOptionConfig) { _configuration = configuration; _logger = logger; - _kafkaOptionConfig = kafkaOptionConfig; + _kafkaOptionConfig = kafkaOptionConfig.Value; } #region private 私有方法 diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs index 27702e0..42fc9cf 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs @@ -8,6 +8,7 @@ using Confluent.Kafka; using JiShe.CollectBus.Kafka.Consumer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using YamlDotNet.Serialization; @@ -20,11 +21,11 @@ namespace JiShe.CollectBus.Kafka.Producer private readonly ConcurrentDictionary _producerCache = new(); private class KafkaProducer where TKey : notnull where TValue : class { } private readonly KafkaOptionConfig _kafkaOptionConfig; - public ProducerService(IConfiguration configuration,ILogger logger, KafkaOptionConfig kafkaOptionConfig) + public ProducerService(IConfiguration configuration,ILogger logger, IOptions kafkaOptionConfig) { _configuration = configuration; _logger = logger; - _kafkaOptionConfig = kafkaOptionConfig; + _kafkaOptionConfig = kafkaOptionConfig.Value; } #region private 私有方法 -- 2.47.2 From 72f8222ec27e7c1ff91834a97d93fca5523c5785 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Thu, 17 Apr 2025 13:56:17 +0800 Subject: [PATCH 122/139] =?UTF-8?q?kafka=E6=B6=88=E8=B4=B9=E8=80=85?= =?UTF-8?q?=E8=AE=A2=E9=98=85=E5=A2=9E=E5=8A=A0=E6=89=B9=E9=87=8F=E6=B6=88?= =?UTF-8?q?=E8=B4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../KafkaSubcribesExtensions.cs | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs index 73ea14c..e830dc4 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -138,21 +138,42 @@ namespace JiShe.CollectBus.Kafka private static async Task StartConsumerAsync(IServiceProvider provider, KafkaSubscribeAttribute attr,MethodInfo method, object subscribe, ILogger logger) { var consumerService = provider.GetRequiredService(); - - await consumerService.SubscribeAsync(attr.Topic, async (message) => + + if (attr.EnableBatch) { - try + await consumerService.SubscribeBatchAsync(attr.Topic, async (message) => { - // 处理消息 - return await ProcessMessageAsync(message, method, subscribe); - } - catch (ConsumeException ex) + try + { + // 处理消息 + return await ProcessMessageAsync(message, method, subscribe); + } + catch (ConsumeException ex) + { + // 处理消费错误 + logger.LogError($"kafka批量消费异常:{ex.Message}"); + } + return await Task.FromResult(false); + }); + } + else + { + await consumerService.SubscribeAsync(attr.Topic, async (message) => { - // 处理消费错误 - logger.LogError($"kafka消费异常:{ex.Message}"); - } - return await Task.FromResult(false); - }); + try + { + // 处理消息 + return await ProcessMessageAsync(message, method, subscribe); + } + catch (ConsumeException ex) + { + // 处理消费错误 + logger.LogError($"kafka消费异常:{ex.Message}"); + } + return await Task.FromResult(false); + }); + } + } -- 2.47.2 From 871ed615a44b314872e90c5ed2a26e895fedfe02 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 17 Apr 2025 14:12:02 +0800 Subject: [PATCH 123/139] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusApplicationModule.cs | 4 +- .../RedisDataCache/RedisDataCacheService.cs | 174 ++++++------------ .../KafkaOptionConfig.cs | 2 +- 3 files changed, 61 insertions(+), 119 deletions(-) diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index f3ed978..1826fa4 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -22,6 +22,7 @@ using Volo.Abp.BackgroundWorkers; using Volo.Abp.BackgroundWorkers.Hangfire; using Volo.Abp.EventBus; using Volo.Abp.Modularity; +using Microsoft.Extensions.Options; namespace JiShe.CollectBus; @@ -69,13 +70,14 @@ public class CollectBusApplicationModule : AbpModule //初始化主题信息 var kafkaAdminClient = context.ServiceProvider.GetRequiredService(); var configuration = context.ServiceProvider.GetRequiredService(); + var kafkaOptions = context.ServiceProvider.GetRequiredService>(); List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); foreach (var item in topics) { - await kafkaAdminClient.CreateTopicAsync(item, configuration.GetValue(CommonConst.NumPartitions), configuration.GetValue(CommonConst.KafkaReplicationFactor)); + await kafkaAdminClient.CreateTopicAsync(item, kafkaOptions.Value.NumPartitions, kafkaOptions.Value.KafkaReplicationFactor); } } diff --git a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs index 3c96410..0d9c80d 100644 --- a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs +++ b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs @@ -389,79 +389,79 @@ namespace JiShe.CollectBus.RedisDataCache pageSize = Math.Clamp(pageSize, 1, 10000); const string luaScript = @" - local command = ARGV[1] - local range_start = ARGV[2] - local range_end = ARGV[3] - local limit = tonumber(ARGV[4]) - local last_score = ARGV[5] - local last_member = ARGV[6] + local command = ARGV[1] + local range_start = ARGV[2] + local range_end = ARGV[3] + local limit = tonumber(ARGV[4]) + local last_score = ARGV[5] + local last_member = ARGV[6] - -- 获取扩展数据(3倍分页大小) - local members - if command == 'ZRANGEBYSCORE' then - members = redis.call('ZRANGEBYSCORE', KEYS[1], range_start, range_end, - 'WITHSCORES', 'LIMIT', 0, limit * 5) - else - members = redis.call('ZREVRANGEBYSCORE', KEYS[1], range_start, range_end, - 'WITHSCORES', 'LIMIT', 0, limit * 5) - end + -- 获取扩展数据(5倍分页大小) + local members + if command == 'ZRANGEBYSCORE' then + members = redis.call('ZRANGEBYSCORE', KEYS[1], range_start, range_end, + 'WITHSCORES', 'LIMIT', 0, limit * 5) + else + members = redis.call('ZREVRANGEBYSCORE', KEYS[1], range_start, range_end, + 'WITHSCORES', 'LIMIT', 0, limit * 5) + end - -- 精确分页过滤 - local filtered = {} - local count = 0 - local start_index = 1 + -- 精确分页过滤 + local filtered = {} + local count = 0 + local start_index = 1 - -- 存在锚点时寻找起始位置 - if last_score ~= '' and last_member ~= '' then - for i=1,#members,2 do - local score = members[i+1] - local member = members[i] + -- 存在锚点时寻找起始位置 + if last_score ~= '' and last_member ~= '' then + for i=1,#members,2 do + local score = members[i+1] + local member = members[i] - if command == 'ZRANGEBYSCORE' then - if tonumber(score) > tonumber(last_score) then - start_index = i - break - elseif tonumber(score) == tonumber(last_score) then - if member > last_member then + if command == 'ZRANGEBYSCORE' then + if tonumber(score) > tonumber(last_score) then start_index = i break + elseif tonumber(score) == tonumber(last_score) then + if member > last_member then + start_index = i + break + end end - end - else - if tonumber(score) < tonumber(last_score) then - start_index = i - break - elseif tonumber(score) == tonumber(last_score) then - if member < last_member then + else + if tonumber(score) < tonumber(last_score) then start_index = i break + elseif tonumber(score) == tonumber(last_score) then + if member < last_member then + start_index = i + break + end end end end end - end - -- 收集有效数据 - for i=start_index,#members,2 do - if count >= limit then break end - table.insert(filtered, members[i]) - table.insert(filtered, members[i+1]) - count = count + 1 - end + -- 收集有效数据 + for i=start_index,#members,2 do + if count >= limit then break end + table.insert(filtered, members[i]) + table.insert(filtered, members[i+1]) + count = count + 1 + end - -- 提取有效数据 - local result_members = {} - local result_scores = {} - for i=1,#filtered,2 do - table.insert(result_members, filtered[i]) - table.insert(result_scores, filtered[i+1]) - end + -- 提取有效数据 + local result_members = {} + local result_scores = {} + for i=1,#filtered,2 do + table.insert(result_members, filtered[i]) + table.insert(result_scores, filtered[i+1]) + end - if #result_members == 0 then return {0,{},{},{}} end + if #result_members == 0 then return {0,{},{},{}} end - -- 获取Hash数据 - local hash_data = redis.call('HMGET', KEYS[2], unpack(result_members)) - return {#result_members, result_members, result_scores, hash_data}"; + -- 获取Hash数据 + local hash_data = redis.call('HMGET', KEYS[2], unpack(result_members)) + return {#result_members, result_members, result_scores, hash_data}"; // 构造查询范围(包含等于) string rangeStart, rangeEnd; @@ -540,67 +540,7 @@ namespace JiShe.CollectBus.RedisDataCache return new BusCacheGlobalPagedResult { Items = new List() }; } } - - /// - /// 并行分页导出方法(百万级数据支持) - /// - public async Task> FullExportParallel( - string hashKey, - string zsetKey, - int parallelDegree = 10, - int pageSize = 5000) where T : DeviceCacheBasicModel - { - var result = new ConcurrentBag(); - var totalCount = await GetTotalCount(zsetKey); - var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize); - - var semaphore = new SemaphoreSlim(parallelDegree); - var tasks = new List(); - - decimal? lastScore = null; - string lastMember = null; - var isDescending = true; - - for (int page = 0; page < totalPages; page++) - { - await semaphore.WaitAsync(); - - tasks.Add(Task.Run(async () => - { - try - { - var pageResult = await GetAllPagedData( - hashKey, - zsetKey, - pageSize, - lastScore, - lastMember, - isDescending); - - foreach (var item in pageResult.Items) - { - result.Add(item); - } - - // 更新分页锚点 - if (pageResult.HasNext) - { - lastScore = pageResult.NextScore; - lastMember = pageResult.NextMember; - } - } - finally - { - semaphore.Release(); - } - })); - } - - await Task.WhenAll(tasks); - return result.ToList(); - } - - + /// /// 通过ZSET索引获取数据,支持10万级别数据处理,控制在13秒以内。 /// diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs index 28b80a5..e592ea2 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs @@ -22,7 +22,7 @@ namespace JiShe.CollectBus.Kafka /// /// kafka主题副本数量 /// - public int KafkaReplicationFactor { get; set; } + public short KafkaReplicationFactor { get; set; } /// /// kafka主题分区数量 -- 2.47.2 From 0ba64a4b903d54c3660f6ad4bb835233dd757696 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Thu, 17 Apr 2025 14:39:14 +0800 Subject: [PATCH 124/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E8=AE=A2=E9=98=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EnergySystem/EnergySystemAppService.cs | 1 + .../Plugins/TcpMonitor.cs | 1 + .../Samples/SampleAppService.cs | 2 +- .../Subscribers/SubscriberAppService.cs | 1 + .../Subscribers/WorkerSubscriberAppService.cs | 1 + .../Consts}/ProtocolConst.cs | 2 +- .../Extensions/ProtocolConstExtensions.cs | 5 ++- .../CollectBusHostModule.Configure.cs | 40 ------------------- .../Consumer/ConsumerService.cs | 2 +- .../KafkaSubcribesExtensions.cs | 39 ++++++++++++++---- .../Abstracts/BaseProtocolPlugin.cs | 1 + ...JiShe.CollectBus.Protocol.Contracts.csproj | 4 ++ 12 files changed, 47 insertions(+), 52 deletions(-) rename src/{JiShe.CollectBus.Protocol.Contracts => JiShe.CollectBus.Common/Consts}/ProtocolConst.cs (99%) rename src/{JiShe.CollectBus.Protocol.Contracts => JiShe.CollectBus.Common}/Extensions/ProtocolConstExtensions.cs (95%) diff --git a/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs b/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs index 1ca731b..9fc8dd4 100644 --- a/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs +++ b/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using DeviceDetectorNET.Class.Device; using DotNetCore.CAP; using JiShe.CollectBus.Common.BuildSendDatas; +using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index 6cbc8c4..c2bd026 100644 --- a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using DeviceDetectorNET.Parser.Device; using DotNetCore.CAP; using JiShe.CollectBus.Ammeters; +using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 15b094a..09bd962 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -265,7 +265,7 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS return aa == null; } - [KafkaSubscribe("test-topic1")] + //[KafkaSubscribe("test-topic1")] public async Task KafkaSubscribeAsync(object obj) { diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index 3ec6936..ce41db1 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -1,4 +1,5 @@ using DotNetCore.CAP; +using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index 47cff47..65ecc01 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using DeviceDetectorNET.Parser.Device; using DotNetCore.CAP; +using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; diff --git a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs b/src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs similarity index 99% rename from src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs rename to src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs index d1e5739..df96c29 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/ProtocolConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.Protocol.Contracts +namespace JiShe.CollectBus.Common.Consts { public class ProtocolConst { diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs b/src/JiShe.CollectBus.Common/Extensions/ProtocolConstExtensions.cs similarity index 95% rename from src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs rename to src/JiShe.CollectBus.Common/Extensions/ProtocolConstExtensions.cs index fc52f31..1dbb301 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Extensions/ProtocolConstExtensions.cs +++ b/src/JiShe.CollectBus.Common/Extensions/ProtocolConstExtensions.cs @@ -1,4 +1,5 @@ -using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using System; using System.Collections.Generic; @@ -7,7 +8,7 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.Protocol.Contracts +namespace JiShe.CollectBus.Common.Extensions { public class ProtocolConstExtensions { diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 35bb46b..82cc214 100644 --- a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -361,45 +361,5 @@ namespace JiShe.CollectBus.Host }); }); } - - /// - /// 配置Kafka主题 - /// - /// - /// - public void ConfigureKafkaTopic(ServiceConfigurationContext context, IConfiguration configuration) - { - var adminClient = new AdminClientBuilder(new AdminClientConfig - { - BootstrapServers = configuration.GetConnectionString(CommonConst.Kafka) - }).Build(); - - try - { - - List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); - topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); - - List topicSpecifications = new List(); - foreach (var item in topics) - { - topicSpecifications.Add(new TopicSpecification - { - Name = item, - NumPartitions = 3, - ReplicationFactor = 1 - }); - } - adminClient.CreateTopicsAsync(topicSpecifications).ConfigureAwait(false).GetAwaiter().GetResult(); - } - catch (CreateTopicsException e) - { - if (e.Results[0].Error.Code != ErrorCode.TopicAlreadyExists) - { - throw; - } - } - - } } } \ No newline at end of file diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index 24f2029..fd11df4 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -50,7 +50,7 @@ namespace JiShe.CollectBus.Kafka.Consumer AutoOffsetReset = AutoOffsetReset.Earliest, EnableAutoCommit = false, // 禁止AutoCommit EnablePartitionEof = true, // 启用分区末尾标记 - AllowAutoCreateTopics= true, // 启用自动创建 + //AllowAutoCreateTopics= true, // 启用自动创建 FetchMaxBytes = 1024 * 1024 * 50 // 增加拉取大小(50MB) }; diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs index e830dc4..c28c82c 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -1,5 +1,6 @@ using Confluent.Kafka; using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Kafka.AdminClient; using JiShe.CollectBus.Kafka.Attributes; using JiShe.CollectBus.Kafka.Consumer; @@ -8,6 +9,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using System.Reflection; namespace JiShe.CollectBus.Kafka @@ -23,6 +25,18 @@ namespace JiShe.CollectBus.Kafka { var lifetime = provider.GetRequiredService(); + //初始化主题信息 + var kafkaAdminClient = provider.GetRequiredService(); + var kafkaOptions = provider.GetRequiredService>(); + + List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); + topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); + + foreach (var item in topics) + { + kafkaAdminClient.CreateTopicAsync(item, kafkaOptions.Value.NumPartitions, kafkaOptions.Value.KafkaReplicationFactor).ConfigureAwait(false).GetAwaiter().GetResult(); + } + lifetime.ApplicationStarted.Register(() => { var logger = provider.GetRequiredService>(); @@ -55,7 +69,7 @@ namespace JiShe.CollectBus.Kafka { if (subscribe!=null) { - Tuple tuple = BuildKafkaSubscriber(subscribe, provider, logger); + Tuple tuple = BuildKafkaSubscriber(subscribe, provider, logger, kafkaOptions.Value); threadCount += tuple.Item1; topicCount += tuple.Item2; } @@ -70,6 +84,17 @@ namespace JiShe.CollectBus.Kafka { var provider = app.ApplicationServices; var lifetime = provider.GetRequiredService(); + //初始化主题信息 + var kafkaAdminClient = provider.GetRequiredService(); + var kafkaOptions = provider.GetRequiredService>(); + + List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); + topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); + + foreach (var item in topics) + { + kafkaAdminClient.CreateTopicAsync(item, kafkaOptions.Value.NumPartitions, kafkaOptions.Value.KafkaReplicationFactor).ConfigureAwait(false).GetAwaiter().GetResult(); + } lifetime.ApplicationStarted.Register(() => { @@ -88,7 +113,7 @@ namespace JiShe.CollectBus.Kafka if (subscribe != null) { - Tuple tuple = BuildKafkaSubscriber(subscribe, provider, logger); + Tuple tuple = BuildKafkaSubscriber(subscribe, provider, logger, kafkaOptions.Value); threadCount += tuple.Item1; topicCount += tuple.Item2; } @@ -103,17 +128,17 @@ namespace JiShe.CollectBus.Kafka /// /// /// - private static Tuple BuildKafkaSubscriber(object subscribe, IServiceProvider provider,ILogger logger) + private static Tuple BuildKafkaSubscriber(object subscribe, IServiceProvider provider,ILogger logger, KafkaOptionConfig kafkaOptionConfig) { var subscribedMethods = subscribe.GetType().GetMethods() .Select(m => new { Method = m, Attribute = m.GetCustomAttribute() }) .Where(x => x.Attribute != null) .ToArray(); - var configuration = provider.GetRequiredService(); + //var configuration = provider.GetRequiredService(); int threadCount = 0; foreach (var sub in subscribedMethods) { - int partitionCount = configuration.GetValue(CommonConst.NumPartitions); + int partitionCount = kafkaOptionConfig.NumPartitions; //var adminClientService = provider.GetRequiredService(); //int partitionCount = sub.Attribute!.TaskCount==-1?adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic) : sub.Attribute!.TaskCount; if (partitionCount <= 0) @@ -154,7 +179,7 @@ namespace JiShe.CollectBus.Kafka logger.LogError($"kafka批量消费异常:{ex.Message}"); } return await Task.FromResult(false); - }); + }, attr.GroupId, attr.BatchSize,attr.BatchTimeout); } else { @@ -171,7 +196,7 @@ namespace JiShe.CollectBus.Kafka logger.LogError($"kafka消费异常:{ex.Message}"); } return await Task.FromResult(false); - }); + }, attr.GroupId); } } diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs index 52ef129..bc066fe 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs @@ -13,6 +13,7 @@ using JiShe.CollectBus.IotSystems.Protocols; using MassTransit; using DotNetCore.CAP; using JiShe.CollectBus.Kafka.Producer; +using JiShe.CollectBus.Common.Consts; namespace JiShe.CollectBus.Protocol.Contracts.Abstracts { diff --git a/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj b/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj index 3aa7a77..fc0f12e 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj +++ b/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj @@ -20,4 +20,8 @@ + + + + -- 2.47.2 From 48b91183c287dbe205466769064c40d715c02380 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Thu, 17 Apr 2025 15:06:30 +0800 Subject: [PATCH 125/139] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E4=B8=BB=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/SampleAppService.cs | 2 +- src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 09bd962..6eba059 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -265,7 +265,7 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS return aa == null; } - //[KafkaSubscribe("test-topic1")] + [KafkaSubscribe(ProtocolConst.TESTTOPIC)] public async Task KafkaSubscribeAsync(object obj) { diff --git a/src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs b/src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs index df96c29..6b65535 100644 --- a/src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs @@ -159,5 +159,11 @@ namespace JiShe.CollectBus.Common.Consts /// AFN10H上行主题格式 /// public const string SubscriberAFN10HReceivedEventNameTemp = "received.afn16h.event"; + + + /// + /// 测试主题格式 + /// + public const string TESTTOPIC = "test-topic"; } } -- 2.47.2 From d002472854b83a546f75c224924536db8caa26fc Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 17 Apr 2025 15:49:57 +0800 Subject: [PATCH 126/139] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=EF=BC=8C=E5=87=8F=E5=B0=91Kafka=E8=AE=A2?= =?UTF-8?q?=E9=98=85=E7=BA=BF=E7=A8=8B=E6=95=B0=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RedisDataCache/IRedisDataCacheService.cs | 13 +- .../RedisDataCache/RedisDataCacheService.cs | 188 +----------------- .../BasicScheduledMeterReadingService.cs | 79 +++++--- .../KafkaSubcribesExtensions.cs | 2 +- 4 files changed, 56 insertions(+), 226 deletions(-) diff --git a/src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs b/src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs index 5f0e8f8..eb400c7 100644 --- a/src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs +++ b/src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs @@ -132,18 +132,7 @@ namespace JiShe.CollectBus.Application.Contracts bool descending = true) where T : DeviceCacheBasicModel; - - /// - /// 优化后的分页获取方法(支持百万级数据) - /// - Task> GetAllPagedDataOptimized( - string redisHashCacheKey, - string redisZSetScoresIndexCacheKey, - int pageSize = 1000, - decimal? lastScore = null, - string lastMember = null, - bool descending = true) where T : DeviceCacheBasicModel; - + ///// ///// 游标分页查询 ///// diff --git a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs index 0d9c80d..43dae23 100644 --- a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs +++ b/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs @@ -365,182 +365,7 @@ namespace JiShe.CollectBus.RedisDataCache { throw new Exception(); } - - - /// - /// 优化后的分页获取方法(支持百万级数据) - /// - public async Task> GetAllPagedDataOptimized( - string redisHashCacheKey, - string redisZSetScoresIndexCacheKey, - int pageSize = 1000, - decimal? lastScore = null, - string lastMember = null, - bool descending = true) where T : DeviceCacheBasicModel - { - // 参数校验 - if (string.IsNullOrWhiteSpace(redisHashCacheKey) || - string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) - { - _logger.LogError("Invalid parameters in {Method}", nameof(GetAllPagedDataOptimized)); - return new BusCacheGlobalPagedResult { Items = new List() }; - } - - pageSize = Math.Clamp(pageSize, 1, 10000); - - const string luaScript = @" - local command = ARGV[1] - local range_start = ARGV[2] - local range_end = ARGV[3] - local limit = tonumber(ARGV[4]) - local last_score = ARGV[5] - local last_member = ARGV[6] - - -- 获取扩展数据(5倍分页大小) - local members - if command == 'ZRANGEBYSCORE' then - members = redis.call('ZRANGEBYSCORE', KEYS[1], range_start, range_end, - 'WITHSCORES', 'LIMIT', 0, limit * 5) - else - members = redis.call('ZREVRANGEBYSCORE', KEYS[1], range_start, range_end, - 'WITHSCORES', 'LIMIT', 0, limit * 5) - end - - -- 精确分页过滤 - local filtered = {} - local count = 0 - local start_index = 1 - - -- 存在锚点时寻找起始位置 - if last_score ~= '' and last_member ~= '' then - for i=1,#members,2 do - local score = members[i+1] - local member = members[i] - - if command == 'ZRANGEBYSCORE' then - if tonumber(score) > tonumber(last_score) then - start_index = i - break - elseif tonumber(score) == tonumber(last_score) then - if member > last_member then - start_index = i - break - end - end - else - if tonumber(score) < tonumber(last_score) then - start_index = i - break - elseif tonumber(score) == tonumber(last_score) then - if member < last_member then - start_index = i - break - end - end - end - end - end - - -- 收集有效数据 - for i=start_index,#members,2 do - if count >= limit then break end - table.insert(filtered, members[i]) - table.insert(filtered, members[i+1]) - count = count + 1 - end - - -- 提取有效数据 - local result_members = {} - local result_scores = {} - for i=1,#filtered,2 do - table.insert(result_members, filtered[i]) - table.insert(result_scores, filtered[i+1]) - end - - if #result_members == 0 then return {0,{},{},{}} end - - -- 获取Hash数据 - local hash_data = redis.call('HMGET', KEYS[2], unpack(result_members)) - return {#result_members, result_members, result_scores, hash_data}"; - - // 构造查询范围(包含等于) - string rangeStart, rangeEnd; - if (descending) - { - rangeStart = lastScore.HasValue ? lastScore.Value.ToString() : "+inf"; - rangeEnd = "-inf"; - } - else - { - rangeStart = lastScore.HasValue ? lastScore.Value.ToString() : "-inf"; - rangeEnd = "+inf"; - } - - try - { - var scriptResult = (object[])await Instance.EvalAsync( - luaScript, - new[] { redisZSetScoresIndexCacheKey, redisHashCacheKey }, - new object[] - { - descending ? "ZREVRANGEBYSCORE" : "ZRANGEBYSCORE", - rangeStart, - rangeEnd, - pageSize, - lastScore?.ToString() ?? "", - lastMember ?? "" - }); - - var itemCount = (long)scriptResult[0]; - if (itemCount == 0) - return new BusCacheGlobalPagedResult { Items = new List() }; - - // 处理结果集 - var members = ((object[])scriptResult[1]).Cast().ToList(); - var scores = ((object[])scriptResult[2]).Cast() - .Select(decimal.Parse).ToList(); - var hashData = ((object[])scriptResult[3]).Cast().ToList(); - - var validItems = members.AsParallel() - .Select((m, i) => - { - try { return BusJsonSerializer.Deserialize(hashData[i]); } - catch { return null; } - }) - .Where(x => x != null) - .ToList(); - - // 精确分页控制 - var hasNext = validItems.Count >= pageSize; - var actualItems = validItems.Take(pageSize).ToList(); - - // 计算下一页锚点(必须基于原始排序) - decimal? nextScore = null; - string nextMember = null; - if (hasNext && actualItems.Count > 0) - { - var lastValidIndex = Math.Min(pageSize - 1, members.Count - 1); - nextScore = scores[lastValidIndex]; - nextMember = members[lastValidIndex]; - } - - return new BusCacheGlobalPagedResult - { - Items = actualItems, - HasNext = hasNext, - NextScore = nextScore, - NextMember = nextMember, - TotalCount = await GetTotalCount(redisZSetScoresIndexCacheKey), - PageSize = pageSize - }; - } - catch (Exception ex) - { - _logger.LogError(ex, "分页查询异常"); - return new BusCacheGlobalPagedResult { Items = new List() }; - } - } - + /// /// 通过ZSET索引获取数据,支持10万级别数据处理,控制在13秒以内。 /// @@ -562,17 +387,14 @@ namespace JiShe.CollectBus.RedisDataCache where T : DeviceCacheBasicModel { // 参数校验增强 - if (string.IsNullOrWhiteSpace(redisHashCacheKey) || string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) + if (string.IsNullOrWhiteSpace(redisHashCacheKey) || + string.IsNullOrWhiteSpace(redisZSetScoresIndexCacheKey)) { _logger.LogError($"{nameof(GetAllPagedData)} 参数异常,-101"); - return null; + return new BusCacheGlobalPagedResult { Items = new List() }; } - if (pageSize < 1 || pageSize > 10000) - { - _logger.LogError($"{nameof(GetAllPagedData)} 分页大小应在1-10000之间,-102"); - return null; - } + pageSize = Math.Clamp(pageSize, 1, 10000); var luaScript = @" local command = ARGV[1] diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 9184a62..b6eb361 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -47,7 +47,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading IMeterReadingRecordRepository meterReadingRecordRepository, IProducerService producerService, IRedisDataCacheService redisDataCacheService, - IIoTDBProvider dbProvider, + IIoTDBProvider dbProvider, IOptions kafkaOptions) { _logger = logger; @@ -125,7 +125,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} 构建待处理的下发指令任务处理时Key=>{item}时间节点不在当前时间范围内,103"); continue; } - + var meterTypes = EnumExtensions.ToEnumDictionary(); if (meteryType == MeterTypeEnum.Ammeter.ToString()) @@ -155,7 +155,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading member = page.HasNext ? page.NextMember : null; hasNext = page.HasNext; } while (hasNext); - + if (meterInfos == null || meterInfos.Count <= 0) { timer.Stop(); @@ -169,9 +169,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading //await DeviceGroupBalanceControl.ProcessGenericListAsync( // items: meterInfos, // deviceIdSelector: data => data.FocusAddress, - // processor: (data, threadId) => + // processor: (data, groupIndex) => // { - // _ = AmmerterCreatePublishTask(timeDensity, data); + // _ = AmmerterCreatePublishTask(timeDensity, data, groupIndex, tasksToBeIssueModel.NextTaskTime.ToString("yyyyMMddHHmmss")); // } //); @@ -180,9 +180,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading await DeviceGroupBalanceControl.ProcessWithThrottleAsync( items: meterInfos, deviceIdSelector: data => data.FocusAddress, - processor: (data,groupIndex) => + processor: (data, groupIndex) => { - _ = AmmerterCreatePublishTask(timeDensity, data, groupIndex,tasksToBeIssueModel.NextTaskTime.ToString("yyyyMMddHHmmss")); + _ = AmmerterCreatePublishTask(timeDensity, data, groupIndex, tasksToBeIssueModel.NextTaskTime.ToString("yyyyMMddHHmmss")); } ); @@ -247,9 +247,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading // meterInfos.Add(tempData); // //focusAddressDataLista.Add(item.FocusAddress); //} - - - + + + var timeDensity = "15"; var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; @@ -282,7 +282,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading while (true) { - var page = await _redisDataCacheService.GetAllPagedDataOptimized( + var page = await _redisDataCacheService.GetAllPagedData( redisCacheMeterInfoHashKeyTemp, redisCacheMeterInfoZSetScoresIndexKeyTemp, pageSize: 1000, @@ -290,17 +290,19 @@ namespace JiShe.CollectBus.ScheduledMeterReading lastMember: member); meterInfos.AddRange(page.Items); - focusAddressDataLista.AddRange(page.Items.Select(d=>d.FocusAddress)); + focusAddressDataLista.AddRange(page.Items.Select(d => d.FocusAddress)); foreach (var item in page.Items) { if (!allIds.Add(item.MemberId)) - throw new Exception("Duplicate data found!"); + { + _logger.LogError($"{item.MemberId}Duplicate data found!"); + } } if (!page.HasNext) break; score = page.NextScore; member = page.NextMember; } - + timer1.Stop(); _logger.LogError($"读取数据更花费时间{timer1.ElapsedMilliseconds}毫秒"); @@ -331,7 +333,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { var redisCacheMeterInfoHashKey = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; var redisCacheMeterInfoSetIndexKey = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; - var redisCacheMeterInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; + var redisCacheMeterInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; List ammeterInfos = new List(); //将表计信息根据集中器分组,获得集中器号 @@ -345,7 +347,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading focusAddressDataList.Add(item.Key); - // var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}{item.Key}"; + // var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}{item.Key}"; #if DEBUG //每次缓存时,删除缓存,避免缓存数据有不准确的问题 @@ -409,7 +411,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading await _redisDataCacheService.BatchInsertDataAsync( redisCacheMeterInfoHashKey, redisCacheMeterInfoSetIndexKey, - redisCacheMeterInfoZSetScoresIndexKey,ammeterInfos); + redisCacheMeterInfoZSetScoresIndexKey, ammeterInfos); //在缓存表信息数据的时候,新增下一个时间的自动处理任务,1分钟后执行所有的采集频率任务 TasksToBeIssueModel nextTask = new TasksToBeIssueModel() @@ -623,7 +625,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading { await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentDateTime); } - + stopwatch.Stop(); @@ -710,7 +712,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// 时间格式的任务批次名称 /// private async Task AmmerterCreatePublishTask(int timeDensity - , AmmeterInfo ammeterInfo,int groupIndex,string taskBatch) + , AmmeterInfo ammeterInfo, int groupIndex, string taskBatch) { var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; //todo 检查需要待补抄的电表的时间点信息,保存到需要待补抄的缓存中。如果此线程异常,该如何补偿? @@ -724,7 +726,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (string.IsNullOrWhiteSpace(ammeterInfo.ItemCodes)) { - // _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,采集项为空,-101"); + // _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,采集项为空,-101"); return; } @@ -750,7 +752,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (string.IsNullOrWhiteSpace(ammeterInfo.AreaCode)) { - // _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信区号为空"); + // _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信区号为空"); return; } if (string.IsNullOrWhiteSpace(ammeterInfo.Address)) @@ -899,14 +901,31 @@ namespace JiShe.CollectBus.ScheduledMeterReading || string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoSetIndexKey) || string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoZSetScoresIndexKey)) { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 写入参数异常,{redisCacheTelemetryPacketInfoHashKey}:{redisCacheTelemetryPacketInfoSetIndexKey}:{redisCacheTelemetryPacketInfoZSetScoresIndexKey},-101"); + _logger.LogError($"{nameof(AmmerterCreatePublishTask)} {ammeterInfo.Name}的写入参数异常,{redisCacheTelemetryPacketInfoHashKey}:{redisCacheTelemetryPacketInfoSetIndexKey}:{redisCacheTelemetryPacketInfoZSetScoresIndexKey},-101"); return; } - await _redisDataCacheService.BatchInsertDataAsync( - redisCacheTelemetryPacketInfoHashKey, - redisCacheTelemetryPacketInfoSetIndexKey, - redisCacheTelemetryPacketInfoZSetScoresIndexKey, - taskList); + + using (var pipe = FreeRedisProvider.Instance.StartPipe()) + { + foreach (var item in taskList) + { + // 主数据存储Hash + pipe.HSet(redisCacheTelemetryPacketInfoHashKey, item.MemberId, item.Serialize()); + + // Set索引缓存 + pipe.SAdd(redisCacheTelemetryPacketInfoSetIndexKey, item.MemberId); + + // ZSET索引缓存Key + pipe.ZAdd(redisCacheTelemetryPacketInfoZSetScoresIndexKey, item.ScoreValue, item.MemberId); + } + pipe.EndPipe(); + } + + //await _redisDataCacheService.BatchInsertDataAsync( + // redisCacheTelemetryPacketInfoHashKey, + // redisCacheTelemetryPacketInfoSetIndexKey, + // redisCacheTelemetryPacketInfoZSetScoresIndexKey, + // taskList); } /// @@ -923,8 +942,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading throw new Exception($"{nameof(KafkaProducerIssuedMessage)} 推送消息失败,参数异常,-101"); } int partition = DeviceGroupBalanceControl.GetDeviceGroupId(taskRecord.FocusAddress); - - await _producerService.ProduceAsync(topicName, partition, taskRecord); + + await _producerService.ProduceAsync(topicName, partition, taskRecord); } private async Task AmmerterCreatePublishTask(int timeDensity, MeterTypeEnum meterType) @@ -1273,7 +1292,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading FocusAddress = ammerterItem.Value.FocusAddress, TimeDensity = timeDensity.ToString(), }; - + //await _producerBus.PublishAsync(ProtocolConst.WatermeterSubscriberWorkerAutoReadingIssuedEventName, tempMsg); //_ = _producerBus.Publish(tempMsg); diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs index c28c82c..067c1a9 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -138,7 +138,7 @@ namespace JiShe.CollectBus.Kafka int threadCount = 0; foreach (var sub in subscribedMethods) { - int partitionCount = kafkaOptionConfig.NumPartitions; + int partitionCount = 3;// kafkaOptionConfig.NumPartitions; //var adminClientService = provider.GetRequiredService(); //int partitionCount = sub.Attribute!.TaskCount==-1?adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic) : sub.Attribute!.TaskCount; if (partitionCount <= 0) -- 2.47.2 From e91db2d0d4f5183d858ec51a180b307fbf2780c9 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Thu, 17 Apr 2025 16:10:47 +0800 Subject: [PATCH 127/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JiShe.CollectBus.Host/appsettings.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 6ca53cc..654e945 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -153,6 +153,12 @@ "Port": 9043, "DataCenter": "dc1", "Rack": "RAC2" + }, + { + "Host": "192.168.1.9", + "Port": 9044, + "DataCenter": "dc1", + "Rack": "RAC2" } ], "Username": "admin", -- 2.47.2 From f1082ce8a299a1b74b8c016941044410292b30fb Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Thu, 17 Apr 2025 16:19:46 +0800 Subject: [PATCH 128/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=BA=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 7 ------- .../BasicScheduledMeterReadingService.cs | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index ea5e278..9c67aae 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -39,8 +39,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.T EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Cassandra", "src\JiShe.CollectBus.Cassandra\JiShe.CollectBus.Cassandra.csproj", "{443B4549-0AC0-4493-8F3E-49C83225DD76}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka.Test", "src\JiShe.CollectBus.Kafka.Test\JiShe.CollectBus.Kafka.Test.csproj", "{FA762E8F-659A-DECF-83D6-5F364144450E}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -115,10 +113,6 @@ Global {443B4549-0AC0-4493-8F3E-49C83225DD76}.Debug|Any CPU.Build.0 = Debug|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.ActiveCfg = Release|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.Build.0 = Release|Any CPU - {FA762E8F-659A-DECF-83D6-5F364144450E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FA762E8F-659A-DECF-83D6-5F364144450E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA762E8F-659A-DECF-83D6-5F364144450E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FA762E8F-659A-DECF-83D6-5F364144450E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -141,7 +135,6 @@ Global {A3F3C092-0A25-450B-BF6A-5983163CBEF5} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {A377955E-7EA1-6F29-8CF7-774569E93925} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} {443B4549-0AC0-4493-8F3E-49C83225DD76} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {FA762E8F-659A-DECF-83D6-5F364144450E} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 9184a62..4cd968c 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -304,8 +304,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading timer1.Stop(); _logger.LogError($"读取数据更花费时间{timer1.ElapsedMilliseconds}毫秒"); - //DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista, _kafkaOptions.NumPartitions); - //return; + DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista, _kafkaOptions.NumPartitions); + return; #else var meterInfos = await GetAmmeterInfoList(gatherCode); #endif -- 2.47.2 From 3020a4967245b87be96e3f1504022eada18b8634 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 17 Apr 2025 17:23:20 +0800 Subject: [PATCH 129/139] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/SampleAppService.cs | 13 + .../BasicScheduledMeterReadingService.cs | 392 +++++------------- .../DeviceGroupBalanceControl.cs | 4 +- 3 files changed, 109 insertions(+), 300 deletions(-) diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 09bd962..a47ae6c 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -226,6 +226,19 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS timer1.Stop(); _logger.LogError($"读取数据更花费时间{timer1.ElapsedMilliseconds}毫秒"); + + List focusAddressDataLista = new List(); + foreach (var item in meterInfos) + { + focusAddressDataLista.Add(item.FocusAddress); + } + + DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista); + + // 打印分布统计 + DeviceGroupBalanceControl.PrintDistributionStats(); + + await Task.CompletedTask; } diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index b6eb361..1537a29 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -25,6 +25,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using static FreeSql.Internal.GlobalFilter; @@ -182,7 +183,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading deviceIdSelector: data => data.FocusAddress, processor: (data, groupIndex) => { - _ = AmmerterCreatePublishTask(timeDensity, data, groupIndex, tasksToBeIssueModel.NextTaskTime.ToString("yyyyMMddHHmmss")); + AmmerterCreatePublishTask(timeDensity, data, groupIndex, tasksToBeIssueModel.NextTaskTime.ToString("yyyyMMddHHmmss")); } ); @@ -582,126 +583,104 @@ namespace JiShe.CollectBus.ScheduledMeterReading int timeDensity = 15; var currentDateTime = DateTime.Now; - var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.Ammeter); - var fifteenMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - if (fifteenMinutekeyList == null || fifteenMinutekeyList.Length <= 0) - { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-101"); - return; - } + // 自动计算最佳并发度 + int recommendedThreads = DeviceGroupBalanceControl.CalculateOptimalThreadCount(); - //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheDictionaryData(fifteenMinutekeyList, SystemType, ServerTagName, timeDensity.ToString(), MeterTypeEnum.Ammeter); - if (meterTaskInfos == null || meterTaskInfos.Count <= 0) + var options = new ParallelOptions { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); - return; - } - - List meterTaskInfosList = new List(); - - //将取出的缓存任务数据发送到Kafka消息队列中 - foreach (var focusItem in meterTaskInfos) + MaxDegreeOfParallelism = recommendedThreads, + }; + string taskBatch = "20250417155016"; + Parallel.For(0, _kafkaOptions.NumPartitions, options, async groupIndex => { - foreach (var ammerterItem in focusItem.Value) + Console.WriteLine($"15分钟采集电表数据:{groupIndex}"); + var redisCacheTelemetryPacketInfoHashKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; + var redisCacheTelemetryPacketInfoSetIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoSetIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; + var redisCacheTelemetryPacketInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; + + List meterInfos = new List(); + decimal? cursor = null; + string member = null; + bool hasNext; + do { - var tempMsg = new ScheduledMeterReadingIssuedEventMessage() - { - MessageHexString = ammerterItem.Value.IssuedMessageHexString, - MessageId = ammerterItem.Value.IssuedMessageId, - FocusAddress = ammerterItem.Value.FocusAddress, - TimeDensity = timeDensity.ToString(), - }; - //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); + var page = await _redisDataCacheService.GetAllPagedData( + redisCacheTelemetryPacketInfoHashKey, + redisCacheTelemetryPacketInfoZSetScoresIndexKey, + pageSize: 1000, + lastScore: cursor, + lastMember: member); - _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); + meterInfos.AddRange(page.Items); + cursor = page.HasNext ? page.NextScore : null; + member = page.HasNext ? page.NextMember : null; + hasNext = page.HasNext; - //_ = _producerBus.Publish(tempMsg); + await DeviceGroupBalanceControl.ProcessWithThrottleAsync( + items: meterInfos, + deviceIdSelector: data => data.FocusAddress, + processor: (data, groupIndex) => + { + _= KafkaProducerIssuedMessage(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName,data, groupIndex); + } + ); - meterTaskInfosList.Add(ammerterItem.Value); - } - } - if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) - { - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentDateTime); - } + } while (hasNext); + }); + + + + //var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.Ammeter); + //var fifteenMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); + //if (fifteenMinutekeyList == null || fifteenMinutekeyList.Length <= 0) + //{ + // _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-101"); + // return; + //} + + ////获取下发任务缓存数据 + //Dictionary> meterTaskInfos = await GetMeterRedisCacheDictionaryData(fifteenMinutekeyList, SystemType, ServerTagName, timeDensity.ToString(), MeterTypeEnum.Ammeter); + //if (meterTaskInfos == null || meterTaskInfos.Count <= 0) + //{ + // _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); + // return; + //} + + //List meterTaskInfosList = new List(); + + ////将取出的缓存任务数据发送到Kafka消息队列中 + //foreach (var focusItem in meterTaskInfos) + //{ + // foreach (var ammerterItem in focusItem.Value) + // { + // var tempMsg = new ScheduledMeterReadingIssuedEventMessage() + // { + // MessageHexString = ammerterItem.Value.IssuedMessageHexString, + // MessageId = ammerterItem.Value.IssuedMessageId, + // FocusAddress = ammerterItem.Value.FocusAddress, + // TimeDensity = timeDensity.ToString(), + // }; + // //_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); + + // _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg); + + // //_ = _producerBus.Publish(tempMsg); + + // meterTaskInfosList.Add(ammerterItem.Value); + // } + //} + //if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) + //{ + // await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentDateTime); + //} - stopwatch.Stop(); + //stopwatch.Stop(); - _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} {timeDensity}分钟采集电表数据处理完成,共消耗{stopwatch.ElapsedMilliseconds}毫秒。"); - } - - /// - /// 电表采集任务指令创建 - /// - /// 采集频率1分钟、5分钟、15分钟 - /// 集中器数据分组 - /// - private async Task AmmerterScheduledMeterReadingIssued(int timeDensity, Dictionary> focusGroup) - { - if (timeDensity <= 0) - { - timeDensity = 1; - } - - if (timeDensity > 15) - { - timeDensity = 15; - } - - if (focusGroup == null || focusGroup.Count <= 0) - { - _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 电表数据采集指令生成失败,参数异常,-101"); - return; - } - try - { - //将采集器编号的hash值取模分组 - const int TotalShards = 1024; - var focusHashGroups = new Dictionary>>(); - - foreach (var (collectorId, ammetersDictionary) in focusGroup) - { - if (string.IsNullOrWhiteSpace(collectorId)) - { - _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 集中器信息分组取模失败,无效Key -102"); - continue; - } - - // 计算哈希分组ID - int hashGroupId = Math.Abs(collectorId.GetHashCode() % TotalShards); - - // 获取或创建分组(避免重复查找) - if (!focusHashGroups.TryGetValue(hashGroupId, out var group)) - { - group = new Dictionary>(); - focusHashGroups[hashGroupId] = group; - } - - // 将当前集中器数据加入分组 - group[collectorId] = ammetersDictionary; - } - - if (focusHashGroups == null) - { - _logger.LogError($"{nameof(AmmerterScheduledMeterReadingIssued)} 集中器信息分组取模失败 -103"); - return; - } - - //根据分组创建线程批处理集中器 - foreach (var group in focusHashGroups) - { - await AmmerterCreatePublishTask2(timeDensity, group.Value); - } - } - catch (Exception) - { - - throw; - } + //_logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} {timeDensity}分钟采集电表数据处理完成,共消耗{stopwatch.ElapsedMilliseconds}毫秒。"); } + /// /// 电表创建发布任务 @@ -711,7 +690,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// 集中器所在分组 /// 时间格式的任务批次名称 /// - private async Task AmmerterCreatePublishTask(int timeDensity + private void AmmerterCreatePublishTask(int timeDensity , AmmeterInfo ammeterInfo, int groupIndex, string taskBatch) { var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; @@ -719,7 +698,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading var currentTime = DateTime.Now; var pendingCopyReadTime = currentTime.AddMinutes(timeDensity); - //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 + var redisCacheTelemetryPacketInfoHashKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; var redisCacheTelemetryPacketInfoSetIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoSetIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; var redisCacheTelemetryPacketInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; @@ -933,15 +912,15 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// /// 主题名称 /// 任务记录 + /// 对应分区,也就是集中器号所在的分组序号 /// private async Task KafkaProducerIssuedMessage(string topicName, - MeterReadingRecords taskRecord) + MeterReadingTelemetryPacketInfo taskRecord,int partition) { if (string.IsNullOrWhiteSpace(topicName) || taskRecord == null) { throw new Exception($"{nameof(KafkaProducerIssuedMessage)} 推送消息失败,参数异常,-101"); } - int partition = DeviceGroupBalanceControl.GetDeviceGroupId(taskRecord.FocusAddress); await _producerService.ProduceAsync(topicName, partition, taskRecord); } @@ -997,192 +976,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentDateTime); } } - - /// - /// 电表创建发布任务 - /// - /// 采集频率 - /// 集中器号hash分组的集中器集合数据 - /// - private async Task AmmerterCreatePublishTask2(int timeDensity - , Dictionary> focusGroup) - { - var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; - //todo 检查需要待补抄的电表的时间点信息,保存到需要待补抄的缓存中。如果此线程异常,该如何补偿? - - var currentTime = DateTime.Now; - var pendingCopyReadTime = currentTime.AddMinutes(timeDensity); - foreach (var focusInfo in focusGroup) - { - //构建缓存任务key,依然 表计类型+采集频率+集中器地址,存hash类型 - var redisCacheKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}{focusInfo.Key}"; - - foreach (var ammeterInfo in focusInfo.Value) - { - var ammeter = ammeterInfo.Value; - - if (string.IsNullOrWhiteSpace(ammeter.ItemCodes)) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}数据采集指令生成失败,采集项为空,-101"); - continue; - } - - //载波的不处理 - if (ammeter.MeteringPort == (int)MeterLinkProtocolEnum.Carrierwave) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}数据采集指令生成失败,载波不处理,-102"); - continue; - } - - if (ammeter.State.Equals(2)) - { - _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} {ammeter.Name} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}状态为禁用,不处理"); - continue; - } - - ////排除1天未在线的集中器生成指令 或 排除集中器配置为自动上报的集中器 - //if (!IsGennerateCmd(ammeter.LastTime, -1)) - //{ - // _logger.LogInformation($"{nameof(CreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name},采集时间:{ammeter.LastTime},已超过1天未在线,不生成指令"); - // continue; - //} - - if (string.IsNullOrWhiteSpace(ammeter.AreaCode)) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeter.MeterId},集中器通信区号为空"); - continue; - } - if (string.IsNullOrWhiteSpace(ammeter.Address)) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeter.MeterId},集中器通信地址为空"); - continue; - } - if (Convert.ToInt32(ammeter.Address) > 65535) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeter.MeterId},集中器通信地址无效,确保大于65535"); - continue; - } - if (ammeter.MeteringCode <= 0 || ammeter.MeteringCode > 2033) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeter.MeterId},非有效测量点号({ammeter.MeteringCode})"); - continue; - } - - List tempCodes = ammeter.ItemCodes.Deserialize>()!; - - //TODO:自动上报数据只主动采集1类数据。 - if (ammeter.AutomaticReport.Equals(1)) - { - var tempSubCodes = new List(); - if (tempCodes.Contains("0C_49")) - { - tempSubCodes.Add("0C_49"); - } - - if (tempSubCodes.Contains("0C_149")) - { - tempSubCodes.Add("0C_149"); - } - - if (ammeter.ItemCodes.Contains("10_97")) - { - tempSubCodes.Add("10_97"); - } - - if (tempSubCodes == null || tempSubCodes.Count <= 0) - { - _logger.LogInformation($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}自动上报数据主动采集1类数据时数据类型为空"); - continue; - } - else - { - tempCodes = tempSubCodes; - } - } - - Dictionary keyValuePairs = new Dictionary(); - - foreach (var tempItem in tempCodes) - { - //排除已发送日冻结和月冻结采集项配置 - if (DayFreezeCodes.Contains(tempItem)) - { - continue; - } - - if (MonthFreezeCodes.Contains(tempItem)) - { - continue; - } - - var itemCodeArr = tempItem.Split('_'); - var aFNStr = itemCodeArr[0]; - var aFN = (AFN)aFNStr.HexToDec(); - var fn = int.Parse(itemCodeArr[1]); - byte[] dataInfos = null; - if (ammeter.AutomaticReport.Equals(1) && aFN == AFN.请求实时数据) - { - //实时数据 - dataInfos = Build3761SendData.BuildAmmeterReadRealTimeDataSendCmd(ammeter.FocusAddress, ammeter.MeteringCode, (ATypeOfDataItems)fn); - } - else - { - string methonCode = $"AFN{aFNStr}_Fn_Send"; - //特殊表暂不处理 - if (handlerPacketBuilder != null && handlerPacketBuilder.TryGetValue(methonCode - , out var handler)) - { - dataInfos = handler(new TelemetryPacketRequest() - { - FocusAddress = ammeter.FocusAddress, - Fn = fn, - Pn = ammeter.MeteringCode - }); - } - else - { - _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}采集项{tempItem}无效编码。"); - continue; - } - } - //TODO:特殊表 - - if (dataInfos == null || dataInfos.Length <= 0) - { - _logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeter.FocusAddress}的电表{ammeter.Name}采集项{tempItem}未能正确获取报文。"); - continue; - } - - - - var meterReadingRecords = new MeterReadingRecords() - { - ProjectID = ammeter.ProjectID, - DatabaseBusiID = ammeter.DatabaseBusiID, - PendingCopyReadTime = pendingCopyReadTime, - CreationTime = currentTime, - MeterAddress = ammeter.AmmerterAddress, - MeterId = ammeter.MeterId, - MeterType = MeterTypeEnum.Ammeter, - FocusAddress = ammeter.FocusAddress, - FocusID = ammeter.FocusId, - AFN = aFN, - Fn = fn, - ItemCode = tempItem, - TaskMark = CommonHelper.GetTaskMark((int)aFN, fn, ammeter.MeteringCode), - ManualOrNot = false, - Pn = ammeter.MeteringCode, - IssuedMessageId = GuidGenerator.Create().ToString(), - IssuedMessageHexString = Convert.ToHexString(dataInfos), - }; - //meterReadingRecords.CreateDataId(GuidGenerator.Create()); - - keyValuePairs.TryAdd($"{ammeter.MeterId}_{tempItem}", meterReadingRecords); - } - await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); - } - } - } + #endregion diff --git a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs index 06b7d70..aba63e8 100644 --- a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs +++ b/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs @@ -230,7 +230,7 @@ namespace JiShe.CollectBus.Common.DeviceBalanceControl /// /// 自动计算最优线程数 /// - private static int CalculateOptimalThreadCount() + public static int CalculateOptimalThreadCount() { int coreCount = Environment.ProcessorCount; return Math.Min( @@ -418,6 +418,8 @@ namespace JiShe.CollectBus.Common.DeviceBalanceControl { Console.WriteLine($"Group {stat.GroupId}: {stat.Count} 条数据"); } + + Console.WriteLine($"总共: {stats.Sum(d=>d.Count)} 条数据"); } } -- 2.47.2 From b064ff3d146af5d573eb35c0de627cad3a8d7b6b Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Thu, 17 Apr 2025 18:08:27 +0800 Subject: [PATCH 130/139] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicScheduledMeterReadingService.cs | 4 +- .../Consts/ProtocolConst.cs | 8 +- src/JiShe.CollectBus.Host/appsettings.json | 4 +- .../CollectBusKafkaModule.cs | 6 +- .../Consumer/ConsumerService.cs | 33 ++++--- .../KafkaSubcribesExtensions.cs | 86 ++++++++++++++----- .../Producer/ProducerService.cs | 2 + 7 files changed, 99 insertions(+), 44 deletions(-) diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 9184a62..4cd968c 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -304,8 +304,8 @@ namespace JiShe.CollectBus.ScheduledMeterReading timer1.Stop(); _logger.LogError($"读取数据更花费时间{timer1.ElapsedMilliseconds}毫秒"); - //DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista, _kafkaOptions.NumPartitions); - //return; + DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista, _kafkaOptions.NumPartitions); + return; #else var meterInfos = await GetAmmeterInfoList(gatherCode); #endif diff --git a/src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs b/src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs index 6b65535..91a41c5 100644 --- a/src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs +++ b/src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs @@ -12,11 +12,11 @@ namespace JiShe.CollectBus.Common.Consts /// /// 心跳下行消息主题 /// - public const string SubscriberHeartbeatIssuedEventName = "issued.heartbeat.event"; + public const string SubscriberHeartbeatIssuedEventName = "issued.heartbeat.event2"; /// /// 登录下行消息主题 /// - public const string SubscriberLoginIssuedEventName = "issued.login.event"; + public const string SubscriberLoginIssuedEventName = "issued.login.event2"; /// /// 上行消息主题,测试使用 @@ -26,11 +26,11 @@ namespace JiShe.CollectBus.Common.Consts /// /// 心跳上行消息主题 /// - public const string SubscriberHeartbeatReceivedEventName = "received.heartbeat.event"; + public const string SubscriberHeartbeatReceivedEventName = "received.heartbeat.event2"; /// /// 登录上行消息主题 /// - public const string SubscriberLoginReceivedEventName = "received.login.event"; + public const string SubscriberLoginReceivedEventName = "received.login.event2"; #region 电表消息主题 /// diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index 501a2a0..affc34d 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -91,8 +91,8 @@ "SaslUserName": "lixiao", "SaslPassword": "lixiao1980", "KafkaReplicationFactor": 3, - "NumPartitions": 30, - "ServerTagName": "JiSheCollectBus3" + "NumPartitions": 1, + "ServerTagName": "JiSheCollectBus2" //"Topic": { // "ReplicationFactor": 3, // "NumPartitions": 1000 diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs index 57e0a3e..39b6444 100644 --- a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs +++ b/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs @@ -44,10 +44,10 @@ namespace JiShe.CollectBus.Kafka public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); - + // 注册Subscriber - app.ApplicationServices.UseKafkaSubscribers(); - + app.ApplicationServices.UseKafkaSubscribe(); + // 获取程序集 //app.UseKafkaSubscribers(Assembly.Load("JiShe.CollectBus.Application")); } diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index fd11df4..ea67b74 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -1,4 +1,5 @@ using Confluent.Kafka; +using JiShe.CollectBus.Common.Consts; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -50,7 +51,7 @@ namespace JiShe.CollectBus.Kafka.Consumer AutoOffsetReset = AutoOffsetReset.Earliest, EnableAutoCommit = false, // 禁止AutoCommit EnablePartitionEof = true, // 启用分区末尾标记 - //AllowAutoCreateTopics= true, // 启用自动创建 + //AllowAutoCreateTopics = true, // 启用自动创建 FetchMaxBytes = 1024 * 1024 * 50 // 增加拉取大小(50MB) }; @@ -122,7 +123,9 @@ namespace JiShe.CollectBus.Kafka.Consumer var result = consumer.Consume(cts.Token); if (result == null || result.Message==null || result.Message.Value == null) { +#if DEBUG _logger.LogWarning($"Kafka消费: {result?.Topic} 分区 {result?.Partition} 值为NULL"); +#endif //consumer.Commit(result); // 手动提交 continue; } @@ -151,7 +154,7 @@ namespace JiShe.CollectBus.Kafka.Consumer } catch (ConsumeException ex) { - _logger.LogError(ex, $"消息消费失败: {ex.Error.Reason}"); + _logger.LogError(ex, $"{string.Join("、", topics)}消息消费失败: {ex.Error.Reason}"); } } }); @@ -170,14 +173,17 @@ namespace JiShe.CollectBus.Kafka.Consumer /// public async Task SubscribeAsync(string[] topics, Func> messageHandler, string? groupId) where TValue : class { - var consumerKey = typeof(KafkaConsumer); + var consumerKey = typeof(KafkaConsumer); var cts = new CancellationTokenSource(); - + if (topics.Contains(ProtocolConst.SubscriberLoginReceivedEventName)) + { + string ssss = ""; + } var consumer = _consumerStore.GetOrAdd(consumerKey, _=> ( - CreateConsumer(groupId), + CreateConsumer(groupId), cts - )).Consumer as IConsumer; + )).Consumer as IConsumer; consumer!.Subscribe(topics); @@ -190,8 +196,11 @@ namespace JiShe.CollectBus.Kafka.Consumer var result = consumer.Consume(cts.Token); if (result == null || result.Message==null || result.Message.Value == null) { +#if DEBUG _logger.LogWarning($"Kafka消费: {result?.Topic} 分区 {result?.Partition} 值为NULL"); +#endif //consumer.Commit(result); // 手动提交 + consumer.StoreOffset(result); continue; } if (result.IsPartitionEOF) @@ -214,10 +223,12 @@ namespace JiShe.CollectBus.Kafka.Consumer bool sucess = await messageHandler(result.Message.Value); if (sucess) consumer.Commit(result); // 手动提交 + else + consumer.StoreOffset(result); } catch (ConsumeException ex) { - _logger.LogError(ex, $"消息消费失败: {ex.Error.Reason}"); + _logger.LogError(ex, $"{string.Join("、", topics)}消息消费失败: {ex.Error.Reason}"); } } }); @@ -339,7 +350,7 @@ namespace JiShe.CollectBus.Kafka.Consumer } catch (ConsumeException ex) { - _logger.LogError(ex, $"消息消费失败: {ex.Error.Reason}"); + _logger.LogError(ex, $"{string.Join("、", topics)} 消息消费失败: {ex.Error.Reason}"); } catch (OperationCanceledException) { @@ -385,14 +396,14 @@ namespace JiShe.CollectBus.Kafka.Consumer /// 消费等待时间 public async Task SubscribeBatchAsync(string[] topics,Func, Task> messageBatchHandler, string? groupId = null, int batchSize = 100,TimeSpan? batchTimeout = null,TimeSpan? consumeTimeout = null)where TValue : class { - var consumerKey = typeof(KafkaConsumer); + var consumerKey = typeof(KafkaConsumer); var cts = new CancellationTokenSource(); var consumer = _consumerStore.GetOrAdd(consumerKey, _ => ( - CreateConsumer(groupId), + CreateConsumer(groupId), cts - )).Consumer as IConsumer; + )).Consumer as IConsumer; consumer!.Subscribe(topics); diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs index c28c82c..0ea4d55 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Newtonsoft.Json; using System.Reflection; namespace JiShe.CollectBus.Kafka @@ -21,7 +22,7 @@ namespace JiShe.CollectBus.Kafka /// /// /// - public static void UseKafkaSubscribers(this IServiceProvider provider) + public static void UseKafkaSubscribe(this IServiceProvider provider) { var lifetime = provider.GetRequiredService(); @@ -69,7 +70,7 @@ namespace JiShe.CollectBus.Kafka { if (subscribe!=null) { - Tuple tuple = BuildKafkaSubscriber(subscribe, provider, logger, kafkaOptions.Value); + Tuple tuple = BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value); threadCount += tuple.Item1; topicCount += tuple.Item2; } @@ -77,6 +78,7 @@ namespace JiShe.CollectBus.Kafka } } logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); + }); } @@ -113,7 +115,7 @@ namespace JiShe.CollectBus.Kafka if (subscribe != null) { - Tuple tuple = BuildKafkaSubscriber(subscribe, provider, logger, kafkaOptions.Value); + Tuple tuple = BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value); threadCount += tuple.Item1; topicCount += tuple.Item2; } @@ -128,7 +130,7 @@ namespace JiShe.CollectBus.Kafka /// /// /// - private static Tuple BuildKafkaSubscriber(object subscribe, IServiceProvider provider,ILogger logger, KafkaOptionConfig kafkaOptionConfig) + private static Tuple BuildKafkaSubscribe(object subscribe, IServiceProvider provider,ILogger logger, KafkaOptionConfig kafkaOptionConfig) { var subscribedMethods = subscribe.GetType().GetMethods() .Select(m => new { Method = m, Attribute = m.GetCustomAttribute() }) @@ -166,7 +168,7 @@ namespace JiShe.CollectBus.Kafka if (attr.EnableBatch) { - await consumerService.SubscribeBatchAsync(attr.Topic, async (message) => + await consumerService.SubscribeBatchAsync(attr.Topic, async (message) => { try { @@ -183,7 +185,7 @@ namespace JiShe.CollectBus.Kafka } else { - await consumerService.SubscribeAsync(attr.Topic, async (message) => + await consumerService.SubscribeAsync(attr.Topic, async (message) => { try { @@ -209,36 +211,76 @@ namespace JiShe.CollectBus.Kafka /// /// /// - private static async Task ProcessMessageAsync(dynamic message, MethodInfo method, object subscribe) + private static async Task ProcessMessageAsync(object message, MethodInfo method, object subscribe) { var parameters = method.GetParameters(); bool isGenericTask = method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>); bool existParameters = parameters.Length > 0; - //dynamic? messageObj= null; + //dynamic? messageObj = null; //if (existParameters) //{ - // var paramType = parameters[0].ParameterType; - // messageObj = paramType == typeof(string) ? message : message.Deserialize(paramType); + // var paramType = parameters[0].ParameterType; + // if (paramType.IsInstanceOfType(message)) + // return message; // 类型兼容则直接使用 + + // var json = message.ToString(); + // messageObj= message.de JsonConvert.DeserializeObject(json, targetType); //} - if (isGenericTask) + + object[] args = null; + if (existParameters) { - object? result = await (Task)method.Invoke(subscribe, existParameters? new[] { message } :null)!; - if (result is ISubscribeAck ackResult) - { - return ackResult.Ack; - } + var paramType = parameters[0].ParameterType; + // 类型转换逻辑 + object convertedMessage = paramType.IsInstanceOfType(message) + ? message + : ConvertMessage(message, paramType); + + args = new object[] { convertedMessage }; } - else + var result = method.Invoke(subscribe, args); + if (result is Task genericTask) { - object? result = method.Invoke(subscribe, existParameters ? new[] { message } : null); - if (result is ISubscribeAck ackResult) - { - return ackResult.Ack; - } + await genericTask.ConfigureAwait(false); + return genericTask.Result.Ack; } + else if (result is Task nonGenericTask) + { + await nonGenericTask.ConfigureAwait(false); + return true; + } + else if (result is ISubscribeAck ackResult) + { + return ackResult.Ack; + } + + //if (isGenericTask) + //{ + // object? result = await (Task)method.Invoke(subscribe, existParameters? new[] { message } :null)!; + // if (result is ISubscribeAck ackResult) + // { + // return ackResult.Ack; + // } + //} + //else + //{ + // object? result = method.Invoke(subscribe, existParameters ? new[] { message } : null); + // if (result is ISubscribeAck ackResult) + // { + // return ackResult.Ack; + // } + //} return false; } + // 可扩展的类型转换器 + private static object ConvertMessage(object rawMessage, Type targetType) + { + if (targetType.IsInstanceOfType(rawMessage)) + return rawMessage; // 类型兼容则直接使用 + var json = rawMessage.ToString(); + return JsonConvert.DeserializeObject(json, targetType); + } } } diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs index 42fc9cf..8231760 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs @@ -129,6 +129,7 @@ namespace JiShe.CollectBus.Kafka.Producer var producer = GetProducer(typeKey); var message = new Message { + Key= _kafkaOptionConfig.ServerTagName, Value = value, Headers = new Headers{ { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } @@ -187,6 +188,7 @@ namespace JiShe.CollectBus.Kafka.Producer { var message = new Message { + Key = _kafkaOptionConfig.ServerTagName, Value = value, Headers = new Headers{ { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } -- 2.47.2 From 532acc575f2bc4c23ddf111e001bac39a0886691 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Thu, 17 Apr 2025 19:41:09 +0800 Subject: [PATCH 131/139] =?UTF-8?q?=E4=BC=98=E5=8C=96kafka=20=E8=AE=A2?= =?UTF-8?q?=E9=98=85=E5=A2=9E=E5=8A=A0=E5=88=A4=E6=96=AD=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E5=88=86=E5=8C=BA=E6=95=B0=E6=98=AF=E5=90=A6=E5=A4=A7=E4=BA=8E?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 6 ++ src/JiShe.CollectBus.Host/appsettings.json | 2 +- .../Consumer/ConsumerService.cs | 9 ++- .../KafkaSubcribesExtensions.cs | 81 ++++++------------- 4 files changed, 38 insertions(+), 60 deletions(-) diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index 9c67aae..0d0c5e4 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.T EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Cassandra", "src\JiShe.CollectBus.Cassandra\JiShe.CollectBus.Cassandra.csproj", "{443B4549-0AC0-4493-8F3E-49C83225DD76}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka.Test", "src\JiShe.CollectBus.Kafka.Test\JiShe.CollectBus.Kafka.Test.csproj", "{FA762E8F-659A-DECF-83D6-5F364144450E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -113,6 +115,10 @@ Global {443B4549-0AC0-4493-8F3E-49C83225DD76}.Debug|Any CPU.Build.0 = Debug|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.ActiveCfg = Release|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.Build.0 = Release|Any CPU + {FA762E8F-659A-DECF-83D6-5F364144450E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA762E8F-659A-DECF-83D6-5F364144450E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA762E8F-659A-DECF-83D6-5F364144450E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA762E8F-659A-DECF-83D6-5F364144450E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/src/JiShe.CollectBus.Host/appsettings.json index affc34d..57c1823 100644 --- a/src/JiShe.CollectBus.Host/appsettings.json +++ b/src/JiShe.CollectBus.Host/appsettings.json @@ -91,7 +91,7 @@ "SaslUserName": "lixiao", "SaslPassword": "lixiao1980", "KafkaReplicationFactor": 3, - "NumPartitions": 1, + "NumPartitions": 30, "ServerTagName": "JiSheCollectBus2" //"Topic": { // "ReplicationFactor": 3, diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs index ea67b74..4340524 100644 --- a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs +++ b/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs @@ -175,16 +175,17 @@ namespace JiShe.CollectBus.Kafka.Consumer { var consumerKey = typeof(KafkaConsumer); var cts = new CancellationTokenSource(); - if (topics.Contains(ProtocolConst.SubscriberLoginReceivedEventName)) - { - string ssss = ""; - } + //if (topics.Contains(ProtocolConst.SubscriberLoginReceivedEventName)) + //{ + // string ssss = ""; + //} var consumer = _consumerStore.GetOrAdd(consumerKey, _=> ( CreateConsumer(groupId), cts )).Consumer as IConsumer; + consumer!.Subscribe(topics); _ = Task.Run(async () => diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs index 8962327..b09b3be 100644 --- a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs +++ b/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs @@ -1,6 +1,7 @@ using Confluent.Kafka; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Kafka.AdminClient; using JiShe.CollectBus.Kafka.Attributes; using JiShe.CollectBus.Kafka.Consumer; @@ -12,6 +13,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; using System.Reflection; +using System.Threading.Tasks; namespace JiShe.CollectBus.Kafka { @@ -66,11 +68,11 @@ namespace JiShe.CollectBus.Kafka foreach (var subscribeType in subscribeTypes) { var subscribes = provider.GetServices(subscribeType).ToList(); - subscribes.ForEach(subscribe => + subscribes.ForEach(async subscribe => { if (subscribe!=null) { - Tuple tuple = BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value); + Tuple tuple = await BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value); threadCount += tuple.Item1; topicCount += tuple.Item2; } @@ -111,11 +113,11 @@ namespace JiShe.CollectBus.Kafka foreach (var subscribeType in subscribeTypes) { var subscribes = provider.GetServices(subscribeType).ToList(); - subscribes.ForEach(subscribe => { + subscribes.ForEach(async subscribe => { if (subscribe != null) { - Tuple tuple = BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value); + Tuple tuple =await BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value); threadCount += tuple.Item1; topicCount += tuple.Item2; } @@ -130,7 +132,7 @@ namespace JiShe.CollectBus.Kafka /// /// /// - private static Tuple BuildKafkaSubscribe(object subscribe, IServiceProvider provider,ILogger logger, KafkaOptionConfig kafkaOptionConfig) + private static async Task> BuildKafkaSubscribe(object subscribe, IServiceProvider provider,ILogger logger, KafkaOptionConfig kafkaOptionConfig) { var subscribedMethods = subscribe.GetType().GetMethods() .Select(m => new { Method = m, Attribute = m.GetCustomAttribute() }) @@ -141,13 +143,16 @@ namespace JiShe.CollectBus.Kafka foreach (var sub in subscribedMethods) { int partitionCount = 3;// kafkaOptionConfig.NumPartitions; - //var adminClientService = provider.GetRequiredService(); + var adminClientService = provider.GetRequiredService(); + int topicCount = adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic); + partitionCount= partitionCount> topicCount ? topicCount: partitionCount; //int partitionCount = sub.Attribute!.TaskCount==-1?adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic) : sub.Attribute!.TaskCount; if (partitionCount <= 0) partitionCount = 1; for (int i = 0; i < partitionCount; i++) { - Task.Run(() => StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe, logger)); + //if (sub.Attribute!.Topic == ProtocolConst.SubscriberLoginReceivedEventName) + await StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe, logger); threadCount++; } } @@ -173,7 +178,7 @@ namespace JiShe.CollectBus.Kafka try { // 处理消息 - return await ProcessMessageAsync(message, method, subscribe); + return await ProcessMessageAsync(message.ToList(), method, subscribe); } catch (ConsumeException ex) { @@ -190,7 +195,7 @@ namespace JiShe.CollectBus.Kafka try { // 处理消息 - return await ProcessMessageAsync(message, method, subscribe); + return await ProcessMessageAsync(new List() { message }, method, subscribe); } catch (ConsumeException ex) { @@ -211,35 +216,26 @@ namespace JiShe.CollectBus.Kafka /// /// /// - private static async Task ProcessMessageAsync(object message, MethodInfo method, object subscribe) + private static async Task ProcessMessageAsync(List messages, MethodInfo method, object subscribe) { var parameters = method.GetParameters(); bool isGenericTask = method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>); bool existParameters = parameters.Length > 0; - //dynamic? messageObj = null; - //if (existParameters) - //{ - // var paramType = parameters[0].ParameterType; - // if (paramType.IsInstanceOfType(message)) - // return message; // 类型兼容则直接使用 - - // var json = message.ToString(); - // messageObj= message.de JsonConvert.DeserializeObject(json, targetType); - //} - - object[] args = null; + List? messageObj = null; if (existParameters) { + messageObj = new List(); var paramType = parameters[0].ParameterType; - // 类型转换逻辑 - object convertedMessage = paramType.IsInstanceOfType(message) - ? message - : ConvertMessage(message, paramType); - - args = new object[] { convertedMessage }; + foreach (var msg in messages) + { + var data = paramType != typeof(string) ? msg?.ToString()?.Deserialize(paramType) : msg; + if (data != null) + messageObj.Add(data); + } } - var result = method.Invoke(subscribe, args); + + var result = method.Invoke(subscribe, messageObj?.ToArray()); if (result is Task genericTask) { await genericTask.ConfigureAwait(false); @@ -254,33 +250,8 @@ namespace JiShe.CollectBus.Kafka { return ackResult.Ack; } - - //if (isGenericTask) - //{ - // object? result = await (Task)method.Invoke(subscribe, existParameters? new[] { message } :null)!; - // if (result is ISubscribeAck ackResult) - // { - // return ackResult.Ack; - // } - //} - //else - //{ - // object? result = method.Invoke(subscribe, existParameters ? new[] { message } : null); - // if (result is ISubscribeAck ackResult) - // { - // return ackResult.Ack; - // } - //} return false; } - // 可扩展的类型转换器 - private static object ConvertMessage(object rawMessage, Type targetType) - { - if (targetType.IsInstanceOfType(rawMessage)) - return rawMessage; // 类型兼容则直接使用 - - var json = rawMessage.ToString(); - return JsonConvert.DeserializeObject(json, targetType); - } + } } -- 2.47.2 From db6e314acaf2e7e6a724ac302ab1922c99078383 Mon Sep 17 00:00:00 2001 From: Dai Mr <1822802785@qq.com> Date: Thu, 17 Apr 2025 20:28:50 +0800 Subject: [PATCH 132/139] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 82 +++++++++--------- .../CassandraConfig.cs | 0 .../CassandraProvider.cs | 0 .../CassandraQueryOptimizer.cs | 0 .../CassandraRepository.cs | 1 - .../CollectBusCassandraModule.cs | 0 .../Extensions/ServiceCollectionExtensions.cs | 0 .../Extensions/SessionExtension.cs | 0 .../ICassandraProvider.cs | 0 .../ICassandraRepository.cs | 0 .../JiShe.CollectBus.Cassandra.csproj | 4 +- .../Mappers/CollectBusMapping.cs | 0 .../CollectBusFreeRedisModule.cs | 4 +- .../FreeRedisProvider.cs | 13 +-- .../IFreeRedisProvider.cs | 3 +- .../JiShe.CollectBus.FreeRedis.csproj | 2 +- .../Options/FreeRedisOptions.cs | 8 +- .../CollectBusFreeSqlModule.cs | 0 .../JiShe.CollectBus.FreeSql/DbEnum.cs | 0 .../FreeSqlProvider.cs | 0 .../IFreeSqlProvider.cs | 0 .../JiShe.CollectBus.FreeSql.csproj | 0 .../Attribute/ATTRIBUTEColumnAttribute.cs | 10 +++ .../Attribute/FIELDColumnAttribute.cs | 10 +++ .../Attribute/SingleMeasuringAttribute.cs | 10 +-- .../Attribute/TAGColumnAttribute.cs | 10 +++ .../CollectBusIoTDBModule.cs | 15 ++-- .../Context/IoTDBRuntimeContext.cs | 10 +-- .../Interface/IIoTDBProvider.cs | 4 +- .../Interface/IIoTDBSessionFactory.cs | 8 +- .../Interface/IIoTDBSessionPool.cs | 7 +- .../JiShe.CollectBus.IoTDB.csproj | 2 +- .../Options/IoTDBOptions.cs | 8 +- .../Options/QueryCondition.cs | 8 +- .../Options/QueryOptions.cs | 8 +- .../Provider/DeviceMetadata.cs | 7 +- .../Provider/DevicePathBuilder.cs | 8 +- .../Provider/IoTDBProvider.cs | 20 ++--- .../Provider/IoTDBSessionFactory.cs | 12 +-- .../Provider/IoTEntity.cs | 4 +- .../Provider/SessionPoolAdapter.cs | 14 ++- .../Provider/TableSessionPoolAdapter.cs | 14 ++- .../AdminClient/AdminClientService.cs | 0 .../AdminClient/IAdminClientService.cs | 0 .../Attributes/KafkaSubscribeAttribute.cs | 0 .../Attributes/TopicAttribute.cs | 0 .../CollectBusKafkaModule.cs | 0 .../Consumer/ConsumerService.cs | 0 .../Consumer/IConsumerService.cs | 0 .../JiShe.CollectBus.Kafka}/HeadersFilter.cs | 0 .../IKafkaSubscribe.cs | 0 .../JiShe.CollectBus.Kafka}/ISubscribeAck.cs | 0 .../JiShe.CollectBus.Kafka.csproj | 2 +- .../JiShe.CollectBus.Kafka}/JsonSerializer.cs | 0 .../KafkaOptionConfig.cs | 0 .../KafkaSubcribesExtensions.cs | 0 .../Producer/IProducerService.cs | 0 .../Producer/ProducerService.cs | 0 .../SubscribeResult.cs | 0 .../JiShe.CollectBus.MongoDB}/FodyWeavers.xml | 0 .../JiShe.CollectBus.MongoDB.abppkg | 0 .../JiShe.CollectBus.MongoDB.csproj | 2 +- .../MongoDB/CollectBusDbSchemaMigrator.cs | 0 .../MongoDB/CollectBusMongoDbContext.cs | 0 .../CollectBusMongoDbContextExtensions.cs | 0 .../MongoDB/CollectBusMongoDbModule.cs | 0 .../MongoDB/ICollectBusMongoDbContext.cs | 0 .../IMeterReadingRecordRepository.cs | 0 .../MeterReadingRecordRepository.cs | 0 .../ShardingStrategy/DayShardingStrategy.cs | 0 .../ShardingStrategy/IShardingStrategy.cs | 0 .../Abstracts/BaseProtocolPlugin.cs | 0 .../StandardFixedHeaderDataHandlingAdapter.cs | 0 .../AnalysisData/Appendix.cs | 0 .../Attributes/ProtocolNameAttribute.cs | 0 .../Interfaces/IProtocolPlugin.cs | 0 ...JiShe.CollectBus.Protocol.Contracts.csproj | 16 ++-- .../Models/CustomFixedHeaderRequestInfo.cs | 0 .../Models/TB3761.cs | 0 .../QGDW3761Config.cs | 0 .../JiShe.CollectBus.Protocol.Test.csproj | 6 +- .../JiSheCollectBusProtocolModule.cs | 0 .../TestProtocolPlugin.cs | 0 .../JiShe.CollectBus.Protocol.csproj | 8 +- .../JiSheCollectBusProtocolModule.cs | 0 .../StandardProtocolPlugin.cs | 0 .../BaseResultDto.cs | 0 .../CollectBusApplicationContractsModule.cs | 0 .../CollectBusRemoteServiceConsts.cs | 0 .../DataMigration/IDataMigrationService.cs | 0 .../Options/DataMigrationOptions.cs | 0 .../Dto/AddConrOnlineRecordInput.cs | 0 .../EnergySystem/Dto/AddFocusLogInput.cs | 0 .../Dto/AddSignalStrengthInput.cs | 0 .../Dto/AdjustMeterTimingInput.cs | 0 .../Dto/AmmeterArchivesDownInput.cs | 0 .../Dto/AmmeterArchivesDownOutput.cs | 0 .../Dto/AmmeterArchivesMatchInput.cs | 0 .../Dto/AutoReportCollectionItemsSetInput.cs | 0 .../EnergySystem/Dto/AutoReportSetInput.cs | 0 .../EnergySystem/Dto/BaseInput.cs | 0 .../EnergySystem/Dto/BatchReadVersionInput.cs | 0 .../Dto/BatchReadVersionOutput.cs | 0 .../EnergySystem/Dto/CallTimeTestingInput.cs | 0 .../Dto/CommunicationParametersSetInput.cs | 0 .../EnergySystem/Dto/EquitDubgInput.cs | 0 .../Dto/QueryAutoReportOpenStatusInput.cs | 0 .../EnergySystem/Dto/QueryRecordLogInput.cs | 0 .../EnergySystem/Dto/QueryRecordLogOutput.cs | 0 .../EnergySystem/Dto/ReadMeterNumInput.cs | 0 .../EnergySystem/Dto/ReadMeterNumOutput.cs | 0 .../EnergySystem/Dto/ReadTimeInput.cs | 0 .../EnergySystem/Dto/ReadTimeOutput.cs | 0 .../EnergySystem/Dto/ReadingInput.cs | 0 .../EnergySystem/Dto/ReadingOutput.cs | 0 .../EnergySystem/Dto/TerminalRestartInput.cs | 0 .../EnergySystem/Dto/TimeAdjustInput.cs | 0 .../EnergySystem/Dto/TimeSetInput.cs | 0 .../EnergySystem/Dto/TimeSetOutput.cs | 0 .../EnergySystem/Dto/ValveControlInput.cs | 0 .../EnergySystem/Dto/ValveControlOutput.cs | 0 .../EnergySystem/ICacheAppService.cs | 0 .../EnergySystem/IEnergySystemAppService.cs | 0 .../FodyWeavers.xml | 0 .../ICollectWorker.cs | 0 ...up.CollectBus.Application.Contracts.csproj | 0 ...he.CollectBus.Application.Contracts.abppkg | 0 ...he.CollectBus.Application.Contracts.csproj | 6 +- .../CollectBusPermissionDefinitionProvider.cs | 0 .../Permissions/CollectBusPermissions.cs | 0 .../RedisDataCache/IRedisDataCacheService.cs | 0 .../Samples/ISampleAppService.cs | 0 .../Samples/SampleDto.cs | 0 .../IScheduledMeterReadingService.cs | 0 .../Subscribers/ISubscriberAppService.cs | 0 .../IWorkerSubscriberAppService.cs | 0 .../CollectBusAppService.cs | 2 +- .../CollectBusApplicationAutoMapperProfile.cs | 0 .../CollectBusApplicationModule.cs | 4 +- .../Consumers/IssuedConsumer.cs | 0 .../Consumers/IssuedFaultConsumer.cs | 0 .../Consumers/ReceivedConsumer.cs | 0 .../Consumers/ReceivedFaultConsumer.cs | 0 .../Consumers/ReceivedHeartbeatConsumer.cs | 0 .../Consumers/ReceivedLoginConsumer.cs | 0 .../ScheduledMeterReadingConsumer.cs | 0 .../DataMigration/DataMigrationService.cs | 0 .../EnergySystem/CacheAppService.cs | 0 .../EnergySystem/EnergySystemAppService.cs | 0 .../Exceptions/CloseException.cs | 0 .../FodyWeavers.xml | 0 .../Handlers/SampleHandler.cs | 0 ...She - Backup.CollectBus.Application.csproj | 0 .../JiShe.CollectBus.Application.abppkg | 0 .../JiShe.CollectBus.Application.csproj | 10 +-- .../Plugins/CloseMonitor.cs | 0 .../Plugins/ServerMonitor.cs | 0 .../Plugins/TcpMonitor.cs | 0 .../Plugins/UdpMonitor.cs | 0 .../RedisDataCache/RedisDataCacheService.cs | 2 +- .../Samples/SampleAppService.cs | 5 +- .../Samples/TestAppService.cs | 2 - .../BasicScheduledMeterReadingService.cs | 2 +- ...nergySystemScheduledMeterReadingService.cs | 2 +- .../Subscribers/SubscriberAppService.cs | 2 +- .../Subscribers/WorkerSubscriberAppService.cs | 0 .../Workers/CreateToBeIssueTaskWorker.cs | 0 .../Workers/EpiCollectWorker.cs | 0 .../Workers/SubscriberFifteenMinuteWorker.cs | 0 .../Workers/SubscriberFiveMinuteWorker.cs | 0 .../Workers/SubscriberOneMinuteWorker.cs | 0 .../cmd3761Matching.txt | 0 .../cmd3761TdcMatching.txt | 0 .../CollectBusDbMigratorModule.cs | 0 .../DbMigratorHostedService.cs | 0 .../JiShe.CollectBus.DbMigrator/Dockerfile | 0 .../FodyWeavers.xml | 0 .../JiShe.CollectBus.DbMigrator.abppkg | 0 .../JiShe.CollectBus.DbMigrator.csproj | 2 +- .../JiShe.CollectBus.DbMigrator/Program.cs | 0 .../appsettings.json | 0 .../appsettings.secrets.json | 0 .../Ammeters/AmmeterInfo.cs | 0 .../Ammeters/AmmeterInfoTemp.cs | 0 .../Ammeters/ElectricityMeter.cs | 5 +- .../Analyze3761Data.cs | 0 .../CollectBusConsts.cs | 0 .../CollectBusDbProperties.cs | 0 .../CollectBusDomainModule.cs | 0 .../Data/CollectBusDbMigrationService.cs | 0 .../Data/ICollectBusDbSchemaMigrator.cs | 0 .../Data/NullCollectBusDbSchemaMigrator.cs | 0 .../EnergySystems/Entities/TB_AmmeterInfo.cs | 0 .../TableViews/V_FocusAmmeter.cs | 0 .../JiShe.CollectBus.Domain/FodyWeavers.xml | 0 .../GatherItem/GatherItemInfo.cs | 0 .../AFNEntity/SingleMeasuringAFNDataEntity.cs | 5 +- .../IotSystems/Devices/Device.cs | 0 .../MessageIssueds/MessageIssued.cs | 0 ...ScheduledMeterReadingIssuedEventMessage.cs | 0 .../IotSystems/MessageReceiveds/IReceived.cs | 0 .../MessageReceiveds/MessageReceived.cs | 0 .../MeterReadingRecords.cs | 0 .../MeterReadingTelemetryPacketInfo.cs | 0 .../PrepayModel/Vi_BaseAmmeterInfo.cs | 0 .../IotSystems/Protocols/ProtocolInfo.cs | 0 .../IotSystems/Records/ConrOnlineRecord.cs | 0 .../IotSystems/Records/CsqRecord.cs | 0 .../IotSystems/Records/FocusRecord.cs | 0 .../IotSystems/Watermeter/WatermeterInfo.cs | 0 .../JiShe - Backup.CollectBus.Domain.csproj | 0 .../JiShe.CollectBus.Domain.abppkg | 0 .../JiShe.CollectBus.Domain.csproj | 8 +- .../Properties/AssemblyInfo.cs | 0 .../CollectBusSettingDefinitionProvider.cs | 0 .../Settings/CollectBusSettings.cs | 0 .../CollectBusDbContext.cs | 0 .../CollectBusDbContextFactory.cs | 0 ...CollectBusEfCoreEntityExtensionMappings.cs | 0 .../CollectBusEntityFrameworkCoreModule.cs | 0 ...FrameworkCoreCollectBusDbSchemaMigrator.cs | 0 .../FodyWeavers.xml | 0 ...iShe.CollectBus.EntityFrameworkCore.abppkg | 0 ...iShe.CollectBus.EntityFrameworkCore.csproj | 0 .../Properties/AssemblyInfo.cs | 0 .../AttributeInfo/NumericalOrderAttribute.cs | 0 .../Attributes/CassandraTableAttribute.cs | 0 .../Attributes/IgnoreJobAttribute.cs | 0 .../Attributes/TopicNameAttribute.cs | 0 .../BuildSendDatas/Build188SendData.cs | 0 .../BuildSendDatas/Build3761SendData.cs | 0 .../BuildSendDatas/Build645SendData.cs | 0 .../BuildSendDatas/TasksToBeIssueModel.cs | 0 .../BuildSendDatas/TelemetryPacketBuilder.cs | 0 .../BuildSendDatas/TelemetryPacketRequest.cs | 0 .../Consts/CommonConst.cs | 0 .../Consts/ProtocolConst.cs | 0 .../Consts/RedisConst.cs | 0 .../Consts/RegexConst.cs | 0 .../Consts/SystemTypeConst.cs | 0 .../DeviceGroupBalanceControl.cs | 0 .../JiShe.CollectBus.Common/Enums/188Enums.cs | 0 .../JiShe.CollectBus.Common/Enums/376Enums.cs | 0 .../JiShe.CollectBus.Common/Enums/645Enums.cs | 0 .../Enums/ComandCodeEnum.cs | 0 .../Enums/CommandChunkEnum.cs | 0 .../Enums/F25DataItemEnum.cs | 0 .../Enums/MaskTypeEnum.cs | 0 .../Enums/MeterLinkProtocolEnum.cs | 0 .../Enums/MeterTypeEnum.cs | 0 .../Enums/RecordsDataMigrationStatusEnum.cs | 0 .../JiShe.CollectBus.Common/Enums/SortEnum.cs | 0 .../Enums/TransparentForwardingFlagEnum.cs | 0 .../Extensions/CollectionExtensions.cs | 0 .../Extensions/ComparableExtensions.cs | 0 .../Extensions/DataTableExtensions.cs | 0 .../Extensions/DateTimeExtensions.cs | 0 .../Extensions/DayOfWeekExtensions.cs | 0 .../Extensions/DecimalOrIntExtensions.cs | 0 .../Extensions/DictionaryExtensions.cs | 0 .../Extensions/EnumExtensions.cs | 0 .../Extensions/EnumerableExtensions.cs | 0 .../Extensions/EventHandlerExtensions.cs | 0 .../Extensions/ExceptionExtensions.cs | 0 .../Extensions/HexStringExtensions.cs | 0 .../Extensions/HttpResponseExtensions.cs | 0 .../Extensions/IntExtensions.cs | 0 .../Extensions/ListExtensions.cs | 0 .../Extensions/LockExtensions.cs | 0 .../Extensions/ObjectExtensions.cs | 0 .../Extensions/ProtocolConstExtensions.cs | 0 .../Extensions/StreamExtensions.cs | 0 .../Extensions/StringExtensions.cs | 0 .../Extensions/XmlExtensions.cs | 0 .../Helpers/BusJsonSerializer.cs | 0 .../Helpers/CommonHelper.cs | 0 .../Helpers/SelectResult.cs | 0 .../Helpers/TypeHelper.cs | 0 .../JiShe.CollectBus.Common.csproj | 0 .../JiShe.CollectBus.Common/Jobs/IBaseJob.cs | 0 .../Models/BusCacheGlobalPagedResult.cs | 0 .../Models/BusPagedResult.cs | 0 .../Models/CommandReuslt.cs | 0 .../CurrentPositiveActiveEnergyAnalyze.cs | 0 .../Models/DeviceCacheBasicModel.cs | 0 .../Models/F25ReadingAnalyze.cs | 0 .../Models/IssuedEventMessage.cs | 0 .../Models/ReqParameter.cs | 0 .../Models/SetAmmeterJFPGEntity.cs | 0 .../Models/TerminalVersionInfoAnalyze.cs | 0 .../CollectBusDomainSharedConsts.cs | 0 .../CollectBusDomainSharedModule.cs | 0 .../CollectBusErrorCodes.cs | 0 .../CollectBusGlobalFeatureConfigurator.cs | 0 .../CollectBusModuleExtensionConfigurator.cs | 0 .../Enums/DeviceStatus.cs | 0 .../FodyWeavers.xml | 0 .../Interceptors/ProtocolInspectAttribute.cs | 0 .../ProtocolInspectInterceptor.cs | 0 .../ProtocolInspectInterceptorRegistrar.cs | 0 .../Interfaces/IProtocolInfo.cs | 0 .../JiShe.CollectBus.Domain.Shared.abppkg | 0 .../JiShe.CollectBus.Domain.Shared.csproj | 0 .../Jobs/EPICollectArgs.cs | 0 .../Localization/CollectBus/ar.json | 0 .../Localization/CollectBus/cs.json | 0 .../Localization/CollectBus/de.json | 0 .../Localization/CollectBus/en-GB.json | 0 .../Localization/CollectBus/en.json | 0 .../Localization/CollectBus/es.json | 0 .../Localization/CollectBus/fi.json | 0 .../Localization/CollectBus/fr.json | 0 .../Localization/CollectBus/hi.json | 0 .../Localization/CollectBus/hr.json | 0 .../Localization/CollectBus/hu.json | 0 .../Localization/CollectBus/is.json | 0 .../Localization/CollectBus/it.json | 0 .../Localization/CollectBus/nl.json | 0 .../Localization/CollectBus/pl-PL.json | 0 .../Localization/CollectBus/pt-BR.json | 0 .../Localization/CollectBus/ro-RO.json | 0 .../Localization/CollectBus/ru.json | 0 .../Localization/CollectBus/sk.json | 0 .../Localization/CollectBus/sl.json | 0 .../Localization/CollectBus/tr.json | 0 .../Localization/CollectBus/vi.json | 0 .../Localization/CollectBus/zh-Hans.json | 0 .../Localization/CollectBus/zh-Hant.json | 0 .../Localization/CollectBusResource.cs | 0 .../Loggers/LoggerExtensions.cs | 0 .../Attribute/ATTRIBUTEColumnAttribute.cs | 16 ---- .../Attribute/FIELDColumnAttribute.cs | 16 ---- .../Attribute/TAGColumnAttribute.cs | 16 ---- .../CollectBusApplicationTestBase.cs | 12 --- .../CollectBusApplicationTestModule.cs | 12 --- .../FodyWeavers.xml | 3 - .../JiShe.CollectBus.Application.Tests.abppkg | 3 - .../JiShe.CollectBus.Application.Tests.csproj | 17 ---- .../Samples/SampleAppService_Tests.cs | 31 ------- .../CollectBusDomainTestBase.cs | 12 --- .../CollectBusDomainTestModule.cs | 12 --- .../FodyWeavers.xml | 3 - .../JiShe.CollectBus.Domain.Tests.abppkg | 3 - .../JiShe.CollectBus.Domain.Tests.csproj | 21 ----- .../Samples/SampleManager_Tests.cs | 22 ----- .../EfCoreSampleAppService_Tests.cs | 9 -- .../CollectBusEntityFrameworkCoreTestBase.cs | 9 -- ...CollectBusEntityFrameworkCoreTestModule.cs | 45 ---------- .../Domains/EfCoreSampleDomain_Tests.cs | 9 -- .../Samples/SampleRepository_Tests.cs | 11 --- .../FodyWeavers.xml | 3 - ...ollectBus.EntityFrameworkCore.Tests.abppkg | 3 - ...ollectBus.EntityFrameworkCore.Tests.csproj | 19 ---- .../FodyWeavers.xml | 3 - .../JiShe.CollectBus.MongoDB.Tests.abppkg | 3 - .../JiShe.CollectBus.MongoDB.Tests.csproj | 21 ----- .../MongoDBSampleAppService_Tests.cs | 11 --- .../MongoDB/CollectBusMongoDbTestBase.cs | 9 -- .../MongoDB/CollectBusMongoDbTestModule.cs | 21 ----- .../Domains/MongoDBSampleDomain_Tests.cs | 10 --- .../MongoDB/MongoDbFixture.cs | 34 -------- .../MongoDB/MongoTestCollection.cs | 9 -- .../MongoDB/Samples/SampleRepository_Tests.cs | 13 --- .../CollectBusDataSeedContributor.cs | 32 ------- .../CollectBusTestBase.cs | 59 ------------- .../CollectBusTestBaseModule.cs | 42 --------- .../JiShe.CollectBus.TestBase/FodyWeavers.xml | 3 - .../JiShe.CollectBus.TestBase.abppkg | 3 - .../JiShe.CollectBus.TestBase.csproj | 28 ------ .../Samples/SampleRepository_Tests.cs | 26 ------ .../Security/FakeCurrentPrincipalAccessor.cs | 27 ------ .../CollectBusHostConst.cs | 0 .../CollectBusHostModule.Configure.cs | 0 .../CollectBusHostModule.cs | 0 .../Controllers/HomeController.cs | 0 .../CustomApplicationBuilderExtensions.cs | 0 .../ServiceCollectionExtensions.cs | 0 .../Hangfire/JobRetryLastFilter.cs | 0 .../HealthChecks/HealthCheckResponse.cs | 0 .../JiShe.CollectBus.Host.csproj | 6 +- .../Pages/Monitor.cshtml | 0 .../Pages/Monitor.cshtml.cs | 0 .../JiShe.CollectBus.Protocol.Test.dll | Bin 0 -> 12288 bytes .../Plugins/JiShe.CollectBus.Protocol.dll | Bin 0 -> 14336 bytes .../JiShe.CollectBus.Host/Plugins/ignore.txt | 0 {src => web}/JiShe.CollectBus.Host/Program.cs | 0 .../Properties/launchSettings.json | 0 {src => web}/JiShe.CollectBus.Host/Startup.cs | 0 .../Swaggers/EnumSchemaFilter.cs | 0 .../Swaggers/HiddenAbpDefaultApiFilter.cs | 0 .../Swaggers/SwaggerConfig.cs | 0 .../appsettings.Development.json | 0 .../JiShe.CollectBus.Host/appsettings.json | 0 .../wwwroot/images/cap.png | Bin .../wwwroot/images/hangfire.png | Bin .../wwwroot/images/miniprofiler.png | Bin .../wwwroot/images/more.png | Bin .../wwwroot/images/swagger.png | Bin .../libs/bootstrap/css/bootstrap.min.css | 0 .../CollectBusController.cs | 0 .../CollectBusHttpApiModule.cs | 0 .../JiShe.CollectBus.HttpApi}/FodyWeavers.xml | 0 .../JiShe.CollectBus.HttpApi.abppkg | 0 .../JiShe.CollectBus.HttpApi.csproj | 4 +- .../Samples/SampleController.cs | 0 405 files changed, 183 insertions(+), 885 deletions(-) rename {src => modules}/JiShe.CollectBus.Cassandra/CassandraConfig.cs (100%) rename {src => modules}/JiShe.CollectBus.Cassandra/CassandraProvider.cs (100%) rename {src => modules}/JiShe.CollectBus.Cassandra/CassandraQueryOptimizer.cs (100%) rename {src => modules}/JiShe.CollectBus.Cassandra/CassandraRepository.cs (98%) rename {src => modules}/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs (100%) rename {src => modules}/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs (100%) rename {src => modules}/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs (100%) rename {src => modules}/JiShe.CollectBus.Cassandra/ICassandraProvider.cs (100%) rename {src => modules}/JiShe.CollectBus.Cassandra/ICassandraRepository.cs (100%) rename {src => modules}/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj (75%) rename {src => modules}/JiShe.CollectBus.Cassandra/Mappers/CollectBusMapping.cs (100%) rename {src/JiShe.CollectBus.FreeRedisProvider => modules/JiShe.CollectBus.FreeRedis}/CollectBusFreeRedisModule.cs (83%) rename {src/JiShe.CollectBus.FreeRedisProvider => modules/JiShe.CollectBus.FreeRedis}/FreeRedisProvider.cs (98%) rename {src/JiShe.CollectBus.FreeRedisProvider => modules/JiShe.CollectBus.FreeRedis}/IFreeRedisProvider.cs (73%) rename src/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj => modules/JiShe.CollectBus.FreeRedis/JiShe.CollectBus.FreeRedis.csproj (78%) rename {src/JiShe.CollectBus.FreeRedisProvider => modules/JiShe.CollectBus.FreeRedis}/Options/FreeRedisOptions.cs (75%) rename {src => modules}/JiShe.CollectBus.FreeSql/CollectBusFreeSqlModule.cs (100%) rename {src => modules}/JiShe.CollectBus.FreeSql/DbEnum.cs (100%) rename {src => modules}/JiShe.CollectBus.FreeSql/FreeSqlProvider.cs (100%) rename {src => modules}/JiShe.CollectBus.FreeSql/IFreeSqlProvider.cs (100%) rename {src => modules}/JiShe.CollectBus.FreeSql/JiShe.CollectBus.FreeSql.csproj (100%) create mode 100644 modules/JiShe.CollectBus.IoTDB/Attribute/ATTRIBUTEColumnAttribute.cs create mode 100644 modules/JiShe.CollectBus.IoTDB/Attribute/FIELDColumnAttribute.cs rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Attribute/SingleMeasuringAttribute.cs (66%) create mode 100644 modules/JiShe.CollectBus.IoTDB/Attribute/TAGColumnAttribute.cs rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/CollectBusIoTDBModule.cs (70%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Context/IoTDBRuntimeContext.cs (78%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Interface/IIoTDBProvider.cs (92%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Interface/IIoTDBSessionFactory.cs (53%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Interface/IIoTDBSessionPool.cs (80%) rename src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj => modules/JiShe.CollectBus.IoTDB/JiShe.CollectBus.IoTDB.csproj (81%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Options/IoTDBOptions.cs (89%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Options/QueryCondition.cs (72%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Options/QueryOptions.cs (79%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Provider/DeviceMetadata.cs (84%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Provider/DevicePathBuilder.cs (85%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Provider/IoTDBProvider.cs (98%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Provider/IoTDBSessionFactory.cs (83%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Provider/IoTEntity.cs (91%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Provider/SessionPoolAdapter.cs (88%) rename {src/JiShe.CollectBus.IoTDBProvider => modules/JiShe.CollectBus.IoTDB}/Provider/TableSessionPoolAdapter.cs (88%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/AdminClient/AdminClientService.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/AdminClient/IAdminClientService.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/Attributes/KafkaSubscribeAttribute.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/Attributes/TopicAttribute.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/CollectBusKafkaModule.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/Consumer/ConsumerService.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/Consumer/IConsumerService.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/HeadersFilter.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/IKafkaSubscribe.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/ISubscribeAck.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/JiShe.CollectBus.Kafka.csproj (82%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/JsonSerializer.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/KafkaOptionConfig.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/KafkaSubcribesExtensions.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/Producer/IProducerService.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/Producer/ProducerService.cs (100%) rename {src/JiShe.CollectBus.KafkaProducer => modules/JiShe.CollectBus.Kafka}/SubscribeResult.cs (100%) rename {src/JiShe.CollectBus.Application.Contracts => modules/JiShe.CollectBus.MongoDB}/FodyWeavers.xml (100%) rename {src => modules}/JiShe.CollectBus.MongoDB/JiShe.CollectBus.MongoDB.abppkg (100%) rename {src => modules}/JiShe.CollectBus.MongoDB/JiShe.CollectBus.MongoDB.csproj (85%) rename {src => modules}/JiShe.CollectBus.MongoDB/MongoDB/CollectBusDbSchemaMigrator.cs (100%) rename {src => modules}/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs (100%) rename {src => modules}/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContextExtensions.cs (100%) rename {src => modules}/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs (100%) rename {src => modules}/JiShe.CollectBus.MongoDB/MongoDB/ICollectBusMongoDbContext.cs (100%) rename {src => modules}/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs (100%) rename {src => modules}/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs (100%) rename {src => modules}/JiShe.CollectBus.MongoDB/ShardingStrategy/DayShardingStrategy.cs (100%) rename {src => modules}/JiShe.CollectBus.MongoDB/ShardingStrategy/IShardingStrategy.cs (100%) rename {src => protocols}/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs (100%) rename {src => protocols}/JiShe.CollectBus.Protocol.Contracts/Adapters/StandardFixedHeaderDataHandlingAdapter.cs (100%) rename {src => protocols}/JiShe.CollectBus.Protocol.Contracts/AnalysisData/Appendix.cs (100%) rename {src => protocols}/JiShe.CollectBus.Protocol.Contracts/Attributes/ProtocolNameAttribute.cs (100%) rename {src => protocols}/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs (100%) rename {src => protocols}/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj (57%) rename {src => protocols}/JiShe.CollectBus.Protocol.Contracts/Models/CustomFixedHeaderRequestInfo.cs (100%) rename {src => protocols}/JiShe.CollectBus.Protocol.Contracts/Models/TB3761.cs (100%) rename {src => protocols}/JiShe.CollectBus.Protocol.Contracts/QGDW3761Config.cs (100%) rename {src => protocols}/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj (75%) rename {src => protocols}/JiShe.CollectBus.Protocol.Test/JiSheCollectBusProtocolModule.cs (100%) rename {src => protocols}/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs (100%) rename {src => protocols}/JiShe.CollectBus.Protocol/JiShe.CollectBus.Protocol.csproj (64%) rename {src => protocols}/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs (100%) rename {src => protocols}/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/BaseResultDto.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/CollectBusApplicationContractsModule.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/CollectBusRemoteServiceConsts.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/DataMigration/IDataMigrationService.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/DataMigration/Options/DataMigrationOptions.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddConrOnlineRecordInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddFocusLogInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddSignalStrengthInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AdjustMeterTimingInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesDownInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesDownOutput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesMatchInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AutoReportCollectionItemsSetInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AutoReportSetInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BaseInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BatchReadVersionInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BatchReadVersionOutput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/CallTimeTestingInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/CommunicationParametersSetInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/EquitDubgInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryAutoReportOpenStatusInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryRecordLogInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryRecordLogOutput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadMeterNumInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadMeterNumOutput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadTimeInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadTimeOutput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadingInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadingOutput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TerminalRestartInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeAdjustInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeSetInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeSetOutput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ValveControlInput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ValveControlOutput.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/ICacheAppService.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/EnergySystem/IEnergySystemAppService.cs (100%) rename {src/JiShe.CollectBus.Application => services/JiShe.CollectBus.Application.Contracts}/FodyWeavers.xml (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/ICollectWorker.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/JiShe - Backup.CollectBus.Application.Contracts.csproj (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.abppkg (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj (73%) rename {src => services}/JiShe.CollectBus.Application.Contracts/Permissions/CollectBusPermissionDefinitionProvider.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/Permissions/CollectBusPermissions.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/Samples/ISampleAppService.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/Samples/SampleDto.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs (100%) rename {src => services}/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs (100%) rename {src => services}/JiShe.CollectBus.Application/CollectBusAppService.cs (99%) rename {src => services}/JiShe.CollectBus.Application/CollectBusApplicationAutoMapperProfile.cs (100%) rename {src => services}/JiShe.CollectBus.Application/CollectBusApplicationModule.cs (97%) rename {src => services}/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Consumers/IssuedFaultConsumer.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Consumers/ReceivedFaultConsumer.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Consumers/ReceivedHeartbeatConsumer.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Consumers/ReceivedLoginConsumer.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Consumers/ScheduledMeterReadingConsumer.cs (100%) rename {src => services}/JiShe.CollectBus.Application/DataMigration/DataMigrationService.cs (100%) rename {src => services}/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs (100%) rename {src => services}/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Exceptions/CloseException.cs (100%) rename {src/JiShe.CollectBus.Domain.Shared => services/JiShe.CollectBus.Application}/FodyWeavers.xml (100%) rename {src => services}/JiShe.CollectBus.Application/Handlers/SampleHandler.cs (100%) rename {src => services}/JiShe.CollectBus.Application/JiShe - Backup.CollectBus.Application.csproj (100%) rename {src => services}/JiShe.CollectBus.Application/JiShe.CollectBus.Application.abppkg (100%) rename {src => services}/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj (69%) rename {src => services}/JiShe.CollectBus.Application/Plugins/CloseMonitor.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Plugins/ServerMonitor.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Plugins/UdpMonitor.cs (100%) rename {src => services}/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs (99%) rename {src => services}/JiShe.CollectBus.Application/Samples/SampleAppService.cs (98%) rename {src => services}/JiShe.CollectBus.Application/Samples/TestAppService.cs (98%) rename {src => services}/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs (99%) rename {src => services}/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs (99%) rename {src => services}/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs (99%) rename {src => services}/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs (100%) rename {src => services}/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs (100%) rename {src => services}/JiShe.CollectBus.Application/cmd3761Matching.txt (100%) rename {src => services}/JiShe.CollectBus.Application/cmd3761TdcMatching.txt (100%) rename {src => services}/JiShe.CollectBus.DbMigrator/CollectBusDbMigratorModule.cs (100%) rename {src => services}/JiShe.CollectBus.DbMigrator/DbMigratorHostedService.cs (100%) rename {src => services}/JiShe.CollectBus.DbMigrator/Dockerfile (100%) rename {src => services}/JiShe.CollectBus.DbMigrator/FodyWeavers.xml (100%) rename {src => services}/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.abppkg (100%) rename {src => services}/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.csproj (93%) rename {src => services}/JiShe.CollectBus.DbMigrator/Program.cs (100%) rename {src => services}/JiShe.CollectBus.DbMigrator/appsettings.json (100%) rename {src => services}/JiShe.CollectBus.DbMigrator/appsettings.secrets.json (100%) rename {src => services}/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/Ammeters/AmmeterInfoTemp.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs (89%) rename {src => services}/JiShe.CollectBus.Domain/Analyze3761Data.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/CollectBusConsts.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/CollectBusDbProperties.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/CollectBusDomainModule.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/Data/CollectBusDbMigrationService.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/Data/ICollectBusDbSchemaMigrator.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/Data/NullCollectBusDbSchemaMigrator.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/EnergySystems/Entities/TB_AmmeterInfo.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/EnergySystems/TableViews/V_FocusAmmeter.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/FodyWeavers.xml (100%) rename {src => services}/JiShe.CollectBus.Domain/GatherItem/GatherItemInfo.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs (83%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/IReceived.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/MessageReceived.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/PrepayModel/Vi_BaseAmmeterInfo.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/Protocols/ProtocolInfo.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/Records/ConrOnlineRecord.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/Records/CsqRecord.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/Records/FocusRecord.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/IotSystems/Watermeter/WatermeterInfo.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/JiShe - Backup.CollectBus.Domain.csproj (100%) rename {src => services}/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.abppkg (100%) rename {src => services}/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj (72%) rename {src => services}/JiShe.CollectBus.Domain/Properties/AssemblyInfo.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/Settings/CollectBusSettingDefinitionProvider.cs (100%) rename {src => services}/JiShe.CollectBus.Domain/Settings/CollectBusSettings.cs (100%) rename {src => services}/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusDbContext.cs (100%) rename {src => services}/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusDbContextFactory.cs (100%) rename {src => services}/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusEfCoreEntityExtensionMappings.cs (100%) rename {src => services}/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusEntityFrameworkCoreModule.cs (100%) rename {src => services}/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreCollectBusDbSchemaMigrator.cs (100%) rename {src => services}/JiShe.CollectBus.EntityFrameworkCore/FodyWeavers.xml (100%) rename {src => services}/JiShe.CollectBus.EntityFrameworkCore/JiShe.CollectBus.EntityFrameworkCore.abppkg (100%) rename {src => services}/JiShe.CollectBus.EntityFrameworkCore/JiShe.CollectBus.EntityFrameworkCore.csproj (100%) rename {src => services}/JiShe.CollectBus.EntityFrameworkCore/Properties/AssemblyInfo.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Attributes/IgnoreJobAttribute.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Attributes/TopicNameAttribute.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/BuildSendDatas/Build188SendData.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/BuildSendDatas/Build645SendData.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketRequest.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Consts/CommonConst.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Consts/ProtocolConst.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Consts/RedisConst.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Consts/RegexConst.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Consts/SystemTypeConst.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Enums/188Enums.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Enums/376Enums.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Enums/645Enums.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Enums/ComandCodeEnum.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Enums/CommandChunkEnum.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Enums/F25DataItemEnum.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Enums/MaskTypeEnum.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Enums/MeterLinkProtocolEnum.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Enums/MeterTypeEnum.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Enums/RecordsDataMigrationStatusEnum.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Enums/SortEnum.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Enums/TransparentForwardingFlagEnum.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/CollectionExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/ComparableExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/DataTableExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/DayOfWeekExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/DecimalOrIntExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/DictionaryExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/EnumerableExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/EventHandlerExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/ExceptionExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/HexStringExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/HttpResponseExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/IntExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/ListExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/LockExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/ObjectExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/ProtocolConstExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/StreamExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/StringExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Extensions/XmlExtensions.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Helpers/BusJsonSerializer.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Helpers/CommonHelper.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Helpers/SelectResult.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Helpers/TypeHelper.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj (100%) rename {src => shared}/JiShe.CollectBus.Common/Jobs/IBaseJob.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Models/BusCacheGlobalPagedResult.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Models/BusPagedResult.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Models/CommandReuslt.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Models/CurrentPositiveActiveEnergyAnalyze.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Models/F25ReadingAnalyze.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Models/IssuedEventMessage.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Models/ReqParameter.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Models/SetAmmeterJFPGEntity.cs (100%) rename {src => shared}/JiShe.CollectBus.Common/Models/TerminalVersionInfoAnalyze.cs (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/CollectBusDomainSharedConsts.cs (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/CollectBusDomainSharedModule.cs (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/CollectBusErrorCodes.cs (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/CollectBusGlobalFeatureConfigurator.cs (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/CollectBusModuleExtensionConfigurator.cs (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Enums/DeviceStatus.cs (100%) rename {src/JiShe.CollectBus.HttpApi => shared/JiShe.CollectBus.Domain.Shared}/FodyWeavers.xml (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectAttribute.cs (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectInterceptor.cs (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectInterceptorRegistrar.cs (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Interfaces/IProtocolInfo.cs (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.abppkg (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.csproj (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Jobs/EPICollectArgs.cs (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ar.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/cs.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/de.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/en-GB.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/en.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/es.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/fi.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/fr.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hi.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hr.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hu.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/is.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/it.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/nl.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/pl-PL.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/pt-BR.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ro-RO.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ru.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/sk.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/sl.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/tr.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/vi.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/zh-Hans.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/zh-Hant.json (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Localization/CollectBusResource.cs (100%) rename {src => shared}/JiShe.CollectBus.Domain.Shared/Loggers/LoggerExtensions.cs (100%) delete mode 100644 src/JiShe.CollectBus.IoTDBProvider/Attribute/ATTRIBUTEColumnAttribute.cs delete mode 100644 src/JiShe.CollectBus.IoTDBProvider/Attribute/FIELDColumnAttribute.cs delete mode 100644 src/JiShe.CollectBus.IoTDBProvider/Attribute/TAGColumnAttribute.cs delete mode 100644 test/JiShe.CollectBus.Application.Tests/CollectBusApplicationTestBase.cs delete mode 100644 test/JiShe.CollectBus.Application.Tests/CollectBusApplicationTestModule.cs delete mode 100644 test/JiShe.CollectBus.Application.Tests/FodyWeavers.xml delete mode 100644 test/JiShe.CollectBus.Application.Tests/JiShe.CollectBus.Application.Tests.abppkg delete mode 100644 test/JiShe.CollectBus.Application.Tests/JiShe.CollectBus.Application.Tests.csproj delete mode 100644 test/JiShe.CollectBus.Application.Tests/Samples/SampleAppService_Tests.cs delete mode 100644 test/JiShe.CollectBus.Domain.Tests/CollectBusDomainTestBase.cs delete mode 100644 test/JiShe.CollectBus.Domain.Tests/CollectBusDomainTestModule.cs delete mode 100644 test/JiShe.CollectBus.Domain.Tests/FodyWeavers.xml delete mode 100644 test/JiShe.CollectBus.Domain.Tests/JiShe.CollectBus.Domain.Tests.abppkg delete mode 100644 test/JiShe.CollectBus.Domain.Tests/JiShe.CollectBus.Domain.Tests.csproj delete mode 100644 test/JiShe.CollectBus.Domain.Tests/Samples/SampleManager_Tests.cs delete mode 100644 test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/Applications/EfCoreSampleAppService_Tests.cs delete mode 100644 test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/CollectBusEntityFrameworkCoreTestBase.cs delete mode 100644 test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/CollectBusEntityFrameworkCoreTestModule.cs delete mode 100644 test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/Domains/EfCoreSampleDomain_Tests.cs delete mode 100644 test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/Samples/SampleRepository_Tests.cs delete mode 100644 test/JiShe.CollectBus.EntityFrameworkCore.Tests/FodyWeavers.xml delete mode 100644 test/JiShe.CollectBus.EntityFrameworkCore.Tests/JiShe.CollectBus.EntityFrameworkCore.Tests.abppkg delete mode 100644 test/JiShe.CollectBus.EntityFrameworkCore.Tests/JiShe.CollectBus.EntityFrameworkCore.Tests.csproj delete mode 100644 test/JiShe.CollectBus.MongoDB.Tests/FodyWeavers.xml delete mode 100644 test/JiShe.CollectBus.MongoDB.Tests/JiShe.CollectBus.MongoDB.Tests.abppkg delete mode 100644 test/JiShe.CollectBus.MongoDB.Tests/JiShe.CollectBus.MongoDB.Tests.csproj delete mode 100644 test/JiShe.CollectBus.MongoDB.Tests/MongoDB/Applications/MongoDBSampleAppService_Tests.cs delete mode 100644 test/JiShe.CollectBus.MongoDB.Tests/MongoDB/CollectBusMongoDbTestBase.cs delete mode 100644 test/JiShe.CollectBus.MongoDB.Tests/MongoDB/CollectBusMongoDbTestModule.cs delete mode 100644 test/JiShe.CollectBus.MongoDB.Tests/MongoDB/Domains/MongoDBSampleDomain_Tests.cs delete mode 100644 test/JiShe.CollectBus.MongoDB.Tests/MongoDB/MongoDbFixture.cs delete mode 100644 test/JiShe.CollectBus.MongoDB.Tests/MongoDB/MongoTestCollection.cs delete mode 100644 test/JiShe.CollectBus.MongoDB.Tests/MongoDB/Samples/SampleRepository_Tests.cs delete mode 100644 test/JiShe.CollectBus.TestBase/CollectBusDataSeedContributor.cs delete mode 100644 test/JiShe.CollectBus.TestBase/CollectBusTestBase.cs delete mode 100644 test/JiShe.CollectBus.TestBase/CollectBusTestBaseModule.cs delete mode 100644 test/JiShe.CollectBus.TestBase/FodyWeavers.xml delete mode 100644 test/JiShe.CollectBus.TestBase/JiShe.CollectBus.TestBase.abppkg delete mode 100644 test/JiShe.CollectBus.TestBase/JiShe.CollectBus.TestBase.csproj delete mode 100644 test/JiShe.CollectBus.TestBase/Samples/SampleRepository_Tests.cs delete mode 100644 test/JiShe.CollectBus.TestBase/Security/FakeCurrentPrincipalAccessor.cs rename {src => web}/JiShe.CollectBus.Host/CollectBusHostConst.cs (100%) rename {src => web}/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs (100%) rename {src => web}/JiShe.CollectBus.Host/CollectBusHostModule.cs (100%) rename {src => web}/JiShe.CollectBus.Host/Controllers/HomeController.cs (100%) rename {src => web}/JiShe.CollectBus.Host/Extensions/CustomApplicationBuilderExtensions.cs (100%) rename {src => web}/JiShe.CollectBus.Host/Extensions/ServiceCollections/ServiceCollectionExtensions.cs (100%) rename {src => web}/JiShe.CollectBus.Host/Hangfire/JobRetryLastFilter.cs (100%) rename {src => web}/JiShe.CollectBus.Host/HealthChecks/HealthCheckResponse.cs (100%) rename {src => web}/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj (90%) rename {src => web}/JiShe.CollectBus.Host/Pages/Monitor.cshtml (100%) rename {src => web}/JiShe.CollectBus.Host/Pages/Monitor.cshtml.cs (100%) create mode 100644 web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.Test.dll create mode 100644 web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.dll rename {src => web}/JiShe.CollectBus.Host/Plugins/ignore.txt (100%) rename {src => web}/JiShe.CollectBus.Host/Program.cs (100%) rename {src => web}/JiShe.CollectBus.Host/Properties/launchSettings.json (100%) rename {src => web}/JiShe.CollectBus.Host/Startup.cs (100%) rename {src => web}/JiShe.CollectBus.Host/Swaggers/EnumSchemaFilter.cs (100%) rename {src => web}/JiShe.CollectBus.Host/Swaggers/HiddenAbpDefaultApiFilter.cs (100%) rename {src => web}/JiShe.CollectBus.Host/Swaggers/SwaggerConfig.cs (100%) rename {src => web}/JiShe.CollectBus.Host/appsettings.Development.json (100%) rename {src => web}/JiShe.CollectBus.Host/appsettings.json (100%) rename {src => web}/JiShe.CollectBus.Host/wwwroot/images/cap.png (100%) rename {src => web}/JiShe.CollectBus.Host/wwwroot/images/hangfire.png (100%) rename {src => web}/JiShe.CollectBus.Host/wwwroot/images/miniprofiler.png (100%) rename {src => web}/JiShe.CollectBus.Host/wwwroot/images/more.png (100%) rename {src => web}/JiShe.CollectBus.Host/wwwroot/images/swagger.png (100%) rename {src => web}/JiShe.CollectBus.Host/wwwroot/libs/bootstrap/css/bootstrap.min.css (100%) rename {src => web}/JiShe.CollectBus.HttpApi/CollectBusController.cs (100%) rename {src => web}/JiShe.CollectBus.HttpApi/CollectBusHttpApiModule.cs (100%) rename {src/JiShe.CollectBus.MongoDB => web/JiShe.CollectBus.HttpApi}/FodyWeavers.xml (100%) rename {src => web}/JiShe.CollectBus.HttpApi/JiShe.CollectBus.HttpApi.abppkg (100%) rename {src => web}/JiShe.CollectBus.HttpApi/JiShe.CollectBus.HttpApi.csproj (68%) rename {src => web}/JiShe.CollectBus.HttpApi/Samples/SampleController.cs (100%) diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index 0d0c5e4..d4e6591 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -3,43 +3,49 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.9.34728.123 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Domain.Shared", "src\JiShe.CollectBus.Domain.Shared\JiShe.CollectBus.Domain.Shared.csproj", "{D64C1577-4929-4B60-939E-96DE1534891A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Domain.Shared", "shared\JiShe.CollectBus.Domain.Shared\JiShe.CollectBus.Domain.Shared.csproj", "{D64C1577-4929-4B60-939E-96DE1534891A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Domain", "src\JiShe.CollectBus.Domain\JiShe.CollectBus.Domain.csproj", "{F2840BC7-0188-4606-9126-DADD0F5ABF7A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Domain", "services\JiShe.CollectBus.Domain\JiShe.CollectBus.Domain.csproj", "{F2840BC7-0188-4606-9126-DADD0F5ABF7A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Application.Contracts", "src\JiShe.CollectBus.Application.Contracts\JiShe.CollectBus.Application.Contracts.csproj", "{BD65D04F-08D5-40C1-8C24-77CA0BACB877}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Application.Contracts", "services\JiShe.CollectBus.Application.Contracts\JiShe.CollectBus.Application.Contracts.csproj", "{BD65D04F-08D5-40C1-8C24-77CA0BACB877}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Application", "src\JiShe.CollectBus.Application\JiShe.CollectBus.Application.csproj", "{78040F9E-3501-4A40-82DF-00A597710F35}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Application", "services\JiShe.CollectBus.Application\JiShe.CollectBus.Application.csproj", "{78040F9E-3501-4A40-82DF-00A597710F35}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{649A3FFA-182F-4E56-9717-E6A9A2BEC545}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.MongoDB", "modules\JiShe.CollectBus.MongoDB\JiShe.CollectBus.MongoDB.csproj", "{F1C58097-4C08-4D88-8976-6B3389391481}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.MongoDB", "src\JiShe.CollectBus.MongoDB\JiShe.CollectBus.MongoDB.csproj", "{F1C58097-4C08-4D88-8976-6B3389391481}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.HttpApi", "web\JiShe.CollectBus.HttpApi\JiShe.CollectBus.HttpApi.csproj", "{077AA5F8-8B61-420C-A6B5-0150A66FDB34}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.HttpApi", "src\JiShe.CollectBus.HttpApi\JiShe.CollectBus.HttpApi.csproj", "{077AA5F8-8B61-420C-A6B5-0150A66FDB34}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Host", "web\JiShe.CollectBus.Host\JiShe.CollectBus.Host.csproj", "{35829A15-4127-4F69-8BDE-9405DEAACA9A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Host", "src\JiShe.CollectBus.Host\JiShe.CollectBus.Host.csproj", "{35829A15-4127-4F69-8BDE-9405DEAACA9A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Common", "shared\JiShe.CollectBus.Common\JiShe.CollectBus.Common.csproj", "{AD2F1928-4411-4511-B564-5FB996EC08B9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Common", "src\JiShe.CollectBus.Common\JiShe.CollectBus.Common.csproj", "{AD2F1928-4411-4511-B564-5FB996EC08B9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Protocol", "protocols\JiShe.CollectBus.Protocol\JiShe.CollectBus.Protocol.csproj", "{C62EFF95-5C32-435F-BD78-6977E828F894}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Protocol", "src\JiShe.CollectBus.Protocol\JiShe.CollectBus.Protocol.csproj", "{C62EFF95-5C32-435F-BD78-6977E828F894}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Protocol.Contracts", "protocols\JiShe.CollectBus.Protocol.Contracts\JiShe.CollectBus.Protocol.Contracts.csproj", "{38C1808B-009A-418B-B17B-AB3626341B5D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Protocol.Contracts", "src\JiShe.CollectBus.Protocol.Contracts\JiShe.CollectBus.Protocol.Contracts.csproj", "{38C1808B-009A-418B-B17B-AB3626341B5D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.DbMigrator", "services\JiShe.CollectBus.DbMigrator\JiShe.CollectBus.DbMigrator.csproj", "{8BA01C3D-297D-42DF-BD63-EF07202A0A67}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.DbMigrator", "src\JiShe.CollectBus.DbMigrator\JiShe.CollectBus.DbMigrator.csproj", "{8BA01C3D-297D-42DF-BD63-EF07202A0A67}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.FreeSql", "modules\JiShe.CollectBus.FreeSql\JiShe.CollectBus.FreeSql.csproj", "{FE0457D9-4038-4A17-8808-DCAD06CFC0A0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.FreeSql", "src\JiShe.CollectBus.FreeSql\JiShe.CollectBus.FreeSql.csproj", "{FE0457D9-4038-4A17-8808-DCAD06CFC0A0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.FreeRedis", "modules\JiShe.CollectBus.FreeRedis\JiShe.CollectBus.FreeRedis.csproj", "{C06C4082-638F-2996-5FED-7784475766C1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.FreeRedisProvider", "src\JiShe.CollectBus.FreeRedisProvider\JiShe.CollectBus.FreeRedisProvider.csproj", "{C06C4082-638F-2996-5FED-7784475766C1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Kafka", "modules\JiShe.CollectBus.Kafka\JiShe.CollectBus.Kafka.csproj", "{F0288175-F0EC-48BD-945F-CF1512850943}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka", "src\JiShe.CollectBus.KafkaProducer\JiShe.CollectBus.Kafka.csproj", "{F0288175-F0EC-48BD-945F-CF1512850943}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.IoTDB", "modules\JiShe.CollectBus.IoTDB\JiShe.CollectBus.IoTDB.csproj", "{A3F3C092-0A25-450B-BF6A-5983163CBEF5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.IoTDBProvider", "src\JiShe.CollectBus.IoTDBProvider\JiShe.CollectBus.IoTDBProvider.csproj", "{A3F3C092-0A25-450B-BF6A-5983163CBEF5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Protocol.Test", "protocols\JiShe.CollectBus.Protocol.Test\JiShe.CollectBus.Protocol.Test.csproj", "{A377955E-7EA1-6F29-8CF7-774569E93925}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.Test", "src\JiShe.CollectBus.Protocol.Test\JiShe.CollectBus.Protocol.Test.csproj", "{A377955E-7EA1-6F29-8CF7-774569E93925}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Cassandra", "modules\JiShe.CollectBus.Cassandra\JiShe.CollectBus.Cassandra.csproj", "{443B4549-0AC0-4493-8F3E-49C83225DD76}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Cassandra", "src\JiShe.CollectBus.Cassandra\JiShe.CollectBus.Cassandra.csproj", "{443B4549-0AC0-4493-8F3E-49C83225DD76}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1.Web", "1.Web", "{A02F7D8A-04DC-44D6-94D4-3F65712D6B94}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka.Test", "src\JiShe.CollectBus.Kafka.Test\JiShe.CollectBus.Kafka.Test.csproj", "{FA762E8F-659A-DECF-83D6-5F364144450E}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.Modules", "4.Modules", "{2E0FE301-34C3-4561-9CAE-C7A9E65AEE59}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3.Protocols", "3.Protocols", "{3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2.Services", "2.Services", "{BA4DA3E7-9AD0-47AD-A0E6-A0BB6700DA23}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5.Shared", "5.Shared", "{EBF7C01F-9B4F-48E6-8418-2CBFDA51EB0B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -115,32 +121,28 @@ Global {443B4549-0AC0-4493-8F3E-49C83225DD76}.Debug|Any CPU.Build.0 = Debug|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.ActiveCfg = Release|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.Build.0 = Release|Any CPU - {FA762E8F-659A-DECF-83D6-5F364144450E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FA762E8F-659A-DECF-83D6-5F364144450E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA762E8F-659A-DECF-83D6-5F364144450E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FA762E8F-659A-DECF-83D6-5F364144450E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {D64C1577-4929-4B60-939E-96DE1534891A} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {F2840BC7-0188-4606-9126-DADD0F5ABF7A} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {BD65D04F-08D5-40C1-8C24-77CA0BACB877} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {78040F9E-3501-4A40-82DF-00A597710F35} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {F1C58097-4C08-4D88-8976-6B3389391481} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {077AA5F8-8B61-420C-A6B5-0150A66FDB34} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {35829A15-4127-4F69-8BDE-9405DEAACA9A} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {AD2F1928-4411-4511-B564-5FB996EC08B9} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {C62EFF95-5C32-435F-BD78-6977E828F894} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {38C1808B-009A-418B-B17B-AB3626341B5D} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {8BA01C3D-297D-42DF-BD63-EF07202A0A67} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {FE0457D9-4038-4A17-8808-DCAD06CFC0A0} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {C06C4082-638F-2996-5FED-7784475766C1} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {F0288175-F0EC-48BD-945F-CF1512850943} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {A3F3C092-0A25-450B-BF6A-5983163CBEF5} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {A377955E-7EA1-6F29-8CF7-774569E93925} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} - {443B4549-0AC0-4493-8F3E-49C83225DD76} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {D64C1577-4929-4B60-939E-96DE1534891A} = {EBF7C01F-9B4F-48E6-8418-2CBFDA51EB0B} + {F2840BC7-0188-4606-9126-DADD0F5ABF7A} = {BA4DA3E7-9AD0-47AD-A0E6-A0BB6700DA23} + {BD65D04F-08D5-40C1-8C24-77CA0BACB877} = {BA4DA3E7-9AD0-47AD-A0E6-A0BB6700DA23} + {78040F9E-3501-4A40-82DF-00A597710F35} = {BA4DA3E7-9AD0-47AD-A0E6-A0BB6700DA23} + {F1C58097-4C08-4D88-8976-6B3389391481} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59} + {077AA5F8-8B61-420C-A6B5-0150A66FDB34} = {A02F7D8A-04DC-44D6-94D4-3F65712D6B94} + {35829A15-4127-4F69-8BDE-9405DEAACA9A} = {A02F7D8A-04DC-44D6-94D4-3F65712D6B94} + {AD2F1928-4411-4511-B564-5FB996EC08B9} = {EBF7C01F-9B4F-48E6-8418-2CBFDA51EB0B} + {C62EFF95-5C32-435F-BD78-6977E828F894} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC} + {38C1808B-009A-418B-B17B-AB3626341B5D} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC} + {8BA01C3D-297D-42DF-BD63-EF07202A0A67} = {BA4DA3E7-9AD0-47AD-A0E6-A0BB6700DA23} + {FE0457D9-4038-4A17-8808-DCAD06CFC0A0} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59} + {C06C4082-638F-2996-5FED-7784475766C1} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59} + {F0288175-F0EC-48BD-945F-CF1512850943} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59} + {A3F3C092-0A25-450B-BF6A-5983163CBEF5} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59} + {A377955E-7EA1-6F29-8CF7-774569E93925} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC} + {443B4549-0AC0-4493-8F3E-49C83225DD76} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/src/JiShe.CollectBus.Cassandra/CassandraConfig.cs b/modules/JiShe.CollectBus.Cassandra/CassandraConfig.cs similarity index 100% rename from src/JiShe.CollectBus.Cassandra/CassandraConfig.cs rename to modules/JiShe.CollectBus.Cassandra/CassandraConfig.cs diff --git a/src/JiShe.CollectBus.Cassandra/CassandraProvider.cs b/modules/JiShe.CollectBus.Cassandra/CassandraProvider.cs similarity index 100% rename from src/JiShe.CollectBus.Cassandra/CassandraProvider.cs rename to modules/JiShe.CollectBus.Cassandra/CassandraProvider.cs diff --git a/src/JiShe.CollectBus.Cassandra/CassandraQueryOptimizer.cs b/modules/JiShe.CollectBus.Cassandra/CassandraQueryOptimizer.cs similarity index 100% rename from src/JiShe.CollectBus.Cassandra/CassandraQueryOptimizer.cs rename to modules/JiShe.CollectBus.Cassandra/CassandraQueryOptimizer.cs diff --git a/src/JiShe.CollectBus.Cassandra/CassandraRepository.cs b/modules/JiShe.CollectBus.Cassandra/CassandraRepository.cs similarity index 98% rename from src/JiShe.CollectBus.Cassandra/CassandraRepository.cs rename to modules/JiShe.CollectBus.Cassandra/CassandraRepository.cs index 66ac6d7..25a51e3 100644 --- a/src/JiShe.CollectBus.Cassandra/CassandraRepository.cs +++ b/modules/JiShe.CollectBus.Cassandra/CassandraRepository.cs @@ -3,7 +3,6 @@ using Cassandra.Data.Linq; using Cassandra.Mapping; using JiShe.CollectBus.Cassandra.Extensions; using JiShe.CollectBus.Common.Attributes; -using JiShe.CollectBus.IoTDBProvider; using Microsoft.AspNetCore.Http; using System.Reflection; using Thrift.Protocol.Entities; diff --git a/src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs b/modules/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs similarity index 100% rename from src/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs rename to modules/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs diff --git a/src/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs b/modules/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs rename to modules/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs diff --git a/src/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs b/modules/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs similarity index 100% rename from src/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs rename to modules/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs diff --git a/src/JiShe.CollectBus.Cassandra/ICassandraProvider.cs b/modules/JiShe.CollectBus.Cassandra/ICassandraProvider.cs similarity index 100% rename from src/JiShe.CollectBus.Cassandra/ICassandraProvider.cs rename to modules/JiShe.CollectBus.Cassandra/ICassandraProvider.cs diff --git a/src/JiShe.CollectBus.Cassandra/ICassandraRepository.cs b/modules/JiShe.CollectBus.Cassandra/ICassandraRepository.cs similarity index 100% rename from src/JiShe.CollectBus.Cassandra/ICassandraRepository.cs rename to modules/JiShe.CollectBus.Cassandra/ICassandraRepository.cs diff --git a/src/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj b/modules/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj similarity index 75% rename from src/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj rename to modules/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj index db7ccf3..edc303c 100644 --- a/src/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj +++ b/modules/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/src/JiShe.CollectBus.Cassandra/Mappers/CollectBusMapping.cs b/modules/JiShe.CollectBus.Cassandra/Mappers/CollectBusMapping.cs similarity index 100% rename from src/JiShe.CollectBus.Cassandra/Mappers/CollectBusMapping.cs rename to modules/JiShe.CollectBus.Cassandra/Mappers/CollectBusMapping.cs diff --git a/src/JiShe.CollectBus.FreeRedisProvider/CollectBusFreeRedisModule.cs b/modules/JiShe.CollectBus.FreeRedis/CollectBusFreeRedisModule.cs similarity index 83% rename from src/JiShe.CollectBus.FreeRedisProvider/CollectBusFreeRedisModule.cs rename to modules/JiShe.CollectBus.FreeRedis/CollectBusFreeRedisModule.cs index 6485e92..fb86fdd 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/CollectBusFreeRedisModule.cs +++ b/modules/JiShe.CollectBus.FreeRedis/CollectBusFreeRedisModule.cs @@ -1,9 +1,9 @@ -using JiShe.CollectBus.FreeRedisProvider.Options; +using JiShe.CollectBus.FreeRedis.Options; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; -namespace JiShe.CollectBus.FreeRedisProvider +namespace JiShe.CollectBus.FreeRedis { public class CollectBusFreeRedisModule : AbpModule { diff --git a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs b/modules/JiShe.CollectBus.FreeRedis/FreeRedisProvider.cs similarity index 98% rename from src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs rename to modules/JiShe.CollectBus.FreeRedis/FreeRedisProvider.cs index d3a9bff..4c81d02 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/FreeRedisProvider.cs +++ b/modules/JiShe.CollectBus.FreeRedis/FreeRedisProvider.cs @@ -1,16 +1,11 @@ -using FreeRedis; +using System.Diagnostics; +using FreeRedis; using JiShe.CollectBus.Common.Helpers; -using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.Common.Extensions; -using JiShe.CollectBus.FreeRedisProvider.Options; +using JiShe.CollectBus.FreeRedis.Options; using Microsoft.Extensions.Options; -using System.Diagnostics; -using System.Text.Json; using Volo.Abp.DependencyInjection; -using static System.Runtime.InteropServices.JavaScript.JSType; -using System.Collections.Concurrent; -namespace JiShe.CollectBus.FreeRedisProvider +namespace JiShe.CollectBus.FreeRedis { public class FreeRedisProvider : IFreeRedisProvider, ISingletonDependency diff --git a/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs b/modules/JiShe.CollectBus.FreeRedis/IFreeRedisProvider.cs similarity index 73% rename from src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs rename to modules/JiShe.CollectBus.FreeRedis/IFreeRedisProvider.cs index 8fc6861..e9e8a40 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/IFreeRedisProvider.cs +++ b/modules/JiShe.CollectBus.FreeRedis/IFreeRedisProvider.cs @@ -1,7 +1,6 @@ using FreeRedis; -using JiShe.CollectBus.Common.Models; -namespace JiShe.CollectBus.FreeRedisProvider +namespace JiShe.CollectBus.FreeRedis { public interface IFreeRedisProvider { diff --git a/src/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj b/modules/JiShe.CollectBus.FreeRedis/JiShe.CollectBus.FreeRedis.csproj similarity index 78% rename from src/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj rename to modules/JiShe.CollectBus.FreeRedis/JiShe.CollectBus.FreeRedis.csproj index 7db0478..92b3223 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/JiShe.CollectBus.FreeRedisProvider.csproj +++ b/modules/JiShe.CollectBus.FreeRedis/JiShe.CollectBus.FreeRedis.csproj @@ -10,6 +10,6 @@ - + diff --git a/src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs b/modules/JiShe.CollectBus.FreeRedis/Options/FreeRedisOptions.cs similarity index 75% rename from src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs rename to modules/JiShe.CollectBus.FreeRedis/Options/FreeRedisOptions.cs index 2ed7e49..331da88 100644 --- a/src/JiShe.CollectBus.FreeRedisProvider/Options/FreeRedisOptions.cs +++ b/modules/JiShe.CollectBus.FreeRedis/Options/FreeRedisOptions.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.FreeRedisProvider.Options +namespace JiShe.CollectBus.FreeRedis.Options { public class FreeRedisOptions { diff --git a/src/JiShe.CollectBus.FreeSql/CollectBusFreeSqlModule.cs b/modules/JiShe.CollectBus.FreeSql/CollectBusFreeSqlModule.cs similarity index 100% rename from src/JiShe.CollectBus.FreeSql/CollectBusFreeSqlModule.cs rename to modules/JiShe.CollectBus.FreeSql/CollectBusFreeSqlModule.cs diff --git a/src/JiShe.CollectBus.FreeSql/DbEnum.cs b/modules/JiShe.CollectBus.FreeSql/DbEnum.cs similarity index 100% rename from src/JiShe.CollectBus.FreeSql/DbEnum.cs rename to modules/JiShe.CollectBus.FreeSql/DbEnum.cs diff --git a/src/JiShe.CollectBus.FreeSql/FreeSqlProvider.cs b/modules/JiShe.CollectBus.FreeSql/FreeSqlProvider.cs similarity index 100% rename from src/JiShe.CollectBus.FreeSql/FreeSqlProvider.cs rename to modules/JiShe.CollectBus.FreeSql/FreeSqlProvider.cs diff --git a/src/JiShe.CollectBus.FreeSql/IFreeSqlProvider.cs b/modules/JiShe.CollectBus.FreeSql/IFreeSqlProvider.cs similarity index 100% rename from src/JiShe.CollectBus.FreeSql/IFreeSqlProvider.cs rename to modules/JiShe.CollectBus.FreeSql/IFreeSqlProvider.cs diff --git a/src/JiShe.CollectBus.FreeSql/JiShe.CollectBus.FreeSql.csproj b/modules/JiShe.CollectBus.FreeSql/JiShe.CollectBus.FreeSql.csproj similarity index 100% rename from src/JiShe.CollectBus.FreeSql/JiShe.CollectBus.FreeSql.csproj rename to modules/JiShe.CollectBus.FreeSql/JiShe.CollectBus.FreeSql.csproj diff --git a/modules/JiShe.CollectBus.IoTDB/Attribute/ATTRIBUTEColumnAttribute.cs b/modules/JiShe.CollectBus.IoTDB/Attribute/ATTRIBUTEColumnAttribute.cs new file mode 100644 index 0000000..d188c36 --- /dev/null +++ b/modules/JiShe.CollectBus.IoTDB/Attribute/ATTRIBUTEColumnAttribute.cs @@ -0,0 +1,10 @@ +namespace JiShe.CollectBus.IoTDB.Attribute +{ + /// + /// Column分类标记特性(ATTRIBUTE字段),也就是属性字段 + /// + [AttributeUsage(AttributeTargets.Property)] + public class ATTRIBUTEColumnAttribute : System.Attribute + { + } +} diff --git a/modules/JiShe.CollectBus.IoTDB/Attribute/FIELDColumnAttribute.cs b/modules/JiShe.CollectBus.IoTDB/Attribute/FIELDColumnAttribute.cs new file mode 100644 index 0000000..7cabdf4 --- /dev/null +++ b/modules/JiShe.CollectBus.IoTDB/Attribute/FIELDColumnAttribute.cs @@ -0,0 +1,10 @@ +namespace JiShe.CollectBus.IoTDB.Attribute +{ + /// + /// Column分类标记特性(FIELD字段),数据列字段 + /// + [AttributeUsage(AttributeTargets.Property)] + public class FIELDColumnAttribute : System.Attribute + { + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs b/modules/JiShe.CollectBus.IoTDB/Attribute/SingleMeasuringAttribute.cs similarity index 66% rename from src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs rename to modules/JiShe.CollectBus.IoTDB/Attribute/SingleMeasuringAttribute.cs index ba5af69..cebb85a 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Attribute/SingleMeasuringAttribute.cs +++ b/modules/JiShe.CollectBus.IoTDB/Attribute/SingleMeasuringAttribute.cs @@ -1,16 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.IoTDBProvider +namespace JiShe.CollectBus.IoTDB.Attribute { /// /// 用于标识当前实体为单侧点模式,单侧点模式只有一个Filed标识字段,类型是Tuple,Item1=>测点名称,Item2=>测点值,泛型 /// [AttributeUsage(AttributeTargets.Property)] - public class SingleMeasuringAttribute : Attribute + public class SingleMeasuringAttribute : System.Attribute { public string FieldName { get; set;} diff --git a/modules/JiShe.CollectBus.IoTDB/Attribute/TAGColumnAttribute.cs b/modules/JiShe.CollectBus.IoTDB/Attribute/TAGColumnAttribute.cs new file mode 100644 index 0000000..6f40a47 --- /dev/null +++ b/modules/JiShe.CollectBus.IoTDB/Attribute/TAGColumnAttribute.cs @@ -0,0 +1,10 @@ +namespace JiShe.CollectBus.IoTDB.Attribute +{ + /// + /// Column分类标记特性(TAG字段),标签字段 + /// + [AttributeUsage(AttributeTargets.Property)] + public class TAGColumnAttribute : System.Attribute + { + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs b/modules/JiShe.CollectBus.IoTDB/CollectBusIoTDBModule.cs similarity index 70% rename from src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs rename to modules/JiShe.CollectBus.IoTDB/CollectBusIoTDBModule.cs index 62c63c4..93bbbfe 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/CollectBusIoTDBModule.cs +++ b/modules/JiShe.CollectBus.IoTDB/CollectBusIoTDBModule.cs @@ -1,17 +1,12 @@ -using JiShe.CollectBus.IoTDBProvider.Context; -using JiShe.CollectBus.IoTDBProvider.Interface; -using JiShe.CollectBus.IoTDBProvider.Provider; +using JiShe.CollectBus.IoTDB.Context; +using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Options; +using JiShe.CollectBus.IoTDB.Provider; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Volo.Abp.Modularity; -using static Thrift.Server.TThreadPoolAsyncServer; -namespace JiShe.CollectBus.IoTDBProvider +namespace JiShe.CollectBus.IoTDB { public class CollectBusIoTDBModule : AbpModule { diff --git a/src/JiShe.CollectBus.IoTDBProvider/Context/IoTDBRuntimeContext.cs b/modules/JiShe.CollectBus.IoTDB/Context/IoTDBRuntimeContext.cs similarity index 78% rename from src/JiShe.CollectBus.IoTDBProvider/Context/IoTDBRuntimeContext.cs rename to modules/JiShe.CollectBus.IoTDB/Context/IoTDBRuntimeContext.cs index 41f48cb..cd99b00 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Context/IoTDBRuntimeContext.cs +++ b/modules/JiShe.CollectBus.IoTDB/Context/IoTDBRuntimeContext.cs @@ -1,11 +1,7 @@ -using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using JiShe.CollectBus.IoTDB.Options; +using Microsoft.Extensions.Options; -namespace JiShe.CollectBus.IoTDBProvider.Context +namespace JiShe.CollectBus.IoTDB.Context { /// /// IoTDB SessionPool 运行时上下文 diff --git a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBProvider.cs similarity index 92% rename from src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs rename to modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBProvider.cs index 8fe46a9..bb47841 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs +++ b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBProvider.cs @@ -1,6 +1,8 @@ using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.IoTDB.Options; +using JiShe.CollectBus.IoTDB.Provider; -namespace JiShe.CollectBus.IoTDBProvider +namespace JiShe.CollectBus.IoTDB.Interface { /// /// IoTDB数据源,数据库能同时存多个时序模型,但数据是完全隔离的,不能跨时序模型查询,通过连接字符串配置 diff --git a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionFactory.cs b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionFactory.cs similarity index 53% rename from src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionFactory.cs rename to modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionFactory.cs index c7d9b10..03cd4a6 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionFactory.cs +++ b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionFactory.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.IoTDBProvider.Interface +namespace JiShe.CollectBus.IoTDB.Interface { /// /// Session 工厂接口 diff --git a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionPool.cs b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionPool.cs similarity index 80% rename from src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionPool.cs rename to modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionPool.cs index be2c2b7..026a83a 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBSessionPool.cs +++ b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionPool.cs @@ -1,11 +1,6 @@ using Apache.IoTDB.DataStructure; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace JiShe.CollectBus.IoTDBProvider.Interface +namespace JiShe.CollectBus.IoTDB.Interface { /// /// Session 连接池 diff --git a/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj b/modules/JiShe.CollectBus.IoTDB/JiShe.CollectBus.IoTDB.csproj similarity index 81% rename from src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj rename to modules/JiShe.CollectBus.IoTDB/JiShe.CollectBus.IoTDB.csproj index 9153601..dc8f2fb 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj +++ b/modules/JiShe.CollectBus.IoTDB/JiShe.CollectBus.IoTDB.csproj @@ -11,6 +11,6 @@ - + diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs b/modules/JiShe.CollectBus.IoTDB/Options/IoTDBOptions.cs similarity index 89% rename from src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs rename to modules/JiShe.CollectBus.IoTDB/Options/IoTDBOptions.cs index 62cbd92..c9c0610 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Options/IoTDBOptions.cs +++ b/modules/JiShe.CollectBus.IoTDB/Options/IoTDBOptions.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.IoTDBProvider +namespace JiShe.CollectBus.IoTDB.Options { /// /// IOTDB配置 diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/QueryCondition.cs b/modules/JiShe.CollectBus.IoTDB/Options/QueryCondition.cs similarity index 72% rename from src/JiShe.CollectBus.IoTDBProvider/Options/QueryCondition.cs rename to modules/JiShe.CollectBus.IoTDB/Options/QueryCondition.cs index b1aa53b..cf6d3a9 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Options/QueryCondition.cs +++ b/modules/JiShe.CollectBus.IoTDB/Options/QueryCondition.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.IoTDBProvider +namespace JiShe.CollectBus.IoTDB.Options { /// /// 查询条件 diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs b/modules/JiShe.CollectBus.IoTDB/Options/QueryOptions.cs similarity index 79% rename from src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs rename to modules/JiShe.CollectBus.IoTDB/Options/QueryOptions.cs index 3600f12..90e44b2 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs +++ b/modules/JiShe.CollectBus.IoTDB/Options/QueryOptions.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.IoTDBProvider +namespace JiShe.CollectBus.IoTDB.Options { /// /// 查询条件 diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs b/modules/JiShe.CollectBus.IoTDB/Provider/DeviceMetadata.cs similarity index 84% rename from src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs rename to modules/JiShe.CollectBus.IoTDB/Provider/DeviceMetadata.cs index 6bdbd56..447f6ce 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/DeviceMetadata.cs @@ -1,11 +1,6 @@ using Apache.IoTDB; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace JiShe.CollectBus.IoTDBProvider +namespace JiShe.CollectBus.IoTDB.Provider { /// /// 设备元数据 diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs b/modules/JiShe.CollectBus.IoTDB/Provider/DevicePathBuilder.cs similarity index 85% rename from src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs rename to modules/JiShe.CollectBus.IoTDB/Provider/DevicePathBuilder.cs index e170577..46ce091 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/DevicePathBuilder.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/DevicePathBuilder.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.IoTDBProvider +namespace JiShe.CollectBus.IoTDB.Provider { /// /// 设备路径构建器 diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs b/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBProvider.cs similarity index 98% rename from src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs rename to modules/JiShe.CollectBus.IoTDB/Provider/IoTDBProvider.cs index 3dd1341..9e18ac8 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBProvider.cs @@ -1,16 +1,16 @@ -using Apache.IoTDB; -using Apache.IoTDB.DataStructure; -using JiShe.CollectBus.Common.Extensions; -using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.IoTDBProvider.Context; -using JiShe.CollectBus.IoTDBProvider.Interface; -using Microsoft.Extensions.Logging; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Reflection; using System.Text; +using Apache.IoTDB; +using Apache.IoTDB.DataStructure; +using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.IoTDB.Attribute; +using JiShe.CollectBus.IoTDB.Context; +using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Options; +using Microsoft.Extensions.Logging; - -namespace JiShe.CollectBus.IoTDBProvider +namespace JiShe.CollectBus.IoTDB.Provider { /// /// IoTDB数据源 diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBSessionFactory.cs b/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBSessionFactory.cs similarity index 83% rename from src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBSessionFactory.cs rename to modules/JiShe.CollectBus.IoTDB/Provider/IoTDBSessionFactory.cs index cbc3869..572e93d 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBSessionFactory.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBSessionFactory.cs @@ -1,13 +1,9 @@ -using JiShe.CollectBus.IoTDBProvider.Interface; +using System.Collections.Concurrent; +using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Options; using Microsoft.Extensions.Options; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace JiShe.CollectBus.IoTDBProvider.Provider +namespace JiShe.CollectBus.IoTDB.Provider { /// diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs b/modules/JiShe.CollectBus.IoTDB/Provider/IoTEntity.cs similarity index 91% rename from src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs rename to modules/JiShe.CollectBus.IoTDB/Provider/IoTEntity.cs index d9bf4fa..39a584d 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTEntity.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/IoTEntity.cs @@ -1,4 +1,6 @@ -namespace JiShe.CollectBus.IoTDBProvider +using JiShe.CollectBus.IoTDB.Attribute; + +namespace JiShe.CollectBus.IoTDB.Provider { /// /// IoT实体基类 diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs b/modules/JiShe.CollectBus.IoTDB/Provider/SessionPoolAdapter.cs similarity index 88% rename from src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs rename to modules/JiShe.CollectBus.IoTDB/Provider/SessionPoolAdapter.cs index e836672..44692bd 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/SessionPoolAdapter.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/SessionPoolAdapter.cs @@ -1,14 +1,10 @@ -using Apache.IoTDB.DataStructure; -using Apache.IoTDB; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using JiShe.CollectBus.IoTDBProvider.Interface; +using Apache.IoTDB; +using Apache.IoTDB.DataStructure; +using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Options; using Microsoft.Extensions.Logging; -namespace JiShe.CollectBus.IoTDBProvider.Provider +namespace JiShe.CollectBus.IoTDB.Provider { /// /// 树模型连接池 diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/TableSessionPoolAdapter.cs b/modules/JiShe.CollectBus.IoTDB/Provider/TableSessionPoolAdapter.cs similarity index 88% rename from src/JiShe.CollectBus.IoTDBProvider/Provider/TableSessionPoolAdapter.cs rename to modules/JiShe.CollectBus.IoTDB/Provider/TableSessionPoolAdapter.cs index 22de1b0..1efd04f 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/TableSessionPoolAdapter.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/TableSessionPoolAdapter.cs @@ -1,14 +1,10 @@ -using Apache.IoTDB.DataStructure; -using Apache.IoTDB; -using JiShe.CollectBus.IoTDBProvider.Interface; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Apache.IoTDB; +using Apache.IoTDB.DataStructure; +using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Options; using Microsoft.Extensions.Logging; -namespace JiShe.CollectBus.IoTDBProvider.Provider +namespace JiShe.CollectBus.IoTDB.Provider { /// /// 表模型Session连接池 diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs b/modules/JiShe.CollectBus.Kafka/AdminClient/AdminClientService.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/AdminClient/AdminClientService.cs rename to modules/JiShe.CollectBus.Kafka/AdminClient/AdminClientService.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs b/modules/JiShe.CollectBus.Kafka/AdminClient/IAdminClientService.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/AdminClient/IAdminClientService.cs rename to modules/JiShe.CollectBus.Kafka/AdminClient/IAdminClientService.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs b/modules/JiShe.CollectBus.Kafka/Attributes/KafkaSubscribeAttribute.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/Attributes/KafkaSubscribeAttribute.cs rename to modules/JiShe.CollectBus.Kafka/Attributes/KafkaSubscribeAttribute.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/Attributes/TopicAttribute.cs b/modules/JiShe.CollectBus.Kafka/Attributes/TopicAttribute.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/Attributes/TopicAttribute.cs rename to modules/JiShe.CollectBus.Kafka/Attributes/TopicAttribute.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs b/modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/CollectBusKafkaModule.cs rename to modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs b/modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/Consumer/ConsumerService.cs rename to modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs b/modules/JiShe.CollectBus.Kafka/Consumer/IConsumerService.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/Consumer/IConsumerService.cs rename to modules/JiShe.CollectBus.Kafka/Consumer/IConsumerService.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/HeadersFilter.cs b/modules/JiShe.CollectBus.Kafka/HeadersFilter.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/HeadersFilter.cs rename to modules/JiShe.CollectBus.Kafka/HeadersFilter.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/IKafkaSubscribe.cs b/modules/JiShe.CollectBus.Kafka/IKafkaSubscribe.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/IKafkaSubscribe.cs rename to modules/JiShe.CollectBus.Kafka/IKafkaSubscribe.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/ISubscribeAck.cs b/modules/JiShe.CollectBus.Kafka/ISubscribeAck.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/ISubscribeAck.cs rename to modules/JiShe.CollectBus.Kafka/ISubscribeAck.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj b/modules/JiShe.CollectBus.Kafka/JiShe.CollectBus.Kafka.csproj similarity index 82% rename from src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj rename to modules/JiShe.CollectBus.Kafka/JiShe.CollectBus.Kafka.csproj index 9518b0b..ce31120 100644 --- a/src/JiShe.CollectBus.KafkaProducer/JiShe.CollectBus.Kafka.csproj +++ b/modules/JiShe.CollectBus.Kafka/JiShe.CollectBus.Kafka.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/JiShe.CollectBus.KafkaProducer/JsonSerializer.cs b/modules/JiShe.CollectBus.Kafka/JsonSerializer.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/JsonSerializer.cs rename to modules/JiShe.CollectBus.Kafka/JsonSerializer.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs b/modules/JiShe.CollectBus.Kafka/KafkaOptionConfig.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/KafkaOptionConfig.cs rename to modules/JiShe.CollectBus.Kafka/KafkaOptionConfig.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs b/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/KafkaSubcribesExtensions.cs rename to modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs b/modules/JiShe.CollectBus.Kafka/Producer/IProducerService.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/Producer/IProducerService.cs rename to modules/JiShe.CollectBus.Kafka/Producer/IProducerService.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs b/modules/JiShe.CollectBus.Kafka/Producer/ProducerService.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/Producer/ProducerService.cs rename to modules/JiShe.CollectBus.Kafka/Producer/ProducerService.cs diff --git a/src/JiShe.CollectBus.KafkaProducer/SubscribeResult.cs b/modules/JiShe.CollectBus.Kafka/SubscribeResult.cs similarity index 100% rename from src/JiShe.CollectBus.KafkaProducer/SubscribeResult.cs rename to modules/JiShe.CollectBus.Kafka/SubscribeResult.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/FodyWeavers.xml b/modules/JiShe.CollectBus.MongoDB/FodyWeavers.xml similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/FodyWeavers.xml rename to modules/JiShe.CollectBus.MongoDB/FodyWeavers.xml diff --git a/src/JiShe.CollectBus.MongoDB/JiShe.CollectBus.MongoDB.abppkg b/modules/JiShe.CollectBus.MongoDB/JiShe.CollectBus.MongoDB.abppkg similarity index 100% rename from src/JiShe.CollectBus.MongoDB/JiShe.CollectBus.MongoDB.abppkg rename to modules/JiShe.CollectBus.MongoDB/JiShe.CollectBus.MongoDB.abppkg diff --git a/src/JiShe.CollectBus.MongoDB/JiShe.CollectBus.MongoDB.csproj b/modules/JiShe.CollectBus.MongoDB/JiShe.CollectBus.MongoDB.csproj similarity index 85% rename from src/JiShe.CollectBus.MongoDB/JiShe.CollectBus.MongoDB.csproj rename to modules/JiShe.CollectBus.MongoDB/JiShe.CollectBus.MongoDB.csproj index fcb97fa..2987d33 100644 --- a/src/JiShe.CollectBus.MongoDB/JiShe.CollectBus.MongoDB.csproj +++ b/modules/JiShe.CollectBus.MongoDB/JiShe.CollectBus.MongoDB.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusDbSchemaMigrator.cs b/modules/JiShe.CollectBus.MongoDB/MongoDB/CollectBusDbSchemaMigrator.cs similarity index 100% rename from src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusDbSchemaMigrator.cs rename to modules/JiShe.CollectBus.MongoDB/MongoDB/CollectBusDbSchemaMigrator.cs diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs b/modules/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs similarity index 100% rename from src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs rename to modules/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContext.cs diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContextExtensions.cs b/modules/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContextExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContextExtensions.cs rename to modules/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbContextExtensions.cs diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs b/modules/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs similarity index 100% rename from src/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs rename to modules/JiShe.CollectBus.MongoDB/MongoDB/CollectBusMongoDbModule.cs diff --git a/src/JiShe.CollectBus.MongoDB/MongoDB/ICollectBusMongoDbContext.cs b/modules/JiShe.CollectBus.MongoDB/MongoDB/ICollectBusMongoDbContext.cs similarity index 100% rename from src/JiShe.CollectBus.MongoDB/MongoDB/ICollectBusMongoDbContext.cs rename to modules/JiShe.CollectBus.MongoDB/MongoDB/ICollectBusMongoDbContext.cs diff --git a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs b/modules/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs similarity index 100% rename from src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs rename to modules/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/IMeterReadingRecordRepository.cs diff --git a/src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs b/modules/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs similarity index 100% rename from src/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs rename to modules/JiShe.CollectBus.MongoDB/Repository/MeterReadingRecord/MeterReadingRecordRepository.cs diff --git a/src/JiShe.CollectBus.MongoDB/ShardingStrategy/DayShardingStrategy.cs b/modules/JiShe.CollectBus.MongoDB/ShardingStrategy/DayShardingStrategy.cs similarity index 100% rename from src/JiShe.CollectBus.MongoDB/ShardingStrategy/DayShardingStrategy.cs rename to modules/JiShe.CollectBus.MongoDB/ShardingStrategy/DayShardingStrategy.cs diff --git a/src/JiShe.CollectBus.MongoDB/ShardingStrategy/IShardingStrategy.cs b/modules/JiShe.CollectBus.MongoDB/ShardingStrategy/IShardingStrategy.cs similarity index 100% rename from src/JiShe.CollectBus.MongoDB/ShardingStrategy/IShardingStrategy.cs rename to modules/JiShe.CollectBus.MongoDB/ShardingStrategy/IShardingStrategy.cs diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs similarity index 100% rename from src/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs rename to protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Adapters/StandardFixedHeaderDataHandlingAdapter.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Adapters/StandardFixedHeaderDataHandlingAdapter.cs similarity index 100% rename from src/JiShe.CollectBus.Protocol.Contracts/Adapters/StandardFixedHeaderDataHandlingAdapter.cs rename to protocols/JiShe.CollectBus.Protocol.Contracts/Adapters/StandardFixedHeaderDataHandlingAdapter.cs diff --git a/src/JiShe.CollectBus.Protocol.Contracts/AnalysisData/Appendix.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/AnalysisData/Appendix.cs similarity index 100% rename from src/JiShe.CollectBus.Protocol.Contracts/AnalysisData/Appendix.cs rename to protocols/JiShe.CollectBus.Protocol.Contracts/AnalysisData/Appendix.cs diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Attributes/ProtocolNameAttribute.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Attributes/ProtocolNameAttribute.cs similarity index 100% rename from src/JiShe.CollectBus.Protocol.Contracts/Attributes/ProtocolNameAttribute.cs rename to protocols/JiShe.CollectBus.Protocol.Contracts/Attributes/ProtocolNameAttribute.cs diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs similarity index 100% rename from src/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs rename to protocols/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs diff --git a/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj b/protocols/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj similarity index 57% rename from src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj rename to protocols/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj index fc0f12e..cb60dbd 100644 --- a/src/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj +++ b/protocols/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj @@ -6,6 +6,12 @@ enable + + + + + + @@ -15,13 +21,9 @@ - - - - - - - + + + diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Models/CustomFixedHeaderRequestInfo.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Models/CustomFixedHeaderRequestInfo.cs similarity index 100% rename from src/JiShe.CollectBus.Protocol.Contracts/Models/CustomFixedHeaderRequestInfo.cs rename to protocols/JiShe.CollectBus.Protocol.Contracts/Models/CustomFixedHeaderRequestInfo.cs diff --git a/src/JiShe.CollectBus.Protocol.Contracts/Models/TB3761.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Models/TB3761.cs similarity index 100% rename from src/JiShe.CollectBus.Protocol.Contracts/Models/TB3761.cs rename to protocols/JiShe.CollectBus.Protocol.Contracts/Models/TB3761.cs diff --git a/src/JiShe.CollectBus.Protocol.Contracts/QGDW3761Config.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/QGDW3761Config.cs similarity index 100% rename from src/JiShe.CollectBus.Protocol.Contracts/QGDW3761Config.cs rename to protocols/JiShe.CollectBus.Protocol.Contracts/QGDW3761Config.cs diff --git a/src/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj b/protocols/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj similarity index 75% rename from src/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj rename to protocols/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj index d7b4af6..899ebad 100644 --- a/src/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj +++ b/protocols/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj @@ -16,13 +16,13 @@ - - + + - + diff --git a/src/JiShe.CollectBus.Protocol.Test/JiSheCollectBusProtocolModule.cs b/protocols/JiShe.CollectBus.Protocol.Test/JiSheCollectBusProtocolModule.cs similarity index 100% rename from src/JiShe.CollectBus.Protocol.Test/JiSheCollectBusProtocolModule.cs rename to protocols/JiShe.CollectBus.Protocol.Test/JiSheCollectBusProtocolModule.cs diff --git a/src/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs similarity index 100% rename from src/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs rename to protocols/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs diff --git a/src/JiShe.CollectBus.Protocol/JiShe.CollectBus.Protocol.csproj b/protocols/JiShe.CollectBus.Protocol/JiShe.CollectBus.Protocol.csproj similarity index 64% rename from src/JiShe.CollectBus.Protocol/JiShe.CollectBus.Protocol.csproj rename to protocols/JiShe.CollectBus.Protocol/JiShe.CollectBus.Protocol.csproj index 0ee8a46..1497183 100644 --- a/src/JiShe.CollectBus.Protocol/JiShe.CollectBus.Protocol.csproj +++ b/protocols/JiShe.CollectBus.Protocol/JiShe.CollectBus.Protocol.csproj @@ -16,13 +16,13 @@ - - - + + + - + diff --git a/src/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs b/protocols/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs similarity index 100% rename from src/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs rename to protocols/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs diff --git a/src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs similarity index 100% rename from src/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs rename to protocols/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/BaseResultDto.cs b/services/JiShe.CollectBus.Application.Contracts/BaseResultDto.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/BaseResultDto.cs rename to services/JiShe.CollectBus.Application.Contracts/BaseResultDto.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/CollectBusApplicationContractsModule.cs b/services/JiShe.CollectBus.Application.Contracts/CollectBusApplicationContractsModule.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/CollectBusApplicationContractsModule.cs rename to services/JiShe.CollectBus.Application.Contracts/CollectBusApplicationContractsModule.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/CollectBusRemoteServiceConsts.cs b/services/JiShe.CollectBus.Application.Contracts/CollectBusRemoteServiceConsts.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/CollectBusRemoteServiceConsts.cs rename to services/JiShe.CollectBus.Application.Contracts/CollectBusRemoteServiceConsts.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/DataMigration/IDataMigrationService.cs b/services/JiShe.CollectBus.Application.Contracts/DataMigration/IDataMigrationService.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/DataMigration/IDataMigrationService.cs rename to services/JiShe.CollectBus.Application.Contracts/DataMigration/IDataMigrationService.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/DataMigration/Options/DataMigrationOptions.cs b/services/JiShe.CollectBus.Application.Contracts/DataMigration/Options/DataMigrationOptions.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/DataMigration/Options/DataMigrationOptions.cs rename to services/JiShe.CollectBus.Application.Contracts/DataMigration/Options/DataMigrationOptions.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddConrOnlineRecordInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddConrOnlineRecordInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddConrOnlineRecordInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddConrOnlineRecordInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddFocusLogInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddFocusLogInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddFocusLogInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddFocusLogInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddSignalStrengthInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddSignalStrengthInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddSignalStrengthInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AddSignalStrengthInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AdjustMeterTimingInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AdjustMeterTimingInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AdjustMeterTimingInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AdjustMeterTimingInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesDownInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesDownInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesDownInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesDownInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesDownOutput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesDownOutput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesDownOutput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesDownOutput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesMatchInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesMatchInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesMatchInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AmmeterArchivesMatchInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AutoReportCollectionItemsSetInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AutoReportCollectionItemsSetInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AutoReportCollectionItemsSetInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AutoReportCollectionItemsSetInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AutoReportSetInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AutoReportSetInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AutoReportSetInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/AutoReportSetInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BaseInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BaseInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BaseInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BaseInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BatchReadVersionInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BatchReadVersionInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BatchReadVersionInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BatchReadVersionInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BatchReadVersionOutput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BatchReadVersionOutput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BatchReadVersionOutput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/BatchReadVersionOutput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/CallTimeTestingInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/CallTimeTestingInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/CallTimeTestingInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/CallTimeTestingInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/CommunicationParametersSetInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/CommunicationParametersSetInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/CommunicationParametersSetInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/CommunicationParametersSetInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/EquitDubgInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/EquitDubgInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/EquitDubgInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/EquitDubgInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryAutoReportOpenStatusInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryAutoReportOpenStatusInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryAutoReportOpenStatusInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryAutoReportOpenStatusInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryRecordLogInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryRecordLogInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryRecordLogInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryRecordLogInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryRecordLogOutput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryRecordLogOutput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryRecordLogOutput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/QueryRecordLogOutput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadMeterNumInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadMeterNumInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadMeterNumInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadMeterNumInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadMeterNumOutput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadMeterNumOutput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadMeterNumOutput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadMeterNumOutput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadTimeInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadTimeInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadTimeInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadTimeInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadTimeOutput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadTimeOutput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadTimeOutput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadTimeOutput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadingInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadingInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadingInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadingInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadingOutput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadingOutput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadingOutput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ReadingOutput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TerminalRestartInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TerminalRestartInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TerminalRestartInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TerminalRestartInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeAdjustInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeAdjustInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeAdjustInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeAdjustInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeSetInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeSetInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeSetInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeSetInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeSetOutput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeSetOutput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeSetOutput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/TimeSetOutput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ValveControlInput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ValveControlInput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ValveControlInput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ValveControlInput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ValveControlOutput.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ValveControlOutput.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ValveControlOutput.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/Dto/ValveControlOutput.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/ICacheAppService.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/ICacheAppService.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/ICacheAppService.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/ICacheAppService.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/EnergySystem/IEnergySystemAppService.cs b/services/JiShe.CollectBus.Application.Contracts/EnergySystem/IEnergySystemAppService.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/EnergySystem/IEnergySystemAppService.cs rename to services/JiShe.CollectBus.Application.Contracts/EnergySystem/IEnergySystemAppService.cs diff --git a/src/JiShe.CollectBus.Application/FodyWeavers.xml b/services/JiShe.CollectBus.Application.Contracts/FodyWeavers.xml similarity index 100% rename from src/JiShe.CollectBus.Application/FodyWeavers.xml rename to services/JiShe.CollectBus.Application.Contracts/FodyWeavers.xml diff --git a/src/JiShe.CollectBus.Application.Contracts/ICollectWorker.cs b/services/JiShe.CollectBus.Application.Contracts/ICollectWorker.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/ICollectWorker.cs rename to services/JiShe.CollectBus.Application.Contracts/ICollectWorker.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/JiShe - Backup.CollectBus.Application.Contracts.csproj b/services/JiShe.CollectBus.Application.Contracts/JiShe - Backup.CollectBus.Application.Contracts.csproj similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/JiShe - Backup.CollectBus.Application.Contracts.csproj rename to services/JiShe.CollectBus.Application.Contracts/JiShe - Backup.CollectBus.Application.Contracts.csproj diff --git a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.abppkg b/services/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.abppkg similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.abppkg rename to services/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.abppkg diff --git a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj b/services/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj similarity index 73% rename from src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj rename to services/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj index 6e4fed5..fedbafd 100644 --- a/src/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj +++ b/services/JiShe.CollectBus.Application.Contracts/JiShe.CollectBus.Application.Contracts.csproj @@ -23,10 +23,10 @@ - - + + - + diff --git a/src/JiShe.CollectBus.Application.Contracts/Permissions/CollectBusPermissionDefinitionProvider.cs b/services/JiShe.CollectBus.Application.Contracts/Permissions/CollectBusPermissionDefinitionProvider.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/Permissions/CollectBusPermissionDefinitionProvider.cs rename to services/JiShe.CollectBus.Application.Contracts/Permissions/CollectBusPermissionDefinitionProvider.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/Permissions/CollectBusPermissions.cs b/services/JiShe.CollectBus.Application.Contracts/Permissions/CollectBusPermissions.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/Permissions/CollectBusPermissions.cs rename to services/JiShe.CollectBus.Application.Contracts/Permissions/CollectBusPermissions.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs b/services/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs rename to services/JiShe.CollectBus.Application.Contracts/RedisDataCache/IRedisDataCacheService.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/Samples/ISampleAppService.cs b/services/JiShe.CollectBus.Application.Contracts/Samples/ISampleAppService.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/Samples/ISampleAppService.cs rename to services/JiShe.CollectBus.Application.Contracts/Samples/ISampleAppService.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/Samples/SampleDto.cs b/services/JiShe.CollectBus.Application.Contracts/Samples/SampleDto.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/Samples/SampleDto.cs rename to services/JiShe.CollectBus.Application.Contracts/Samples/SampleDto.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs b/services/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs rename to services/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs b/services/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs rename to services/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs diff --git a/src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs b/services/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs similarity index 100% rename from src/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs rename to services/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs diff --git a/src/JiShe.CollectBus.Application/CollectBusAppService.cs b/services/JiShe.CollectBus.Application/CollectBusAppService.cs similarity index 99% rename from src/JiShe.CollectBus.Application/CollectBusAppService.cs rename to services/JiShe.CollectBus.Application/CollectBusAppService.cs index ad5b585..e155c65 100644 --- a/src/JiShe.CollectBus.Application/CollectBusAppService.cs +++ b/services/JiShe.CollectBus.Application/CollectBusAppService.cs @@ -2,7 +2,6 @@ using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; -using JiShe.CollectBus.FreeRedisProvider; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.Localization; using Microsoft.AspNetCore.Mvc; @@ -10,6 +9,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using JiShe.CollectBus.FreeRedis; using Volo.Abp.Application.Services; namespace JiShe.CollectBus; diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationAutoMapperProfile.cs b/services/JiShe.CollectBus.Application/CollectBusApplicationAutoMapperProfile.cs similarity index 100% rename from src/JiShe.CollectBus.Application/CollectBusApplicationAutoMapperProfile.cs rename to services/JiShe.CollectBus.Application/CollectBusApplicationAutoMapperProfile.cs diff --git a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/services/JiShe.CollectBus.Application/CollectBusApplicationModule.cs similarity index 97% rename from src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs rename to services/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index 19aa539..e5077cc 100644 --- a/src/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/services/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -1,8 +1,6 @@ using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Extensions; -using JiShe.CollectBus.FreeRedisProvider; using JiShe.CollectBus.FreeSql; -using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.Kafka; using JiShe.CollectBus.Kafka.AdminClient; using JiShe.CollectBus.Protocol.Contracts; @@ -14,6 +12,8 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using JiShe.CollectBus.Cassandra; +using JiShe.CollectBus.FreeRedis; +using JiShe.CollectBus.IoTDB; using Volo.Abp; using Volo.Abp.Application; using Volo.Abp.Autofac; diff --git a/src/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs rename to services/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs diff --git a/src/JiShe.CollectBus.Application/Consumers/IssuedFaultConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/IssuedFaultConsumer.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Consumers/IssuedFaultConsumer.cs rename to services/JiShe.CollectBus.Application/Consumers/IssuedFaultConsumer.cs diff --git a/src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs rename to services/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs diff --git a/src/JiShe.CollectBus.Application/Consumers/ReceivedFaultConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/ReceivedFaultConsumer.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Consumers/ReceivedFaultConsumer.cs rename to services/JiShe.CollectBus.Application/Consumers/ReceivedFaultConsumer.cs diff --git a/src/JiShe.CollectBus.Application/Consumers/ReceivedHeartbeatConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/ReceivedHeartbeatConsumer.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Consumers/ReceivedHeartbeatConsumer.cs rename to services/JiShe.CollectBus.Application/Consumers/ReceivedHeartbeatConsumer.cs diff --git a/src/JiShe.CollectBus.Application/Consumers/ReceivedLoginConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/ReceivedLoginConsumer.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Consumers/ReceivedLoginConsumer.cs rename to services/JiShe.CollectBus.Application/Consumers/ReceivedLoginConsumer.cs diff --git a/src/JiShe.CollectBus.Application/Consumers/ScheduledMeterReadingConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/ScheduledMeterReadingConsumer.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Consumers/ScheduledMeterReadingConsumer.cs rename to services/JiShe.CollectBus.Application/Consumers/ScheduledMeterReadingConsumer.cs diff --git a/src/JiShe.CollectBus.Application/DataMigration/DataMigrationService.cs b/services/JiShe.CollectBus.Application/DataMigration/DataMigrationService.cs similarity index 100% rename from src/JiShe.CollectBus.Application/DataMigration/DataMigrationService.cs rename to services/JiShe.CollectBus.Application/DataMigration/DataMigrationService.cs diff --git a/src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs b/services/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs similarity index 100% rename from src/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs rename to services/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs diff --git a/src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs b/services/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs similarity index 100% rename from src/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs rename to services/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs diff --git a/src/JiShe.CollectBus.Application/Exceptions/CloseException.cs b/services/JiShe.CollectBus.Application/Exceptions/CloseException.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Exceptions/CloseException.cs rename to services/JiShe.CollectBus.Application/Exceptions/CloseException.cs diff --git a/src/JiShe.CollectBus.Domain.Shared/FodyWeavers.xml b/services/JiShe.CollectBus.Application/FodyWeavers.xml similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/FodyWeavers.xml rename to services/JiShe.CollectBus.Application/FodyWeavers.xml diff --git a/src/JiShe.CollectBus.Application/Handlers/SampleHandler.cs b/services/JiShe.CollectBus.Application/Handlers/SampleHandler.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Handlers/SampleHandler.cs rename to services/JiShe.CollectBus.Application/Handlers/SampleHandler.cs diff --git a/src/JiShe.CollectBus.Application/JiShe - Backup.CollectBus.Application.csproj b/services/JiShe.CollectBus.Application/JiShe - Backup.CollectBus.Application.csproj similarity index 100% rename from src/JiShe.CollectBus.Application/JiShe - Backup.CollectBus.Application.csproj rename to services/JiShe.CollectBus.Application/JiShe - Backup.CollectBus.Application.csproj diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.abppkg b/services/JiShe.CollectBus.Application/JiShe.CollectBus.Application.abppkg similarity index 100% rename from src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.abppkg rename to services/JiShe.CollectBus.Application/JiShe.CollectBus.Application.abppkg diff --git a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/services/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj similarity index 69% rename from src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj rename to services/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index 3fa551c..b473dea 100644 --- a/src/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/services/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -25,14 +25,14 @@ + - - - - - + + + + diff --git a/src/JiShe.CollectBus.Application/Plugins/CloseMonitor.cs b/services/JiShe.CollectBus.Application/Plugins/CloseMonitor.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Plugins/CloseMonitor.cs rename to services/JiShe.CollectBus.Application/Plugins/CloseMonitor.cs diff --git a/src/JiShe.CollectBus.Application/Plugins/ServerMonitor.cs b/services/JiShe.CollectBus.Application/Plugins/ServerMonitor.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Plugins/ServerMonitor.cs rename to services/JiShe.CollectBus.Application/Plugins/ServerMonitor.cs diff --git a/src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/services/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs rename to services/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs diff --git a/src/JiShe.CollectBus.Application/Plugins/UdpMonitor.cs b/services/JiShe.CollectBus.Application/Plugins/UdpMonitor.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Plugins/UdpMonitor.cs rename to services/JiShe.CollectBus.Application/Plugins/UdpMonitor.cs diff --git a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs b/services/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs similarity index 99% rename from src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs rename to services/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs index 43dae23..1a056a7 100644 --- a/src/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs +++ b/services/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs @@ -4,7 +4,6 @@ using JiShe.CollectBus.Application.Contracts; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.FreeRedisProvider; using Microsoft.Extensions.Logging; using System; using System.Collections.Concurrent; @@ -12,6 +11,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using JiShe.CollectBus.FreeRedis; using Volo.Abp.DependencyInjection; using static FreeSql.Internal.GlobalFilter; using static System.Runtime.InteropServices.JavaScript.JSType; diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/services/JiShe.CollectBus.Application/Samples/SampleAppService.cs similarity index 98% rename from src/JiShe.CollectBus.Application/Samples/SampleAppService.cs rename to services/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 169b3f8..f0aa541 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/services/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -6,12 +6,10 @@ using Apache.IoTDB; using Confluent.Kafka; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.FreeSql; -using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.PrepayModel; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -using JiShe.CollectBus.IoTDBProvider.Context; using Microsoft.Extensions.Logging; using JiShe.CollectBus.IotSystems.AFNEntity; using JiShe.CollectBus.Protocol.Contracts.Interfaces; @@ -26,6 +24,9 @@ using JiShe.CollectBus.Kafka; using JiShe.CollectBus.Application.Contracts; using JiShe.CollectBus.Common.Models; using System.Diagnostics; +using JiShe.CollectBus.IoTDB.Context; +using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Options; namespace JiShe.CollectBus.Samples; diff --git a/src/JiShe.CollectBus.Application/Samples/TestAppService.cs b/services/JiShe.CollectBus.Application/Samples/TestAppService.cs similarity index 98% rename from src/JiShe.CollectBus.Application/Samples/TestAppService.cs rename to services/JiShe.CollectBus.Application/Samples/TestAppService.cs index 53f4b9e..2de458f 100644 --- a/src/JiShe.CollectBus.Application/Samples/TestAppService.cs +++ b/services/JiShe.CollectBus.Application/Samples/TestAppService.cs @@ -6,12 +6,10 @@ using Apache.IoTDB; using Confluent.Kafka; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.FreeSql; -using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.PrepayModel; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -using JiShe.CollectBus.IoTDBProvider.Context; using Microsoft.Extensions.Logging; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.IotSystems.AFNEntity; diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/services/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs similarity index 99% rename from src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs rename to services/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 97bcc50..42f0f1c 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/services/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -9,7 +9,6 @@ using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.GatherItem; -using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; @@ -27,6 +26,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using JiShe.CollectBus.IoTDB.Interface; using static FreeSql.Internal.GlobalFilter; namespace JiShe.CollectBus.ScheduledMeterReading diff --git a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/services/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs similarity index 99% rename from src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs rename to services/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index cfb0193..733f14a 100644 --- a/src/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/services/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -10,7 +10,7 @@ using JiShe.CollectBus.Common.DeviceBalanceControl; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.GatherItem; -using JiShe.CollectBus.IoTDBProvider; +using JiShe.CollectBus.IoTDB.Interface; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; diff --git a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/services/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs similarity index 99% rename from src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs rename to services/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index ce41db1..eb3abd9 100644 --- a/src/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/services/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -3,7 +3,6 @@ using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.IoTDBProvider; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; @@ -18,6 +17,7 @@ using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Threading.Tasks; +using JiShe.CollectBus.IoTDB.Interface; using TouchSocket.Sockets; using Volo.Abp.Domain.Repositories; diff --git a/src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/services/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs rename to services/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs diff --git a/src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs b/services/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs rename to services/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs diff --git a/src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs b/services/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs rename to services/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs b/services/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs rename to services/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs b/services/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs rename to services/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs diff --git a/src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs b/services/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs similarity index 100% rename from src/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs rename to services/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs diff --git a/src/JiShe.CollectBus.Application/cmd3761Matching.txt b/services/JiShe.CollectBus.Application/cmd3761Matching.txt similarity index 100% rename from src/JiShe.CollectBus.Application/cmd3761Matching.txt rename to services/JiShe.CollectBus.Application/cmd3761Matching.txt diff --git a/src/JiShe.CollectBus.Application/cmd3761TdcMatching.txt b/services/JiShe.CollectBus.Application/cmd3761TdcMatching.txt similarity index 100% rename from src/JiShe.CollectBus.Application/cmd3761TdcMatching.txt rename to services/JiShe.CollectBus.Application/cmd3761TdcMatching.txt diff --git a/src/JiShe.CollectBus.DbMigrator/CollectBusDbMigratorModule.cs b/services/JiShe.CollectBus.DbMigrator/CollectBusDbMigratorModule.cs similarity index 100% rename from src/JiShe.CollectBus.DbMigrator/CollectBusDbMigratorModule.cs rename to services/JiShe.CollectBus.DbMigrator/CollectBusDbMigratorModule.cs diff --git a/src/JiShe.CollectBus.DbMigrator/DbMigratorHostedService.cs b/services/JiShe.CollectBus.DbMigrator/DbMigratorHostedService.cs similarity index 100% rename from src/JiShe.CollectBus.DbMigrator/DbMigratorHostedService.cs rename to services/JiShe.CollectBus.DbMigrator/DbMigratorHostedService.cs diff --git a/src/JiShe.CollectBus.DbMigrator/Dockerfile b/services/JiShe.CollectBus.DbMigrator/Dockerfile similarity index 100% rename from src/JiShe.CollectBus.DbMigrator/Dockerfile rename to services/JiShe.CollectBus.DbMigrator/Dockerfile diff --git a/src/JiShe.CollectBus.DbMigrator/FodyWeavers.xml b/services/JiShe.CollectBus.DbMigrator/FodyWeavers.xml similarity index 100% rename from src/JiShe.CollectBus.DbMigrator/FodyWeavers.xml rename to services/JiShe.CollectBus.DbMigrator/FodyWeavers.xml diff --git a/src/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.abppkg b/services/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.abppkg similarity index 100% rename from src/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.abppkg rename to services/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.abppkg diff --git a/src/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.csproj b/services/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.csproj similarity index 93% rename from src/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.csproj rename to services/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.csproj index e163f7e..f875a56 100644 --- a/src/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.csproj +++ b/services/JiShe.CollectBus.DbMigrator/JiShe.CollectBus.DbMigrator.csproj @@ -18,8 +18,8 @@ + - diff --git a/src/JiShe.CollectBus.DbMigrator/Program.cs b/services/JiShe.CollectBus.DbMigrator/Program.cs similarity index 100% rename from src/JiShe.CollectBus.DbMigrator/Program.cs rename to services/JiShe.CollectBus.DbMigrator/Program.cs diff --git a/src/JiShe.CollectBus.DbMigrator/appsettings.json b/services/JiShe.CollectBus.DbMigrator/appsettings.json similarity index 100% rename from src/JiShe.CollectBus.DbMigrator/appsettings.json rename to services/JiShe.CollectBus.DbMigrator/appsettings.json diff --git a/src/JiShe.CollectBus.DbMigrator/appsettings.secrets.json b/services/JiShe.CollectBus.DbMigrator/appsettings.secrets.json similarity index 100% rename from src/JiShe.CollectBus.DbMigrator/appsettings.secrets.json rename to services/JiShe.CollectBus.DbMigrator/appsettings.secrets.json diff --git a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs b/services/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs rename to services/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs diff --git a/src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfoTemp.cs b/services/JiShe.CollectBus.Domain/Ammeters/AmmeterInfoTemp.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/Ammeters/AmmeterInfoTemp.cs rename to services/JiShe.CollectBus.Domain/Ammeters/AmmeterInfoTemp.cs diff --git a/src/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs b/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs similarity index 89% rename from src/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs rename to services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs index 987013c..2d9cc67 100644 --- a/src/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs +++ b/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs @@ -1,9 +1,10 @@ -using JiShe.CollectBus.IoTDBProvider; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using JiShe.CollectBus.IoTDB.Attribute; +using JiShe.CollectBus.IoTDB.Provider; namespace JiShe.CollectBus.Ammeters { diff --git a/src/JiShe.CollectBus.Domain/Analyze3761Data.cs b/services/JiShe.CollectBus.Domain/Analyze3761Data.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/Analyze3761Data.cs rename to services/JiShe.CollectBus.Domain/Analyze3761Data.cs diff --git a/src/JiShe.CollectBus.Domain/CollectBusConsts.cs b/services/JiShe.CollectBus.Domain/CollectBusConsts.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/CollectBusConsts.cs rename to services/JiShe.CollectBus.Domain/CollectBusConsts.cs diff --git a/src/JiShe.CollectBus.Domain/CollectBusDbProperties.cs b/services/JiShe.CollectBus.Domain/CollectBusDbProperties.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/CollectBusDbProperties.cs rename to services/JiShe.CollectBus.Domain/CollectBusDbProperties.cs diff --git a/src/JiShe.CollectBus.Domain/CollectBusDomainModule.cs b/services/JiShe.CollectBus.Domain/CollectBusDomainModule.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/CollectBusDomainModule.cs rename to services/JiShe.CollectBus.Domain/CollectBusDomainModule.cs diff --git a/src/JiShe.CollectBus.Domain/Data/CollectBusDbMigrationService.cs b/services/JiShe.CollectBus.Domain/Data/CollectBusDbMigrationService.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/Data/CollectBusDbMigrationService.cs rename to services/JiShe.CollectBus.Domain/Data/CollectBusDbMigrationService.cs diff --git a/src/JiShe.CollectBus.Domain/Data/ICollectBusDbSchemaMigrator.cs b/services/JiShe.CollectBus.Domain/Data/ICollectBusDbSchemaMigrator.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/Data/ICollectBusDbSchemaMigrator.cs rename to services/JiShe.CollectBus.Domain/Data/ICollectBusDbSchemaMigrator.cs diff --git a/src/JiShe.CollectBus.Domain/Data/NullCollectBusDbSchemaMigrator.cs b/services/JiShe.CollectBus.Domain/Data/NullCollectBusDbSchemaMigrator.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/Data/NullCollectBusDbSchemaMigrator.cs rename to services/JiShe.CollectBus.Domain/Data/NullCollectBusDbSchemaMigrator.cs diff --git a/src/JiShe.CollectBus.Domain/EnergySystems/Entities/TB_AmmeterInfo.cs b/services/JiShe.CollectBus.Domain/EnergySystems/Entities/TB_AmmeterInfo.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/EnergySystems/Entities/TB_AmmeterInfo.cs rename to services/JiShe.CollectBus.Domain/EnergySystems/Entities/TB_AmmeterInfo.cs diff --git a/src/JiShe.CollectBus.Domain/EnergySystems/TableViews/V_FocusAmmeter.cs b/services/JiShe.CollectBus.Domain/EnergySystems/TableViews/V_FocusAmmeter.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/EnergySystems/TableViews/V_FocusAmmeter.cs rename to services/JiShe.CollectBus.Domain/EnergySystems/TableViews/V_FocusAmmeter.cs diff --git a/src/JiShe.CollectBus.Domain/FodyWeavers.xml b/services/JiShe.CollectBus.Domain/FodyWeavers.xml similarity index 100% rename from src/JiShe.CollectBus.Domain/FodyWeavers.xml rename to services/JiShe.CollectBus.Domain/FodyWeavers.xml diff --git a/src/JiShe.CollectBus.Domain/GatherItem/GatherItemInfo.cs b/services/JiShe.CollectBus.Domain/GatherItem/GatherItemInfo.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/GatherItem/GatherItemInfo.cs rename to services/JiShe.CollectBus.Domain/GatherItem/GatherItemInfo.cs diff --git a/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs b/services/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs similarity index 83% rename from src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs rename to services/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs index 751e9e3..0440c1d 100644 --- a/src/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs +++ b/services/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs @@ -1,9 +1,10 @@ -using JiShe.CollectBus.IoTDBProvider; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using JiShe.CollectBus.IoTDB.Attribute; +using JiShe.CollectBus.IoTDB.Provider; namespace JiShe.CollectBus.IotSystems.AFNEntity { diff --git a/src/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs b/services/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs rename to services/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs b/services/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs rename to services/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs b/services/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs rename to services/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/ScheduledMeterReadingIssuedEventMessage.cs diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/IReceived.cs b/services/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/IReceived.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/IReceived.cs rename to services/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/IReceived.cs diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/MessageReceived.cs b/services/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/MessageReceived.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/MessageReceived.cs rename to services/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/MessageReceived.cs diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs b/services/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs rename to services/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingRecords.cs diff --git a/src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs b/services/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs rename to services/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs diff --git a/src/JiShe.CollectBus.Domain/IotSystems/PrepayModel/Vi_BaseAmmeterInfo.cs b/services/JiShe.CollectBus.Domain/IotSystems/PrepayModel/Vi_BaseAmmeterInfo.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/IotSystems/PrepayModel/Vi_BaseAmmeterInfo.cs rename to services/JiShe.CollectBus.Domain/IotSystems/PrepayModel/Vi_BaseAmmeterInfo.cs diff --git a/src/JiShe.CollectBus.Domain/IotSystems/Protocols/ProtocolInfo.cs b/services/JiShe.CollectBus.Domain/IotSystems/Protocols/ProtocolInfo.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/IotSystems/Protocols/ProtocolInfo.cs rename to services/JiShe.CollectBus.Domain/IotSystems/Protocols/ProtocolInfo.cs diff --git a/src/JiShe.CollectBus.Domain/IotSystems/Records/ConrOnlineRecord.cs b/services/JiShe.CollectBus.Domain/IotSystems/Records/ConrOnlineRecord.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/IotSystems/Records/ConrOnlineRecord.cs rename to services/JiShe.CollectBus.Domain/IotSystems/Records/ConrOnlineRecord.cs diff --git a/src/JiShe.CollectBus.Domain/IotSystems/Records/CsqRecord.cs b/services/JiShe.CollectBus.Domain/IotSystems/Records/CsqRecord.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/IotSystems/Records/CsqRecord.cs rename to services/JiShe.CollectBus.Domain/IotSystems/Records/CsqRecord.cs diff --git a/src/JiShe.CollectBus.Domain/IotSystems/Records/FocusRecord.cs b/services/JiShe.CollectBus.Domain/IotSystems/Records/FocusRecord.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/IotSystems/Records/FocusRecord.cs rename to services/JiShe.CollectBus.Domain/IotSystems/Records/FocusRecord.cs diff --git a/src/JiShe.CollectBus.Domain/IotSystems/Watermeter/WatermeterInfo.cs b/services/JiShe.CollectBus.Domain/IotSystems/Watermeter/WatermeterInfo.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/IotSystems/Watermeter/WatermeterInfo.cs rename to services/JiShe.CollectBus.Domain/IotSystems/Watermeter/WatermeterInfo.cs diff --git a/src/JiShe.CollectBus.Domain/JiShe - Backup.CollectBus.Domain.csproj b/services/JiShe.CollectBus.Domain/JiShe - Backup.CollectBus.Domain.csproj similarity index 100% rename from src/JiShe.CollectBus.Domain/JiShe - Backup.CollectBus.Domain.csproj rename to services/JiShe.CollectBus.Domain/JiShe - Backup.CollectBus.Domain.csproj diff --git a/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.abppkg b/services/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.abppkg similarity index 100% rename from src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.abppkg rename to services/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.abppkg diff --git a/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj b/services/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj similarity index 72% rename from src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj rename to services/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj index 16eef7f..a4dbc5d 100644 --- a/src/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj +++ b/services/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj @@ -21,10 +21,10 @@ - - - - + + + + diff --git a/src/JiShe.CollectBus.Domain/Properties/AssemblyInfo.cs b/services/JiShe.CollectBus.Domain/Properties/AssemblyInfo.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/Properties/AssemblyInfo.cs rename to services/JiShe.CollectBus.Domain/Properties/AssemblyInfo.cs diff --git a/src/JiShe.CollectBus.Domain/Settings/CollectBusSettingDefinitionProvider.cs b/services/JiShe.CollectBus.Domain/Settings/CollectBusSettingDefinitionProvider.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/Settings/CollectBusSettingDefinitionProvider.cs rename to services/JiShe.CollectBus.Domain/Settings/CollectBusSettingDefinitionProvider.cs diff --git a/src/JiShe.CollectBus.Domain/Settings/CollectBusSettings.cs b/services/JiShe.CollectBus.Domain/Settings/CollectBusSettings.cs similarity index 100% rename from src/JiShe.CollectBus.Domain/Settings/CollectBusSettings.cs rename to services/JiShe.CollectBus.Domain/Settings/CollectBusSettings.cs diff --git a/src/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusDbContext.cs b/services/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusDbContext.cs similarity index 100% rename from src/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusDbContext.cs rename to services/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusDbContext.cs diff --git a/src/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusDbContextFactory.cs b/services/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusDbContextFactory.cs similarity index 100% rename from src/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusDbContextFactory.cs rename to services/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusDbContextFactory.cs diff --git a/src/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusEfCoreEntityExtensionMappings.cs b/services/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusEfCoreEntityExtensionMappings.cs similarity index 100% rename from src/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusEfCoreEntityExtensionMappings.cs rename to services/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusEfCoreEntityExtensionMappings.cs diff --git a/src/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusEntityFrameworkCoreModule.cs b/services/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusEntityFrameworkCoreModule.cs similarity index 100% rename from src/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusEntityFrameworkCoreModule.cs rename to services/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/CollectBusEntityFrameworkCoreModule.cs diff --git a/src/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreCollectBusDbSchemaMigrator.cs b/services/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreCollectBusDbSchemaMigrator.cs similarity index 100% rename from src/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreCollectBusDbSchemaMigrator.cs rename to services/JiShe.CollectBus.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreCollectBusDbSchemaMigrator.cs diff --git a/src/JiShe.CollectBus.EntityFrameworkCore/FodyWeavers.xml b/services/JiShe.CollectBus.EntityFrameworkCore/FodyWeavers.xml similarity index 100% rename from src/JiShe.CollectBus.EntityFrameworkCore/FodyWeavers.xml rename to services/JiShe.CollectBus.EntityFrameworkCore/FodyWeavers.xml diff --git a/src/JiShe.CollectBus.EntityFrameworkCore/JiShe.CollectBus.EntityFrameworkCore.abppkg b/services/JiShe.CollectBus.EntityFrameworkCore/JiShe.CollectBus.EntityFrameworkCore.abppkg similarity index 100% rename from src/JiShe.CollectBus.EntityFrameworkCore/JiShe.CollectBus.EntityFrameworkCore.abppkg rename to services/JiShe.CollectBus.EntityFrameworkCore/JiShe.CollectBus.EntityFrameworkCore.abppkg diff --git a/src/JiShe.CollectBus.EntityFrameworkCore/JiShe.CollectBus.EntityFrameworkCore.csproj b/services/JiShe.CollectBus.EntityFrameworkCore/JiShe.CollectBus.EntityFrameworkCore.csproj similarity index 100% rename from src/JiShe.CollectBus.EntityFrameworkCore/JiShe.CollectBus.EntityFrameworkCore.csproj rename to services/JiShe.CollectBus.EntityFrameworkCore/JiShe.CollectBus.EntityFrameworkCore.csproj diff --git a/src/JiShe.CollectBus.EntityFrameworkCore/Properties/AssemblyInfo.cs b/services/JiShe.CollectBus.EntityFrameworkCore/Properties/AssemblyInfo.cs similarity index 100% rename from src/JiShe.CollectBus.EntityFrameworkCore/Properties/AssemblyInfo.cs rename to services/JiShe.CollectBus.EntityFrameworkCore/Properties/AssemblyInfo.cs diff --git a/src/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs b/shared/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs similarity index 100% rename from src/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs rename to shared/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs diff --git a/src/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs b/shared/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs rename to shared/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs diff --git a/src/JiShe.CollectBus.Common/Attributes/IgnoreJobAttribute.cs b/shared/JiShe.CollectBus.Common/Attributes/IgnoreJobAttribute.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Attributes/IgnoreJobAttribute.cs rename to shared/JiShe.CollectBus.Common/Attributes/IgnoreJobAttribute.cs diff --git a/src/JiShe.CollectBus.Common/Attributes/TopicNameAttribute.cs b/shared/JiShe.CollectBus.Common/Attributes/TopicNameAttribute.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Attributes/TopicNameAttribute.cs rename to shared/JiShe.CollectBus.Common/Attributes/TopicNameAttribute.cs diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/Build188SendData.cs b/shared/JiShe.CollectBus.Common/BuildSendDatas/Build188SendData.cs similarity index 100% rename from src/JiShe.CollectBus.Common/BuildSendDatas/Build188SendData.cs rename to shared/JiShe.CollectBus.Common/BuildSendDatas/Build188SendData.cs diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs b/shared/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs similarity index 100% rename from src/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs rename to shared/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/Build645SendData.cs b/shared/JiShe.CollectBus.Common/BuildSendDatas/Build645SendData.cs similarity index 100% rename from src/JiShe.CollectBus.Common/BuildSendDatas/Build645SendData.cs rename to shared/JiShe.CollectBus.Common/BuildSendDatas/Build645SendData.cs diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs b/shared/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs similarity index 100% rename from src/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs rename to shared/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs b/shared/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs similarity index 100% rename from src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs rename to shared/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs diff --git a/src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketRequest.cs b/shared/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketRequest.cs similarity index 100% rename from src/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketRequest.cs rename to shared/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketRequest.cs diff --git a/src/JiShe.CollectBus.Common/Consts/CommonConst.cs b/shared/JiShe.CollectBus.Common/Consts/CommonConst.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Consts/CommonConst.cs rename to shared/JiShe.CollectBus.Common/Consts/CommonConst.cs diff --git a/src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs b/shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Consts/ProtocolConst.cs rename to shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs diff --git a/src/JiShe.CollectBus.Common/Consts/RedisConst.cs b/shared/JiShe.CollectBus.Common/Consts/RedisConst.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Consts/RedisConst.cs rename to shared/JiShe.CollectBus.Common/Consts/RedisConst.cs diff --git a/src/JiShe.CollectBus.Common/Consts/RegexConst.cs b/shared/JiShe.CollectBus.Common/Consts/RegexConst.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Consts/RegexConst.cs rename to shared/JiShe.CollectBus.Common/Consts/RegexConst.cs diff --git a/src/JiShe.CollectBus.Common/Consts/SystemTypeConst.cs b/shared/JiShe.CollectBus.Common/Consts/SystemTypeConst.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Consts/SystemTypeConst.cs rename to shared/JiShe.CollectBus.Common/Consts/SystemTypeConst.cs diff --git a/src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs b/shared/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs similarity index 100% rename from src/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs rename to shared/JiShe.CollectBus.Common/DeviceBalanceControl/DeviceGroupBalanceControl.cs diff --git a/src/JiShe.CollectBus.Common/Enums/188Enums.cs b/shared/JiShe.CollectBus.Common/Enums/188Enums.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Enums/188Enums.cs rename to shared/JiShe.CollectBus.Common/Enums/188Enums.cs diff --git a/src/JiShe.CollectBus.Common/Enums/376Enums.cs b/shared/JiShe.CollectBus.Common/Enums/376Enums.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Enums/376Enums.cs rename to shared/JiShe.CollectBus.Common/Enums/376Enums.cs diff --git a/src/JiShe.CollectBus.Common/Enums/645Enums.cs b/shared/JiShe.CollectBus.Common/Enums/645Enums.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Enums/645Enums.cs rename to shared/JiShe.CollectBus.Common/Enums/645Enums.cs diff --git a/src/JiShe.CollectBus.Common/Enums/ComandCodeEnum.cs b/shared/JiShe.CollectBus.Common/Enums/ComandCodeEnum.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Enums/ComandCodeEnum.cs rename to shared/JiShe.CollectBus.Common/Enums/ComandCodeEnum.cs diff --git a/src/JiShe.CollectBus.Common/Enums/CommandChunkEnum.cs b/shared/JiShe.CollectBus.Common/Enums/CommandChunkEnum.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Enums/CommandChunkEnum.cs rename to shared/JiShe.CollectBus.Common/Enums/CommandChunkEnum.cs diff --git a/src/JiShe.CollectBus.Common/Enums/F25DataItemEnum.cs b/shared/JiShe.CollectBus.Common/Enums/F25DataItemEnum.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Enums/F25DataItemEnum.cs rename to shared/JiShe.CollectBus.Common/Enums/F25DataItemEnum.cs diff --git a/src/JiShe.CollectBus.Common/Enums/MaskTypeEnum.cs b/shared/JiShe.CollectBus.Common/Enums/MaskTypeEnum.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Enums/MaskTypeEnum.cs rename to shared/JiShe.CollectBus.Common/Enums/MaskTypeEnum.cs diff --git a/src/JiShe.CollectBus.Common/Enums/MeterLinkProtocolEnum.cs b/shared/JiShe.CollectBus.Common/Enums/MeterLinkProtocolEnum.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Enums/MeterLinkProtocolEnum.cs rename to shared/JiShe.CollectBus.Common/Enums/MeterLinkProtocolEnum.cs diff --git a/src/JiShe.CollectBus.Common/Enums/MeterTypeEnum.cs b/shared/JiShe.CollectBus.Common/Enums/MeterTypeEnum.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Enums/MeterTypeEnum.cs rename to shared/JiShe.CollectBus.Common/Enums/MeterTypeEnum.cs diff --git a/src/JiShe.CollectBus.Common/Enums/RecordsDataMigrationStatusEnum.cs b/shared/JiShe.CollectBus.Common/Enums/RecordsDataMigrationStatusEnum.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Enums/RecordsDataMigrationStatusEnum.cs rename to shared/JiShe.CollectBus.Common/Enums/RecordsDataMigrationStatusEnum.cs diff --git a/src/JiShe.CollectBus.Common/Enums/SortEnum.cs b/shared/JiShe.CollectBus.Common/Enums/SortEnum.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Enums/SortEnum.cs rename to shared/JiShe.CollectBus.Common/Enums/SortEnum.cs diff --git a/src/JiShe.CollectBus.Common/Enums/TransparentForwardingFlagEnum.cs b/shared/JiShe.CollectBus.Common/Enums/TransparentForwardingFlagEnum.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Enums/TransparentForwardingFlagEnum.cs rename to shared/JiShe.CollectBus.Common/Enums/TransparentForwardingFlagEnum.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/CollectionExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/CollectionExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/CollectionExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/CollectionExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/ComparableExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/ComparableExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/ComparableExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/ComparableExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/DataTableExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/DataTableExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/DataTableExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/DataTableExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/DayOfWeekExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/DayOfWeekExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/DayOfWeekExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/DayOfWeekExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/DecimalOrIntExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/DecimalOrIntExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/DecimalOrIntExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/DecimalOrIntExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/DictionaryExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/DictionaryExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/DictionaryExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/DictionaryExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/EnumerableExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/EnumerableExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/EnumerableExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/EnumerableExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/EventHandlerExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/EventHandlerExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/EventHandlerExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/EventHandlerExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/ExceptionExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/ExceptionExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/ExceptionExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/ExceptionExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/HexStringExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/HexStringExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/HexStringExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/HexStringExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/HttpResponseExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/HttpResponseExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/HttpResponseExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/HttpResponseExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/IntExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/IntExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/IntExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/IntExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/ListExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/ListExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/ListExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/ListExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/LockExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/LockExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/LockExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/LockExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/ObjectExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/ObjectExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/ObjectExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/ObjectExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/ProtocolConstExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/ProtocolConstExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/ProtocolConstExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/ProtocolConstExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/StreamExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/StreamExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/StreamExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/StreamExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/StringExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/StringExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/StringExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/StringExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Extensions/XmlExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/XmlExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Extensions/XmlExtensions.cs rename to shared/JiShe.CollectBus.Common/Extensions/XmlExtensions.cs diff --git a/src/JiShe.CollectBus.Common/Helpers/BusJsonSerializer.cs b/shared/JiShe.CollectBus.Common/Helpers/BusJsonSerializer.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Helpers/BusJsonSerializer.cs rename to shared/JiShe.CollectBus.Common/Helpers/BusJsonSerializer.cs diff --git a/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs b/shared/JiShe.CollectBus.Common/Helpers/CommonHelper.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs rename to shared/JiShe.CollectBus.Common/Helpers/CommonHelper.cs diff --git a/src/JiShe.CollectBus.Common/Helpers/SelectResult.cs b/shared/JiShe.CollectBus.Common/Helpers/SelectResult.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Helpers/SelectResult.cs rename to shared/JiShe.CollectBus.Common/Helpers/SelectResult.cs diff --git a/src/JiShe.CollectBus.Common/Helpers/TypeHelper.cs b/shared/JiShe.CollectBus.Common/Helpers/TypeHelper.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Helpers/TypeHelper.cs rename to shared/JiShe.CollectBus.Common/Helpers/TypeHelper.cs diff --git a/src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj b/shared/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj similarity index 100% rename from src/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj rename to shared/JiShe.CollectBus.Common/JiShe.CollectBus.Common.csproj diff --git a/src/JiShe.CollectBus.Common/Jobs/IBaseJob.cs b/shared/JiShe.CollectBus.Common/Jobs/IBaseJob.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Jobs/IBaseJob.cs rename to shared/JiShe.CollectBus.Common/Jobs/IBaseJob.cs diff --git a/src/JiShe.CollectBus.Common/Models/BusCacheGlobalPagedResult.cs b/shared/JiShe.CollectBus.Common/Models/BusCacheGlobalPagedResult.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Models/BusCacheGlobalPagedResult.cs rename to shared/JiShe.CollectBus.Common/Models/BusCacheGlobalPagedResult.cs diff --git a/src/JiShe.CollectBus.Common/Models/BusPagedResult.cs b/shared/JiShe.CollectBus.Common/Models/BusPagedResult.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Models/BusPagedResult.cs rename to shared/JiShe.CollectBus.Common/Models/BusPagedResult.cs diff --git a/src/JiShe.CollectBus.Common/Models/CommandReuslt.cs b/shared/JiShe.CollectBus.Common/Models/CommandReuslt.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Models/CommandReuslt.cs rename to shared/JiShe.CollectBus.Common/Models/CommandReuslt.cs diff --git a/src/JiShe.CollectBus.Common/Models/CurrentPositiveActiveEnergyAnalyze.cs b/shared/JiShe.CollectBus.Common/Models/CurrentPositiveActiveEnergyAnalyze.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Models/CurrentPositiveActiveEnergyAnalyze.cs rename to shared/JiShe.CollectBus.Common/Models/CurrentPositiveActiveEnergyAnalyze.cs diff --git a/src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs b/shared/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs rename to shared/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs diff --git a/src/JiShe.CollectBus.Common/Models/F25ReadingAnalyze.cs b/shared/JiShe.CollectBus.Common/Models/F25ReadingAnalyze.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Models/F25ReadingAnalyze.cs rename to shared/JiShe.CollectBus.Common/Models/F25ReadingAnalyze.cs diff --git a/src/JiShe.CollectBus.Common/Models/IssuedEventMessage.cs b/shared/JiShe.CollectBus.Common/Models/IssuedEventMessage.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Models/IssuedEventMessage.cs rename to shared/JiShe.CollectBus.Common/Models/IssuedEventMessage.cs diff --git a/src/JiShe.CollectBus.Common/Models/ReqParameter.cs b/shared/JiShe.CollectBus.Common/Models/ReqParameter.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Models/ReqParameter.cs rename to shared/JiShe.CollectBus.Common/Models/ReqParameter.cs diff --git a/src/JiShe.CollectBus.Common/Models/SetAmmeterJFPGEntity.cs b/shared/JiShe.CollectBus.Common/Models/SetAmmeterJFPGEntity.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Models/SetAmmeterJFPGEntity.cs rename to shared/JiShe.CollectBus.Common/Models/SetAmmeterJFPGEntity.cs diff --git a/src/JiShe.CollectBus.Common/Models/TerminalVersionInfoAnalyze.cs b/shared/JiShe.CollectBus.Common/Models/TerminalVersionInfoAnalyze.cs similarity index 100% rename from src/JiShe.CollectBus.Common/Models/TerminalVersionInfoAnalyze.cs rename to shared/JiShe.CollectBus.Common/Models/TerminalVersionInfoAnalyze.cs diff --git a/src/JiShe.CollectBus.Domain.Shared/CollectBusDomainSharedConsts.cs b/shared/JiShe.CollectBus.Domain.Shared/CollectBusDomainSharedConsts.cs similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/CollectBusDomainSharedConsts.cs rename to shared/JiShe.CollectBus.Domain.Shared/CollectBusDomainSharedConsts.cs diff --git a/src/JiShe.CollectBus.Domain.Shared/CollectBusDomainSharedModule.cs b/shared/JiShe.CollectBus.Domain.Shared/CollectBusDomainSharedModule.cs similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/CollectBusDomainSharedModule.cs rename to shared/JiShe.CollectBus.Domain.Shared/CollectBusDomainSharedModule.cs diff --git a/src/JiShe.CollectBus.Domain.Shared/CollectBusErrorCodes.cs b/shared/JiShe.CollectBus.Domain.Shared/CollectBusErrorCodes.cs similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/CollectBusErrorCodes.cs rename to shared/JiShe.CollectBus.Domain.Shared/CollectBusErrorCodes.cs diff --git a/src/JiShe.CollectBus.Domain.Shared/CollectBusGlobalFeatureConfigurator.cs b/shared/JiShe.CollectBus.Domain.Shared/CollectBusGlobalFeatureConfigurator.cs similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/CollectBusGlobalFeatureConfigurator.cs rename to shared/JiShe.CollectBus.Domain.Shared/CollectBusGlobalFeatureConfigurator.cs diff --git a/src/JiShe.CollectBus.Domain.Shared/CollectBusModuleExtensionConfigurator.cs b/shared/JiShe.CollectBus.Domain.Shared/CollectBusModuleExtensionConfigurator.cs similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/CollectBusModuleExtensionConfigurator.cs rename to shared/JiShe.CollectBus.Domain.Shared/CollectBusModuleExtensionConfigurator.cs diff --git a/src/JiShe.CollectBus.Domain.Shared/Enums/DeviceStatus.cs b/shared/JiShe.CollectBus.Domain.Shared/Enums/DeviceStatus.cs similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Enums/DeviceStatus.cs rename to shared/JiShe.CollectBus.Domain.Shared/Enums/DeviceStatus.cs diff --git a/src/JiShe.CollectBus.HttpApi/FodyWeavers.xml b/shared/JiShe.CollectBus.Domain.Shared/FodyWeavers.xml similarity index 100% rename from src/JiShe.CollectBus.HttpApi/FodyWeavers.xml rename to shared/JiShe.CollectBus.Domain.Shared/FodyWeavers.xml diff --git a/src/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectAttribute.cs b/shared/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectAttribute.cs similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectAttribute.cs rename to shared/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectAttribute.cs diff --git a/src/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectInterceptor.cs b/shared/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectInterceptor.cs similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectInterceptor.cs rename to shared/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectInterceptor.cs diff --git a/src/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectInterceptorRegistrar.cs b/shared/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectInterceptorRegistrar.cs similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectInterceptorRegistrar.cs rename to shared/JiShe.CollectBus.Domain.Shared/Interceptors/ProtocolInspectInterceptorRegistrar.cs diff --git a/src/JiShe.CollectBus.Domain.Shared/Interfaces/IProtocolInfo.cs b/shared/JiShe.CollectBus.Domain.Shared/Interfaces/IProtocolInfo.cs similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Interfaces/IProtocolInfo.cs rename to shared/JiShe.CollectBus.Domain.Shared/Interfaces/IProtocolInfo.cs diff --git a/src/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.abppkg b/shared/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.abppkg similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.abppkg rename to shared/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.abppkg diff --git a/src/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.csproj b/shared/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.csproj similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.csproj rename to shared/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.csproj diff --git a/src/JiShe.CollectBus.Domain.Shared/Jobs/EPICollectArgs.cs b/shared/JiShe.CollectBus.Domain.Shared/Jobs/EPICollectArgs.cs similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Jobs/EPICollectArgs.cs rename to shared/JiShe.CollectBus.Domain.Shared/Jobs/EPICollectArgs.cs diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ar.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ar.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ar.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ar.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/cs.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/cs.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/cs.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/cs.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/de.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/de.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/de.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/de.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/en-GB.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/en-GB.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/en-GB.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/en-GB.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/en.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/en.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/en.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/en.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/es.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/es.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/es.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/es.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/fi.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/fi.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/fi.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/fi.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/fr.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/fr.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/fr.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/fr.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hi.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hi.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hi.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hi.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hr.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hr.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hr.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hr.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hu.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hu.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hu.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/hu.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/is.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/is.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/is.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/is.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/it.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/it.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/it.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/it.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/nl.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/nl.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/nl.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/nl.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/pl-PL.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/pl-PL.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/pl-PL.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/pl-PL.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/pt-BR.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/pt-BR.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/pt-BR.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/pt-BR.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ro-RO.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ro-RO.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ro-RO.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ro-RO.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ru.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ru.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ru.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/ru.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/sk.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/sk.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/sk.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/sk.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/sl.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/sl.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/sl.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/sl.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/tr.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/tr.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/tr.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/tr.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/vi.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/vi.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/vi.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/vi.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/zh-Hans.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/zh-Hans.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/zh-Hans.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/zh-Hans.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/zh-Hant.json b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/zh-Hant.json similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/zh-Hant.json rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBus/zh-Hant.json diff --git a/src/JiShe.CollectBus.Domain.Shared/Localization/CollectBusResource.cs b/shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBusResource.cs similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Localization/CollectBusResource.cs rename to shared/JiShe.CollectBus.Domain.Shared/Localization/CollectBusResource.cs diff --git a/src/JiShe.CollectBus.Domain.Shared/Loggers/LoggerExtensions.cs b/shared/JiShe.CollectBus.Domain.Shared/Loggers/LoggerExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Domain.Shared/Loggers/LoggerExtensions.cs rename to shared/JiShe.CollectBus.Domain.Shared/Loggers/LoggerExtensions.cs diff --git a/src/JiShe.CollectBus.IoTDBProvider/Attribute/ATTRIBUTEColumnAttribute.cs b/src/JiShe.CollectBus.IoTDBProvider/Attribute/ATTRIBUTEColumnAttribute.cs deleted file mode 100644 index 1780bf9..0000000 --- a/src/JiShe.CollectBus.IoTDBProvider/Attribute/ATTRIBUTEColumnAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.IoTDBProvider -{ - /// - /// Column分类标记特性(ATTRIBUTE字段),也就是属性字段 - /// - [AttributeUsage(AttributeTargets.Property)] - public class ATTRIBUTEColumnAttribute : Attribute - { - } -} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Attribute/FIELDColumnAttribute.cs b/src/JiShe.CollectBus.IoTDBProvider/Attribute/FIELDColumnAttribute.cs deleted file mode 100644 index 2f8470b..0000000 --- a/src/JiShe.CollectBus.IoTDBProvider/Attribute/FIELDColumnAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.IoTDBProvider -{ - /// - /// Column分类标记特性(FIELD字段),数据列字段 - /// - [AttributeUsage(AttributeTargets.Property)] - public class FIELDColumnAttribute : Attribute - { - } -} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Attribute/TAGColumnAttribute.cs b/src/JiShe.CollectBus.IoTDBProvider/Attribute/TAGColumnAttribute.cs deleted file mode 100644 index b6149fe..0000000 --- a/src/JiShe.CollectBus.IoTDBProvider/Attribute/TAGColumnAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.IoTDBProvider -{ - /// - /// Column分类标记特性(TAG字段),标签字段 - /// - [AttributeUsage(AttributeTargets.Property)] - public class TAGColumnAttribute : Attribute - { - } -} diff --git a/test/JiShe.CollectBus.Application.Tests/CollectBusApplicationTestBase.cs b/test/JiShe.CollectBus.Application.Tests/CollectBusApplicationTestBase.cs deleted file mode 100644 index f680698..0000000 --- a/test/JiShe.CollectBus.Application.Tests/CollectBusApplicationTestBase.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Volo.Abp.Modularity; - -namespace JiShe.CollectBus; - -/* Inherit from this class for your application layer tests. - * See SampleAppService_Tests for example. - */ -public abstract class CollectBusApplicationTestBase : CollectBusTestBase - where TStartupModule : IAbpModule -{ - -} diff --git a/test/JiShe.CollectBus.Application.Tests/CollectBusApplicationTestModule.cs b/test/JiShe.CollectBus.Application.Tests/CollectBusApplicationTestModule.cs deleted file mode 100644 index bc81f70..0000000 --- a/test/JiShe.CollectBus.Application.Tests/CollectBusApplicationTestModule.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Volo.Abp.Modularity; - -namespace JiShe.CollectBus; - -[DependsOn( - typeof(CollectBusApplicationModule), - typeof(CollectBusDomainTestModule) - )] -public class CollectBusApplicationTestModule : AbpModule -{ - -} diff --git a/test/JiShe.CollectBus.Application.Tests/FodyWeavers.xml b/test/JiShe.CollectBus.Application.Tests/FodyWeavers.xml deleted file mode 100644 index 1715698..0000000 --- a/test/JiShe.CollectBus.Application.Tests/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/test/JiShe.CollectBus.Application.Tests/JiShe.CollectBus.Application.Tests.abppkg b/test/JiShe.CollectBus.Application.Tests/JiShe.CollectBus.Application.Tests.abppkg deleted file mode 100644 index a686451..0000000 --- a/test/JiShe.CollectBus.Application.Tests/JiShe.CollectBus.Application.Tests.abppkg +++ /dev/null @@ -1,3 +0,0 @@ -{ - "role": "lib.test" -} \ No newline at end of file diff --git a/test/JiShe.CollectBus.Application.Tests/JiShe.CollectBus.Application.Tests.csproj b/test/JiShe.CollectBus.Application.Tests/JiShe.CollectBus.Application.Tests.csproj deleted file mode 100644 index 04959f7..0000000 --- a/test/JiShe.CollectBus.Application.Tests/JiShe.CollectBus.Application.Tests.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - net8.0 - enable - JiShe.CollectBus - - - - - - - - - diff --git a/test/JiShe.CollectBus.Application.Tests/Samples/SampleAppService_Tests.cs b/test/JiShe.CollectBus.Application.Tests/Samples/SampleAppService_Tests.cs deleted file mode 100644 index 9b81ebf..0000000 --- a/test/JiShe.CollectBus.Application.Tests/Samples/SampleAppService_Tests.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp.Modularity; -using Xunit; - -namespace JiShe.CollectBus.Samples; - -public abstract class SampleAppService_Tests : CollectBusApplicationTestBase - where TStartupModule : IAbpModule -{ - private readonly ISampleAppService _sampleAppService; - - protected SampleAppService_Tests() - { - _sampleAppService = GetRequiredService(); - } - - [Fact] - public async Task GetAsync() - { - var result = await _sampleAppService.GetAsync(); - result.Value.ShouldBe(42); - } - - [Fact] - public async Task GetAuthorizedAsync() - { - var result = await _sampleAppService.GetAuthorizedAsync(); - result.Value.ShouldBe(42); - } -} diff --git a/test/JiShe.CollectBus.Domain.Tests/CollectBusDomainTestBase.cs b/test/JiShe.CollectBus.Domain.Tests/CollectBusDomainTestBase.cs deleted file mode 100644 index 71a0f76..0000000 --- a/test/JiShe.CollectBus.Domain.Tests/CollectBusDomainTestBase.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Volo.Abp.Modularity; - -namespace JiShe.CollectBus; - -/* Inherit from this class for your domain layer tests. - * See SampleManager_Tests for example. - */ -public abstract class CollectBusDomainTestBase : CollectBusTestBase - where TStartupModule : IAbpModule -{ - -} diff --git a/test/JiShe.CollectBus.Domain.Tests/CollectBusDomainTestModule.cs b/test/JiShe.CollectBus.Domain.Tests/CollectBusDomainTestModule.cs deleted file mode 100644 index ff4d85b..0000000 --- a/test/JiShe.CollectBus.Domain.Tests/CollectBusDomainTestModule.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Volo.Abp.Modularity; - -namespace JiShe.CollectBus; - -[DependsOn( - typeof(CollectBusDomainModule), - typeof(CollectBusTestBaseModule) -)] -public class CollectBusDomainTestModule : AbpModule -{ - -} diff --git a/test/JiShe.CollectBus.Domain.Tests/FodyWeavers.xml b/test/JiShe.CollectBus.Domain.Tests/FodyWeavers.xml deleted file mode 100644 index 1715698..0000000 --- a/test/JiShe.CollectBus.Domain.Tests/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/test/JiShe.CollectBus.Domain.Tests/JiShe.CollectBus.Domain.Tests.abppkg b/test/JiShe.CollectBus.Domain.Tests/JiShe.CollectBus.Domain.Tests.abppkg deleted file mode 100644 index a686451..0000000 --- a/test/JiShe.CollectBus.Domain.Tests/JiShe.CollectBus.Domain.Tests.abppkg +++ /dev/null @@ -1,3 +0,0 @@ -{ - "role": "lib.test" -} \ No newline at end of file diff --git a/test/JiShe.CollectBus.Domain.Tests/JiShe.CollectBus.Domain.Tests.csproj b/test/JiShe.CollectBus.Domain.Tests/JiShe.CollectBus.Domain.Tests.csproj deleted file mode 100644 index e9b2d08..0000000 --- a/test/JiShe.CollectBus.Domain.Tests/JiShe.CollectBus.Domain.Tests.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - net8.0 - enable - JiShe.CollectBus - - - - - - - - - - - - - diff --git a/test/JiShe.CollectBus.Domain.Tests/Samples/SampleManager_Tests.cs b/test/JiShe.CollectBus.Domain.Tests/Samples/SampleManager_Tests.cs deleted file mode 100644 index 431484b..0000000 --- a/test/JiShe.CollectBus.Domain.Tests/Samples/SampleManager_Tests.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.Modularity; -using Xunit; - -namespace JiShe.CollectBus.Samples; - -public abstract class SampleManager_Tests : CollectBusDomainTestBase - where TStartupModule : IAbpModule -{ - //private readonly SampleManager _sampleManager; - - public SampleManager_Tests() - { - //_sampleManager = GetRequiredService(); - } - - [Fact] - public Task Method1Async() - { - return Task.CompletedTask; - } -} diff --git a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/Applications/EfCoreSampleAppService_Tests.cs b/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/Applications/EfCoreSampleAppService_Tests.cs deleted file mode 100644 index d63e41d..0000000 --- a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/Applications/EfCoreSampleAppService_Tests.cs +++ /dev/null @@ -1,9 +0,0 @@ -using JiShe.CollectBus.Samples; -using Xunit; - -namespace JiShe.CollectBus.EntityFrameworkCore.Applications; - -public class EfCoreSampleAppService_Tests : SampleAppService_Tests -{ - -} diff --git a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/CollectBusEntityFrameworkCoreTestBase.cs b/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/CollectBusEntityFrameworkCoreTestBase.cs deleted file mode 100644 index dbb442b..0000000 --- a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/CollectBusEntityFrameworkCoreTestBase.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace JiShe.CollectBus.EntityFrameworkCore; - -/* This class can be used as a base class for EF Core integration tests, - * while SampleRepository_Tests uses a different approach. - */ -public abstract class CollectBusEntityFrameworkCoreTestBase : CollectBusTestBase -{ - -} diff --git a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/CollectBusEntityFrameworkCoreTestModule.cs b/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/CollectBusEntityFrameworkCoreTestModule.cs deleted file mode 100644 index ec59a28..0000000 --- a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/CollectBusEntityFrameworkCoreTestModule.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; -using Volo.Abp.EntityFrameworkCore; -using Volo.Abp.EntityFrameworkCore.Sqlite; -using Volo.Abp.Modularity; -using Volo.Abp.Uow; - -namespace JiShe.CollectBus.EntityFrameworkCore; - -[DependsOn( - typeof(CollectBusApplicationTestModule), - typeof(CollectBusEntityFrameworkCoreModule), - typeof(AbpEntityFrameworkCoreSqliteModule) -)] -public class CollectBusEntityFrameworkCoreTestModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddAlwaysDisableUnitOfWorkTransaction(); - - var sqliteConnection = CreateDatabaseAndGetConnection(); - - Configure(options => - { - options.Configure(abpDbContextConfigurationContext => - { - abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection); - }); - }); - } - - private static SqliteConnection CreateDatabaseAndGetConnection() - { - var connection = new SqliteConnection("Data Source=:memory:"); - connection.Open(); - - new CollectBusDbContext( - new DbContextOptionsBuilder().UseSqlite(connection).Options - ).GetService().CreateTables(); - - return connection; - } -} diff --git a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/Domains/EfCoreSampleDomain_Tests.cs b/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/Domains/EfCoreSampleDomain_Tests.cs deleted file mode 100644 index 2a115f5..0000000 --- a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/Domains/EfCoreSampleDomain_Tests.cs +++ /dev/null @@ -1,9 +0,0 @@ -using JiShe.CollectBus.Samples; -using Xunit; - -namespace JiShe.CollectBus.EntityFrameworkCore.Domains; - -public class EfCoreSampleDomain_Tests : SampleManager_Tests -{ - -} diff --git a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/Samples/SampleRepository_Tests.cs b/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/Samples/SampleRepository_Tests.cs deleted file mode 100644 index 01dd9e2..0000000 --- a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/EntityFrameworkCore/Samples/SampleRepository_Tests.cs +++ /dev/null @@ -1,11 +0,0 @@ -using JiShe.CollectBus.Samples; - -namespace JiShe.CollectBus.EntityFrameworkCore.Samples; - -public class SampleRepository_Tests : SampleRepository_Tests -{ - /* Don't write custom repository tests here, instead write to - * the base class. - * One exception can be some specific tests related to EF core. - */ -} diff --git a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/FodyWeavers.xml b/test/JiShe.CollectBus.EntityFrameworkCore.Tests/FodyWeavers.xml deleted file mode 100644 index 1715698..0000000 --- a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/JiShe.CollectBus.EntityFrameworkCore.Tests.abppkg b/test/JiShe.CollectBus.EntityFrameworkCore.Tests/JiShe.CollectBus.EntityFrameworkCore.Tests.abppkg deleted file mode 100644 index a686451..0000000 --- a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/JiShe.CollectBus.EntityFrameworkCore.Tests.abppkg +++ /dev/null @@ -1,3 +0,0 @@ -{ - "role": "lib.test" -} \ No newline at end of file diff --git a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/JiShe.CollectBus.EntityFrameworkCore.Tests.csproj b/test/JiShe.CollectBus.EntityFrameworkCore.Tests/JiShe.CollectBus.EntityFrameworkCore.Tests.csproj deleted file mode 100644 index 63c7e8e..0000000 --- a/test/JiShe.CollectBus.EntityFrameworkCore.Tests/JiShe.CollectBus.EntityFrameworkCore.Tests.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - net8.0 - enable - JiShe.CollectBus - - - - - - - - - - - diff --git a/test/JiShe.CollectBus.MongoDB.Tests/FodyWeavers.xml b/test/JiShe.CollectBus.MongoDB.Tests/FodyWeavers.xml deleted file mode 100644 index 1715698..0000000 --- a/test/JiShe.CollectBus.MongoDB.Tests/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/test/JiShe.CollectBus.MongoDB.Tests/JiShe.CollectBus.MongoDB.Tests.abppkg b/test/JiShe.CollectBus.MongoDB.Tests/JiShe.CollectBus.MongoDB.Tests.abppkg deleted file mode 100644 index a686451..0000000 --- a/test/JiShe.CollectBus.MongoDB.Tests/JiShe.CollectBus.MongoDB.Tests.abppkg +++ /dev/null @@ -1,3 +0,0 @@ -{ - "role": "lib.test" -} \ No newline at end of file diff --git a/test/JiShe.CollectBus.MongoDB.Tests/JiShe.CollectBus.MongoDB.Tests.csproj b/test/JiShe.CollectBus.MongoDB.Tests/JiShe.CollectBus.MongoDB.Tests.csproj deleted file mode 100644 index c17ce34..0000000 --- a/test/JiShe.CollectBus.MongoDB.Tests/JiShe.CollectBus.MongoDB.Tests.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - net8.0 - enable - JiShe.CollectBus - - - - - - - - - - - - - diff --git a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/Applications/MongoDBSampleAppService_Tests.cs b/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/Applications/MongoDBSampleAppService_Tests.cs deleted file mode 100644 index f6dd5c7..0000000 --- a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/Applications/MongoDBSampleAppService_Tests.cs +++ /dev/null @@ -1,11 +0,0 @@ -using JiShe.CollectBus.MongoDB; -using JiShe.CollectBus.Samples; -using Xunit; - -namespace JiShe.CollectBus.MongoDb.Applications; - -[Collection(MongoTestCollection.Name)] -public class MongoDBSampleAppService_Tests : SampleAppService_Tests -{ - -} diff --git a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/CollectBusMongoDbTestBase.cs b/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/CollectBusMongoDbTestBase.cs deleted file mode 100644 index 242879a..0000000 --- a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/CollectBusMongoDbTestBase.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace JiShe.CollectBus.MongoDB; - -/* This class can be used as a base class for MongoDB integration tests, - * while SampleRepository_Tests uses a different approach. - */ -public abstract class CollectBusMongoDbTestBase : CollectBusTestBase -{ - -} diff --git a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/CollectBusMongoDbTestModule.cs b/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/CollectBusMongoDbTestModule.cs deleted file mode 100644 index 02028e7..0000000 --- a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/CollectBusMongoDbTestModule.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using Volo.Abp.Data; -using Volo.Abp.Modularity; -using Volo.Abp.Uow; - -namespace JiShe.CollectBus.MongoDB; - -[DependsOn( - typeof(CollectBusApplicationTestModule), - typeof(CollectBusMongoDbModule) -)] -public class CollectBusMongoDbTestModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.ConnectionStrings.Default = MongoDbFixture.GetRandomConnectionString(); - }); - } -} diff --git a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/Domains/MongoDBSampleDomain_Tests.cs b/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/Domains/MongoDBSampleDomain_Tests.cs deleted file mode 100644 index 466ae53..0000000 --- a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/Domains/MongoDBSampleDomain_Tests.cs +++ /dev/null @@ -1,10 +0,0 @@ -using JiShe.CollectBus.Samples; -using Xunit; - -namespace JiShe.CollectBus.MongoDB.Domains; - -[Collection(MongoTestCollection.Name)] -public class MongoDBSampleDomain_Tests : SampleManager_Tests -{ - -} diff --git a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/MongoDbFixture.cs b/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/MongoDbFixture.cs deleted file mode 100644 index 540bd3c..0000000 --- a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/MongoDbFixture.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using EphemeralMongo; - -namespace JiShe.CollectBus.MongoDB; - -public class MongoDbFixture : IDisposable -{ - public readonly static IMongoRunner MongoDbRunner; - - static MongoDbFixture() - { - MongoDbRunner = MongoRunner.Run(new MongoRunnerOptions - { - UseSingleNodeReplicaSet = true - }); - } - - public static string GetRandomConnectionString() - { - return GetConnectionString("Db_" + Guid.NewGuid().ToString("N")); - } - - public static string GetConnectionString(string databaseName) - { - var stringArray = MongoDbRunner.ConnectionString.Split('?'); - var connectionString = stringArray[0].EnsureEndsWith('/') + databaseName + "/?" + stringArray[1]; - return connectionString; - } - - public void Dispose() - { - MongoDbRunner?.Dispose(); - } -} diff --git a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/MongoTestCollection.cs b/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/MongoTestCollection.cs deleted file mode 100644 index 08fc3b9..0000000 --- a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/MongoTestCollection.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Xunit; - -namespace JiShe.CollectBus.MongoDB; - -[CollectionDefinition(Name)] -public class MongoTestCollection : ICollectionFixture -{ - public const string Name = "MongoDB Collection"; -} diff --git a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/Samples/SampleRepository_Tests.cs b/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/Samples/SampleRepository_Tests.cs deleted file mode 100644 index 2fc8587..0000000 --- a/test/JiShe.CollectBus.MongoDB.Tests/MongoDB/Samples/SampleRepository_Tests.cs +++ /dev/null @@ -1,13 +0,0 @@ -using JiShe.CollectBus.Samples; -using Xunit; - -namespace JiShe.CollectBus.MongoDB.Samples; - -[Collection(MongoTestCollection.Name)] -public class SampleRepository_Tests : SampleRepository_Tests -{ - /* Don't write custom repository tests here, instead write to - * the base class. - * One exception can be some specific tests related to MongoDB. - */ -} diff --git a/test/JiShe.CollectBus.TestBase/CollectBusDataSeedContributor.cs b/test/JiShe.CollectBus.TestBase/CollectBusDataSeedContributor.cs deleted file mode 100644 index a72a0d1..0000000 --- a/test/JiShe.CollectBus.TestBase/CollectBusDataSeedContributor.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Guids; -using Volo.Abp.MultiTenancy; - -namespace JiShe.CollectBus; - -public class CollectBusDataSeedContributor : IDataSeedContributor, ITransientDependency -{ - private readonly IGuidGenerator _guidGenerator; - private readonly ICurrentTenant _currentTenant; - - public CollectBusDataSeedContributor( - IGuidGenerator guidGenerator, ICurrentTenant currentTenant) - { - _guidGenerator = guidGenerator; - _currentTenant = currentTenant; - } - - public Task SeedAsync(DataSeedContext context) - { - /* Instead of returning the Task.CompletedTask, you can insert your test data - * at this point! - */ - - using (_currentTenant.Change(context?.TenantId)) - { - return Task.CompletedTask; - } - } -} diff --git a/test/JiShe.CollectBus.TestBase/CollectBusTestBase.cs b/test/JiShe.CollectBus.TestBase/CollectBusTestBase.cs deleted file mode 100644 index 7db1dce..0000000 --- a/test/JiShe.CollectBus.TestBase/CollectBusTestBase.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp; -using Volo.Abp.Modularity; -using Volo.Abp.Uow; -using Volo.Abp.Testing; - -namespace JiShe.CollectBus; - -/* All test classes are derived from this class, directly or indirectly. */ -public abstract class CollectBusTestBase : AbpIntegratedTest - where TStartupModule : IAbpModule -{ - protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) - { - options.UseAutofac(); - } - - protected virtual Task WithUnitOfWorkAsync(Func func) - { - return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); - } - - protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func action) - { - using (var scope = ServiceProvider.CreateScope()) - { - var uowManager = scope.ServiceProvider.GetRequiredService(); - - using (var uow = uowManager.Begin(options)) - { - await action(); - - await uow.CompleteAsync(); - } - } - } - - protected virtual Task WithUnitOfWorkAsync(Func> func) - { - return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); - } - - protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func> func) - { - using (var scope = ServiceProvider.CreateScope()) - { - var uowManager = scope.ServiceProvider.GetRequiredService(); - - using (var uow = uowManager.Begin(options)) - { - var result = await func(); - await uow.CompleteAsync(); - return result; - } - } - } -} diff --git a/test/JiShe.CollectBus.TestBase/CollectBusTestBaseModule.cs b/test/JiShe.CollectBus.TestBase/CollectBusTestBaseModule.cs deleted file mode 100644 index 89b154e..0000000 --- a/test/JiShe.CollectBus.TestBase/CollectBusTestBaseModule.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp; -using Volo.Abp.Authorization; -using Volo.Abp.Autofac; -using Volo.Abp.Data; -using Volo.Abp.Guids; -using Volo.Abp.Modularity; -using Volo.Abp.Threading; - -namespace JiShe.CollectBus; - -[DependsOn( - typeof(AbpAutofacModule), - typeof(AbpTestBaseModule), - typeof(AbpAuthorizationModule), - typeof(AbpGuidsModule) -)] -public class CollectBusTestBaseModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddAlwaysAllowAuthorization(); - } - - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - SeedTestData(context); - } - - private static void SeedTestData(ApplicationInitializationContext context) - { - AsyncHelper.RunSync(async () => - { - using (var scope = context.ServiceProvider.CreateScope()) - { - await scope.ServiceProvider - .GetRequiredService() - .SeedAsync(); - } - }); - } -} diff --git a/test/JiShe.CollectBus.TestBase/FodyWeavers.xml b/test/JiShe.CollectBus.TestBase/FodyWeavers.xml deleted file mode 100644 index 1715698..0000000 --- a/test/JiShe.CollectBus.TestBase/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/test/JiShe.CollectBus.TestBase/JiShe.CollectBus.TestBase.abppkg b/test/JiShe.CollectBus.TestBase/JiShe.CollectBus.TestBase.abppkg deleted file mode 100644 index a686451..0000000 --- a/test/JiShe.CollectBus.TestBase/JiShe.CollectBus.TestBase.abppkg +++ /dev/null @@ -1,3 +0,0 @@ -{ - "role": "lib.test" -} \ No newline at end of file diff --git a/test/JiShe.CollectBus.TestBase/JiShe.CollectBus.TestBase.csproj b/test/JiShe.CollectBus.TestBase/JiShe.CollectBus.TestBase.csproj deleted file mode 100644 index cda873b..0000000 --- a/test/JiShe.CollectBus.TestBase/JiShe.CollectBus.TestBase.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - net8.0 - enable - JiShe.CollectBus - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - - - - - - - - - - diff --git a/test/JiShe.CollectBus.TestBase/Samples/SampleRepository_Tests.cs b/test/JiShe.CollectBus.TestBase/Samples/SampleRepository_Tests.cs deleted file mode 100644 index 06874fd..0000000 --- a/test/JiShe.CollectBus.TestBase/Samples/SampleRepository_Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.Modularity; -using Xunit; - -namespace JiShe.CollectBus.Samples; - -/* Write your custom repository tests like that, in this project, as abstract classes. - * Then inherit these abstract classes from EF Core & MongoDB test projects. - * In this way, both database providers are tests with the same set tests. - */ -public abstract class SampleRepository_Tests : CollectBusTestBase - where TStartupModule : IAbpModule -{ - //private readonly ISampleRepository _sampleRepository; - - protected SampleRepository_Tests() - { - //_sampleRepository = GetRequiredService(); - } - - [Fact] - public Task Method1Async() - { - return Task.CompletedTask; - } -} diff --git a/test/JiShe.CollectBus.TestBase/Security/FakeCurrentPrincipalAccessor.cs b/test/JiShe.CollectBus.TestBase/Security/FakeCurrentPrincipalAccessor.cs deleted file mode 100644 index a976624..0000000 --- a/test/JiShe.CollectBus.TestBase/Security/FakeCurrentPrincipalAccessor.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using System.Security.Claims; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Security.Claims; - -namespace JiShe.CollectBus.Security; - -[Dependency(ReplaceServices = true)] -public class FakeCurrentPrincipalAccessor : ThreadCurrentPrincipalAccessor -{ - protected override ClaimsPrincipal GetClaimsPrincipal() - { - return GetPrincipal(); - } - - private ClaimsPrincipal GetPrincipal() - { - return new ClaimsPrincipal(new ClaimsIdentity(new List - { - new Claim(AbpClaimTypes.UserId, "2e701e62-0953-4dd3-910b-dc6cc93ccb0d"), - new Claim(AbpClaimTypes.UserName, "admin"), - new Claim(AbpClaimTypes.Email, "admin@abp.io") - } - ) - ); - } -} diff --git a/src/JiShe.CollectBus.Host/CollectBusHostConst.cs b/web/JiShe.CollectBus.Host/CollectBusHostConst.cs similarity index 100% rename from src/JiShe.CollectBus.Host/CollectBusHostConst.cs rename to web/JiShe.CollectBus.Host/CollectBusHostConst.cs diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/web/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs similarity index 100% rename from src/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs rename to web/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs diff --git a/src/JiShe.CollectBus.Host/CollectBusHostModule.cs b/web/JiShe.CollectBus.Host/CollectBusHostModule.cs similarity index 100% rename from src/JiShe.CollectBus.Host/CollectBusHostModule.cs rename to web/JiShe.CollectBus.Host/CollectBusHostModule.cs diff --git a/src/JiShe.CollectBus.Host/Controllers/HomeController.cs b/web/JiShe.CollectBus.Host/Controllers/HomeController.cs similarity index 100% rename from src/JiShe.CollectBus.Host/Controllers/HomeController.cs rename to web/JiShe.CollectBus.Host/Controllers/HomeController.cs diff --git a/src/JiShe.CollectBus.Host/Extensions/CustomApplicationBuilderExtensions.cs b/web/JiShe.CollectBus.Host/Extensions/CustomApplicationBuilderExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Host/Extensions/CustomApplicationBuilderExtensions.cs rename to web/JiShe.CollectBus.Host/Extensions/CustomApplicationBuilderExtensions.cs diff --git a/src/JiShe.CollectBus.Host/Extensions/ServiceCollections/ServiceCollectionExtensions.cs b/web/JiShe.CollectBus.Host/Extensions/ServiceCollections/ServiceCollectionExtensions.cs similarity index 100% rename from src/JiShe.CollectBus.Host/Extensions/ServiceCollections/ServiceCollectionExtensions.cs rename to web/JiShe.CollectBus.Host/Extensions/ServiceCollections/ServiceCollectionExtensions.cs diff --git a/src/JiShe.CollectBus.Host/Hangfire/JobRetryLastFilter.cs b/web/JiShe.CollectBus.Host/Hangfire/JobRetryLastFilter.cs similarity index 100% rename from src/JiShe.CollectBus.Host/Hangfire/JobRetryLastFilter.cs rename to web/JiShe.CollectBus.Host/Hangfire/JobRetryLastFilter.cs diff --git a/src/JiShe.CollectBus.Host/HealthChecks/HealthCheckResponse.cs b/web/JiShe.CollectBus.Host/HealthChecks/HealthCheckResponse.cs similarity index 100% rename from src/JiShe.CollectBus.Host/HealthChecks/HealthCheckResponse.cs rename to web/JiShe.CollectBus.Host/HealthChecks/HealthCheckResponse.cs diff --git a/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj b/web/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj similarity index 90% rename from src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj rename to web/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj index 7d9bc47..76dfe60 100644 --- a/src/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj +++ b/web/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj @@ -52,10 +52,10 @@ - - + + + - diff --git a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml b/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml similarity index 100% rename from src/JiShe.CollectBus.Host/Pages/Monitor.cshtml rename to web/JiShe.CollectBus.Host/Pages/Monitor.cshtml diff --git a/src/JiShe.CollectBus.Host/Pages/Monitor.cshtml.cs b/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml.cs similarity index 100% rename from src/JiShe.CollectBus.Host/Pages/Monitor.cshtml.cs rename to web/JiShe.CollectBus.Host/Pages/Monitor.cshtml.cs diff --git a/web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.Test.dll b/web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.Test.dll new file mode 100644 index 0000000000000000000000000000000000000000..ccc12db0bf1d2359a55c6e66fa1cbf9d068118a8 GIT binary patch literal 12288 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 literal 0 HcmV?d00001 diff --git a/web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.dll b/web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.dll new file mode 100644 index 0000000000000000000000000000000000000000..ecd494bfd0ab1e0eb8f61ef75d993874669daf6d GIT binary patch literal 14336 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 VtBk-p>;DXuU-ehn`@i~5|1a?Q%0~bI literal 0 HcmV?d00001 diff --git a/src/JiShe.CollectBus.Host/Plugins/ignore.txt b/web/JiShe.CollectBus.Host/Plugins/ignore.txt similarity index 100% rename from src/JiShe.CollectBus.Host/Plugins/ignore.txt rename to web/JiShe.CollectBus.Host/Plugins/ignore.txt diff --git a/src/JiShe.CollectBus.Host/Program.cs b/web/JiShe.CollectBus.Host/Program.cs similarity index 100% rename from src/JiShe.CollectBus.Host/Program.cs rename to web/JiShe.CollectBus.Host/Program.cs diff --git a/src/JiShe.CollectBus.Host/Properties/launchSettings.json b/web/JiShe.CollectBus.Host/Properties/launchSettings.json similarity index 100% rename from src/JiShe.CollectBus.Host/Properties/launchSettings.json rename to web/JiShe.CollectBus.Host/Properties/launchSettings.json diff --git a/src/JiShe.CollectBus.Host/Startup.cs b/web/JiShe.CollectBus.Host/Startup.cs similarity index 100% rename from src/JiShe.CollectBus.Host/Startup.cs rename to web/JiShe.CollectBus.Host/Startup.cs diff --git a/src/JiShe.CollectBus.Host/Swaggers/EnumSchemaFilter.cs b/web/JiShe.CollectBus.Host/Swaggers/EnumSchemaFilter.cs similarity index 100% rename from src/JiShe.CollectBus.Host/Swaggers/EnumSchemaFilter.cs rename to web/JiShe.CollectBus.Host/Swaggers/EnumSchemaFilter.cs diff --git a/src/JiShe.CollectBus.Host/Swaggers/HiddenAbpDefaultApiFilter.cs b/web/JiShe.CollectBus.Host/Swaggers/HiddenAbpDefaultApiFilter.cs similarity index 100% rename from src/JiShe.CollectBus.Host/Swaggers/HiddenAbpDefaultApiFilter.cs rename to web/JiShe.CollectBus.Host/Swaggers/HiddenAbpDefaultApiFilter.cs diff --git a/src/JiShe.CollectBus.Host/Swaggers/SwaggerConfig.cs b/web/JiShe.CollectBus.Host/Swaggers/SwaggerConfig.cs similarity index 100% rename from src/JiShe.CollectBus.Host/Swaggers/SwaggerConfig.cs rename to web/JiShe.CollectBus.Host/Swaggers/SwaggerConfig.cs diff --git a/src/JiShe.CollectBus.Host/appsettings.Development.json b/web/JiShe.CollectBus.Host/appsettings.Development.json similarity index 100% rename from src/JiShe.CollectBus.Host/appsettings.Development.json rename to web/JiShe.CollectBus.Host/appsettings.Development.json diff --git a/src/JiShe.CollectBus.Host/appsettings.json b/web/JiShe.CollectBus.Host/appsettings.json similarity index 100% rename from src/JiShe.CollectBus.Host/appsettings.json rename to web/JiShe.CollectBus.Host/appsettings.json diff --git a/src/JiShe.CollectBus.Host/wwwroot/images/cap.png b/web/JiShe.CollectBus.Host/wwwroot/images/cap.png similarity index 100% rename from src/JiShe.CollectBus.Host/wwwroot/images/cap.png rename to web/JiShe.CollectBus.Host/wwwroot/images/cap.png diff --git a/src/JiShe.CollectBus.Host/wwwroot/images/hangfire.png b/web/JiShe.CollectBus.Host/wwwroot/images/hangfire.png similarity index 100% rename from src/JiShe.CollectBus.Host/wwwroot/images/hangfire.png rename to web/JiShe.CollectBus.Host/wwwroot/images/hangfire.png diff --git a/src/JiShe.CollectBus.Host/wwwroot/images/miniprofiler.png b/web/JiShe.CollectBus.Host/wwwroot/images/miniprofiler.png similarity index 100% rename from src/JiShe.CollectBus.Host/wwwroot/images/miniprofiler.png rename to web/JiShe.CollectBus.Host/wwwroot/images/miniprofiler.png diff --git a/src/JiShe.CollectBus.Host/wwwroot/images/more.png b/web/JiShe.CollectBus.Host/wwwroot/images/more.png similarity index 100% rename from src/JiShe.CollectBus.Host/wwwroot/images/more.png rename to web/JiShe.CollectBus.Host/wwwroot/images/more.png diff --git a/src/JiShe.CollectBus.Host/wwwroot/images/swagger.png b/web/JiShe.CollectBus.Host/wwwroot/images/swagger.png similarity index 100% rename from src/JiShe.CollectBus.Host/wwwroot/images/swagger.png rename to web/JiShe.CollectBus.Host/wwwroot/images/swagger.png diff --git a/src/JiShe.CollectBus.Host/wwwroot/libs/bootstrap/css/bootstrap.min.css b/web/JiShe.CollectBus.Host/wwwroot/libs/bootstrap/css/bootstrap.min.css similarity index 100% rename from src/JiShe.CollectBus.Host/wwwroot/libs/bootstrap/css/bootstrap.min.css rename to web/JiShe.CollectBus.Host/wwwroot/libs/bootstrap/css/bootstrap.min.css diff --git a/src/JiShe.CollectBus.HttpApi/CollectBusController.cs b/web/JiShe.CollectBus.HttpApi/CollectBusController.cs similarity index 100% rename from src/JiShe.CollectBus.HttpApi/CollectBusController.cs rename to web/JiShe.CollectBus.HttpApi/CollectBusController.cs diff --git a/src/JiShe.CollectBus.HttpApi/CollectBusHttpApiModule.cs b/web/JiShe.CollectBus.HttpApi/CollectBusHttpApiModule.cs similarity index 100% rename from src/JiShe.CollectBus.HttpApi/CollectBusHttpApiModule.cs rename to web/JiShe.CollectBus.HttpApi/CollectBusHttpApiModule.cs diff --git a/src/JiShe.CollectBus.MongoDB/FodyWeavers.xml b/web/JiShe.CollectBus.HttpApi/FodyWeavers.xml similarity index 100% rename from src/JiShe.CollectBus.MongoDB/FodyWeavers.xml rename to web/JiShe.CollectBus.HttpApi/FodyWeavers.xml diff --git a/src/JiShe.CollectBus.HttpApi/JiShe.CollectBus.HttpApi.abppkg b/web/JiShe.CollectBus.HttpApi/JiShe.CollectBus.HttpApi.abppkg similarity index 100% rename from src/JiShe.CollectBus.HttpApi/JiShe.CollectBus.HttpApi.abppkg rename to web/JiShe.CollectBus.HttpApi/JiShe.CollectBus.HttpApi.abppkg diff --git a/src/JiShe.CollectBus.HttpApi/JiShe.CollectBus.HttpApi.csproj b/web/JiShe.CollectBus.HttpApi/JiShe.CollectBus.HttpApi.csproj similarity index 68% rename from src/JiShe.CollectBus.HttpApi/JiShe.CollectBus.HttpApi.csproj rename to web/JiShe.CollectBus.HttpApi/JiShe.CollectBus.HttpApi.csproj index d5ecd25..3ea1d32 100644 --- a/src/JiShe.CollectBus.HttpApi/JiShe.CollectBus.HttpApi.csproj +++ b/web/JiShe.CollectBus.HttpApi/JiShe.CollectBus.HttpApi.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/src/JiShe.CollectBus.HttpApi/Samples/SampleController.cs b/web/JiShe.CollectBus.HttpApi/Samples/SampleController.cs similarity index 100% rename from src/JiShe.CollectBus.HttpApi/Samples/SampleController.cs rename to web/JiShe.CollectBus.HttpApi/Samples/SampleController.cs -- 2.47.2 From ca1e4e28e50ebeb178e39585dd22f099905dbf2b Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Thu, 17 Apr 2025 22:21:34 +0800 Subject: [PATCH 133/139] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Consumer/ConsumerService.cs | 22 ++---- .../KafkaSubcribesExtensions.cs | 78 ++++++++++--------- .../Consts/ProtocolConst.cs | 8 +- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs b/modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs index 4340524..8eacb5b 100644 --- a/modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs +++ b/modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs @@ -114,21 +114,18 @@ namespace JiShe.CollectBus.Kafka.Consumer consumer!.Subscribe(topics); - _ = Task.Run(async () => + await Task.Run(async () => { while (!cts.IsCancellationRequested) { try { + //_logger.LogInformation($"Kafka消费: {string.Join("", topics)} 开始拉取消息...."); + var result = consumer.Consume(cts.Token); if (result == null || result.Message==null || result.Message.Value == null) - { -#if DEBUG - _logger.LogWarning($"Kafka消费: {result?.Topic} 分区 {result?.Partition} 值为NULL"); -#endif - //consumer.Commit(result); // 手动提交 continue; - } + if (result.IsPartitionEOF) { _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); @@ -188,22 +185,17 @@ namespace JiShe.CollectBus.Kafka.Consumer consumer!.Subscribe(topics); - _ = Task.Run(async () => + await Task.Run(async () => { while (!cts.IsCancellationRequested) { try { + //_logger.LogInformation($"Kafka消费: {string.Join("", topics)} 开始拉取消息...."); var result = consumer.Consume(cts.Token); if (result == null || result.Message==null || result.Message.Value == null) - { -#if DEBUG - _logger.LogWarning($"Kafka消费: {result?.Topic} 分区 {result?.Partition} 值为NULL"); -#endif - //consumer.Commit(result); // 手动提交 - consumer.StoreOffset(result); continue; - } + if (result.IsPartitionEOF) { _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); diff --git a/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs b/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs index b09b3be..f94d296 100644 --- a/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs +++ b/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; +using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; @@ -40,7 +41,7 @@ namespace JiShe.CollectBus.Kafka kafkaAdminClient.CreateTopicAsync(item, kafkaOptions.Value.NumPartitions, kafkaOptions.Value.KafkaReplicationFactor).ConfigureAwait(false).GetAwaiter().GetResult(); } - lifetime.ApplicationStarted.Register(() => + lifetime.ApplicationStarted.Register(async() => { var logger = provider.GetRequiredService>(); int threadCount = 0; @@ -65,6 +66,7 @@ namespace JiShe.CollectBus.Kafka !type.IsAbstract && !type.IsInterface).ToList(); ; if (subscribeTypes.Count == 0) continue; + foreach (var subscribeType in subscribeTypes) { var subscribes = provider.GetServices(subscribeType).ToList(); @@ -80,7 +82,6 @@ namespace JiShe.CollectBus.Kafka } } logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); - }); } @@ -100,7 +101,7 @@ namespace JiShe.CollectBus.Kafka kafkaAdminClient.CreateTopicAsync(item, kafkaOptions.Value.NumPartitions, kafkaOptions.Value.KafkaReplicationFactor).ConfigureAwait(false).GetAwaiter().GetResult(); } - lifetime.ApplicationStarted.Register(() => + lifetime.ApplicationStarted.Register(async () => { var logger = provider.GetRequiredService>(); int threadCount = 0; @@ -140,6 +141,7 @@ namespace JiShe.CollectBus.Kafka .ToArray(); //var configuration = provider.GetRequiredService(); int threadCount = 0; + List tasks = new List(); foreach (var sub in subscribedMethods) { int partitionCount = 3;// kafkaOptionConfig.NumPartitions; @@ -156,7 +158,7 @@ namespace JiShe.CollectBus.Kafka threadCount++; } } - return Tuple.Create(threadCount, subscribedMethods.Length); + return await Task.FromResult(Tuple.Create(threadCount, subscribedMethods.Length)); } /// @@ -169,42 +171,48 @@ namespace JiShe.CollectBus.Kafka /// private static async Task StartConsumerAsync(IServiceProvider provider, KafkaSubscribeAttribute attr,MethodInfo method, object subscribe, ILogger logger) { - var consumerService = provider.GetRequiredService(); + await Task.Run(async () => + { + var consumerService = provider.GetRequiredService(); - if (attr.EnableBatch) - { - await consumerService.SubscribeBatchAsync(attr.Topic, async (message) => + if (attr.EnableBatch) { - try + await consumerService.SubscribeBatchAsync(attr.Topic, async (message) => { - // 处理消息 - return await ProcessMessageAsync(message.ToList(), method, subscribe); - } - catch (ConsumeException ex) - { - // 处理消费错误 - logger.LogError($"kafka批量消费异常:{ex.Message}"); - } - return await Task.FromResult(false); - }, attr.GroupId, attr.BatchSize,attr.BatchTimeout); - } - else - { - await consumerService.SubscribeAsync(attr.Topic, async (message) => + try + { + logger.LogInformation($"kafka批量消费消息:{message}"); + // 处理消息 + return await ProcessMessageAsync(message.ToList(), method, subscribe); + } + catch (ConsumeException ex) + { + // 处理消费错误 + logger.LogError($"kafka批量消费异常:{ex.Message}"); + } + return await Task.FromResult(false); + }, attr.GroupId, attr.BatchSize, attr.BatchTimeout); + } + else { - try + await consumerService.SubscribeAsync(attr.Topic, async (message) => { - // 处理消息 - return await ProcessMessageAsync(new List() { message }, method, subscribe); - } - catch (ConsumeException ex) - { - // 处理消费错误 - logger.LogError($"kafka消费异常:{ex.Message}"); - } - return await Task.FromResult(false); - }, attr.GroupId); - } + try + { + logger.LogInformation($"kafka消费消息:{message}"); + // 处理消息 + return await ProcessMessageAsync(new List() { message }, method, subscribe); + } + catch (ConsumeException ex) + { + // 处理消费错误 + logger.LogError($"kafka消费异常:{ex.Message}"); + } + return await Task.FromResult(false); + }, attr.GroupId); + } + }); + } diff --git a/shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs b/shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs index 91a41c5..6b65535 100644 --- a/shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs +++ b/shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs @@ -12,11 +12,11 @@ namespace JiShe.CollectBus.Common.Consts /// /// 心跳下行消息主题 /// - public const string SubscriberHeartbeatIssuedEventName = "issued.heartbeat.event2"; + public const string SubscriberHeartbeatIssuedEventName = "issued.heartbeat.event"; /// /// 登录下行消息主题 /// - public const string SubscriberLoginIssuedEventName = "issued.login.event2"; + public const string SubscriberLoginIssuedEventName = "issued.login.event"; /// /// 上行消息主题,测试使用 @@ -26,11 +26,11 @@ namespace JiShe.CollectBus.Common.Consts /// /// 心跳上行消息主题 /// - public const string SubscriberHeartbeatReceivedEventName = "received.heartbeat.event2"; + public const string SubscriberHeartbeatReceivedEventName = "received.heartbeat.event"; /// /// 登录上行消息主题 /// - public const string SubscriberLoginReceivedEventName = "received.login.event2"; + public const string SubscriberLoginReceivedEventName = "received.login.event"; #region 电表消息主题 /// -- 2.47.2 From 02d189358ece8326b8f7e14c35191a9ce3fe1d54 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Fri, 18 Apr 2025 08:22:52 +0800 Subject: [PATCH 134/139] =?UTF-8?q?=E8=AE=A2=E9=98=85=E6=B6=88=E8=B4=B9?= =?UTF-8?q?=E6=9A=82=E6=97=B6=E5=8F=96=E6=B6=88=E7=BC=93=E5=AD=98=E6=B6=88?= =?UTF-8?q?=E8=B4=B9=E5=AE=9E=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JiShe.CollectBus.sln | 7 + .../Attributes/KafkaSubscribeAttribute.cs | 22 +-- .../CollectBusKafkaModule.cs | 4 +- .../Consumer/ConsumerService.cs | 117 ++++++------ .../JiShe.CollectBus.Kafka/HostedService.cs | 45 +++++ .../KafkaSubcribesExtensions.cs | 173 +++++++++--------- .../Producer/IProducerService.cs | 2 +- .../Producer/ProducerService.cs | 16 +- 8 files changed, 223 insertions(+), 163 deletions(-) create mode 100644 modules/JiShe.CollectBus.Kafka/HostedService.cs diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index d4e6591..fa3fd6c 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -47,6 +47,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2.Services", "2.Services", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5.Shared", "5.Shared", "{EBF7C01F-9B4F-48E6-8418-2CBFDA51EB0B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka.Test", "modules\JiShe.CollectBus.Kafka.Test\JiShe.CollectBus.Kafka.Test.csproj", "{6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -121,6 +123,10 @@ Global {443B4549-0AC0-4493-8F3E-49C83225DD76}.Debug|Any CPU.Build.0 = Debug|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.ActiveCfg = Release|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.Build.0 = Release|Any CPU + {6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -143,6 +149,7 @@ Global {A3F3C092-0A25-450B-BF6A-5983163CBEF5} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59} {A377955E-7EA1-6F29-8CF7-774569E93925} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC} {443B4549-0AC0-4493-8F3E-49C83225DD76} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59} + {6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/modules/JiShe.CollectBus.Kafka/Attributes/KafkaSubscribeAttribute.cs b/modules/JiShe.CollectBus.Kafka/Attributes/KafkaSubscribeAttribute.cs index df75b89..c74aa2e 100644 --- a/modules/JiShe.CollectBus.Kafka/Attributes/KafkaSubscribeAttribute.cs +++ b/modules/JiShe.CollectBus.Kafka/Attributes/KafkaSubscribeAttribute.cs @@ -12,7 +12,7 @@ namespace JiShe.CollectBus.Kafka.Attributes /// /// 订阅的主题 /// - public string Topic { get; set; } + public string Topic { get; set; } = null!; /// /// 分区 @@ -22,7 +22,7 @@ namespace JiShe.CollectBus.Kafka.Attributes /// /// 消费者组 /// - public string GroupId { get; set; } + public string GroupId { get; set; } = "default"; /// /// 任务数(默认是多少个分区多少个任务) @@ -42,35 +42,27 @@ namespace JiShe.CollectBus.Kafka.Attributes /// /// 批次超时时间 + /// 格式:("00:05:00") /// public TimeSpan? BatchTimeout { get; set; }=null; + /// /// 订阅主题 /// - /// batchTimeout格式:("00:05:00") - public KafkaSubscribeAttribute(string topic, string groupId = "default", bool enableBatch = false, int batchSize = 100, string? batchTimeout = null) + /// + public KafkaSubscribeAttribute(string topic) { this.Topic = topic; - this.GroupId = groupId; - this.EnableBatch = enableBatch; - this.BatchSize = batchSize; - this.BatchTimeout = batchTimeout != null? TimeSpan.Parse(batchTimeout): null; } /// /// 订阅主题 /// - /// batchTimeout格式:("00:05:00") - public KafkaSubscribeAttribute(string topic, int partition, string groupId = "default", bool enableBatch = false, int batchSize = 100, string? batchTimeout = null) + public KafkaSubscribeAttribute(string topic, int partition) { this.Topic = topic; this.Partition = partition; - this.GroupId = groupId; - this.TaskCount = 1; - this.EnableBatch = enableBatch; - this.BatchSize = batchSize; - this.BatchTimeout = batchTimeout != null ? TimeSpan.Parse(batchTimeout) : null; } } } diff --git a/modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs b/modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs index 39b6444..867e2b7 100644 --- a/modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs +++ b/modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs @@ -39,6 +39,8 @@ namespace JiShe.CollectBus.Kafka context.Services.AddSingleton(); // 注册Consumer context.Services.AddSingleton(); + + context.Services.AddHostedService(); } public override void OnApplicationInitialization(ApplicationInitializationContext context) @@ -46,7 +48,7 @@ namespace JiShe.CollectBus.Kafka var app = context.GetApplicationBuilder(); // 注册Subscriber - app.ApplicationServices.UseKafkaSubscribe(); + //app.ApplicationServices.UseKafkaSubscribe(); // 获取程序集 //app.UseKafkaSubscribers(Assembly.Load("JiShe.CollectBus.Application")); diff --git a/modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs b/modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs index 8eacb5b..0ec5bd0 100644 --- a/modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs +++ b/modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs @@ -51,7 +51,7 @@ namespace JiShe.CollectBus.Kafka.Consumer AutoOffsetReset = AutoOffsetReset.Earliest, EnableAutoCommit = false, // 禁止AutoCommit EnablePartitionEof = true, // 启用分区末尾标记 - //AllowAutoCreateTopics = true, // 启用自动创建 + AllowAutoCreateTopics = true, // 启用自动创建 FetchMaxBytes = 1024 * 1024 * 50 // 增加拉取大小(50MB) }; @@ -106,12 +106,12 @@ namespace JiShe.CollectBus.Kafka.Consumer var consumerKey = typeof(KafkaConsumer); var cts = new CancellationTokenSource(); - var consumer = _consumerStore.GetOrAdd(consumerKey, _ => - ( - CreateConsumer(groupId), - cts - )).Consumer as IConsumer; - + //var consumer = _consumerStore.GetOrAdd(consumerKey, _ => + //( + // CreateConsumer(groupId), + // cts + //)).Consumer as IConsumer; + var consumer = CreateConsumer(groupId); consumer!.Subscribe(topics); await Task.Run(async () => @@ -170,61 +170,74 @@ namespace JiShe.CollectBus.Kafka.Consumer /// public async Task SubscribeAsync(string[] topics, Func> messageHandler, string? groupId) where TValue : class { - var consumerKey = typeof(KafkaConsumer); - var cts = new CancellationTokenSource(); - //if (topics.Contains(ProtocolConst.SubscriberLoginReceivedEventName)) - //{ - // string ssss = ""; - //} - var consumer = _consumerStore.GetOrAdd(consumerKey, _=> - ( - CreateConsumer(groupId), - cts - )).Consumer as IConsumer; + try { + var consumerKey = typeof(KafkaConsumer); + var cts = new CancellationTokenSource(); + //if (topics.Contains(ProtocolConst.SubscriberLoginReceivedEventName)) + //{ + // string ssss = ""; + //} + //var consumer = _consumerStore.GetOrAdd(consumerKey, _ => + //( + // CreateConsumer(groupId), + // cts + //)).Consumer as IConsumer; - - consumer!.Subscribe(topics); + var consumer = CreateConsumer(groupId); + consumer!.Subscribe(topics); - await Task.Run(async () => - { - while (!cts.IsCancellationRequested) + _ = Task.Run(async () => { - try + int count = 0; + while (!cts.IsCancellationRequested) { - //_logger.LogInformation($"Kafka消费: {string.Join("", topics)} 开始拉取消息...."); - var result = consumer.Consume(cts.Token); - if (result == null || result.Message==null || result.Message.Value == null) - continue; - - if (result.IsPartitionEOF) + try { - _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); - await Task.Delay(100, cts.Token); - continue; - } - if (_kafkaOptionConfig.EnableFilter) - { - var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } }; - // 检查 Header 是否符合条件 - if (!headersFilter.Match(result.Message.Headers)) + //_logger.LogInformation($"Kafka消费: {string.Join("", topics)}_{count} 开始拉取消息...."); + count++; + var result = consumer.Consume(cts.Token); + if (result == null || result.Message == null || result.Message.Value == null) { - //consumer.Commit(result); // 提交偏移量 - // 跳过消息 + await Task.Delay(500, cts.Token); continue; } + + if (result.IsPartitionEOF) + { + _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); + await Task.Delay(100, cts.Token); + continue; + } + if (_kafkaOptionConfig.EnableFilter) + { + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } }; + // 检查 Header 是否符合条件 + if (!headersFilter.Match(result.Message.Headers)) + { + await Task.Delay(500, cts.Token); + //consumer.Commit(result); // 提交偏移量 + // 跳过消息 + continue; + } + } + bool sucess = await messageHandler(result.Message.Value); + if (sucess) + consumer.Commit(result); // 手动提交 + else + consumer.StoreOffset(result); + } + catch (ConsumeException ex) + { + _logger.LogError(ex, $"{string.Join("、", topics)}消息消费失败: {ex.Error.Reason}"); } - bool sucess = await messageHandler(result.Message.Value); - if (sucess) - consumer.Commit(result); // 手动提交 - else - consumer.StoreOffset(result); } - catch (ConsumeException ex) - { - _logger.LogError(ex, $"{string.Join("、", topics)}消息消费失败: {ex.Error.Reason}"); - } - } - }); + }); + } catch (Exception ex) + { + _logger.LogWarning($"Kafka消费异常: {ex.Message}"); + + } + await Task.CompletedTask; } diff --git a/modules/JiShe.CollectBus.Kafka/HostedService.cs b/modules/JiShe.CollectBus.Kafka/HostedService.cs new file mode 100644 index 0000000..dcd5197 --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka/HostedService.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka +{ + public class HostedService : IHostedService, IDisposable + { + private readonly ILogger _logger; + private readonly IServiceProvider _provider; + public HostedService(ILogger logger, IServiceProvider provider) + { + _logger = logger; + _provider = provider; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("程序启动"); + Task.Run(() => + { + _provider.UseKafkaSubscribe(); + }); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("结束"); + + + return Task.CompletedTask; + } + + public void Dispose() + { + + } + } +} diff --git a/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs b/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs index f94d296..fa26e53 100644 --- a/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs +++ b/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs @@ -25,9 +25,9 @@ namespace JiShe.CollectBus.Kafka /// /// /// - public static void UseKafkaSubscribe(this IServiceProvider provider) + public static async Task UseKafkaSubscribe(this IServiceProvider provider) { - var lifetime = provider.GetRequiredService(); + //var lifetime = provider.GetRequiredService(); //初始化主题信息 var kafkaAdminClient = provider.GetRequiredService(); @@ -40,10 +40,10 @@ namespace JiShe.CollectBus.Kafka { kafkaAdminClient.CreateTopicAsync(item, kafkaOptions.Value.NumPartitions, kafkaOptions.Value.KafkaReplicationFactor).ConfigureAwait(false).GetAwaiter().GetResult(); } - - lifetime.ApplicationStarted.Register(async() => - { - var logger = provider.GetRequiredService>(); + List tasks = new List(); + //lifetime.ApplicationStarted.Register(async() => + //{ + var logger = provider.GetRequiredService>(); int threadCount = 0; int topicCount = 0; var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); @@ -74,7 +74,7 @@ namespace JiShe.CollectBus.Kafka { if (subscribe!=null) { - Tuple tuple = await BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value); + Tuple tuple = await BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value,tasks); threadCount += tuple.Item1; topicCount += tuple.Item2; } @@ -82,58 +82,59 @@ namespace JiShe.CollectBus.Kafka } } logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); - }); + //}); + await Task.WhenAll(tasks); } - public static void UseKafkaSubscribers(this IApplicationBuilder app, Assembly assembly) - { - var provider = app.ApplicationServices; - var lifetime = provider.GetRequiredService(); - //初始化主题信息 - var kafkaAdminClient = provider.GetRequiredService(); - var kafkaOptions = provider.GetRequiredService>(); + //public static void UseKafkaSubscribers(this IApplicationBuilder app, Assembly assembly) + //{ + // var provider = app.ApplicationServices; + // var lifetime = provider.GetRequiredService(); + // //初始化主题信息 + // var kafkaAdminClient = provider.GetRequiredService(); + // var kafkaOptions = provider.GetRequiredService>(); - List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); - topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); + // List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); + // topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); - foreach (var item in topics) - { - kafkaAdminClient.CreateTopicAsync(item, kafkaOptions.Value.NumPartitions, kafkaOptions.Value.KafkaReplicationFactor).ConfigureAwait(false).GetAwaiter().GetResult(); - } + // foreach (var item in topics) + // { + // kafkaAdminClient.CreateTopicAsync(item, kafkaOptions.Value.NumPartitions, kafkaOptions.Value.KafkaReplicationFactor).ConfigureAwait(false).GetAwaiter().GetResult(); + // } - lifetime.ApplicationStarted.Register(async () => - { - var logger = provider.GetRequiredService>(); - int threadCount = 0; - int topicCount = 0; - var subscribeTypes = assembly.GetTypes() - .Where(t => typeof(IKafkaSubscribe).IsAssignableFrom(t)) - .ToList(); + // lifetime.ApplicationStarted.Register(async () => + // { + // var logger = provider.GetRequiredService>(); + // int threadCount = 0; + // int topicCount = 0; + // var subscribeTypes = assembly.GetTypes() + // .Where(t => typeof(IKafkaSubscribe).IsAssignableFrom(t)) + // .ToList(); - if (subscribeTypes.Count == 0) return; - foreach (var subscribeType in subscribeTypes) - { - var subscribes = provider.GetServices(subscribeType).ToList(); - subscribes.ForEach(async subscribe => { + // if (subscribeTypes.Count == 0) return; + // foreach (var subscribeType in subscribeTypes) + // { + // var subscribes = provider.GetServices(subscribeType).ToList(); + // subscribes.ForEach(async subscribe => { - if (subscribe != null) - { - Tuple tuple =await BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value); - threadCount += tuple.Item1; - topicCount += tuple.Item2; - } - }); - } - logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); - }); - } + // if (subscribe != null) + // { + // Tuple tuple =await BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value); + // threadCount += tuple.Item1; + // topicCount += tuple.Item2; + // } + // }); + // } + // logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); + // }); + //} /// /// 构建Kafka订阅 /// /// /// - private static async Task> BuildKafkaSubscribe(object subscribe, IServiceProvider provider,ILogger logger, KafkaOptionConfig kafkaOptionConfig) + private static async Task> BuildKafkaSubscribe(object subscribe, IServiceProvider provider,ILogger logger, KafkaOptionConfig kafkaOptionConfig, List tasks) { var subscribedMethods = subscribe.GetType().GetMethods() .Select(m => new { Method = m, Attribute = m.GetCustomAttribute() }) @@ -141,20 +142,22 @@ namespace JiShe.CollectBus.Kafka .ToArray(); //var configuration = provider.GetRequiredService(); int threadCount = 0; - List tasks = new List(); + foreach (var sub in subscribedMethods) { int partitionCount = 3;// kafkaOptionConfig.NumPartitions; +#if DEBUG var adminClientService = provider.GetRequiredService(); int topicCount = adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic); partitionCount= partitionCount> topicCount ? topicCount: partitionCount; +#endif //int partitionCount = sub.Attribute!.TaskCount==-1?adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic) : sub.Attribute!.TaskCount; if (partitionCount <= 0) partitionCount = 1; for (int i = 0; i < partitionCount; i++) { //if (sub.Attribute!.Topic == ProtocolConst.SubscriberLoginReceivedEventName) - await StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe, logger); + tasks.Add( Task.Run(()=> StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe, logger))); threadCount++; } } @@ -171,49 +174,47 @@ namespace JiShe.CollectBus.Kafka /// private static async Task StartConsumerAsync(IServiceProvider provider, KafkaSubscribeAttribute attr,MethodInfo method, object subscribe, ILogger logger) { - await Task.Run(async () => - { - var consumerService = provider.GetRequiredService(); + var consumerService = provider.GetRequiredService(); - if (attr.EnableBatch) + if (attr.EnableBatch) + { + await consumerService.SubscribeBatchAsync(attr.Topic, async (message) => { - await consumerService.SubscribeBatchAsync(attr.Topic, async (message) => + try { - try - { - logger.LogInformation($"kafka批量消费消息:{message}"); - // 处理消息 - return await ProcessMessageAsync(message.ToList(), method, subscribe); - } - catch (ConsumeException ex) - { - // 处理消费错误 - logger.LogError($"kafka批量消费异常:{ex.Message}"); - } - return await Task.FromResult(false); - }, attr.GroupId, attr.BatchSize, attr.BatchTimeout); - } - else + logger.LogInformation($"kafka批量消费消息:{message}"); + // 处理消息 + return await ProcessMessageAsync(message.ToList(), method, subscribe); + } + catch (ConsumeException ex) + { + // 处理消费错误 + logger.LogError($"kafka批量消费异常:{ex.Message}"); + } + return await Task.FromResult(false); + }, attr.GroupId, attr.BatchSize, attr.BatchTimeout); + } + else + { + await consumerService.SubscribeAsync(attr.Topic, async (message) => { - await consumerService.SubscribeAsync(attr.Topic, async (message) => + try { - try - { - logger.LogInformation($"kafka消费消息:{message}"); - // 处理消息 - return await ProcessMessageAsync(new List() { message }, method, subscribe); - } - catch (ConsumeException ex) - { - // 处理消费错误 - logger.LogError($"kafka消费异常:{ex.Message}"); - } - return await Task.FromResult(false); - }, attr.GroupId); - } - }); - - +#if DEBUG + logger.LogInformation($"kafka消费消息:{message}"); +#endif + // 处理消息 + return await ProcessMessageAsync(new List() { message }, method, subscribe); + } + catch (ConsumeException ex) + { + // 处理消费错误 + logger.LogError($"kafka消费异常:{ex.Message}"); + } + return await Task.FromResult(false); + }, attr.GroupId); + } + } diff --git a/modules/JiShe.CollectBus.Kafka/Producer/IProducerService.cs b/modules/JiShe.CollectBus.Kafka/Producer/IProducerService.cs index a401775..becea90 100644 --- a/modules/JiShe.CollectBus.Kafka/Producer/IProducerService.cs +++ b/modules/JiShe.CollectBus.Kafka/Producer/IProducerService.cs @@ -15,6 +15,6 @@ namespace JiShe.CollectBus.Kafka.Producer Task ProduceAsync(string topic, TKey key, TValue value, int? partition, Action>? deliveryHandler = null) where TKey : notnull where TValue : class; - Task ProduceAsync(string topic, TValue value, int? partition = null, Action>? deliveryHandler = null) where TValue : class; + Task ProduceAsync(string topic, TValue value, int? partition = null, Action>? deliveryHandler = null) where TValue : class; } } diff --git a/modules/JiShe.CollectBus.Kafka/Producer/ProducerService.cs b/modules/JiShe.CollectBus.Kafka/Producer/ProducerService.cs index 8231760..529d293 100644 --- a/modules/JiShe.CollectBus.Kafka/Producer/ProducerService.cs +++ b/modules/JiShe.CollectBus.Kafka/Producer/ProducerService.cs @@ -126,10 +126,10 @@ namespace JiShe.CollectBus.Kafka.Producer public async Task ProduceAsync(string topic, TValue value) where TValue : class { var typeKey = typeof(KafkaProducer); - var producer = GetProducer(typeKey); - var message = new Message + var producer = GetProducer(typeKey); + var message = new Message { - Key= _kafkaOptionConfig.ServerTagName, + //Key= _kafkaOptionConfig.ServerTagName, Value = value, Headers = new Headers{ { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } @@ -184,18 +184,18 @@ namespace JiShe.CollectBus.Kafka.Producer /// /// /// - public async Task ProduceAsync(string topic, TValue value, int? partition=null, Action>? deliveryHandler = null) where TValue : class + public async Task ProduceAsync(string topic, TValue value, int? partition=null, Action>? deliveryHandler = null) where TValue : class { - var message = new Message + var message = new Message { - Key = _kafkaOptionConfig.ServerTagName, + //Key = _kafkaOptionConfig.ServerTagName, Value = value, Headers = new Headers{ { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } } }; - var typeKey = typeof(KafkaProducer); - var producer = GetProducer(typeKey); + var typeKey = typeof(KafkaProducer); + var producer = GetProducer(typeKey); if (partition.HasValue) { var topicPartition = new TopicPartition(topic, partition.Value); -- 2.47.2 From c93feb631d62340a534d617946542ea6e6caa384 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Fri, 18 Apr 2025 08:23:31 +0800 Subject: [PATCH 135/139] =?UTF-8?q?=E8=AE=A2=E9=98=85=E7=89=B9=E6=80=A7?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs b/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs index fa26e53..52e5c93 100644 --- a/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs +++ b/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs @@ -182,7 +182,9 @@ namespace JiShe.CollectBus.Kafka { try { +#if DEBUG logger.LogInformation($"kafka批量消费消息:{message}"); +#endif // 处理消息 return await ProcessMessageAsync(message.ToList(), method, subscribe); } -- 2.47.2 From 26e361075d8bf79de56e20fcec34bd55adcfbf11 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Fri, 18 Apr 2025 08:31:34 +0800 Subject: [PATCH 136/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=BF=BD=E7=95=A5?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +-- JiShe.CollectBus.sln | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 68f674a..4242f15 100644 --- a/.gitignore +++ b/.gitignore @@ -400,5 +400,4 @@ FodyWeavers.xsd # ABP Studio **/.abpstudio/ -/src/JiShe.CollectBus.Host/Plugins/*.dll -JiShe.CollectBus.Kafka.Test +/web/JiShe.CollectBus.Host/Plugins/*.dll diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index fa3fd6c..d4e6591 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -47,8 +47,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2.Services", "2.Services", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5.Shared", "5.Shared", "{EBF7C01F-9B4F-48E6-8418-2CBFDA51EB0B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Kafka.Test", "modules\JiShe.CollectBus.Kafka.Test\JiShe.CollectBus.Kafka.Test.csproj", "{6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -123,10 +121,6 @@ Global {443B4549-0AC0-4493-8F3E-49C83225DD76}.Debug|Any CPU.Build.0 = Debug|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.ActiveCfg = Release|Any CPU {443B4549-0AC0-4493-8F3E-49C83225DD76}.Release|Any CPU.Build.0 = Release|Any CPU - {6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -149,7 +143,6 @@ Global {A3F3C092-0A25-450B-BF6A-5983163CBEF5} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59} {A377955E-7EA1-6F29-8CF7-774569E93925} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC} {443B4549-0AC0-4493-8F3E-49C83225DD76} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59} - {6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} -- 2.47.2 From 7704c6374ed9f6a3989d2a96dde8c2d1d4cdf217 Mon Sep 17 00:00:00 2001 From: cli <377476583@qq.com> Date: Fri, 18 Apr 2025 08:33:15 +0800 Subject: [PATCH 137/139] =?UTF-8?q?=E4=BF=AE=E6=94=B9gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ .../Plugins/JiShe.CollectBus.Protocol.Test.dll | Bin 12288 -> 0 bytes .../Plugins/JiShe.CollectBus.Protocol.dll | Bin 14336 -> 0 bytes 3 files changed, 2 insertions(+) delete mode 100644 web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.Test.dll delete mode 100644 web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.dll diff --git a/.gitignore b/.gitignore index 4242f15..d639003 100644 --- a/.gitignore +++ b/.gitignore @@ -401,3 +401,5 @@ FodyWeavers.xsd # ABP Studio **/.abpstudio/ /web/JiShe.CollectBus.Host/Plugins/*.dll +/web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.dll +/web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.Test.dll diff --git a/web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.Test.dll b/web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.Test.dll deleted file mode 100644 index ccc12db0bf1d2359a55c6e66fa1cbf9d068118a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 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 diff --git a/web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.dll b/web/JiShe.CollectBus.Host/Plugins/JiShe.CollectBus.Protocol.dll deleted file mode 100644 index ecd494bfd0ab1e0eb8f61ef75d993874669daf6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14336 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 VtBk-p>;DXuU-ehn`@i~5|1a?Q%0~bI -- 2.47.2 From cb36fc463a30eef6637e056c90b82037232239b1 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Fri, 18 Apr 2025 09:28:48 +0800 Subject: [PATCH 138/139] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectBusKafkaModule.cs | 4 +- .../JiShe.CollectBus.Kafka/HostedService.cs | 2 - .../KafkaSubcribesExtensions.cs | 108 +++++++++--------- 3 files changed, 55 insertions(+), 59 deletions(-) diff --git a/modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs b/modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs index 867e2b7..b467162 100644 --- a/modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs +++ b/modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs @@ -40,7 +40,7 @@ namespace JiShe.CollectBus.Kafka // 注册Consumer context.Services.AddSingleton(); - context.Services.AddHostedService(); + //context.Services.AddHostedService(); } public override void OnApplicationInitialization(ApplicationInitializationContext context) @@ -48,7 +48,7 @@ namespace JiShe.CollectBus.Kafka var app = context.GetApplicationBuilder(); // 注册Subscriber - //app.ApplicationServices.UseKafkaSubscribe(); + app.ApplicationServices.UseKafkaSubscribe(); // 获取程序集 //app.UseKafkaSubscribers(Assembly.Load("JiShe.CollectBus.Application")); diff --git a/modules/JiShe.CollectBus.Kafka/HostedService.cs b/modules/JiShe.CollectBus.Kafka/HostedService.cs index dcd5197..c2e672c 100644 --- a/modules/JiShe.CollectBus.Kafka/HostedService.cs +++ b/modules/JiShe.CollectBus.Kafka/HostedService.cs @@ -32,8 +32,6 @@ namespace JiShe.CollectBus.Kafka public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("结束"); - - return Task.CompletedTask; } diff --git a/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs b/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs index 52e5c93..ff60130 100644 --- a/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs +++ b/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs @@ -25,9 +25,9 @@ namespace JiShe.CollectBus.Kafka /// /// /// - public static async Task UseKafkaSubscribe(this IServiceProvider provider) + public static void UseKafkaSubscribe(this IServiceProvider provider) { - //var lifetime = provider.GetRequiredService(); + var lifetime = provider.GetRequiredService(); //初始化主题信息 var kafkaAdminClient = provider.GetRequiredService(); @@ -40,10 +40,9 @@ namespace JiShe.CollectBus.Kafka { kafkaAdminClient.CreateTopicAsync(item, kafkaOptions.Value.NumPartitions, kafkaOptions.Value.KafkaReplicationFactor).ConfigureAwait(false).GetAwaiter().GetResult(); } - List tasks = new List(); - //lifetime.ApplicationStarted.Register(async() => - //{ - var logger = provider.GetRequiredService>(); + lifetime.ApplicationStarted.Register(() => + { + var logger = provider.GetRequiredService>(); int threadCount = 0; int topicCount = 0; var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); @@ -62,19 +61,19 @@ namespace JiShe.CollectBus.Kafka var assembly = existingAssembly ?? Assembly.LoadFrom(file); // 实现IKafkaSubscribe接口 var subscribeTypes = assembly.GetTypes().Where(type => - typeof(IKafkaSubscribe).IsAssignableFrom(type) && + typeof(IKafkaSubscribe).IsAssignableFrom(type) && !type.IsAbstract && !type.IsInterface).ToList(); ; - if (subscribeTypes.Count == 0) + if (subscribeTypes.Count == 0) continue; - + foreach (var subscribeType in subscribeTypes) { var subscribes = provider.GetServices(subscribeType).ToList(); - subscribes.ForEach(async subscribe => + subscribes.ForEach(subscribe => { - if (subscribe!=null) + if (subscribe != null) { - Tuple tuple = await BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value,tasks); + Tuple tuple = BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value); threadCount += tuple.Item1; topicCount += tuple.Item2; } @@ -82,59 +81,58 @@ namespace JiShe.CollectBus.Kafka } } logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); - //}); - await Task.WhenAll(tasks); + }); } - //public static void UseKafkaSubscribers(this IApplicationBuilder app, Assembly assembly) - //{ - // var provider = app.ApplicationServices; - // var lifetime = provider.GetRequiredService(); - // //初始化主题信息 - // var kafkaAdminClient = provider.GetRequiredService(); - // var kafkaOptions = provider.GetRequiredService>(); + public static void UseKafkaSubscribersAsync(this IApplicationBuilder app, Assembly assembly) + { + var provider = app.ApplicationServices; + var lifetime = provider.GetRequiredService(); + //初始化主题信息 + var kafkaAdminClient = provider.GetRequiredService(); + var kafkaOptions = provider.GetRequiredService>(); - // List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); - // topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); + List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); + topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived()); - // foreach (var item in topics) - // { - // kafkaAdminClient.CreateTopicAsync(item, kafkaOptions.Value.NumPartitions, kafkaOptions.Value.KafkaReplicationFactor).ConfigureAwait(false).GetAwaiter().GetResult(); - // } + foreach (var item in topics) + { + kafkaAdminClient.CreateTopicAsync(item, kafkaOptions.Value.NumPartitions, kafkaOptions.Value.KafkaReplicationFactor).ConfigureAwait(false).GetAwaiter().GetResult(); + } + lifetime.ApplicationStarted.Register(() => + { + var logger = provider.GetRequiredService>(); + int threadCount = 0; + int topicCount = 0; + var subscribeTypes = assembly.GetTypes() + .Where(t => typeof(IKafkaSubscribe).IsAssignableFrom(t)) + .ToList(); - // lifetime.ApplicationStarted.Register(async () => - // { - // var logger = provider.GetRequiredService>(); - // int threadCount = 0; - // int topicCount = 0; - // var subscribeTypes = assembly.GetTypes() - // .Where(t => typeof(IKafkaSubscribe).IsAssignableFrom(t)) - // .ToList(); + if (subscribeTypes.Count == 0) return; + foreach (var subscribeType in subscribeTypes) + { + var subscribes = provider.GetServices(subscribeType).ToList(); + subscribes.ForEach(subscribe => + { - // if (subscribeTypes.Count == 0) return; - // foreach (var subscribeType in subscribeTypes) - // { - // var subscribes = provider.GetServices(subscribeType).ToList(); - // subscribes.ForEach(async subscribe => { - - // if (subscribe != null) - // { - // Tuple tuple =await BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value); - // threadCount += tuple.Item1; - // topicCount += tuple.Item2; - // } - // }); - // } - // logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); - // }); - //} + if (subscribe != null) + { + Tuple tuple = BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value); + threadCount += tuple.Item1; + topicCount += tuple.Item2; + } + }); + } + logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程"); + }); + } /// /// 构建Kafka订阅 /// /// /// - private static async Task> BuildKafkaSubscribe(object subscribe, IServiceProvider provider,ILogger logger, KafkaOptionConfig kafkaOptionConfig, List tasks) + private static Tuple BuildKafkaSubscribe(object subscribe, IServiceProvider provider,ILogger logger, KafkaOptionConfig kafkaOptionConfig) { var subscribedMethods = subscribe.GetType().GetMethods() .Select(m => new { Method = m, Attribute = m.GetCustomAttribute() }) @@ -157,11 +155,11 @@ namespace JiShe.CollectBus.Kafka for (int i = 0; i < partitionCount; i++) { //if (sub.Attribute!.Topic == ProtocolConst.SubscriberLoginReceivedEventName) - tasks.Add( Task.Run(()=> StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe, logger))); + Task.Run(() => StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe, logger)); threadCount++; } } - return await Task.FromResult(Tuple.Create(threadCount, subscribedMethods.Length)); + return Tuple.Create(threadCount, subscribedMethods.Length); } /// -- 2.47.2 From e6327a57df6fb8c229dc394435e751c31d104b57 Mon Sep 17 00:00:00 2001 From: zenghongyao <873884283@qq.com> Date: Fri, 18 Apr 2025 09:29:38 +0800 Subject: [PATCH 139/139] =?UTF-8?q?=E5=90=88=E5=B9=B6=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConsoleApplicationBuilder.cs | 72 +++++++ .../JiShe.CollectBus.Kafka.Test.csproj | 40 ++++ .../KafkaProduceBenchmark.cs | 108 +++++++++++ .../KafkaSubscribeTest.cs | 68 +++++++ .../Lib/JiShe.CollectBus.Kafka.dll | Bin 0 -> 59904 bytes .../JiShe.CollectBus.Kafka.Test/Program.cs | 172 +++++++++++++++++ .../appsettings.json | 180 ++++++++++++++++++ 7 files changed, 640 insertions(+) create mode 100644 modules/JiShe.CollectBus.Kafka.Test/ConsoleApplicationBuilder.cs create mode 100644 modules/JiShe.CollectBus.Kafka.Test/JiShe.CollectBus.Kafka.Test.csproj create mode 100644 modules/JiShe.CollectBus.Kafka.Test/KafkaProduceBenchmark.cs create mode 100644 modules/JiShe.CollectBus.Kafka.Test/KafkaSubscribeTest.cs create mode 100644 modules/JiShe.CollectBus.Kafka.Test/Lib/JiShe.CollectBus.Kafka.dll create mode 100644 modules/JiShe.CollectBus.Kafka.Test/Program.cs create mode 100644 modules/JiShe.CollectBus.Kafka.Test/appsettings.json diff --git a/modules/JiShe.CollectBus.Kafka.Test/ConsoleApplicationBuilder.cs b/modules/JiShe.CollectBus.Kafka.Test/ConsoleApplicationBuilder.cs new file mode 100644 index 0000000..f6b0891 --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka.Test/ConsoleApplicationBuilder.cs @@ -0,0 +1,72 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka.Test +{ + public class ConsoleApplicationBuilder: IApplicationBuilder + { + public IServiceProvider ApplicationServices { get; set; } + public IDictionary Properties { get; set; } = new Dictionary(); + + public IFeatureCollection ServerFeatures => throw new NotImplementedException(); + + private readonly List> _middlewares = new(); + + public IApplicationBuilder Use(Func middleware) + { + _middlewares.Add(middleware); + return this; + } + + public RequestDelegate Build() + { + RequestDelegate app = context => Task.CompletedTask; + foreach (var middleware in _middlewares) + { + app = middleware(app); + } + return app; + } + + public IApplicationBuilder New() + { + return new ConsoleApplicationBuilder + { + ApplicationServices = this.ApplicationServices, + Properties = new Dictionary(this.Properties) + }; + } + } + + + public static class HostBuilderExtensions + { + public static IHostBuilder ConfigureConsoleAppBuilder( + this IHostBuilder hostBuilder, + Action configure) + { + hostBuilder.ConfigureServices((context, services) => + { + // 注册 ConsoleApplicationBuilder 到 DI 容器 + services.AddSingleton(provider => + { + var appBuilder = new ConsoleApplicationBuilder + { + ApplicationServices = provider // 注入服务提供者 + }; + configure(appBuilder); // 执行配置委托 + return appBuilder; + }); + }); + return hostBuilder; + } + } +} diff --git a/modules/JiShe.CollectBus.Kafka.Test/JiShe.CollectBus.Kafka.Test.csproj b/modules/JiShe.CollectBus.Kafka.Test/JiShe.CollectBus.Kafka.Test.csproj new file mode 100644 index 0000000..db97b00 --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka.Test/JiShe.CollectBus.Kafka.Test.csproj @@ -0,0 +1,40 @@ + + + + Exe + net8.0 + enable + enable + + + + + Always + true + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/JiShe.CollectBus.Kafka.Test/KafkaProduceBenchmark.cs b/modules/JiShe.CollectBus.Kafka.Test/KafkaProduceBenchmark.cs new file mode 100644 index 0000000..a8b8d93 --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka.Test/KafkaProduceBenchmark.cs @@ -0,0 +1,108 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using Confluent.Kafka; +using JiShe.CollectBus.Kafka.AdminClient; +using JiShe.CollectBus.Kafka.Consumer; +using JiShe.CollectBus.Kafka.Producer; + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Serilog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Kafka.Test +{ + [SimpleJob(RuntimeMoniker.Net80)] + //[SimpleJob(RuntimeMoniker.NativeAot80)] + [RPlotExporter] + public class KafkaProduceBenchmark + { + + // 每批消息数量 + [Params(1000, 10000, 100000)] + public int N; + public ServiceProvider _serviceProvider; + public IConsumerService _consumerService; + public IProducerService _producerService; + public string topic = "test-topic1"; + + [GlobalSetup] + public void Setup() + { + // 构建配置 + var config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + // 直接读取配置项 + var greeting = config["ServerTagName"]; + Console.WriteLine(greeting); // 输出: Hello, World! + // 创建服务容器 + var services = new ServiceCollection(); + // 注册 IConfiguration 实例 + services.AddSingleton(config); + + // 初始化日志 + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(config) // 从 appsettings.json 读取配置 + .CreateLogger(); + + // 配置日志系统 + services.AddLogging(logging => + { + logging.ClearProviders(); + logging.AddSerilog(); + }); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + // 构建ServiceProvider + _serviceProvider = services.BuildServiceProvider(); + + // 获取日志记录器工厂 + var loggerFactory = _serviceProvider.GetRequiredService(); + var logger = loggerFactory.CreateLogger(); + logger.LogInformation("程序启动"); + + var adminClientService = _serviceProvider.GetRequiredService(); + + + //await adminClientService.DeleteTopicAsync(topic); + // 创建 topic + adminClientService.CreateTopicAsync(topic, 3, 3).ConfigureAwait(false).GetAwaiter(); + + _consumerService = _serviceProvider.GetRequiredService(); + + _producerService = _serviceProvider.GetRequiredService(); + } + + [Benchmark] + public async Task UseAsync() + { + List tasks = new(); + for (int i = 0; i < N; ++i) + { + var task = _producerService.ProduceAsync(topic, i.ToString()); + tasks.Add(task); + } + await Task.WhenAll(tasks); + } + + [Benchmark] + public async Task UseLibrd() + { + List tasks = new(); + for (int i = 0; i < N; ++i) + { + var task = _producerService.ProduceAsync(topic, i.ToString(),null); + } + await Task.WhenAll(tasks); + } + } +} diff --git a/modules/JiShe.CollectBus.Kafka.Test/KafkaSubscribeTest.cs b/modules/JiShe.CollectBus.Kafka.Test/KafkaSubscribeTest.cs new file mode 100644 index 0000000..4c06e22 --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka.Test/KafkaSubscribeTest.cs @@ -0,0 +1,68 @@ +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.Kafka.Attributes; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Volo.Abp.Timing; + +namespace JiShe.CollectBus.Kafka.Test +{ + public class KafkaSubscribeTest: IKafkaSubscribe + { + [KafkaSubscribe(ProtocolConst.TESTTOPIC, EnableBatch=false,BatchSize=1000)] + + public async Task KafkaSubscribeAsync(object obj) + { + Console.WriteLine($"收到订阅消息: {JsonSerializer.Serialize(obj)}"); + return SubscribeAck.Success(); + } + + + [KafkaSubscribe(ProtocolConst.SubscriberLoginIssuedEventName)] + //[CapSubscribe(ProtocolConst.SubscriberLoginIssuedEventName)] + public async Task LoginIssuedEvent(IssuedEventMessage issuedEventMessage) + { + Console.WriteLine($"收到订阅消息: {JsonSerializer.Serialize(issuedEventMessage)}"); + return SubscribeAck.Success(); + } + + [KafkaSubscribe(ProtocolConst.SubscriberHeartbeatIssuedEventName)] + //[CapSubscribe(ProtocolConst.SubscriberHeartbeatIssuedEventName)] + public async Task HeartbeatIssuedEvent(IssuedEventMessage issuedEventMessage) + { + Console.WriteLine($"收到订阅消息: {JsonSerializer.Serialize(issuedEventMessage)}"); + return SubscribeAck.Success(); + } + + [KafkaSubscribe(ProtocolConst.SubscriberReceivedEventName)] + //[CapSubscribe(ProtocolConst.SubscriberReceivedEventName)] + public async Task ReceivedEvent(MessageReceived receivedMessage) + { + Console.WriteLine($"收到订阅消息: {JsonSerializer.Serialize(receivedMessage)}"); + return SubscribeAck.Success(); + } + + [KafkaSubscribe(ProtocolConst.SubscriberHeartbeatReceivedEventName)] + //[CapSubscribe(ProtocolConst.SubscriberHeartbeatReceivedEventName)] + public async Task ReceivedHeartbeatEvent(MessageReceivedHeartbeat receivedHeartbeatMessage) + { + Console.WriteLine($"收到订阅消息: {JsonSerializer.Serialize(receivedHeartbeatMessage)}"); + return SubscribeAck.Success(); + } + + [KafkaSubscribe(ProtocolConst.SubscriberLoginReceivedEventName)] + //[CapSubscribe(ProtocolConst.SubscriberLoginReceivedEventName)] + public async Task ReceivedLoginEvent(MessageReceivedLogin receivedLoginMessage) + { + Console.WriteLine($"收到订阅消息: {JsonSerializer.Serialize(receivedLoginMessage)}"); + return SubscribeAck.Success(); + } + } +} diff --git a/modules/JiShe.CollectBus.Kafka.Test/Lib/JiShe.CollectBus.Kafka.dll b/modules/JiShe.CollectBus.Kafka.Test/Lib/JiShe.CollectBus.Kafka.dll new file mode 100644 index 0000000000000000000000000000000000000000..7ca63d6d702394a85aa034669c75c10d3f975481 GIT binary patch literal 59904 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 literal 0 HcmV?d00001 diff --git a/modules/JiShe.CollectBus.Kafka.Test/Program.cs b/modules/JiShe.CollectBus.Kafka.Test/Program.cs new file mode 100644 index 0000000..a359e14 --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka.Test/Program.cs @@ -0,0 +1,172 @@ +// See https://aka.ms/new-console-template for more information +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Running; +using Confluent.Kafka; +using DeviceDetectorNET.Parser.Device; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Kafka; +using JiShe.CollectBus.Kafka.AdminClient; +using JiShe.CollectBus.Kafka.Consumer; +using JiShe.CollectBus.Kafka.Producer; +using JiShe.CollectBus.Kafka.Test; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Serilog; +using System.Diagnostics; +using System.Reflection; +using System.Reflection.PortableExecutable; +using System.Text.Json; + +#region 基准测试 +//var summary = BenchmarkRunner.Run(); +//Console.WriteLine("压测完成"); +//return; +#endregion 基准测试 + + +var host = Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + // 构建配置 + var config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + // 直接读取配置项 + var greeting = config["Kafka:ServerTagName"]; + Console.WriteLine(greeting); // 输出: Hello, World! + + + // 创建服务容器 + //var services = new ServiceCollection(); + // 注册 IConfiguration 实例 + services.AddSingleton(config); + + // 初始化日志 + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(config) // 从 appsettings.json 读取配置 + .CreateLogger(); + + // 配置日志系统 + services.AddLogging(logging => + { + logging.ClearProviders(); + logging.AddSerilog(); + }); + services.Configure(config.GetSection("Kafka")); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddTransient(); + + }) + .ConfigureConsoleAppBuilder(appBuilder => + { + + }) + .Build(); + + + await host.StartAsync(); + var appBuilder = host.Services.GetRequiredService(); + appBuilder.ApplicationServices.UseKafkaSubscribe(); + + +// 构建ServiceProvider +//var serviceProvider = services.BuildServiceProvider(); + +// 获取日志记录器工厂 +var loggerFactory = host.Services.GetRequiredService(); +var logger = loggerFactory.CreateLogger(); +logger.LogInformation("程序启动"); +var adminClientService = host.Services.GetRequiredService(); +var configuration = host.Services.GetRequiredService(); +string topic = "test-topic"; +//await adminClientService.DeleteTopicAsync(topic); +// 创建 topic +//await adminClientService.CreateTopicAsync(topic, configuration.GetValue(CommonConst.NumPartitions), 3); + +var consumerService = host.Services.GetRequiredService(); +//var kafkaOptions = host.Services.GetRequiredService>(); +//await consumerService.SubscribeAsync(topic, (message) => +//{ +// try +// { +// logger.LogInformation($"消费消息:{message}"); +// return Task.FromResult(true); + +// } +// catch (ConsumeException ex) +// { +// // 处理消费错误 +// logger.LogError($"kafka消费异常:{ex.Message}"); +// } +// return Task.FromResult(false); +//}, "default"); + +//Stopwatch stopwatch = Stopwatch.StartNew(); + +//for (int i = 0; i < 3; i++) +//{ +// await consumerService.SubscribeBatchAsync(topic, (message) => +// { +// try +// { +// int index = 0; +// logger.LogInformation($"消费消息_{index}消费总数:{message.Count()}:{JsonSerializer.Serialize(message)}"); +// return Task.FromResult(true); + +// } +// catch (ConsumeException ex) +// { +// // 处理消费错误 +// logger.LogError($"kafka消费异常:{ex.Message}"); +// } +// return Task.FromResult(false); +// }); +//} +//stopwatch.Stop(); +//Console.WriteLine($"耗时: {stopwatch.ElapsedMilliseconds} 毫秒,{stopwatch.ElapsedMilliseconds/1000} 秒"); + +var producerService = host.Services.GetRequiredService(); +//int num = 840; +//while (num <= 900) +//{ +// //await producerService.ProduceAsync(topic, new TestTopic { Topic = topic, Val = i }); +// await producerService.ProduceAsync(topic, num.ToString()); +// num++; +//} +await Task.Factory.StartNew(async() => { + int num = 0; + while (true) + { + //await producerService.ProduceAsync(topic, new TestTopic { Topic = topic, Val = i }); + await producerService.ProduceAsync(topic, num.ToString()); + num++; + } +}); +Console.WriteLine("\n按Esc键退出"); +while (true) +{ + var key = Console.ReadKey(intercept: true); // intercept:true 隐藏按键显示 + + if (key.Key == ConsoleKey.Escape) + { + await host.StopAsync(); + Console.WriteLine("\n程序已退出"); + break; + } +} +(host.Services as IDisposable)?.Dispose(); + + +public class TestTopic +{ + public string Topic { get; set; } + public int Val { get; set; } +} \ No newline at end of file diff --git a/modules/JiShe.CollectBus.Kafka.Test/appsettings.json b/modules/JiShe.CollectBus.Kafka.Test/appsettings.json new file mode 100644 index 0000000..b2579c6 --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka.Test/appsettings.json @@ -0,0 +1,180 @@ +{ + "Serilog": { + "Using": [ + "Serilog.Sinks.Console", + "Serilog.Sinks.File" + ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Volo.Abp": "Warning", + "Hangfire": "Warning", + "DotNetCore.CAP": "Warning", + "Serilog.AspNetCore": "Information", + "Microsoft.EntityFrameworkCore": "Warning", + "Microsoft.AspNetCore": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console" + }, + { + "Name": "File", + "Args": { + "path": "logs/logs-.txt", + "rollingInterval": "Day" + } + } + ] + }, + "App": { + "SelfUrl": "http://localhost:44315", + "CorsOrigins": "http://localhost:4200,http://localhost:3100" + }, + "ConnectionStrings": { + "Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000", + "Kafka": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092", + "PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False", + "EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False" + }, + "Redis": { + "Configuration": "192.168.1.9:6380,password=1q2w3e!@#,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", + "MaxPoolSize": "50", + "DefaultDB": "14", + "HangfireDB": "15" + }, + "Jwt": { + "Audience": "JiShe.CollectBus", + "SecurityKey": "dzehzRz9a8asdfasfdadfasdfasdfafsdadfasbasdf=", + "Issuer": "JiShe.CollectBus", + "ExpirationTime": 2 + }, + "HealthCheck": { + "IsEnable": true, + "MySql": { + "IsEnable": true + }, + "Pings": { + "IsEnable": true, + "Host": "https://www.baidu.com/", + "TimeOut": 5000 + } + }, + "SwaggerConfig": [ + { + "GroupName": "Basic", + "Title": "【后台管理】基础模块", + "Version": "V1" + }, + { + "GroupName": "Business", + "Title": "【后台管理】业务模块", + "Version": "V1" + } + ], + "Cap": { + "RabbitMq": { + "HostName": "118.190.144.92", + "UserName": "collectbus", + "Password": "123456", + "Port": 5672 + } + }, + "Kafka": { + "BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092", + "EnableFilter": true, + "EnableAuthorization": false, + "SecurityProtocol": "SaslPlaintext", + "SaslMechanism": "Plain", + "SaslUserName": "lixiao", + "SaslPassword": "lixiao1980", + "KafkaReplicationFactor": 3, + "NumPartitions": 1, + "ServerTagName": "JiSheCollectBus2" + //"Topic": { + // "ReplicationFactor": 3, + // "NumPartitions": 1000 + //} + }, + //"Kafka": { + // "Connections": { + // "Default": { + // "BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092" + // // "SecurityProtocol": "SASL_PLAINTEXT", + // // "SaslMechanism": "PLAIN", + // // "SaslUserName": "lixiao", + // // "SaslPassword": "lixiao1980", + // } + // }, + // "Consumer": { + // "GroupId": "JiShe.CollectBus" + // }, + // "Producer": { + // "MessageTimeoutMs": 6000, + // "Acks": -1 + // }, + // "Topic": { + // "ReplicationFactor": 3, + // "NumPartitions": 1000 + // }, + // "EventBus": { + // "GroupId": "JiShe.CollectBus", + // "TopicName": "DefaultTopicName" + // } + //}, + "IoTDBOptions": { + "UserName": "root", + "Password": "root", + "ClusterList": [ "192.168.1.9:6667" ], + "PoolSize": 2, + "DataBaseName": "energy", + "OpenDebugMode": true, + "UseTableSessionPoolByDefault": false + }, + "ServerTagName": "JiSheCollectBus3", + "Cassandra": { + "ReplicationStrategy": { + "Class": "NetworkTopologyStrategy", //策略为NetworkTopologyStrategy时才会有多个数据中心,SimpleStrategy用在只有一个数据中心的情况下 + "DataCenters": [ + { + "Name": "dc1", + "ReplicationFactor": 3 + } + ] + }, + "Nodes": [ + { + "Host": "192.168.1.9", + "Port": 9042, + "DataCenter": "dc1", + "Rack": "RAC1" + }, + { + "Host": "192.168.1.9", + "Port": 9043, + "DataCenter": "dc1", + "Rack": "RAC2" + } + ], + "Username": "admin", + "Password": "lixiao1980", + "Keyspace": "jishecollectbus", + "ConsistencyLevel": "Quorum", + "PoolingOptions": { + "CoreConnectionsPerHost": 4, + "MaxConnectionsPerHost": 8, + "MaxRequestsPerConnection": 2000 + }, + "SocketOptions": { + "ConnectTimeoutMillis": 10000, + "ReadTimeoutMillis": 20000 + }, + "QueryOptions": { + "ConsistencyLevel": "Quorum", + "SerialConsistencyLevel": "Serial", + "DefaultIdempotence": true + } + } +} \ No newline at end of file -- 2.47.2