diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/Delete-BIN-OBJ-Folders.bat b/Delete-BIN-OBJ-Folders.bat new file mode 100644 index 0000000..02cfaa5 --- /dev/null +++ b/Delete-BIN-OBJ-Folders.bat @@ -0,0 +1,20 @@ +@ECHO off +cls + +ECHO Deleting all BIN and OBJ folders... +ECHO. + +FOR /d /r . %%d in (bin,obj) DO ( + IF EXIST "%%d" ( + ECHO %%d | FIND /I "\node_modules\" > Nul && ( + ECHO.Skipping: %%d + ) || ( + ECHO.Deleting: %%d + rd /s/q "%%d" + ) + ) +) + +ECHO. +ECHO.BIN and OBJ folders have been successfully deleted. Press any key to exit. +pause > nul \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d4999c8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,75 @@ +# FROM mcr.microsoft.com/dotnet/aspnet:8.0 + +# # 创建目录 +# RUN mkdir /app + +# COPY publish /app + + + +# # 设置工作目录 +# WORKDIR /app + +# # 暴露80端口 +# EXPOSE 80 +# # 设置时区 .net6 才有这个问题 +# ENV TZ=Asia/Shanghai + +# # 设置环境变量 +# ENV ASPNETCORE_ENVIRONMENT=Production + +# ENTRYPOINT ["dotnet", "JiShe.IOT.HttpApi.Host.dll"] + + + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 +ENV TZ=Asia/Shanghai +ENV ASPNETCORE_ENVIRONMENT=Production + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src +COPY ["JiShe.CollectBus.sln", "."] +COPY ["common.props", "."] +COPY ["NuGet.Config", "."] +COPY ["web/", "web/"] +COPY ["modules/", "modules/"] +COPY ["services/", "services/"] +COPY ["shared/", "shared/"] +COPY ["protocols/", "protocols/"] + +# 恢复项目依赖 +RUN dotnet restore "JiShe.CollectBus.sln" + +# 构建项目 +WORKDIR "/src/web/JiShe.CollectBus.Host" +RUN dotnet build "JiShe.CollectBus.Host.csproj" -c Release -o /app/build + +# 发布项目 +FROM build AS publish +RUN dotnet publish "JiShe.CollectBus.Host.csproj" -c Release -o /app/publish /p:UseAppHost=false + +# 创建最终镜像 +FROM base AS final +WORKDIR /app + +# 创建Plugins目录 +RUN mkdir -p /app/Plugins + +# 复制发布内容 +COPY --from=publish /app/publish . + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:80/health || exit 1 + +# 设置入口点 +ENTRYPOINT ["dotnet", "JiShe.CollectBus.Host.dll"] + +# 启动命令 +# 可选:添加命令行参数 +# CMD ["--urls", "http://+:80"] + + diff --git a/JiShe.CollectBus.Protocols.sln b/JiShe.CollectBus.Protocols.sln new file mode 100644 index 0000000..55e4109 --- /dev/null +++ b/JiShe.CollectBus.Protocols.sln @@ -0,0 +1,179 @@ + +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", "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", "services\JiShe.CollectBus.Domain\JiShe.CollectBus.Domain.csproj", "{F2840BC7-0188-4606-9126-DADD0F5ABF7A}" +EndProject +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", "services\JiShe.CollectBus.Application\JiShe.CollectBus.Application.csproj", "{78040F9E-3501-4A40-82DF-00A597710F35}" +EndProject +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.HttpApi", "web\JiShe.CollectBus.HttpApi\JiShe.CollectBus.HttpApi.csproj", "{077AA5F8-8B61-420C-A6B5-0150A66FDB34}" +EndProject +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.Common", "shared\JiShe.CollectBus.Common\JiShe.CollectBus.Common.csproj", "{AD2F1928-4411-4511-B564-5FB996EC08B9}" +EndProject +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.FreeSql", "modules\JiShe.CollectBus.FreeSql\JiShe.CollectBus.FreeSql.csproj", "{FE0457D9-4038-4A17-8808-DCAD06CFC0A0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.FreeRedis", "modules\JiShe.CollectBus.FreeRedis\JiShe.CollectBus.FreeRedis.csproj", "{C06C4082-638F-2996-5FED-7784475766C1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Kafka", "modules\JiShe.CollectBus.Kafka\JiShe.CollectBus.Kafka.csproj", "{F0288175-F0EC-48BD-945F-CF1512850943}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.IoTDB", "modules\JiShe.CollectBus.IoTDB\JiShe.CollectBus.IoTDB.csproj", "{A3F3C092-0A25-450B-BF6A-5983163CBEF5}" +EndProject +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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Cassandra", "modules\JiShe.CollectBus.Cassandra\JiShe.CollectBus.Cassandra.csproj", "{443B4549-0AC0-4493-8F3E-49C83225DD76}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1.Web", "1.Web", "{A02F7D8A-04DC-44D6-94D4-3F65712D6B94}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.Modules", "4.Modules", "{2E0FE301-34C3-4561-9CAE-C7A9E65AEE59}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "6.Protocols", "6.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}") = "3.Shared", "3.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 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.T1882018", "protocols\JiShe.CollectBus.Protocol.T1882018\JiShe.CollectBus.Protocol.T1882018.csproj", "{430D298B-377E-49B8-83AA-ADC7C0EBDB0F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.T37612012", "protocols\JiShe.CollectBus.Protocol.T37612012\JiShe.CollectBus.Protocol.T37612012.csproj", "{8A61DF78-069B-40B5-8811-614E2960443E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol", "protocols\JiShe.CollectBus.Protocol\JiShe.CollectBus.Protocol.csproj", "{E27377CC-E2D3-4237-060F-96EA214D3129}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.T6452007", "protocols\JiShe.CollectBus.Protocol.T6452007\JiShe.CollectBus.Protocol.T6452007.csproj", "{75B7D419-C261-577D-58D6-AA3ACED9129F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "0.Docs", "0.Docs", "{D8346C4C-55B8-43E8-A6B8-E59D56FE6D92}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + Dockerfile = Dockerfile + NuGet.Config = NuGet.Config + readme.md = readme.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D64C1577-4929-4B60-939E-96DE1534891A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D64C1577-4929-4B60-939E-96DE1534891A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D64C1577-4929-4B60-939E-96DE1534891A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D64C1577-4929-4B60-939E-96DE1534891A}.Release|Any CPU.Build.0 = Release|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Release|Any CPU.Build.0 = Release|Any CPU + {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Release|Any CPU.Build.0 = Release|Any CPU + {78040F9E-3501-4A40-82DF-00A597710F35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78040F9E-3501-4A40-82DF-00A597710F35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78040F9E-3501-4A40-82DF-00A597710F35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78040F9E-3501-4A40-82DF-00A597710F35}.Release|Any CPU.Build.0 = Release|Any CPU + {F1C58097-4C08-4D88-8976-6B3389391481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1C58097-4C08-4D88-8976-6B3389391481}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1C58097-4C08-4D88-8976-6B3389391481}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1C58097-4C08-4D88-8976-6B3389391481}.Release|Any CPU.Build.0 = Release|Any CPU + {077AA5F8-8B61-420C-A6B5-0150A66FDB34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {077AA5F8-8B61-420C-A6B5-0150A66FDB34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {077AA5F8-8B61-420C-A6B5-0150A66FDB34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {077AA5F8-8B61-420C-A6B5-0150A66FDB34}.Release|Any CPU.Build.0 = Release|Any CPU + {35829A15-4127-4F69-8BDE-9405DEAACA9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35829A15-4127-4F69-8BDE-9405DEAACA9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35829A15-4127-4F69-8BDE-9405DEAACA9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35829A15-4127-4F69-8BDE-9405DEAACA9A}.Release|Any CPU.Build.0 = Release|Any CPU + {AD2F1928-4411-4511-B564-5FB996EC08B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD2F1928-4411-4511-B564-5FB996EC08B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD2F1928-4411-4511-B564-5FB996EC08B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD2F1928-4411-4511-B564-5FB996EC08B9}.Release|Any CPU.Build.0 = Release|Any CPU + {8BA01C3D-297D-42DF-BD63-EF07202A0A67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BA01C3D-297D-42DF-BD63-EF07202A0A67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BA01C3D-297D-42DF-BD63-EF07202A0A67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BA01C3D-297D-42DF-BD63-EF07202A0A67}.Release|Any CPU.Build.0 = Release|Any CPU + {FE0457D9-4038-4A17-8808-DCAD06CFC0A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 + {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 + {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 + {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 + {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 + {430D298B-377E-49B8-83AA-ADC7C0EBDB0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {430D298B-377E-49B8-83AA-ADC7C0EBDB0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {430D298B-377E-49B8-83AA-ADC7C0EBDB0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {430D298B-377E-49B8-83AA-ADC7C0EBDB0F}.Release|Any CPU.Build.0 = Release|Any CPU + {8A61DF78-069B-40B5-8811-614E2960443E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A61DF78-069B-40B5-8811-614E2960443E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A61DF78-069B-40B5-8811-614E2960443E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A61DF78-069B-40B5-8811-614E2960443E}.Release|Any CPU.Build.0 = Release|Any CPU + {E27377CC-E2D3-4237-060F-96EA214D3129}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E27377CC-E2D3-4237-060F-96EA214D3129}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E27377CC-E2D3-4237-060F-96EA214D3129}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E27377CC-E2D3-4237-060F-96EA214D3129}.Release|Any CPU.Build.0 = Release|Any CPU + {75B7D419-C261-577D-58D6-AA3ACED9129F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75B7D419-C261-577D-58D6-AA3ACED9129F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75B7D419-C261-577D-58D6-AA3ACED9129F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75B7D419-C261-577D-58D6-AA3ACED9129F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {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} + {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} + {6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59} + {430D298B-377E-49B8-83AA-ADC7C0EBDB0F} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC} + {8A61DF78-069B-40B5-8811-614E2960443E} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC} + {E27377CC-E2D3-4237-060F-96EA214D3129} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC} + {75B7D419-C261-577D-58D6-AA3ACED9129F} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} + EndGlobalSection +EndGlobal diff --git a/JiShe.CollectBus.sln b/JiShe.CollectBus.sln index d4e6591..03bd207 100644 --- a/JiShe.CollectBus.sln +++ b/JiShe.CollectBus.sln @@ -19,10 +19,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Host", "we EndProject 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.Protocol", "protocols\JiShe.CollectBus.Protocol\JiShe.CollectBus.Protocol.csproj", "{C62EFF95-5C32-435F-BD78-6977E828F894}" -EndProject -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.DbMigrator", "services\JiShe.CollectBus.DbMigrator\JiShe.CollectBus.DbMigrator.csproj", "{8BA01C3D-297D-42DF-BD63-EF07202A0A67}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.FreeSql", "modules\JiShe.CollectBus.FreeSql\JiShe.CollectBus.FreeSql.csproj", "{FE0457D9-4038-4A17-8808-DCAD06CFC0A0}" @@ -41,11 +37,34 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1.Web", "1.Web", "{A02F7D8A EndProject 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}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "6.Protocols", "6.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}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3.Shared", "3.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 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5.External", "5.External", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.T1882018", "protocols\JiShe.CollectBus.Protocol.T1882018\JiShe.CollectBus.Protocol.T1882018.csproj", "{430D298B-377E-49B8-83AA-ADC7C0EBDB0F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.T37612012", "protocols\JiShe.CollectBus.Protocol.T37612012\JiShe.CollectBus.Protocol.T37612012.csproj", "{8A61DF78-069B-40B5-8811-614E2960443E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol", "protocols\JiShe.CollectBus.Protocol\JiShe.CollectBus.Protocol.csproj", "{E27377CC-E2D3-4237-060F-96EA214D3129}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Protocol.T6452007", "protocols\JiShe.CollectBus.Protocol.T6452007\JiShe.CollectBus.Protocol.T6452007.csproj", "{75B7D419-C261-577D-58D6-AA3ACED9129F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.PluginFileWatcher", "external\JiShe.CollectBus.PluginFileWatcher\JiShe.CollectBus.PluginFileWatcher.csproj", "{0F67A493-A4DF-550E-AB4D-95F55144C706}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "0.Docs", "0.Docs", "{D8346C4C-55B8-43E8-A6B8-E59D56FE6D92}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + Dockerfile = Dockerfile + NuGet.Config = NuGet.Config + PackageAndPublish.bat = PackageAndPublish.bat + readme.md = readme.md + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -85,14 +104,6 @@ Global {AD2F1928-4411-4511-B564-5FB996EC08B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {AD2F1928-4411-4511-B564-5FB996EC08B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD2F1928-4411-4511-B564-5FB996EC08B9}.Release|Any CPU.Build.0 = Release|Any CPU - {C62EFF95-5C32-435F-BD78-6977E828F894}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C62EFF95-5C32-435F-BD78-6977E828F894}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C62EFF95-5C32-435F-BD78-6977E828F894}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C62EFF95-5C32-435F-BD78-6977E828F894}.Release|Any CPU.Build.0 = Release|Any CPU - {38C1808B-009A-418B-B17B-AB3626341B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {38C1808B-009A-418B-B17B-AB3626341B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {38C1808B-009A-418B-B17B-AB3626341B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {38C1808B-009A-418B-B17B-AB3626341B5D}.Release|Any CPU.Build.0 = Release|Any CPU {8BA01C3D-297D-42DF-BD63-EF07202A0A67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8BA01C3D-297D-42DF-BD63-EF07202A0A67}.Debug|Any CPU.Build.0 = Debug|Any CPU {8BA01C3D-297D-42DF-BD63-EF07202A0A67}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -121,6 +132,30 @@ 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 + {430D298B-377E-49B8-83AA-ADC7C0EBDB0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {430D298B-377E-49B8-83AA-ADC7C0EBDB0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {430D298B-377E-49B8-83AA-ADC7C0EBDB0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {430D298B-377E-49B8-83AA-ADC7C0EBDB0F}.Release|Any CPU.Build.0 = Release|Any CPU + {8A61DF78-069B-40B5-8811-614E2960443E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A61DF78-069B-40B5-8811-614E2960443E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A61DF78-069B-40B5-8811-614E2960443E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A61DF78-069B-40B5-8811-614E2960443E}.Release|Any CPU.Build.0 = Release|Any CPU + {E27377CC-E2D3-4237-060F-96EA214D3129}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E27377CC-E2D3-4237-060F-96EA214D3129}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E27377CC-E2D3-4237-060F-96EA214D3129}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E27377CC-E2D3-4237-060F-96EA214D3129}.Release|Any CPU.Build.0 = Release|Any CPU + {75B7D419-C261-577D-58D6-AA3ACED9129F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75B7D419-C261-577D-58D6-AA3ACED9129F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75B7D419-C261-577D-58D6-AA3ACED9129F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75B7D419-C261-577D-58D6-AA3ACED9129F}.Release|Any CPU.Build.0 = Release|Any CPU + {0F67A493-A4DF-550E-AB4D-95F55144C706}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F67A493-A4DF-550E-AB4D-95F55144C706}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F67A493-A4DF-550E-AB4D-95F55144C706}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F67A493-A4DF-550E-AB4D-95F55144C706}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -134,8 +169,6 @@ Global {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} @@ -143,6 +176,12 @@ 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} + {430D298B-377E-49B8-83AA-ADC7C0EBDB0F} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC} + {8A61DF78-069B-40B5-8811-614E2960443E} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC} + {E27377CC-E2D3-4237-060F-96EA214D3129} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC} + {75B7D419-C261-577D-58D6-AA3ACED9129F} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC} + {0F67A493-A4DF-550E-AB4D-95F55144C706} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..44f8e7f --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/PackageAndPublish.bat b/PackageAndPublish.bat new file mode 100644 index 0000000..0bb73cd --- /dev/null +++ b/PackageAndPublish.bat @@ -0,0 +1,77 @@ +@echo off +setlocal enabledelayedexpansion + +set VERSION=1.0.0 +set CONFIGURATION=Release +set OUTPUT_DIR=%~dp0\nupkgs +set API_KEY=your-nuget-api-key + +REM 创建输出目录 +if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%" + +REM 清理 +echo 清理解决方案... +dotnet clean JiShe.CollectBus.sln -c %CONFIGURATION% + +REM 删除之前的包 +echo 删除之前的包... +if exist "%OUTPUT_DIR%\*.nupkg" del /q "%OUTPUT_DIR%\*.nupkg" + +REM 打包项目 +echo 开始打包项目... + +REM 打包 Protocol 项目 +echo 打包 Protocol 项目... +call :PackProject protocols\JiShe.CollectBus.Protocol\JiShe.CollectBus.Protocol.csproj +call :PackProject protocols\JiShe.CollectBus.Protocol.Contracts\JiShe.CollectBus.Protocol.Contracts.csproj +call :PackProject protocols\JiShe.CollectBus.Protocol.T1882018\JiShe.CollectBus.Protocol.T1882018.csproj +call :PackProject protocols\JiShe.CollectBus.Protocol.T37612012\JiShe.CollectBus.Protocol.T37612012.csproj +call :PackProject protocols\JiShe.CollectBus.Protocol.T6452007\JiShe.CollectBus.Protocol.T6452007.csproj + +REM 打包 Modules 项目 +echo 打包 Modules 项目... +call :PackProject modules\JiShe.CollectBus.FreeRedis\JiShe.CollectBus.FreeRedis.csproj +call :PackProject modules\JiShe.CollectBus.Kafka\JiShe.CollectBus.Kafka.csproj +call :PackProject modules\JiShe.CollectBus.IoTDB\JiShe.CollectBus.IoTDB.csproj +call :PackProject modules\JiShe.CollectBus.MongoDB\JiShe.CollectBus.MongoDB.csproj +call :PackProject modules\JiShe.CollectBus.FreeSql\JiShe.CollectBus.FreeSql.csproj +call :PackProject modules\JiShe.CollectBus.Cassandra\JiShe.CollectBus.Cassandra.csproj +call :PackProject modules\JiShe.CollectBusMultiProcessing\JiShe.CollectBusMultiProcessing.csproj + +REM 打包 Shared 项目 +echo 打包 Shared 项目... +call :PackProject shared\JiShe.CollectBus.Common\JiShe.CollectBus.Common.csproj +call :PackProject shared\JiShe.CollectBus.Domain.Shared\JiShe.CollectBus.Domain.Shared.csproj + +REM 打包 Services 项目 +echo 打包 Services 项目... +call :PackProject services\JiShe.CollectBus.Domain\JiShe.CollectBus.Domain.csproj +call :PackProject services\JiShe.CollectBus.Application.Contracts\JiShe.CollectBus.Application.Contracts.csproj +call :PackProject services\JiShe.CollectBus.Application\JiShe.CollectBus.Application.csproj +call :PackProject services\JiShe.CollectBus.EntityFrameworkCore\JiShe.CollectBus.EntityFrameworkCore.csproj + +echo. +echo 是否要发布包到 NuGet? (Y/N) +set /p PUBLISH_CHOICE= + +if /i "%PUBLISH_CHOICE%"=="Y" ( + echo 开始发布包... + for %%f in ("%OUTPUT_DIR%\*.nupkg") do ( + echo 发布: %%f + dotnet nuget push "%%f" --api-key %API_KEY% --source https://api.nuget.org/v3/index.json --skip-duplicate + ) + echo 所有包已发布完成! +) else ( + echo 跳过发布操作。所有包都在 %OUTPUT_DIR% 目录中。 +) + +goto :eof + +:PackProject +if exist "%~1" ( + echo 打包: %~1 + dotnet pack "%~1" -c %CONFIGURATION% --no-build --include-symbols -p:SymbolPackageFormat=snupkg -p:PackageVersion=%VERSION% -o "%OUTPUT_DIR%" +) else ( + echo 警告: 项目不存在 - %~1 +) +goto :eof \ No newline at end of file diff --git a/external/JiShe.CollectBus.PluginFileWatcher/Config.cs b/external/JiShe.CollectBus.PluginFileWatcher/Config.cs new file mode 100644 index 0000000..8de55f6 --- /dev/null +++ b/external/JiShe.CollectBus.PluginFileWatcher/Config.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; + +namespace JiShe.CollectBus.PluginFileWatcher +{ + /// + /// 文件监控程序的配置类 + /// + public class FileMonitorConfig + { + /// + /// 基本配置 + /// + public GeneralConfig General { get; set; } = new GeneralConfig(); + + /// + /// 文件过滤配置 + /// + public FileFiltersConfig FileFilters { get; set; } = new FileFiltersConfig(); + + /// + /// 性能相关配置 + /// + public PerformanceConfig Performance { get; set; } = new PerformanceConfig(); + + /// + /// 健壮性相关配置 + /// + public RobustnessConfig Robustness { get; set; } = new RobustnessConfig(); + + /// + /// 事件存储和回放配置 + /// + public EventStorageConfig EventStorage { get; set; } = new EventStorageConfig(); + + /// + /// 文件系统通知过滤器配置 + /// + public List NotifyFilters { get; set; } = new List(); + + /// + /// 日志配置 + /// + public LoggingConfig Logging { get; set; } = new LoggingConfig(); + } + + /// + /// 常规配置 + /// + public class GeneralConfig + { + /// + /// 是否启用文件过滤 + /// + public bool EnableFileFiltering { get; set; } = true; + + /// + /// 内存监控间隔(分钟) + /// + public int MemoryMonitorIntervalMinutes { get; set; } = 1; + + /// + /// 默认监控路径 + /// + public string DefaultMonitorPath { get; set; } = string.Empty; + } + + /// + /// 文件过滤配置 + /// + public class FileFiltersConfig + { + /// + /// 允许监控的文件扩展名 + /// + public string[] AllowedExtensions { get; set; } = new[] { ".dll" }; + + /// + /// 排除的目录 + /// + public string[] ExcludedDirectories { get; set; } = new[] { "bin", "obj", "node_modules" }; + + /// + /// 是否包含子目录 + /// + public bool IncludeSubdirectories { get; set; } = true; + } + + /// + /// 性能相关配置 + /// + public class PerformanceConfig + { + /// + /// 内存清理阈值(事件数) + /// + public int MemoryCleanupThreshold { get; set; } = 5000; + + /// + /// 通道容量 + /// + public int ChannelCapacity { get; set; } = 1000; + + /// + /// 事件去抖时间(秒) + /// + public int EventDebounceTimeSeconds { get; set; } = 3; + + /// + /// 最大字典大小 + /// + public int MaxDictionarySize { get; set; } = 10000; + + /// + /// 清理间隔(秒) + /// + public int CleanupIntervalSeconds { get; set; } = 5; + + /// + /// 处理延迟(毫秒) + /// + public int ProcessingDelayMs { get; set; } = 5; + } + + /// + /// 健壮性相关配置 + /// + public class RobustnessConfig + { + /// + /// 是否启用自动恢复机制 + /// + public bool EnableAutoRecovery { get; set; } = true; + + /// + /// 监控器健康检查间隔(秒) + /// + public int WatcherHealthCheckIntervalSeconds { get; set; } = 30; + + /// + /// 监控器无响应超时时间(秒) + /// + public int WatcherTimeoutSeconds { get; set; } = 60; + + /// + /// 监控器重启尝试最大次数 + /// + public int MaxRestartAttempts { get; set; } = 3; + + /// + /// 重启尝试之间的延迟(秒) + /// + public int RestartDelaySeconds { get; set; } = 5; + + /// + /// 是否启用文件锁检测 + /// + public bool EnableFileLockDetection { get; set; } = true; + + /// + /// 对锁定文件的处理策略: Skip(跳过), Retry(重试), Log(仅记录) + /// + public string LockedFileStrategy { get; set; } = "Retry"; + + /// + /// 文件锁定重试次数 + /// + public int FileLockRetryCount { get; set; } = 3; + + /// + /// 文件锁定重试间隔(毫秒) + /// + public int FileLockRetryDelayMs { get; set; } = 500; + } + + /// + /// 事件存储和回放配置 + /// + public class EventStorageConfig + { + /// + /// 是否启用事件存储 + /// + public bool EnableEventStorage { get; set; } = true; + + /// + /// 存储类型:SQLite 或 File + /// + public string StorageType { get; set; } = "SQLite"; + + /// + /// 事件存储目录 + /// + public string StorageDirectory { get; set; } = "D:/EventLogs"; + + /// + /// SQLite数据库文件路径 + /// + public string DatabasePath { get; set; } = "D:/EventLogs/events.db"; + + /// + /// SQLite连接字符串 + /// + public string ConnectionString { get; set; } = "Data Source=D:/EventLogs/events.db"; + + /// + /// 数据库命令超时(秒) + /// + public int CommandTimeout { get; set; } = 30; + + /// + /// 事件日志文件名格式 (使用DateTime.ToString格式) + /// + public string LogFileNameFormat { get; set; } = "FileEvents_{0:yyyy-MM-dd}.json"; + + /// + /// 存储间隔(秒),多久将缓存的事件写入一次存储 + /// + public int StorageIntervalSeconds { get; set; } = 60; + + /// + /// 事件批量写入大小,达到此数量时立即写入存储 + /// + public int BatchSize { get; set; } = 100; + + /// + /// 最大保留事件记录条数 + /// + public int MaxEventRecords { get; set; } = 100000; + + /// + /// 数据保留天数 + /// + public int DataRetentionDays { get; set; } = 30; + + /// + /// 最大保留日志文件数 + /// + public int MaxLogFiles { get; set; } = 30; + + /// + /// 是否压缩存储的事件日志 + /// + public bool CompressLogFiles { get; set; } = true; + + /// + /// 是否可以回放事件 + /// + public bool EnableEventReplay { get; set; } = true; + + /// + /// 回放时间间隔(毫秒) + /// + public int ReplayIntervalMs { get; set; } = 100; + + /// + /// 回放速度倍率,大于1加速,小于1减速 + /// + public double ReplaySpeedFactor { get; set; } = 1.0; + } + + /// + /// 日志相关配置 + /// + public class LoggingConfig + { + /// + /// 日志级别:Verbose、Debug、Information、Warning、Error、Fatal + /// + public string LogLevel { get; set; } = "Information"; + + /// + /// 是否记录文件事件处理详情 + /// + public bool LogFileEventDetails { get; set; } = false; + + /// + /// 日志文件保留天数 + /// + public int RetainedLogDays { get; set; } = 30; + + /// + /// 日志文件目录 + /// + public string LogDirectory { get; set; } = "Logs"; + } +} \ No newline at end of file diff --git a/external/JiShe.CollectBus.PluginFileWatcher/DbUtility.cs b/external/JiShe.CollectBus.PluginFileWatcher/DbUtility.cs new file mode 100644 index 0000000..c2dd7a5 --- /dev/null +++ b/external/JiShe.CollectBus.PluginFileWatcher/DbUtility.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace JiShe.CollectBus.PluginFileWatcher +{ + /// + /// 数据库操作工具类,用于命令行测试数据库功能 + /// + public class DbUtility + { + private readonly EventDatabaseManager _dbManager; + private readonly ILogger _logger; + private readonly FileMonitorConfig _config; + + /// + /// 初始化数据库工具类 + /// + /// 配置文件路径 + public DbUtility(string configPath = "appsettings.json") + { + // 从配置文件加载配置 + var configuration = new ConfigurationBuilder() + .AddJsonFile(configPath, optional: false, reloadOnChange: true) + .Build(); + + // 初始化日志 + var serilogLogger = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .CreateLogger(); + + // 将Serilog适配为Microsoft.Extensions.Logging.ILogger + _logger = new SerilogLoggerFactory(serilogLogger).CreateLogger("DbUtility"); + + // 创建配置对象 + _config = new FileMonitorConfig(); + configuration.GetSection("FileMonitor").Bind(_config); + + // 确保SQLite存储已启用 + _config.EventStorage.EnableEventStorage = true; + _config.EventStorage.StorageType = "SQLite"; + + // 创建数据库管理器 + _dbManager = new EventDatabaseManager(_config, _logger); + } + + /// + /// 执行数据库维护操作 + /// + /// 命令行参数 + public async Task ExecuteAsync(string[] args) + { + if (args.Length == 0) + { + PrintUsage(); + return; + } + + string command = args[0].ToLower(); + + switch (command) + { + case "stats": + await ShowStatisticsAsync(); + break; + + case "cleanup": + int days = args.Length > 1 && int.TryParse(args[1], out int d) ? d : _config.EventStorage.DataRetentionDays; + await _dbManager.CleanupOldDataAsync(days); + Console.WriteLine($"已清理 {days} 天前的旧数据"); + break; + + case "query": + await QueryEventsAsync(args); + break; + + case "test": + await GenerateTestDataAsync(args); + break; + + default: + Console.WriteLine($"未知命令: {command}"); + PrintUsage(); + break; + } + } + + /// + /// 显示帮助信息 + /// + private void PrintUsage() + { + Console.WriteLine("数据库工具使用方法:"); + Console.WriteLine(" stats - 显示数据库统计信息"); + Console.WriteLine(" cleanup [days] - 清理指定天数之前的数据(默认使用配置值)"); + Console.WriteLine(" query [limit] - 查询最近的事件(默认10条)"); + Console.WriteLine(" query type:X ext:Y - 按类型和扩展名查询事件"); + Console.WriteLine(" test [count] - 生成测试数据(默认100条)"); + } + + /// + /// 显示数据库统计信息 + /// + private async Task ShowStatisticsAsync() + { + try + { + var stats = await _dbManager.GetDatabaseStatsAsync(); + + Console.WriteLine("数据库统计信息:"); + Console.WriteLine($"事件总数: {stats.TotalEvents}"); + Console.WriteLine($"最早事件时间: {stats.OldestEventTime?.ToLocalTime()}"); + Console.WriteLine($"最新事件时间: {stats.NewestEventTime?.ToLocalTime()}"); + + Console.WriteLine("\n事件类型分布:"); + if (stats.EventTypeCounts != null) + { + foreach (var item in stats.EventTypeCounts) + { + Console.WriteLine($" {item.Key}: {item.Value}"); + } + } + + Console.WriteLine("\n扩展名分布 (Top 10):"); + if (stats.TopExtensions != null) + { + foreach (var item in stats.TopExtensions) + { + Console.WriteLine($" {item.Key}: {item.Value}"); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"获取统计信息出错: {ex.Message}"); + _logger.LogError(ex, "获取数据库统计信息时发生错误"); + } + } + + /// + /// 查询事件 + /// + private async Task QueryEventsAsync(string[] args) + { + try + { + var queryParams = new EventQueryParams + { + PageSize = 10, + PageIndex = 0 + }; + + // 解析查询参数 + if (args.Length > 1) + { + foreach (var arg in args.Skip(1)) + { + if (int.TryParse(arg, out int limit)) + { + queryParams.PageSize = limit; + } + else if (arg.StartsWith("type:", StringComparison.OrdinalIgnoreCase)) + { + string typeValue = arg.Substring(5); + if (Enum.TryParse(typeValue, true, out var eventType)) + { + queryParams.EventType = eventType; + } + } + else if (arg.StartsWith("ext:", StringComparison.OrdinalIgnoreCase)) + { + queryParams.ExtensionFilter = arg.Substring(4); + if (!queryParams.ExtensionFilter.StartsWith(".")) + { + queryParams.ExtensionFilter = "." + queryParams.ExtensionFilter; + } + } + else if (arg.StartsWith("path:", StringComparison.OrdinalIgnoreCase)) + { + queryParams.PathFilter = arg.Substring(5); + } + } + } + + // 执行查询 + var result = await _dbManager.QueryEventsAsync(queryParams); + + Console.WriteLine($"查询结果 (总数: {result.TotalCount}):"); + foreach (var evt in result.Events) + { + string typeStr = evt.EventType.ToString(); + string timestamp = evt.Timestamp.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"); + Console.WriteLine($"[{timestamp}] {typeStr,-10} {evt.FileName} ({evt.Extension})"); + } + + if (result.HasMore) + { + Console.WriteLine($"... 还有更多结果,共 {result.TotalCount} 条"); + } + } + catch (Exception ex) + { + Console.WriteLine($"查询事件出错: {ex.Message}"); + _logger.LogError(ex, "查询事件时发生错误"); + } + } + + /// + /// 生成测试数据 + /// + private async Task GenerateTestDataAsync(string[] args) + { + try + { + int count = args.Length > 1 && int.TryParse(args[1], out int c) ? c : 100; + + var events = new List(); + var rnd = new Random(); + string[] extensions = { ".dll", ".exe", ".txt", ".cs", ".xml", ".json", ".png", ".jpg" }; + string[] directories = { "C:\\Temp", "D:\\Work", "C:\\Program Files", "D:\\Projects", "E:\\Data" }; + + DateTime startTime = DateTime.Now.AddHours(-24); + + for (int i = 0; i < count; i++) + { + var eventType = (FileEventType)rnd.Next(0, 5); + var ext = extensions[rnd.Next(extensions.Length)]; + var dir = directories[rnd.Next(directories.Length)]; + var fileName = $"TestFile_{i:D5}{ext}"; + var timestamp = startTime.AddMinutes(i); + + var fileEvent = new FileEvent + { + Id = Guid.NewGuid(), + Timestamp = timestamp, + EventType = eventType, + FullPath = $"{dir}\\{fileName}", + FileName = fileName, + Directory = dir, + Extension = ext, + FileSize = rnd.Next(1024, 1024 * 1024) + }; + + // 如果是重命名事件,添加旧文件名 + if (eventType == FileEventType.Renamed) + { + fileEvent.OldFileName = $"OldFile_{i:D5}{ext}"; + fileEvent.OldFullPath = $"{dir}\\{fileEvent.OldFileName}"; + } + + // 添加一些元数据 + fileEvent.Metadata["CreationTime"] = timestamp.AddMinutes(-rnd.Next(1, 60)).ToString("o"); + fileEvent.Metadata["LastWriteTime"] = timestamp.ToString("o"); + fileEvent.Metadata["IsReadOnly"] = (rnd.Next(10) < 2).ToString(); + fileEvent.Metadata["TestData"] = $"测试数据 {i}"; + + events.Add(fileEvent); + } + + Console.WriteLine($"正在生成 {count} 条测试数据..."); + await _dbManager.SaveEventsAsync(events); + Console.WriteLine("测试数据生成完成!"); + } + catch (Exception ex) + { + Console.WriteLine($"生成测试数据出错: {ex.Message}"); + _logger.LogError(ex, "生成测试数据时发生错误"); + } + } + } +} \ No newline at end of file diff --git a/external/JiShe.CollectBus.PluginFileWatcher/Dockerfile b/external/JiShe.CollectBus.PluginFileWatcher/Dockerfile new file mode 100644 index 0000000..88a0552 --- /dev/null +++ b/external/JiShe.CollectBus.PluginFileWatcher/Dockerfile @@ -0,0 +1,28 @@ +# 请参阅 https://aka.ms/customizecontainer 以了解如何自定义调试容器,以及 Visual Studio 如何使用此 Dockerfile 生成映像以更快地进行调试。 + +# 此阶段用于在快速模式(默认为调试配置)下从 VS 运行时 +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base +USER $APP_UID +WORKDIR /app + + +# 此阶段用于生成服务项目 +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["external/JiShe.CollectBus.PluginFileWatcher/JiShe.CollectBus.PluginFileWatcher.csproj", "external/JiShe.CollectBus.PluginFileWatcher/"] +RUN dotnet restore "./external/JiShe.CollectBus.PluginFileWatcher/JiShe.CollectBus.PluginFileWatcher.csproj" +COPY . . +WORKDIR "/src/external/JiShe.CollectBus.PluginFileWatcher" +RUN dotnet build "./JiShe.CollectBus.PluginFileWatcher.csproj" -c $BUILD_CONFIGURATION -o /app/build + +# 此阶段用于发布要复制到最终阶段的服务项目 +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./JiShe.CollectBus.PluginFileWatcher.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +# 此阶段在生产中使用,或在常规模式下从 VS 运行时使用(在不使用调试配置时为默认值) +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "JiShe.CollectBus.PluginFileWatcher.dll"] \ No newline at end of file diff --git a/external/JiShe.CollectBus.PluginFileWatcher/EventDatabaseManager.cs b/external/JiShe.CollectBus.PluginFileWatcher/EventDatabaseManager.cs new file mode 100644 index 0000000..9b0f257 --- /dev/null +++ b/external/JiShe.CollectBus.PluginFileWatcher/EventDatabaseManager.cs @@ -0,0 +1,481 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Dapper; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.PluginFileWatcher +{ + /// + /// SQLite数据库管理器,用于管理文件事件的存储和检索 + /// + public class EventDatabaseManager : IDisposable + { + private readonly FileMonitorConfig _config; + private readonly ILogger _logger; + private readonly string _connectionString; + private readonly string _databasePath; + private readonly int _commandTimeout; + private bool _disposed; + + /// + /// 初始化数据库管理器 + /// + /// 配置对象 + /// 日志记录器 + public EventDatabaseManager(FileMonitorConfig config, ILogger logger) + { + _config = config ?? throw new ArgumentNullException(nameof(config)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + // 确保使用配置中的设置 + _databasePath = config.EventStorage.DatabasePath; + _connectionString = config.EventStorage.ConnectionString; + _commandTimeout = config.EventStorage.CommandTimeout; + + // 确保数据库目录存在 + string dbDirectory = Path.GetDirectoryName(_databasePath); + if (!string.IsNullOrEmpty(dbDirectory) && !Directory.Exists(dbDirectory)) + { + Directory.CreateDirectory(dbDirectory); + } + + // 初始化数据库 + InitializeDatabase().GetAwaiter().GetResult(); + } + + /// + /// 初始化数据库,确保必要的表已创建 + /// + private async Task InitializeDatabase() + { + try + { + using var connection = new SqliteConnection(_connectionString); + await connection.OpenAsync(); + + // 启用外键约束 + using (var command = connection.CreateCommand()) + { + command.CommandText = "PRAGMA foreign_keys = ON;"; + await command.ExecuteNonQueryAsync(); + } + + // 创建文件事件表 + string createTableSql = @" + CREATE TABLE IF NOT EXISTS FileEvents ( + Id TEXT PRIMARY KEY, + Timestamp TEXT NOT NULL, + EventType INTEGER NOT NULL, + FullPath TEXT NOT NULL, + FileName TEXT NOT NULL, + Directory TEXT NOT NULL, + Extension TEXT NOT NULL, + OldFileName TEXT, + OldFullPath TEXT, + FileSize INTEGER, + CreatedAt TEXT NOT NULL + ); + + CREATE INDEX IF NOT EXISTS idx_events_timestamp ON FileEvents(Timestamp); + CREATE INDEX IF NOT EXISTS idx_events_eventtype ON FileEvents(EventType); + CREATE INDEX IF NOT EXISTS idx_events_extension ON FileEvents(Extension);"; + + await connection.ExecuteAsync(createTableSql, commandTimeout: _commandTimeout); + + // 创建元数据表 + string createMetadataTableSql = @" + CREATE TABLE IF NOT EXISTS EventMetadata ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + EventId TEXT NOT NULL, + MetadataKey TEXT NOT NULL, + MetadataValue TEXT, + FOREIGN KEY (EventId) REFERENCES FileEvents(Id) ON DELETE CASCADE + ); + + CREATE INDEX IF NOT EXISTS idx_metadata_eventid ON EventMetadata(EventId); + CREATE INDEX IF NOT EXISTS idx_metadata_key ON EventMetadata(MetadataKey);"; + + await connection.ExecuteAsync(createMetadataTableSql, commandTimeout: _commandTimeout); + + _logger.LogInformation("数据库初始化成功"); + } + catch (Exception ex) + { + _logger.LogError(ex, "初始化数据库失败"); + throw; + } + } + + /// + /// 保存文件事件到数据库 + /// + /// 要保存的事件列表 + public async Task SaveEventsAsync(List events) + { + if (events == null || events.Count == 0) + return; + + try + { + using var connection = new SqliteConnection(_connectionString); + await connection.OpenAsync(); + + // 启用外键约束 + using (var command = connection.CreateCommand()) + { + command.CommandText = "PRAGMA foreign_keys = ON;"; + await command.ExecuteNonQueryAsync(); + } + + // 开始事务 + using var transaction = connection.BeginTransaction(); + + try + { + foreach (var fileEvent in events) + { + // 插入事件数据 + string insertEventSql = @" + INSERT INTO FileEvents ( + Id, Timestamp, EventType, FullPath, FileName, + Directory, Extension, OldFileName, OldFullPath, + FileSize, CreatedAt + ) VALUES ( + @Id, @Timestamp, @EventType, @FullPath, @FileName, + @Directory, @Extension, @OldFileName, @OldFullPath, + @FileSize, @CreatedAt + )"; + + await connection.ExecuteAsync(insertEventSql, new + { + Id = fileEvent.Id.ToString(), // 确保ID始终以字符串形式保存 + Timestamp = fileEvent.Timestamp.ToString("o"), + EventType = (int)fileEvent.EventType, + fileEvent.FullPath, + fileEvent.FileName, + fileEvent.Directory, + fileEvent.Extension, + fileEvent.OldFileName, + fileEvent.OldFullPath, + fileEvent.FileSize, + CreatedAt = DateTime.UtcNow.ToString("o") + }, transaction, _commandTimeout); + + // 插入元数据 + if (fileEvent.Metadata != null && fileEvent.Metadata.Count > 0) + { + string insertMetadataSql = @" + INSERT INTO EventMetadata (EventId, MetadataKey, MetadataValue) + VALUES (@EventId, @MetadataKey, @MetadataValue)"; + + foreach (var metadata in fileEvent.Metadata) + { + await connection.ExecuteAsync(insertMetadataSql, new + { + EventId = fileEvent.Id.ToString(), // 确保ID以相同格式保存 + MetadataKey = metadata.Key, + MetadataValue = metadata.Value + }, transaction, _commandTimeout); + } + } + } + + // 提交事务 + transaction.Commit(); + _logger.LogInformation($"已成功保存 {events.Count} 个事件到数据库"); + } + catch (Exception ex) + { + // 回滚事务 + transaction.Rollback(); + _logger.LogError(ex, "保存事件到数据库时发生错误"); + throw; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "连接数据库失败"); + throw; + } + } + + /// + /// 查询事件 + /// + /// 查询参数 + /// 查询结果 + public async Task QueryEventsAsync(EventQueryParams queryParams) + { + if (queryParams == null) + throw new ArgumentNullException(nameof(queryParams)); + + var result = new EventQueryResult + { + StartTime = queryParams.StartTime ?? DateTime.MinValue, + EndTime = queryParams.EndTime ?? DateTime.MaxValue + }; + + try + { + using var connection = new SqliteConnection(_connectionString); + await connection.OpenAsync(); + + // 启用外键约束 + using (var command = connection.CreateCommand()) + { + command.CommandText = "PRAGMA foreign_keys = ON;"; + await command.ExecuteNonQueryAsync(); + } + + // 构建查询条件 + var conditions = new List(); + var parameters = new DynamicParameters(); + + if (queryParams.StartTime.HasValue) + { + conditions.Add("Timestamp >= @StartTime"); + parameters.Add("@StartTime", queryParams.StartTime.Value.ToString("o")); + } + + if (queryParams.EndTime.HasValue) + { + conditions.Add("Timestamp <= @EndTime"); + parameters.Add("@EndTime", queryParams.EndTime.Value.ToString("o")); + } + + if (queryParams.EventType.HasValue) + { + conditions.Add("EventType = @EventType"); + parameters.Add("@EventType", (int)queryParams.EventType.Value); + } + + if (!string.IsNullOrEmpty(queryParams.PathFilter)) + { + conditions.Add("FullPath LIKE @PathFilter"); + parameters.Add("@PathFilter", $"%{queryParams.PathFilter}%"); + } + + if (!string.IsNullOrEmpty(queryParams.ExtensionFilter)) + { + conditions.Add("Extension = @ExtensionFilter"); + parameters.Add("@ExtensionFilter", queryParams.ExtensionFilter); + } + + // 构建WHERE子句 + string whereClause = conditions.Count > 0 + ? $"WHERE {string.Join(" AND ", conditions)}" + : string.Empty; + + // 构建ORDER BY子句 + string orderByClause = queryParams.AscendingOrder + ? "ORDER BY Timestamp ASC" + : "ORDER BY Timestamp DESC"; + + // 获取总记录数 + string countSql = $"SELECT COUNT(*) FROM FileEvents {whereClause}"; + result.TotalCount = await connection.ExecuteScalarAsync(countSql, parameters, commandTimeout: _commandTimeout); + + // 应用分页 + string paginationClause = $"LIMIT @PageSize OFFSET @Offset"; + parameters.Add("@PageSize", queryParams.PageSize); + parameters.Add("@Offset", queryParams.PageIndex * queryParams.PageSize); + + // 查询事件数据 + string querySql = $@" + SELECT Id, + Timestamp, + EventType, + FullPath, + FileName, + Directory, + Extension, + OldFileName, + OldFullPath, + FileSize + FROM FileEvents + {whereClause} + {orderByClause} + {paginationClause}"; + + var events = await connection.QueryAsync(querySql, parameters, commandTimeout: _commandTimeout); + + // 处理查询结果 + foreach (var eventData in events) + { + var fileEvent = new FileEvent + { + Id = Guid.Parse(eventData.Id), + Timestamp = DateTime.Parse(eventData.Timestamp), + EventType = (FileEventType)eventData.EventType, + FullPath = eventData.FullPath, + FileName = eventData.FileName, + Directory = eventData.Directory, + Extension = eventData.Extension, + OldFileName = eventData.OldFileName, + OldFullPath = eventData.OldFullPath, + FileSize = eventData.FileSize + }; + + // 获取元数据 + string metadataSql = "SELECT MetadataKey, MetadataValue FROM EventMetadata WHERE EventId = @EventId"; + var metadata = await connection.QueryAsync(metadataSql, new { EventId = fileEvent.Id.ToString() }, commandTimeout: _commandTimeout); + + foreach (var item in metadata) + { + fileEvent.Metadata[item.MetadataKey] = item.MetadataValue; + } + + result.Events.Add(fileEvent); + } + + result.HasMore = (queryParams.PageIndex + 1) * queryParams.PageSize < result.TotalCount; + + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, "查询事件时发生错误"); + throw; + } + } + + /// + /// 清理旧数据 + /// + /// 数据保留天数 + public async Task CleanupOldDataAsync(int retentionDays) + { + if (retentionDays <= 0) + return; + + try + { + DateTime cutoffDate = DateTime.UtcNow.AddDays(-retentionDays); + string cutoffDateStr = cutoffDate.ToString("o"); + + using var connection = new SqliteConnection(_connectionString); + await connection.OpenAsync(); + + // 启用外键约束 + using (var command = connection.CreateCommand()) + { + command.CommandText = "PRAGMA foreign_keys = ON;"; + await command.ExecuteNonQueryAsync(); + } + + // 删除旧事件(级联删除元数据) + string deleteSql = "DELETE FROM FileEvents WHERE Timestamp < @CutoffDate"; + int deletedCount = await connection.ExecuteAsync(deleteSql, new { CutoffDate = cutoffDateStr }, commandTimeout: _commandTimeout); + + _logger.LogInformation($"已清理 {deletedCount} 条旧事件数据({retentionDays}天前)"); + } + catch (Exception ex) + { + _logger.LogError(ex, "清理旧数据时发生错误"); + throw; + } + } + + /// + /// 获取数据库统计信息 + /// + /// 数据库统计信息 + public async Task GetDatabaseStatsAsync() + { + try + { + using var connection = new SqliteConnection(_connectionString); + await connection.OpenAsync(); + + // 启用外键约束 + using (var command = connection.CreateCommand()) + { + command.CommandText = "PRAGMA foreign_keys = ON;"; + await command.ExecuteNonQueryAsync(); + } + + var stats = new DatabaseStats(); + + // 获取事件总数 + stats.TotalEvents = await connection.ExecuteScalarAsync("SELECT COUNT(*) FROM FileEvents", commandTimeout: _commandTimeout); + + // 获取最早和最新事件时间 + stats.OldestEventTime = await connection.ExecuteScalarAsync("SELECT Timestamp FROM FileEvents ORDER BY Timestamp ASC LIMIT 1", commandTimeout: _commandTimeout); + stats.NewestEventTime = await connection.ExecuteScalarAsync("SELECT Timestamp FROM FileEvents ORDER BY Timestamp DESC LIMIT 1", commandTimeout: _commandTimeout); + + // 获取事件类型分布 + var eventTypeCounts = await connection.QueryAsync("SELECT EventType, COUNT(*) AS Count FROM FileEvents GROUP BY EventType", commandTimeout: _commandTimeout); + stats.EventTypeCounts = new Dictionary(); + + foreach (var item in eventTypeCounts) + { + stats.EventTypeCounts[(FileEventType)item.EventType] = item.Count; + } + + // 获取扩展名分布(前10个) + var extensionCounts = await connection.QueryAsync( + "SELECT Extension, COUNT(*) AS Count FROM FileEvents GROUP BY Extension ORDER BY Count DESC LIMIT 10", + commandTimeout: _commandTimeout); + stats.TopExtensions = new Dictionary(); + + foreach (var item in extensionCounts) + { + stats.TopExtensions[item.Extension] = item.Count; + } + + return stats; + } + catch (Exception ex) + { + _logger.LogError(ex, "获取数据库统计信息时发生错误"); + throw; + } + } + + /// + /// 释放资源 + /// + public void Dispose() + { + if (_disposed) + return; + + _disposed = true; + } + } + + /// + /// 数据库统计信息 + /// + public class DatabaseStats + { + /// + /// 事件总数 + /// + public int TotalEvents { get; set; } + + /// + /// 最早事件时间 + /// + public DateTime? OldestEventTime { get; set; } + + /// + /// 最新事件时间 + /// + public DateTime? NewestEventTime { get; set; } + + /// + /// 事件类型计数 + /// + public Dictionary EventTypeCounts { get; set; } + + /// + /// 排名前列的文件扩展名 + /// + public Dictionary TopExtensions { get; set; } + } +} \ No newline at end of file diff --git a/external/JiShe.CollectBus.PluginFileWatcher/EventStorage.cs b/external/JiShe.CollectBus.PluginFileWatcher/EventStorage.cs new file mode 100644 index 0000000..9bbf619 --- /dev/null +++ b/external/JiShe.CollectBus.PluginFileWatcher/EventStorage.cs @@ -0,0 +1,745 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.PluginFileWatcher +{ + /// + /// 负责文件事件的存储、查询和回放 + /// + public class EventStorage : IDisposable + { + private readonly FileMonitorConfig _config; + private readonly ILogger _logger; + private readonly ConcurrentQueue _eventQueue; + private readonly Timer _storageTimer; + private readonly SemaphoreSlim _storageLock = new SemaphoreSlim(1, 1); + private readonly string _storageDirectory; + private readonly EventDatabaseManager _dbManager; + private bool _disposed; + + /// + /// 创建新的事件存储管理器实例 + /// + /// 文件监控配置 + /// 日志记录器 + public EventStorage(FileMonitorConfig config, ILogger logger) + { + _config = config ?? throw new ArgumentNullException(nameof(config)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _eventQueue = new ConcurrentQueue(); + + // 确保存储目录存在 + _storageDirectory = !string.IsNullOrEmpty(_config.EventStorage.StorageDirectory) + ? _config.EventStorage.StorageDirectory + : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "EventLogs"); + + if (!Directory.Exists(_storageDirectory)) + { + Directory.CreateDirectory(_storageDirectory); + } + + // 创建数据库管理器(如果配置为SQLite存储类型) + if (config.EventStorage.EnableEventStorage && + config.EventStorage.StorageType.Equals("SQLite", StringComparison.OrdinalIgnoreCase)) + { + _dbManager = new EventDatabaseManager(config, logger); + _logger.LogInformation("已初始化SQLite事件存储"); + } + + // 初始化存储定时器(如果启用) + if (_config.EventStorage.EnableEventStorage) + { + var intervalMs = _config.EventStorage.StorageIntervalSeconds * 1000; + _storageTimer = new Timer(SaveEventsTimerCallback, null, intervalMs, intervalMs); + _logger.LogInformation($"事件存储已初始化,存储目录:{_storageDirectory},存储间隔:{_config.EventStorage.StorageIntervalSeconds}秒"); + } + } + + /// + /// 记录一个文件事件 + /// + /// 文件事件 + public void RecordEvent(FileEvent fileEvent) + { + if (fileEvent == null || !_config.EventStorage.EnableEventStorage) return; + + _eventQueue.Enqueue(fileEvent); + _logger.LogDebug($"文件事件已加入队列:{fileEvent.EventType} - {fileEvent.FullPath}"); + } + + /// + /// 从FileSystemEventArgs记录事件 + /// + /// 文件系统事件参数 + public void RecordEvent(FileSystemEventArgs e) + { + if (e == null || !_config.EventStorage.EnableEventStorage) return; + + var fileEvent = FileEvent.FromFileSystemEventArgs(e); + RecordEvent(fileEvent); + } + + /// + /// 定时将事件保存到文件 + /// + private async void SaveEventsTimerCallback(object state) + { + if (_disposed || _eventQueue.IsEmpty) return; + + try + { + // 防止多个定时器回调同时执行 + if (!await _storageLock.WaitAsync(0)) + { + return; + } + + try + { + // 从队列中取出事件 + List eventsToSave = new List(); + int batchSize = _config.EventStorage.BatchSize; + + while (eventsToSave.Count < batchSize && !_eventQueue.IsEmpty) + { + if (_eventQueue.TryDequeue(out var fileEvent)) + { + eventsToSave.Add(fileEvent); + } + } + + if (eventsToSave.Count > 0) + { + await SaveEventsToFileAsync(eventsToSave); + _logger.LogInformation($"已成功保存 {eventsToSave.Count} 个事件"); + } + + // 如果有配置,清理旧日志文件 + if (_config.EventStorage.MaxLogFiles > 0) + { + await CleanupOldLogFilesAsync(); + } + + // 如果有配置,清理旧数据库记录 + if (_dbManager != null && _config.EventStorage.DataRetentionDays > 0) + { + await _dbManager.CleanupOldDataAsync(_config.EventStorage.DataRetentionDays); + } + } + finally + { + _storageLock.Release(); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "保存事件时发生错误"); + } + } + + /// + /// 将事件保存到文件 + /// + /// 要保存的事件列表 + private async Task SaveEventsToFileAsync(List events) + { + if (events == null || events.Count == 0) return; + + try + { + // 根据存储类型选择保存方式 + if (_config.EventStorage.StorageType.Equals("SQLite", StringComparison.OrdinalIgnoreCase) && _dbManager != null) + { + // 保存到SQLite数据库 + await _dbManager.SaveEventsAsync(events); + } + else + { + // 保存到文件 + string fileName = string.Format( + _config.EventStorage.LogFileNameFormat, + DateTime.Now); + + string filePath = Path.Combine(_storageDirectory, fileName); + + // 创建事件日志文件对象 + var logFile = new EventLogFile + { + CreatedTime = DateTime.UtcNow, + Events = events + }; + + // 序列化为JSON + string jsonContent = JsonSerializer.Serialize(logFile, new JsonSerializerOptions + { + WriteIndented = true + }); + + // 是否启用压缩 + if (_config.EventStorage.CompressLogFiles) + { + string gzFilePath = $"{filePath}.gz"; + await CompressAndSaveStringAsync(jsonContent, gzFilePath); + _logger.LogInformation($"已将事件保存到压缩文件:{gzFilePath}"); + } + else + { + await File.WriteAllTextAsync(filePath, jsonContent); + _logger.LogInformation($"已将事件保存到文件:{filePath}"); + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "保存事件到文件时发生错误"); + throw; + } + } + + /// + /// 压缩并保存字符串到文件 + /// + /// 要保存的内容 + /// 文件路径 + private static async Task CompressAndSaveStringAsync(string content, string filePath) + { + using var fileStream = new FileStream(filePath, FileMode.Create); + using var gzipStream = new GZipStream(fileStream, CompressionLevel.Optimal); + using var writer = new StreamWriter(gzipStream); + + await writer.WriteAsync(content); + } + + /// + /// 清理过多的日志文件 + /// + private async Task CleanupOldLogFilesAsync() + { + try + { + // 检查是否需要清理 + if (_config.EventStorage.MaxLogFiles <= 0) return; + + var directory = new DirectoryInfo(_storageDirectory); + var logFiles = directory.GetFiles("*.*") + .Where(f => f.Name.EndsWith(".json") || f.Name.EndsWith(".gz")) + .OrderByDescending(f => f.CreationTime) + .ToArray(); + + // 如果文件数量超过最大值,删除最旧的文件 + if (logFiles.Length > _config.EventStorage.MaxLogFiles) + { + int filesToDelete = logFiles.Length - _config.EventStorage.MaxLogFiles; + var filesToRemove = logFiles.Skip(logFiles.Length - filesToDelete).ToArray(); + + foreach (var file in filesToRemove) + { + try + { + file.Delete(); + _logger.LogInformation($"已删除旧的事件日志文件:{file.Name}"); + } + catch (Exception ex) + { + _logger.LogWarning(ex, $"删除旧日志文件失败:{file.FullName}"); + } + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "清理旧日志文件时发生错误"); + } + + await Task.CompletedTask; + } + + /// + /// 查询历史事件 + /// + /// 查询参数 + /// 查询结果 + public async Task QueryEventsAsync(EventQueryParams queryParams) + { + if (queryParams == null) throw new ArgumentNullException(nameof(queryParams)); + + // 如果是SQLite存储且数据库管理器可用,使用数据库查询 + if (_config.EventStorage.StorageType.Equals("SQLite", StringComparison.OrdinalIgnoreCase) && _dbManager != null) + { + return await _dbManager.QueryEventsAsync(queryParams); + } + + var result = new EventQueryResult + { + StartTime = queryParams.StartTime ?? DateTime.MinValue, + EndTime = queryParams.EndTime ?? DateTime.MaxValue + }; + + try + { + await _storageLock.WaitAsync(); + + try + { + // 获取所有日志文件 + var directory = new DirectoryInfo(_storageDirectory); + if (!directory.Exists) + { + return result; + } + + var logFiles = directory.GetFiles("*.*") + .Where(f => f.Name.EndsWith(".json") || f.Name.EndsWith(".gz")) + .OrderByDescending(f => f.CreationTime) + .ToArray(); + + List allEvents = new List(); + + // 加载所有日志文件中的事件 + foreach (var file in logFiles) + { + try + { + var events = await LoadEventsFromFileAsync(file.FullName); + if (events != null && events.Count > 0) + { + allEvents.AddRange(events); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, $"从文件加载事件失败:{file.FullName}"); + } + } + + // 内存中队列的事件也包含在查询中 + FileEvent[] queuedEvents = _eventQueue.ToArray(); + allEvents.AddRange(queuedEvents); + + // 应用查询过滤条件 + var filteredEvents = allEvents + .Where(e => (queryParams.StartTime == null || e.Timestamp >= queryParams.StartTime) && + (queryParams.EndTime == null || e.Timestamp <= queryParams.EndTime) && + (queryParams.EventType == null || e.EventType == queryParams.EventType.Value) && + (string.IsNullOrEmpty(queryParams.PathFilter) || e.FullPath.Contains(queryParams.PathFilter, StringComparison.OrdinalIgnoreCase)) && + (string.IsNullOrEmpty(queryParams.ExtensionFilter) || e.Extension.Equals(queryParams.ExtensionFilter, StringComparison.OrdinalIgnoreCase))) + .ToList(); + + // 应用排序 + IEnumerable orderedEvents = queryParams.AscendingOrder + ? filteredEvents.OrderBy(e => e.Timestamp) + : filteredEvents.OrderByDescending(e => e.Timestamp); + + // 计算总数 + result.TotalCount = filteredEvents.Count; + + // 应用分页 + int skip = queryParams.PageIndex * queryParams.PageSize; + int take = queryParams.PageSize; + + result.Events = orderedEvents.Skip(skip).Take(take).ToList(); + result.HasMore = (skip + take) < result.TotalCount; + + return result; + } + finally + { + _storageLock.Release(); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "查询事件时发生错误"); + throw; + } + } + + /// + /// 从文件加载事件 + /// + /// 文件路径 + /// 事件列表 + private async Task> LoadEventsFromFileAsync(string filePath) + { + if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) + { + return new List(); + } + + try + { + string jsonContent; + + // 处理压缩文件 + if (filePath.EndsWith(".gz")) + { + using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); + using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress); + using var reader = new StreamReader(gzipStream); + + jsonContent = await reader.ReadToEndAsync(); + } + else + { + jsonContent = await File.ReadAllTextAsync(filePath); + } + + var logFile = JsonSerializer.Deserialize(jsonContent); + return logFile?.Events ?? new List(); + } + catch (Exception ex) + { + _logger.LogError(ex, $"从文件加载事件失败:{filePath}"); + return new List(); + } + } + + /// + /// 启动事件回放会话 + /// + /// 查询参数,定义要回放的事件 + /// 回放处理回调 + /// 取消标记 + /// 回放会话控制器 + public async Task StartReplayAsync( + EventQueryParams queryParams, + Func replayHandler, + CancellationToken cancellationToken = default) + { + if (replayHandler == null) throw new ArgumentNullException(nameof(replayHandler)); + + // 查询要回放的事件 + var queryResult = await QueryEventsAsync(queryParams); + + // 创建并启动回放会话 + var session = new EventReplaySession( + queryResult.Events, + replayHandler, + _config.EventStorage.ReplayIntervalMs, + _config.EventStorage.ReplaySpeedFactor, + _logger, + cancellationToken); + + await session.StartAsync(); + return session; + } + + /// + /// 释放资源 + /// + public void Dispose() + { + if (_disposed) return; + + _disposed = true; + _storageTimer?.Dispose(); + _storageLock?.Dispose(); + _dbManager?.Dispose(); + + // 尝试保存剩余事件 + if (_config.EventStorage.EnableEventStorage && !_eventQueue.IsEmpty) + { + var remainingEvents = new List(); + while (!_eventQueue.IsEmpty && _eventQueue.TryDequeue(out var evt)) + { + remainingEvents.Add(evt); + } + + if (remainingEvents.Count > 0) + { + SaveEventsToFileAsync(remainingEvents).GetAwaiter().GetResult(); + } + } + + GC.SuppressFinalize(this); + } + } + + /// + /// 事件回放会话 + /// + public class EventReplaySession : IDisposable + { + private readonly List _events; + private readonly Func _replayHandler; + private readonly int _replayIntervalMs; + private readonly double _speedFactor; + private readonly ILogger _logger; + private readonly CancellationToken _cancellationToken; + private CancellationTokenSource _linkedCts; + private Task _replayTask; + private bool _disposed; + private bool _isPaused; + private readonly SemaphoreSlim _pauseSemaphore = new SemaphoreSlim(1, 1); + + /// + /// 回放进度(0-100) + /// + public int Progress { get; private set; } + + /// + /// 当前回放的事件索引 + /// + public int CurrentIndex { get; private set; } + + /// + /// 事件总数 + /// + public int TotalEvents => _events?.Count ?? 0; + + /// + /// 回放是否已完成 + /// + public bool IsCompleted { get; private set; } + + /// + /// 回放是否已暂停 + /// + public bool IsPaused => _isPaused; + + /// + /// 回放已处理的事件数 + /// + public int ProcessedEvents { get; private set; } + + /// + /// 回放开始时间 + /// + public DateTime StartTime { get; private set; } + + /// + /// 回放结束时间(如果已完成) + /// + public DateTime? EndTime { get; private set; } + + /// + /// 创建新的事件回放会话 + /// + /// 要回放的事件 + /// 回放处理回调 + /// 回放间隔(毫秒) + /// 速度因子 + /// 日志记录器 + /// 取消标记 + public EventReplaySession( + List events, + Func replayHandler, + int replayIntervalMs, + double speedFactor, + ILogger logger, + CancellationToken cancellationToken = default) + { + _events = events ?? throw new ArgumentNullException(nameof(events)); + _replayHandler = replayHandler ?? throw new ArgumentNullException(nameof(replayHandler)); + _replayIntervalMs = Math.Max(1, replayIntervalMs); + _speedFactor = Math.Max(0.1, speedFactor); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _cancellationToken = cancellationToken; + + _linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + } + + /// + /// 启动回放 + /// + public async Task StartAsync() + { + if (_replayTask != null) return; + + StartTime = DateTime.Now; + _replayTask = Task.Run(ReplayEventsAsync, _linkedCts.Token); + await Task.CompletedTask; + } + + /// + /// 暂停回放 + /// + public async Task PauseAsync() + { + if (_isPaused || IsCompleted) return; + + await _pauseSemaphore.WaitAsync(); + try + { + _isPaused = true; + } + finally + { + _pauseSemaphore.Release(); + } + + _logger.LogInformation("事件回放已暂停"); + } + + /// + /// 恢复回放 + /// + public async Task ResumeAsync() + { + if (!_isPaused || IsCompleted) return; + + await _pauseSemaphore.WaitAsync(); + try + { + _isPaused = false; + // 释放信号量以允许回放任务继续 + _pauseSemaphore.Release(); + } + catch + { + _pauseSemaphore.Release(); + throw; + } + + _logger.LogInformation("事件回放已恢复"); + } + + /// + /// 停止回放 + /// + public async Task StopAsync() + { + if (IsCompleted) return; + + try + { + // 取消回放任务 + _linkedCts?.Cancel(); + + // 如果暂停中,先恢复以允许取消 + if (_isPaused) + { + await ResumeAsync(); + } + + // 等待任务完成 + if (_replayTask != null) + { + await Task.WhenAny(_replayTask, Task.Delay(1000)); + } + + IsCompleted = true; + EndTime = DateTime.Now; + + _logger.LogInformation("事件回放已手动停止"); + } + catch (Exception ex) + { + _logger.LogError(ex, "停止事件回放时发生错误"); + } + } + + /// + /// 回放事件处理 + /// + private async Task ReplayEventsAsync() + { + try + { + _logger.LogInformation($"开始回放{_events.Count}个事件,速度因子:{_speedFactor}"); + + if (_events.Count == 0) + { + IsCompleted = true; + EndTime = DateTime.Now; + return; + } + + DateTime? lastEventTime = null; + + for (int i = 0; i < _events.Count; i++) + { + // 检查是否取消 + if (_linkedCts.Token.IsCancellationRequested) + { + _logger.LogInformation("事件回放已取消"); + break; + } + + // 检查暂停状态 + if (_isPaused) + { + // 等待恢复信号 + await _pauseSemaphore.WaitAsync(_linkedCts.Token); + _pauseSemaphore.Release(); + } + + var currentEvent = _events[i]; + CurrentIndex = i; + + // 计算等待时间(根据事件之间的实际时间差和速度因子) + if (lastEventTime.HasValue && i > 0) + { + var actualTimeDiff = currentEvent.Timestamp - lastEventTime.Value; + var waitTimeMs = (int)(actualTimeDiff.TotalMilliseconds / _speedFactor); + + // 应用最小等待时间 + waitTimeMs = Math.Max(_replayIntervalMs, waitTimeMs); + + // 等待指定时间 + await Task.Delay(waitTimeMs, _linkedCts.Token); + } + + // 处理当前事件 + try + { + await _replayHandler(currentEvent); + ProcessedEvents++; + + // 更新进度 + Progress = (int)((i + 1) * 100.0 / _events.Count); + } + catch (Exception ex) + { + _logger.LogError(ex, $"处理回放事件时发生错误:{currentEvent.EventType} - {currentEvent.FullPath}"); + } + + lastEventTime = currentEvent.Timestamp; + } + + // 完成回放 + IsCompleted = true; + Progress = 100; + EndTime = DateTime.Now; + + _logger.LogInformation($"事件回放已完成,共处理{ProcessedEvents}个事件,耗时:{(EndTime.Value - StartTime).TotalSeconds:F2}秒"); + } + catch (OperationCanceledException) + { + _logger.LogInformation("事件回放已取消"); + IsCompleted = true; + EndTime = DateTime.Now; + } + catch (Exception ex) + { + _logger.LogError(ex, "事件回放过程中发生错误"); + IsCompleted = true; + EndTime = DateTime.Now; + } + } + + /// + /// 释放资源 + /// + public void Dispose() + { + if (_disposed) return; + _disposed = true; + + _linkedCts?.Cancel(); + _linkedCts?.Dispose(); + _pauseSemaphore?.Dispose(); + + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/external/JiShe.CollectBus.PluginFileWatcher/FileEvent.cs b/external/JiShe.CollectBus.PluginFileWatcher/FileEvent.cs new file mode 100644 index 0000000..4e8d905 --- /dev/null +++ b/external/JiShe.CollectBus.PluginFileWatcher/FileEvent.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json.Serialization; + +namespace JiShe.CollectBus.PluginFileWatcher +{ + /// + /// 表示一个文件系统事件的数据模型,用于序列化和存储 + /// + public class FileEvent + { + /// + /// 事件唯一标识 + /// + public Guid Id { get; set; } = Guid.NewGuid(); + + /// + /// 事件发生时间 + /// + public DateTime Timestamp { get; set; } = DateTime.UtcNow; + + /// + /// 事件类型 + /// + public FileEventType EventType { get; set; } + + /// + /// 文件完整路径 + /// + public string FullPath { get; set; } + + /// + /// 文件名 + /// + public string FileName { get; set; } + + /// + /// 文件所在目录 + /// + public string Directory { get; set; } + + /// + /// 文件扩展名 + /// + public string Extension { get; set; } + + /// + /// 重命名前的旧文件名(仅在重命名事件中有效) + /// + public string OldFileName { get; set; } + + /// + /// 重命名前的旧路径(仅在重命名事件中有效) + /// + public string OldFullPath { get; set; } + + /// + /// 文件大小(字节),如果可获取 + /// + public long? FileSize { get; set; } + + /// + /// 自定义属性,可用于存储其他元数据 + /// + public Dictionary Metadata { get; set; } = new Dictionary(); + + /// + /// 从FileSystemEventArgs创建FileEvent + /// + /// FileSystemEventArgs参数 + /// FileEvent对象 + public static FileEvent FromFileSystemEventArgs(FileSystemEventArgs e) + { + var fileEvent = new FileEvent + { + EventType = GetEventType(e.ChangeType), + FullPath = e.FullPath, + FileName = e.Name ?? Path.GetFileName(e.FullPath), + Directory = Path.GetDirectoryName(e.FullPath), + Extension = Path.GetExtension(e.FullPath) + }; + + // 如果是重命名事件,添加旧文件名信息 + if (e is RenamedEventArgs renamedEvent) + { + fileEvent.OldFileName = Path.GetFileName(renamedEvent.OldFullPath); + fileEvent.OldFullPath = renamedEvent.OldFullPath; + } + + // 尝试获取文件大小(如果文件存在且可访问) + try + { + if (File.Exists(e.FullPath) && e.ChangeType != WatcherChangeTypes.Deleted) + { + var fileInfo = new FileInfo(e.FullPath); + fileEvent.FileSize = fileInfo.Length; + + // 添加一些额外的元数据 + fileEvent.Metadata["CreationTime"] = fileInfo.CreationTime.ToString("o"); + fileEvent.Metadata["LastWriteTime"] = fileInfo.LastWriteTime.ToString("o"); + fileEvent.Metadata["IsReadOnly"] = fileInfo.IsReadOnly.ToString(); + } + } + catch + { + // 忽略任何获取文件信息时的错误 + } + + return fileEvent; + } + + /// + /// 将WatcherChangeTypes转换为FileEventType + /// + /// WatcherChangeTypes枚举值 + /// 对应的FileEventType + public static FileEventType GetEventType(WatcherChangeTypes changeType) + { + return changeType switch + { + WatcherChangeTypes.Created => FileEventType.Created, + WatcherChangeTypes.Deleted => FileEventType.Deleted, + WatcherChangeTypes.Changed => FileEventType.Modified, + WatcherChangeTypes.Renamed => FileEventType.Renamed, + _ => FileEventType.Other + }; + } + } + + /// + /// 文件事件类型 + /// + public enum FileEventType + { + /// + /// 文件被创建 + /// + Created, + + /// + /// 文件被修改 + /// + Modified, + + /// + /// 文件被删除 + /// + Deleted, + + /// + /// 文件被重命名 + /// + Renamed, + + /// + /// 其他类型事件 + /// + Other + } + + /// + /// 表示一个事件日志文件 + /// + public class EventLogFile + { + /// + /// 日志文件创建时间 + /// + public DateTime CreatedTime { get; set; } = DateTime.UtcNow; + + /// + /// 日志文件包含的事件列表 + /// + public List Events { get; set; } = new List(); + } + + /// + /// 事件查询结果 + /// + public class EventQueryResult + { + /// + /// 查询到的事件列表 + /// + public List Events { get; set; } = new List(); + + /// + /// 匹配的事件总数 + /// + public int TotalCount { get; set; } + + /// + /// 查询是否有更多结果 + /// + public bool HasMore { get; set; } + + /// + /// 查询时间范围的开始时间 + /// + public DateTime StartTime { get; set; } + + /// + /// 查询时间范围的结束时间 + /// + public DateTime EndTime { get; set; } + } + + /// + /// 事件查询参数 + /// + public class EventQueryParams + { + /// + /// 查询开始时间 + /// + public DateTime? StartTime { get; set; } + + /// + /// 查询结束时间 + /// + public DateTime? EndTime { get; set; } + + /// + /// 事件类型过滤 + /// + public FileEventType? EventType { get; set; } + + /// + /// 文件路径过滤(支持包含关系) + /// + public string PathFilter { get; set; } + + /// + /// 文件扩展名过滤 + /// + public string ExtensionFilter { get; set; } + + /// + /// 分页大小 + /// + public int PageSize { get; set; } = 100; + + /// + /// 分页索引(从0开始) + /// + public int PageIndex { get; set; } = 0; + + /// + /// 排序方向,true为升序,false为降序 + /// + public bool AscendingOrder { get; set; } = false; + } +} \ No newline at end of file diff --git a/external/JiShe.CollectBus.PluginFileWatcher/FileWatcherUtils.cs b/external/JiShe.CollectBus.PluginFileWatcher/FileWatcherUtils.cs new file mode 100644 index 0000000..aeb7f90 --- /dev/null +++ b/external/JiShe.CollectBus.PluginFileWatcher/FileWatcherUtils.cs @@ -0,0 +1,157 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.PluginFileWatcher +{ + /// + /// 文件监控相关工具类 + /// + public static class FileWatcherUtils + { + /// + /// 检测文件是否被锁定 + /// + /// 要检查的文件路径 + /// 如果文件被锁定则返回true,否则返回false + public static bool IsFileLocked(string filePath) + { + if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) + return false; + + try + { + using (FileStream stream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) + { + // 文件可以被完全访问,没有被锁定 + stream.Close(); + } + return false; + } + catch (IOException) + { + // 文件被锁定或正在被其他进程使用 + return true; + } + catch (Exception) + { + // 其他错误(权限不足、路径无效等) + return true; + } + } + + /// + /// 尝试处理一个可能被锁定的文件 + /// + /// 文件路径 + /// 成功解锁后要执行的操作 + /// 健壮性配置 + /// 处理结果:true表示成功处理,false表示处理失败 + public static async Task TryHandleLockedFileAsync(string filePath, Func action, RobustnessConfig config) + { + if (!config.EnableFileLockDetection) + { + // 如果禁用了锁检测,直接执行操作 + try + { + await action(); + return true; + } + catch + { + return false; + } + } + + // 如果文件不存在或不是锁定状态,直接执行操作 + if (!File.Exists(filePath) || !IsFileLocked(filePath)) + { + try + { + await action(); + return true; + } + catch + { + return false; + } + } + + // 文件被锁定,根据策略处理 + switch (config.LockedFileStrategy.ToLower()) + { + case "skip": + // 跳过这个文件 + Console.WriteLine($"文件被锁定,已跳过: {filePath}"); + return false; + + case "retry": + // 重试几次 + for (int i = 0; i < config.FileLockRetryCount; i++) + { + await Task.Delay(config.FileLockRetryDelayMs); + + if (!IsFileLocked(filePath)) + { + try + { + await action(); + Console.WriteLine($"文件锁已释放,成功处理: {filePath}"); + return true; + } + catch + { + // 继续重试 + } + } + } + Console.WriteLine($"文件仍然被锁定,重试{config.FileLockRetryCount}次后放弃: {filePath}"); + return false; + + case "log": + default: + // 只记录不处理 + Console.WriteLine($"文件被锁定,已记录: {filePath}"); + return false; + } + } + + /// + /// 检查文件系统监控器是否健康 + /// + /// 要检查的监控器 + /// 最后一次事件的时间 + /// 健壮性配置 + /// 如果监控器健康则返回true,否则返回false + public static bool IsWatcherHealthy(FileSystemWatcher watcher, DateTime lastEventTime, RobustnessConfig config) + { + if (watcher == null || !watcher.EnableRaisingEvents) + return false; + + // 如果配置了超时时间,检查是否超时 + if (config.WatcherTimeoutSeconds > 0) + { + // 如果最后事件时间超过了超时时间,认为监控器可能已经失效 + TimeSpan timeSinceLastEvent = DateTime.UtcNow - lastEventTime; + if (timeSinceLastEvent.TotalSeconds > config.WatcherTimeoutSeconds) + { + // 执行一个简单的测试:尝试改变一些属性看是否抛出异常 + try + { + var currentFilter = watcher.Filter; + watcher.Filter = currentFilter; + return true; // 如果没有异常,认为监控器仍然正常 + } + catch + { + return false; // 抛出异常,认为监控器已经失效 + } + } + } + + // 默认情况下认为监控器健康 + return true; + } + } +} \ No newline at end of file diff --git a/external/JiShe.CollectBus.PluginFileWatcher/JiShe.CollectBus.PluginFileWatcher.csproj b/external/JiShe.CollectBus.PluginFileWatcher/JiShe.CollectBus.PluginFileWatcher.csproj new file mode 100644 index 0000000..193c217 --- /dev/null +++ b/external/JiShe.CollectBus.PluginFileWatcher/JiShe.CollectBus.PluginFileWatcher.csproj @@ -0,0 +1,39 @@ + + + + Exe + net8.0 + enable + enable + Linux + ..\.. + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/external/JiShe.CollectBus.PluginFileWatcher/Program.cs b/external/JiShe.CollectBus.PluginFileWatcher/Program.cs new file mode 100644 index 0000000..92c47b8 --- /dev/null +++ b/external/JiShe.CollectBus.PluginFileWatcher/Program.cs @@ -0,0 +1,1110 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using System.Linq; +using Microsoft.Extensions.Configuration; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Serilog; +using Serilog.Events; +using Serilog.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace JiShe.CollectBus.PluginFileWatcher +{ + // 删除配置类的定义 + + class Program + { + // 配置对象,从appsettings.json加载 + private static FileMonitorConfig _config = new FileMonitorConfig(); + + // 全局计数器和动态配置参数 + private static long _totalEventsProcessed = 0; + + // 监控器健康状态追踪 + private static DateTime _lastEventTime = DateTime.UtcNow; + private static int _watcherRestartAttempts = 0; + private static FileSystemWatcher _activeWatcher = null; + + // 事件存储和回放 + private static EventStorage _eventStorage = null; + private static EventReplaySession _currentReplaySession = null; + private static ILogger _logger = NullLogger.Instance; + + static async Task Main(string[] args) + { + Console.OutputEncoding = System.Text.Encoding.UTF8; + + // 检查是否以数据库工具模式运行 + if (args.Length > 0 && args[0].Equals("db", StringComparison.OrdinalIgnoreCase)) + { + // 运行数据库工具 + await RunDatabaseUtilityAsync(args.Skip(1).ToArray()); + return; + } + + // 加载配置文件 + LoadConfiguration(); + + // 配置Serilog + ConfigureSerilog(); + + // 初始化日志记录器 + _logger = new SerilogLoggerFactory().CreateLogger("FileMonitor"); + + Log.Information("高性能文件监控程序启动中..."); + + // 初始化事件存储 + if (_config.EventStorage.EnableEventStorage) + { + _eventStorage = new EventStorage(_config, _logger); + Log.Information("事件存储已启用,存储目录:{Directory}", _config.EventStorage.StorageDirectory); + } + + // 打印配置信息 + LogConfiguration(); + + // 获取监控路径:优先命令行参数,其次配置文件,最后使用当前目录 + string pathToMonitor = args.Length > 0 ? args[0] : + !string.IsNullOrEmpty(_config.General.DefaultMonitorPath) ? + _config.General.DefaultMonitorPath : Directory.GetCurrentDirectory(); + + // 命令行参数可以覆盖配置文件 + if (args.Length > 1 && args[1].Equals("--no-filter", StringComparison.OrdinalIgnoreCase)) + { + _config.General.EnableFileFiltering = false; + Log.Information("通过命令行参数禁用文件类型过滤,将监控所有文件"); + } + else if (_config.General.EnableFileFiltering) + { + Log.Information("已启用文件类型过滤,仅监控以下类型: {Extensions}", string.Join(", ", _config.FileFilters.AllowedExtensions)); + } + + if (!Directory.Exists(pathToMonitor)) + { + Log.Warning("错误:监控目录 '{Path}' 不存在。", pathToMonitor); + Log.Information("尝试创建该目录..."); + + try + { + Directory.CreateDirectory(pathToMonitor); + Log.Information("已成功创建目录: {Path}", pathToMonitor); + } + catch (Exception ex) + { + Log.Error(ex, "创建目录失败: {Message}", ex.Message); + Log.Information("将使用当前目录作为监控路径。"); + pathToMonitor = Directory.GetCurrentDirectory(); + } + } + + Log.Information("开始监控目录: {Path}", pathToMonitor); + Log.Information("按 'Q' 退出程序,按 'R' 重新加载配置,按 'H' 检查监控器健康状态。"); + if (_config.EventStorage.EnableEventStorage && _config.EventStorage.EnableEventReplay) + { + Log.Information("按 'P' 开始/暂停回放,按 'S' 停止回放,按 'B' 查询事件。"); + } + + // 创建一个容量由配置文件指定的有界通道 + var channel = Channel.CreateBounded(new BoundedChannelOptions(_config.Performance.ChannelCapacity) + { + FullMode = BoundedChannelFullMode.DropOldest // 当通道满时丢弃旧事件 + }); + + using var cts = new CancellationTokenSource(); + + // 启动内存监控 + var memoryMonitorTask = StartMemoryMonitorAsync(cts.Token); + + // 启动文件监控器 + var monitorTask = StartFileMonitorAsync(pathToMonitor, channel.Writer, cts.Token); + + // 启动健康监控任务 + var healthMonitorTask = _config.Robustness.EnableAutoRecovery ? + StartWatcherHealthMonitorAsync(pathToMonitor, channel.Writer, cts.Token) : Task.CompletedTask; + + // 启动事件处理器 + var processorTask = ProcessEventsAsync(channel.Reader, cts.Token); + + // 等待用户按下键退出或重新加载配置 + bool needRestart = false; + while (true) + { + var key = Console.ReadKey(true).Key; + if (key == ConsoleKey.Q) + { + break; // 退出循环 + } + else if (key == ConsoleKey.R) + { + Log.Information("正在重新加载配置..."); + LoadConfiguration(); + // 重新配置Serilog + ConfigureSerilog(); + LogConfiguration(); + Log.Information("配置已重新加载,部分配置需要重启程序才能生效。"); + } + else if (key == ConsoleKey.H) + { + // 手动检查监控器健康状态 + bool isHealthy = _activeWatcher != null && + FileWatcherUtils.IsWatcherHealthy(_activeWatcher, _lastEventTime, _config.Robustness); + Log.Information("监控器健康状态: {Status}", (isHealthy ? "正常" : "异常")); + Log.Information("上次事件时间: {Time}", _lastEventTime); + Log.Information("重启次数: {Count}/{MaxCount}", _watcherRestartAttempts, _config.Robustness.MaxRestartAttempts); + Log.Information("已处理事件总数: {Count}", _totalEventsProcessed); + } + else if (key == ConsoleKey.P && _config.EventStorage.EnableEventStorage && _config.EventStorage.EnableEventReplay) + { + await HandleEventReplayToggleAsync(); + } + else if (key == ConsoleKey.S && _config.EventStorage.EnableEventStorage && _config.EventStorage.EnableEventReplay) + { + await StopReplayAsync(); + } + else if (key == ConsoleKey.B && _config.EventStorage.EnableEventStorage) + { + await QueryEventsAsync(); + } + + await Task.Delay(100); + } + + // 取消所有任务 + cts.Cancel(); + + // 停止回放(如果正在进行) + await StopReplayAsync(); + + // 释放事件存储 + _eventStorage?.Dispose(); + + try + { + var tasks = new List { monitorTask, processorTask, memoryMonitorTask }; + if (_config.Robustness.EnableAutoRecovery) + { + tasks.Add(healthMonitorTask); + } + + await Task.WhenAll(tasks); + } + catch (OperationCanceledException) + { + Log.Information("程序已正常退出。"); + } + + // 关闭Serilog + Log.CloseAndFlush(); + } + + // 配置Serilog + private static void ConfigureSerilog() + { + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .Build(); + + var loggerConfig = new LoggerConfiguration() + .ReadFrom.Configuration(configuration); + + // 根据配置设置最小日志级别 + LogEventLevel minimumLevel = LogEventLevel.Information; + if (!string.IsNullOrEmpty(_config.Logging?.LogLevel)) + { + switch (_config.Logging.LogLevel.ToLower()) + { + case "verbose": + minimumLevel = LogEventLevel.Verbose; + break; + case "debug": + minimumLevel = LogEventLevel.Debug; + break; + case "information": + minimumLevel = LogEventLevel.Information; + break; + case "warning": + minimumLevel = LogEventLevel.Warning; + break; + case "error": + minimumLevel = LogEventLevel.Error; + break; + case "fatal": + minimumLevel = LogEventLevel.Fatal; + break; + } + } + + // 如果配置文件中没有Serilog节,使用默认配置 + var hasConfig = configuration.GetSection("Serilog").Exists(); + if (!hasConfig) + { + loggerConfig + .MinimumLevel.Is(minimumLevel) + .WriteTo.Console( + outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] {Message:lj}{NewLine}{Exception}" + ) + .WriteTo.File( + Path.Combine(_config.Logging?.LogDirectory ?? "Logs", "filemonitor-.log"), + rollingInterval: RollingInterval.Day, + outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] {Message:lj}{NewLine}{Exception}", + retainedFileCountLimit: _config.Logging?.RetainedLogDays ?? 30 + ); + } + + Log.Logger = loggerConfig.CreateLogger(); + } + + // 查询事件 + private static async Task QueryEventsAsync() + { + if (_eventStorage == null) + { + Log.Warning("事件存储未启用"); + return; + } + + try + { + Log.Information("\n================ 事件查询 ================"); + Console.WriteLine("请输入查询参数,直接回车使用默认值"); + + // 收集查询参数 + var queryParams = new EventQueryParams(); + + Console.Write("开始时间 (yyyy-MM-dd HH:mm:ss),默认24小时前: "); + string startTimeStr = Console.ReadLine(); + if (!string.IsNullOrEmpty(startTimeStr)) + { + if (DateTime.TryParse(startTimeStr, out var startTime)) + { + queryParams.StartTime = startTime; + } + else + { + Log.Warning("无效的日期格式,使用默认值"); + queryParams.StartTime = DateTime.Now.AddDays(-1); + } + } + else + { + queryParams.StartTime = DateTime.Now.AddDays(-1); + } + + Console.Write("结束时间 (yyyy-MM-dd HH:mm:ss),默认当前时间: "); + string endTimeStr = Console.ReadLine(); + if (!string.IsNullOrEmpty(endTimeStr)) + { + if (DateTime.TryParse(endTimeStr, out var endTime)) + { + queryParams.EndTime = endTime; + } + else + { + Log.Warning("无效的日期格式,使用默认值"); + } + } + + Console.Write("事件类型 (Created, Modified, Deleted, Renamed, All),默认All: "); + string eventTypeStr = Console.ReadLine(); + if (!string.IsNullOrEmpty(eventTypeStr) && eventTypeStr.ToLower() != "all") + { + if (Enum.TryParse(eventTypeStr, true, out var eventType)) + { + queryParams.EventType = eventType; + } + else + { + Log.Warning("无效的事件类型,使用默认值All"); + } + } + + Console.Write("路径过滤 (包含此文本的路径),默认无: "); + string pathFilter = Console.ReadLine(); + if (!string.IsNullOrEmpty(pathFilter)) + { + queryParams.PathFilter = pathFilter; + } + + Console.Write("文件扩展名过滤 (.txt, .exe等),默认无: "); + string extensionFilter = Console.ReadLine(); + if (!string.IsNullOrEmpty(extensionFilter)) + { + queryParams.ExtensionFilter = extensionFilter; + } + + Console.Write("每页显示数量,默认20: "); + string pageSizeStr = Console.ReadLine(); + if (!string.IsNullOrEmpty(pageSizeStr) && int.TryParse(pageSizeStr, out var pageSize)) + { + queryParams.PageSize = pageSize; + } + else + { + queryParams.PageSize = 20; + } + + // 执行查询 + var result = await _eventStorage.QueryEventsAsync(queryParams); + + Log.Information("查询结果 (共{Count}条记录):", result.TotalCount); + Log.Information("时间范围: {StartTime} 至 {EndTime}", result.StartTime, result.EndTime); + Console.WriteLine("---------------------------------------------"); + + if (result.Events.Count == 0) + { + Log.Warning("未找到符合条件的事件"); + } + else + { + foreach (var evt in result.Events) + { + string eventType = evt.EventType.ToString(); + string timeStr = evt.Timestamp.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss.fff"); + + Console.WriteLine($"[{timeStr}] [{eventType}] {evt.FileName}"); + Console.WriteLine($" 路径: {evt.Directory}"); + + if (evt.EventType == FileEventType.Renamed && !string.IsNullOrEmpty(evt.OldFileName)) + { + Console.WriteLine($" 原名称: {evt.OldFileName}"); + } + + Console.WriteLine(); + } + + if (result.HasMore) + { + Log.Warning("... 还有更多记录未显示 ..."); + } + } + + // 询问是否开始回放 + if (result.Events.Count > 0 && _config.EventStorage.EnableEventReplay) + { + Console.Write("\n是否要回放这些事件? (Y/N): "); + string replayAnswer = Console.ReadLine(); + if (!string.IsNullOrEmpty(replayAnswer) && replayAnswer.ToUpper().StartsWith("Y")) + { + await StartReplayAsync(queryParams); + } + } + } + catch (Exception ex) + { + Log.Error(ex, "查询事件时出错: {Message}", ex.Message); + } + + Console.WriteLine("=============================================\n"); + } + + // 处理回放切换(开始/暂停) + private static async Task HandleEventReplayToggleAsync() + { + if (_eventStorage == null) return; + + try + { + if (_currentReplaySession == null) + { + // 如果没有活动的回放会话,创建一个简单的查询参数并开始回放 + var queryParams = new EventQueryParams + { + StartTime = DateTime.Now.AddHours(-1), + PageSize = 1000 + }; + + await StartReplayAsync(queryParams); + } + else if (_currentReplaySession.IsPaused) + { + // 恢复回放 + await _currentReplaySession.ResumeAsync(); + Log.Information("已恢复事件回放"); + } + else + { + // 暂停回放 + await _currentReplaySession.PauseAsync(); + Log.Information("已暂停事件回放"); + } + } + catch (Exception ex) + { + Log.Error(ex, "处理回放时出错: {Message}", ex.Message); + } + } + + // 开始回放 + private static async Task StartReplayAsync(EventQueryParams queryParams) + { + if (_eventStorage == null || !_config.EventStorage.EnableEventReplay) return; + + try + { + // 停止现有回放 + await StopReplayAsync(); + + // 创建回放处理器 + Func replayHandler = async (e) => + { + // 在控制台上显示回放的事件 + string eventType = e.EventType.ToString(); + string timeStr = e.Timestamp.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss.fff"); + + Log.Information("[回放] [{Time}] [{Type}] {FileName}", timeStr, eventType, e.FileName); + + if (_config.Logging.LogFileEventDetails) + { + Log.Debug(" 路径: {Path}", e.Directory); + + if (e.EventType == FileEventType.Renamed && !string.IsNullOrEmpty(e.OldFileName)) + { + Log.Debug(" 原名称: {OldName}", e.OldFileName); + } + } + + // 这里可以添加实际的文件操作,比如创建、删除文件等 + // 但在此示例中,我们只是显示事件而不执行实际操作 + + await Task.CompletedTask; + }; + + // 开始回放会话 + _currentReplaySession = await _eventStorage.StartReplayAsync( + queryParams, + replayHandler, + CancellationToken.None); + + // 如果会话创建成功,开始回放 + if (_currentReplaySession != null) + { + await _currentReplaySession.StartAsync(); + Log.Information("开始回放事件,共 {Count} 个事件", _currentReplaySession.TotalEvents); + Log.Information("回放速度: {Speed}", (_config.EventStorage.ReplaySpeedFactor > 1 ? + $"{_config.EventStorage.ReplaySpeedFactor}x (加速)" : + $"{_config.EventStorage.ReplaySpeedFactor}x (减速)")); + } + else + { + Log.Warning("未找到符合条件的事件,回放未开始"); + } + } + catch (Exception ex) + { + Log.Error(ex, "开始回放时出错: {Message}", ex.Message); + } + } + + // 停止回放 + private static async Task StopReplayAsync() + { + if (_currentReplaySession != null) + { + try + { + await _currentReplaySession.StopAsync(); + Log.Information("已停止事件回放"); + + // 输出回放统计 + Log.Information("回放统计: 共处理 {Processed}/{Total} 个事件", + _currentReplaySession.ProcessedEvents, + _currentReplaySession.TotalEvents); + + TimeSpan duration = (_currentReplaySession.EndTime ?? DateTime.Now) - _currentReplaySession.StartTime; + Log.Information("回放持续时间: {Duration:F1} 秒", duration.TotalSeconds); + } + catch (Exception ex) + { + Log.Error(ex, "停止回放时出错: {Message}", ex.Message); + } + finally + { + _currentReplaySession.Dispose(); + _currentReplaySession = null; + } + } + } + + // 加载配置文件 + private static void LoadConfiguration() + { + try + { + // 检查配置文件是否存在 + string configPath = Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"); + if (!File.Exists(configPath)) + { + Log.Warning("配置文件不存在,使用默认配置。"); + + // 使用默认配置 + _config = new FileMonitorConfig + { + General = new GeneralConfig + { + EnableFileFiltering = true, + MemoryMonitorIntervalMinutes = 1, + DefaultMonitorPath = Path.Combine(Directory.GetCurrentDirectory(), "MonitorFiles") + }, + FileFilters = new FileFiltersConfig + { + AllowedExtensions = new[] { ".dll" }, + ExcludedDirectories = new[] { "bin", "obj", "node_modules" }, + IncludeSubdirectories = true + }, + Performance = new PerformanceConfig + { + MemoryCleanupThreshold = 5000, + ChannelCapacity = 1000, + EventDebounceTimeSeconds = 3, + MaxDictionarySize = 10000, + CleanupIntervalSeconds = 5, + ProcessingDelayMs = 5 + }, + Robustness = new RobustnessConfig + { + EnableAutoRecovery = true, + WatcherHealthCheckIntervalSeconds = 30, + WatcherTimeoutSeconds = 60, + MaxRestartAttempts = 3, + RestartDelaySeconds = 5, + EnableFileLockDetection = true, + LockedFileStrategy = "Retry", + FileLockRetryCount = 3, + FileLockRetryDelayMs = 500 + }, + NotifyFilters = new List { "LastWrite", "FileName", "DirectoryName", "CreationTime" }, + EventStorage = new EventStorageConfig + { + EnableEventStorage = false, + StorageDirectory = Path.Combine(Directory.GetCurrentDirectory(), "EventStorage"), + EnableEventReplay = false, + ReplaySpeedFactor = 1 + }, + Logging = new LoggingConfig + { + LogLevel = "Information", + LogFileEventDetails = false, + RetainedLogDays = 30, + LogDirectory = "Logs" + } + }; + + return; + } + + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); + + var configuration = builder.Build(); + + // 绑定配置到对象 + var config = new FileMonitorConfig(); + configuration.GetSection("FileMonitorConfig").Bind(config); + + // 更新全局配置 + _config = config; + } + catch (Exception ex) + { + Log.Error(ex, "加载配置文件失败: {Message}", ex.Message); + Log.Warning("将使用默认配置。"); + + // 使用默认配置 + _config = new FileMonitorConfig + { + General = new GeneralConfig + { + EnableFileFiltering = true, + MemoryMonitorIntervalMinutes = 1, + DefaultMonitorPath = Path.Combine(Directory.GetCurrentDirectory(), "MonitorFiles") + }, + FileFilters = new FileFiltersConfig + { + AllowedExtensions = new[] { ".dll" }, + ExcludedDirectories = new[] { "bin", "obj", "node_modules" }, + IncludeSubdirectories = true + }, + Performance = new PerformanceConfig + { + MemoryCleanupThreshold = 5000, + ChannelCapacity = 1000, + EventDebounceTimeSeconds = 3, + MaxDictionarySize = 10000, + CleanupIntervalSeconds = 5, + ProcessingDelayMs = 5 + }, + Robustness = new RobustnessConfig + { + EnableAutoRecovery = true, + WatcherHealthCheckIntervalSeconds = 30, + WatcherTimeoutSeconds = 60, + MaxRestartAttempts = 3, + RestartDelaySeconds = 5, + EnableFileLockDetection = true, + LockedFileStrategy = "Retry", + FileLockRetryCount = 3, + FileLockRetryDelayMs = 500 + }, + NotifyFilters = new List { "LastWrite", "FileName", "DirectoryName", "CreationTime" }, + EventStorage = new EventStorageConfig + { + EnableEventStorage = false, + StorageDirectory = Path.Combine(Directory.GetCurrentDirectory(), "EventStorage"), + EnableEventReplay = false, + ReplaySpeedFactor = 1 + }, + Logging = new LoggingConfig + { + LogLevel = "Information", + LogFileEventDetails = false, + RetainedLogDays = 30, + LogDirectory = "Logs" + } + }; + } + } + + // 打印当前配置信息 + private static void LogConfiguration() + { + Log.Information("当前配置信息:"); + Log.Information(" 默认监控路径: {Path}", _config.General.DefaultMonitorPath); + Log.Information(" 文件过滤: {Status}", (_config.General.EnableFileFiltering ? "启用" : "禁用")); + if (_config.General.EnableFileFiltering) + { + Log.Information(" 监控文件类型: {Types}", string.Join(", ", _config.FileFilters.AllowedExtensions)); + Log.Information(" 排除目录: {Dirs}", string.Join(", ", _config.FileFilters.ExcludedDirectories)); + } + Log.Information(" 内存监控间隔: {Minutes}分钟", _config.General.MemoryMonitorIntervalMinutes); + Log.Information(" 内存清理阈值: 每{Threshold}个事件", _config.Performance.MemoryCleanupThreshold); + Log.Information(" 通道容量: {Capacity}", _config.Performance.ChannelCapacity); + Log.Information(" 事件去抖时间: {Seconds}秒", _config.Performance.EventDebounceTimeSeconds); + Log.Information(" 自动恢复: {Status}", (_config.Robustness.EnableAutoRecovery ? "启用" : "禁用")); + Log.Information(" 文件锁检测: {Status}", (_config.Robustness.EnableFileLockDetection ? "启用" : "禁用")); + if (_config.Robustness.EnableFileLockDetection) + { + Log.Information(" 锁定文件策略: {Strategy}", _config.Robustness.LockedFileStrategy); + } + Log.Information(" 日志级别: {Level}", _config.Logging?.LogLevel ?? "Information"); + } + + // 检查文件是否应该被处理的方法 + private static bool ShouldProcessFile(string filePath) + { + if (!_config.General.EnableFileFiltering) + return true; + + if (string.IsNullOrEmpty(filePath)) + return false; + + // 检查是否在排除目录中 + foreach (var excludedDir in _config.FileFilters.ExcludedDirectories) + { + if (filePath.Contains($"{Path.DirectorySeparatorChar}{excludedDir}{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) || + filePath.EndsWith($"{Path.DirectorySeparatorChar}{excludedDir}", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + string extension = Path.GetExtension(filePath); + return _config.FileFilters.AllowedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + } + + // 添加内存监控方法 + private static async Task StartMemoryMonitorAsync(CancellationToken cancellationToken) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + // 等待指定时间 + await Task.Delay(TimeSpan.FromMinutes(_config.General.MemoryMonitorIntervalMinutes), cancellationToken); + + // 获取当前内存使用情况 + long currentMemory = GC.GetTotalMemory(false) / (1024 * 1024); // MB + + Log.Information("内存使用: {Memory} MB, 已处理事件: {EventCount}", currentMemory, _totalEventsProcessed); + + // 强制执行垃圾回收 + GC.Collect(); + GC.WaitForPendingFinalizers(); + + // 显示垃圾回收后的内存 + long afterGCMemory = GC.GetTotalMemory(true) / (1024 * 1024); // MB + Log.Debug("内存清理后: {Memory} MB (释放: {Released} MB)", afterGCMemory, currentMemory - afterGCMemory); + } + } + catch (OperationCanceledException) + { + // 预期的取消异常 + } + } + + // 创建文件监控器 + private static FileSystemWatcher CreateFileWatcher(string path, ChannelWriter writer, CancellationToken cancellationToken) + { + // 创建一个新的文件系统监控器 + var watcher = new FileSystemWatcher(path); + + // 从配置设置NotifyFilters + NotifyFilters notifyFilters = NotifyFilters.LastWrite; // 默认值 + foreach (var filterName in _config.NotifyFilters) + { + if (Enum.TryParse(filterName, out var filter)) + { + notifyFilters |= filter; + } + } + + watcher.NotifyFilter = notifyFilters; + watcher.IncludeSubdirectories = _config.FileFilters.IncludeSubdirectories; + watcher.EnableRaisingEvents = true; + + // 设置文件过滤器,如果启用了过滤 + if (_config.General.EnableFileFiltering && _config.FileFilters.AllowedExtensions.Length > 0) + { + // 在FileSystemWatcher级别设置过滤器,这样可以减少系统生成的事件数量 + // 只能设置一个扩展名作为Filter,所以我们选择第一个 + string firstExtension = _config.FileFilters.AllowedExtensions[0]; + watcher.Filter = $"*{firstExtension}"; + Log.Information("已设置文件系统监控过滤器: *{Filter}", firstExtension); + + if (_config.FileFilters.AllowedExtensions.Length > 1) + { + Log.Warning("注意: FileSystemWatcher只支持单一过滤器,其他文件类型将在事件处理时过滤。"); + } + } + + // 注册事件处理程序 + watcher.Created += (sender, e) => HandleFileEvent(sender, e, writer, cancellationToken); + watcher.Changed += (sender, e) => HandleFileEvent(sender, e, writer, cancellationToken); + watcher.Deleted += (sender, e) => HandleFileEvent(sender, e, writer, cancellationToken); + watcher.Renamed += (sender, e) => HandleFileEvent(sender, e, writer, cancellationToken); + watcher.Error += (sender, e) => + { + Log.Error(e.GetException(), "文件监控错误: {Message}", e.GetException().Message); + }; + + return watcher; + } + + // 文件事件处理 + private static void HandleFileEvent(object sender, FileSystemEventArgs e, ChannelWriter writer, CancellationToken cancellationToken) + { + // 更新最后事件时间 + _lastEventTime = DateTime.UtcNow; + + // 如果启用了事件存储,记录事件 + _eventStorage?.RecordEvent(e); + + try + { + // 如果是退出信号,忽略后续处理 + if (cancellationToken.IsCancellationRequested) + { + return; + } + + // 只处理指定类型的文件,如果启用了文件过滤 + if (!ShouldProcessFile(e.FullPath)) + { + return; + } + + // 尝试将事件写入通道,如果满了就丢弃 + // 不等待,以免阻塞文件系统事件 + if (!writer.TryWrite(e)) + { + Log.Warning("警告: 事件处理队列已满,部分事件被丢弃"); + } + } + catch (Exception ex) + { + // 捕获任何异常,防止崩溃 + Log.Error(ex, "处理文件事件时出错: {Message}", ex.Message); + } + } + + private static async Task StartFileMonitorAsync(string path, ChannelWriter writer, CancellationToken cancellationToken) + { + // 使用TaskCompletionSource来控制任务的完成 + var tcs = new TaskCompletionSource(); + + try + { + // 创建并启动监控器 + _activeWatcher = CreateFileWatcher(path, writer, cancellationToken); + + // 注册取消回调 + cancellationToken.Register(() => + { + try + { + if (_activeWatcher != null) + { + // 确保所有资源正确清理 + _activeWatcher.EnableRaisingEvents = false; + _activeWatcher.Dispose(); + } + } + catch (Exception ex) + { + Log.Error(ex, "关闭文件监控器时出错: {Message}", ex.Message); + } + finally + { + tcs.TrySetResult(true); + } + }); + + // 等待任务被取消或完成 + await tcs.Task; + } + catch (Exception ex) + { + Log.Error(ex, "启动文件监控器时出错: {Message}", ex.Message); + tcs.TrySetException(ex); + throw; + } + } + + private static async Task ProcessEventsAsync(ChannelReader reader, CancellationToken cancellationToken) + { + try + { + // 使用配置的字典大小 + var recentlyProcessed = new ConcurrentDictionary(); + + // 清理计时器,频率由配置决定 + using var cleanupTimer = new Timer(_ => + { + try + { + var cutoff = DateTime.UtcNow.AddSeconds(-_config.Performance.EventDebounceTimeSeconds); + int removedCount = 0; + + // 限制字典大小 + if (recentlyProcessed.Count > _config.Performance.MaxDictionarySize) + { + // 找出最旧的条目删除 + var oldestItems = recentlyProcessed + .OrderBy(kv => kv.Value) + .Take(recentlyProcessed.Count - _config.Performance.MaxDictionarySize / 2); + + foreach (var item in oldestItems) + { + DateTime dummy; + if (recentlyProcessed.TryRemove(item.Key, out dummy)) + { + removedCount++; + } + } + } + + // 常规清理 + foreach (var key in recentlyProcessed.Keys) + { + if (recentlyProcessed.TryGetValue(key, out var time) && time < cutoff) + { + DateTime dummy; + if (recentlyProcessed.TryRemove(key, out dummy)) + { + removedCount++; + } + } + } + + if (removedCount > 0) + { + Log.Debug("已清理 {Count} 个过期事件记录", removedCount); + } + } + catch (Exception ex) + { + Log.Error(ex, "清理定时器错误: {Message}", ex.Message); + } + }, null, TimeSpan.FromSeconds(_config.Performance.CleanupIntervalSeconds), + TimeSpan.FromSeconds(_config.Performance.CleanupIntervalSeconds)); + + // 从通道读取事件并处理 + await foreach (var e in reader.ReadAllAsync(cancellationToken)) + { + // 再次确认文件扩展名,双重检查 + if (!ShouldProcessFile(e.FullPath)) + { + continue; + } + + // 增加计数器 + long count = Interlocked.Increment(ref _totalEventsProcessed); + + // 周期性清理内存 + if (count % _config.Performance.MemoryCleanupThreshold == 0) + { + // 在后台线程执行垃圾回收,但直接等待完成避免警告 + await Task.Run(() => + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + }); + } + + // 只使用文件路径作为键,忽略事件类型,这样同一文件的创建和修改事件会被视为同一事件 + var key = e.FullPath; + + // 对于创建事件,如果之前有相同文件的其他事件,则跳过 + if (e.ChangeType == WatcherChangeTypes.Changed && + recentlyProcessed.TryGetValue(key, out var _)) + { + // 如果是修改事件且文件最近被处理过,则跳过 + continue; + } + + // 更新或添加文件处理时间,使用原子操作 + recentlyProcessed[key] = DateTime.UtcNow; + + string eventType = e.ChangeType switch + { + WatcherChangeTypes.Created => "创建", + WatcherChangeTypes.Deleted => "删除", + WatcherChangeTypes.Changed => "修改", + WatcherChangeTypes.Renamed => "重命名", + _ => "未知" + }; + + string fileName = e.Name ?? Path.GetFileName(e.FullPath); + string directoryName = Path.GetDirectoryName(e.FullPath) ?? string.Empty; + + Log.Information("{EventType}: {FileName}", eventType, fileName); + + if (_config.Logging.LogFileEventDetails) + { + Log.Debug(" 路径: {Path}", directoryName); + + var extension = Path.GetExtension(e.FullPath); + Log.Debug(" 类型: {Extension}", extension); + + if (e is RenamedEventArgs renamedEvent) + { + Log.Debug(" 原名称: {OldName}", Path.GetFileName(renamedEvent.OldFullPath)); + } + } + + // 延迟可配置 + await Task.Delay(_config.Performance.ProcessingDelayMs, cancellationToken); + } + } + catch (OperationCanceledException) + { + // 预期的取消异常,正常退出 + } + catch (Exception ex) + { + Log.Error(ex, "处理事件时发生错误: {Message}", ex.Message); + } + finally + { + // 确保资源释放 + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + } + + // 健康监控任务,监视文件监控器的状态并在需要时重启它 + private static async Task StartWatcherHealthMonitorAsync( + string path, ChannelWriter writer, CancellationToken cancellationToken) + { + Log.Information("已启动监控器健康检查,间隔: {Seconds}秒", _config.Robustness.WatcherHealthCheckIntervalSeconds); + + try + { + while (!cancellationToken.IsCancellationRequested) + { + // 等待指定的健康检查间隔 + await Task.Delay(TimeSpan.FromSeconds(_config.Robustness.WatcherHealthCheckIntervalSeconds), + cancellationToken); + + // 如果监控器不健康且重启次数未超过上限,尝试重启 + if (_activeWatcher != null && + !FileWatcherUtils.IsWatcherHealthy(_activeWatcher, _lastEventTime, _config.Robustness) && + _watcherRestartAttempts < _config.Robustness.MaxRestartAttempts) + { + // 停止当前的监控器 + Log.Warning("检测到监控器异常,准备重启..."); + + try + { + _activeWatcher.EnableRaisingEvents = false; + _activeWatcher.Dispose(); + } + catch (Exception ex) + { + Log.Error(ex, "停止异常监控器时出错: {Message}", ex.Message); + } + + _watcherRestartAttempts++; + Log.Warning("正在重启监控器,尝试次数: {Current}/{Max}", + _watcherRestartAttempts, _config.Robustness.MaxRestartAttempts); + + // 等待指定的重启延迟时间 + await Task.Delay(TimeSpan.FromSeconds(_config.Robustness.RestartDelaySeconds), + cancellationToken); + + // 创建新的监控器 + try + { + _activeWatcher = CreateFileWatcher(path, writer, cancellationToken); + _lastEventTime = DateTime.UtcNow; // 重置最后事件时间 + Log.Information("监控器已成功重启"); + } + catch (Exception ex) + { + Log.Error(ex, "重启监控器失败: {Message}", ex.Message); + } + } + else if (_watcherRestartAttempts >= _config.Robustness.MaxRestartAttempts) + { + Log.Error("警告: 已达到最大重启次数({Max}),不再尝试重启", _config.Robustness.MaxRestartAttempts); + Log.Warning("请检查文件系统或手动重启程序"); + break; + } + } + } + catch (OperationCanceledException) + { + // 预期的取消异常,正常退出 + } + catch (Exception ex) + { + Log.Error(ex, "健康监控任务异常: {Message}", ex.Message); + } + } + + /// + /// 运行数据库工具 + /// + private static async Task RunDatabaseUtilityAsync(string[] args) + { + Console.WriteLine("正在启动SQLite数据库管理工具..."); + + try + { + var dbUtility = new DbUtility(); + await dbUtility.ExecuteAsync(args); + } + catch (Exception ex) + { + Console.WriteLine($"运行数据库工具时发生错误: {ex.Message}"); + } + } + } +} diff --git a/external/JiShe.CollectBus.PluginFileWatcher/Properties/launchSettings.json b/external/JiShe.CollectBus.PluginFileWatcher/Properties/launchSettings.json new file mode 100644 index 0000000..9293f4d --- /dev/null +++ b/external/JiShe.CollectBus.PluginFileWatcher/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "JiShe.CollectBus.PluginFileWatcher": { + "commandName": "Project" + }, + "Container (Dockerfile)": { + "commandName": "Docker" + } + } +} \ No newline at end of file diff --git a/external/JiShe.CollectBus.PluginFileWatcher/appsettings.json b/external/JiShe.CollectBus.PluginFileWatcher/appsettings.json new file mode 100644 index 0000000..f0eedfa --- /dev/null +++ b/external/JiShe.CollectBus.PluginFileWatcher/appsettings.json @@ -0,0 +1,88 @@ +{ + "Serilog": { + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/filemonitor-.log", + "rollingInterval": "Day", + "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] {Message:lj}{NewLine}{Exception}", + "retainedFileCountLimit": 31 + } + } + ], + "Enrich": [ "FromLogContext" ] + }, + "FileMonitorConfig": { + "General": { + "EnableFileFiltering": true, + "MemoryMonitorIntervalMinutes": 1, + "DefaultMonitorPath": "D:\\MonitorFiles" + }, + "FileFilters": { + "AllowedExtensions": [ ".dll" ], + "ExcludedDirectories": [ "bin", "obj", "node_modules" ], + "IncludeSubdirectories": true + }, + "Performance": { + "MemoryCleanupThreshold": 5000, + "ChannelCapacity": 1000, + "EventDebounceTimeSeconds": 3, + "MaxDictionarySize": 10000, + "CleanupIntervalSeconds": 5, + "ProcessingDelayMs": 5 + }, + "Robustness": { + "EnableAutoRecovery": true, + "WatcherHealthCheckIntervalSeconds": 30, + "WatcherTimeoutSeconds": 60, + "MaxRestartAttempts": 3, + "RestartDelaySeconds": 5, + "EnableFileLockDetection": true, + "LockedFileStrategy": "Retry", + "FileLockRetryCount": 3, + "FileLockRetryDelayMs": 500 + }, + "EventStorage": { + "EnableEventStorage": true, + "StorageType": "SQLite", + "StorageDirectory": "D:/EventLogs", + "DatabasePath": "D:/EventLogs/events.db", + "ConnectionString": "Data Source=D:/EventLogs/events.db;Foreign Keys=True", + "CommandTimeout": 30, + "LogFileNameFormat": "FileEvents_{0:yyyy-MM-dd}.json", + "StorageIntervalSeconds": 60, + "BatchSize": 100, + "MaxEventRecords": 100000, + "DataRetentionDays": 30, + "MaxLogFiles": 30, + "CompressLogFiles": true, + "EnableEventReplay": true, + "ReplayIntervalMs": 100, + "ReplaySpeedFactor": 1.0 + }, + "NotifyFilters": [ + "LastWrite", + "FileName", + "DirectoryName", + "CreationTime" + ], + "Logging": { + "LogLevel": "Information" + } + } +} diff --git a/modules/JiShe.CollectBus.Cassandra/CassandraQueryOptimizer.cs b/modules/JiShe.CollectBus.Cassandra/CassandraQueryOptimizer.cs deleted file mode 100644 index 0ea1b56..0000000 --- a/modules/JiShe.CollectBus.Cassandra/CassandraQueryOptimizer.cs +++ /dev/null @@ -1,156 +0,0 @@ -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/modules/JiShe.CollectBus.Cassandra/CassandraRepository.cs b/modules/JiShe.CollectBus.Cassandra/CassandraRepository.cs index 25a51e3..1d6cc3c 100644 --- a/modules/JiShe.CollectBus.Cassandra/CassandraRepository.cs +++ b/modules/JiShe.CollectBus.Cassandra/CassandraRepository.cs @@ -1,19 +1,13 @@ -using Cassandra; -using Cassandra.Data.Linq; +using System.Linq.Expressions; using Cassandra.Mapping; using JiShe.CollectBus.Cassandra.Extensions; -using JiShe.CollectBus.Common.Attributes; -using Microsoft.AspNetCore.Http; -using System.Reflection; -using Thrift.Protocol.Entities; using Volo.Abp.Domain.Entities; -using Volo.Abp.Domain.Repositories; namespace JiShe.CollectBus.Cassandra { public class CassandraRepository : ICassandraRepository - where TEntity : class + where TEntity : class, ICassandraEntity { private readonly ICassandraProvider _cassandraProvider; public CassandraRepository(ICassandraProvider cassandraProvider, MappingConfiguration mappingConfig) @@ -27,12 +21,29 @@ namespace JiShe.CollectBus.Cassandra public virtual async Task GetAsync(TKey id) { - return await Mapper.SingleOrDefaultAsync("WHERE id = ?", id); + return await GetAsync("WHERE id = ?", id); } - public virtual async Task> GetListAsync() + public virtual async Task GetAsync(string cql, params object[] args) { - return (await Mapper.FetchAsync()).ToList(); + return await Mapper.SingleAsync(cql, args); + } + + + public virtual async Task FirstOrDefaultAsync(TKey id) + { + return await FirstOrDefaultAsync("WHERE id = ?", id); + } + + public virtual async Task FirstOrDefaultAsync(string cql, params object[] args) + { + return await Mapper.FirstOrDefaultAsync(cql, args); + } + + + public virtual async Task?> GetListAsync(string? cql = null, params object[] args) + { + return cql.IsNullOrWhiteSpace() ? (await Mapper.FetchAsync()).ToList() : (await Mapper.FetchAsync(cql, args)).ToList(); } public virtual async Task InsertAsync(TEntity entity) diff --git a/modules/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs b/modules/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs index b5274f7..bf57bce 100644 --- a/modules/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs +++ b/modules/JiShe.CollectBus.Cassandra/CollectBusCassandraModule.cs @@ -1,7 +1,4 @@ -using Cassandra; -using Cassandra.Mapping; -using JiShe.CollectBus.Cassandra.Mappers; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp; using Volo.Abp.Autofac; using Volo.Abp.Modularity; diff --git a/modules/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs b/modules/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs index fe69268..c9ec0de 100644 --- a/modules/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs +++ b/modules/JiShe.CollectBus.Cassandra/Extensions/ServiceCollectionExtensions.cs @@ -1,10 +1,4 @@ -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 JiShe.CollectBus.Cassandra; using Volo.Abp; using Volo.Abp.Modularity; @@ -26,8 +20,6 @@ namespace Microsoft.Extensions.DependencyInjection public static void AddCassandra(this ServiceConfigurationContext context) { context.Services.AddTransient(typeof(ICassandraRepository<,>), typeof(CassandraRepository<,>)); - context.Services.AddSingleton(new MappingConfiguration() - .Define(new CollectBusMapping())); } } } diff --git a/modules/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs b/modules/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs index c313d0c..dd0ff66 100644 --- a/modules/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs +++ b/modules/JiShe.CollectBus.Cassandra/Extensions/SessionExtension.cs @@ -3,9 +3,7 @@ using System.Text; using Cassandra; using System.ComponentModel.DataAnnotations; using JiShe.CollectBus.Common.Attributes; -using Cassandra.Mapping; -using Cassandra.Data.Linq; -using Thrift.Protocol.Entities; +using Volo.Abp.Data; namespace JiShe.CollectBus.Cassandra.Extensions { @@ -16,17 +14,26 @@ 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 = session.Keyspace; var properties = type.GetProperties(); - var primaryKey = properties.FirstOrDefault(p => p.GetCustomAttribute() != null); + + // 分区键设置 + var primaryKey = properties.FirstOrDefault(p => p.GetCustomAttribute() != null); if (primaryKey == null) { throw new InvalidOperationException($"No primary key defined for type {type.Name}"); } + // 集群键设置 + var clusteringKeys = properties.Where(p => p.GetCustomAttribute() != null).Select(a=>a.Name).ToList(); + var clusteringKeyCql = string.Empty; + if (clusteringKeys.Any()) + { + clusteringKeyCql = $", {string.Join(", ", clusteringKeys)}"; + } + var cql = new StringBuilder(); cql.Append($"CREATE TABLE IF NOT EXISTS {tableKeyspace}.{tableName} ("); @@ -40,7 +47,7 @@ namespace JiShe.CollectBus.Cassandra.Extensions cql.Append($"{columnName} {cqlType}, "); } cql.Length -= 2; // Remove last comma and space - cql.Append($", PRIMARY KEY ({primaryKey.Name.ToLower()}))"); + cql.Append($", PRIMARY KEY (({primaryKey.Name.ToLower()}){clusteringKeyCql}))"); session.Execute(cql.ToString()); } @@ -61,6 +68,7 @@ namespace JiShe.CollectBus.Cassandra.Extensions if (type == typeof(Guid)) return "uuid"; if (type == typeof(DateTimeOffset)) return "timestamp"; if (type == typeof(Byte[])) return "blob"; + if (type == typeof(ExtraPropertyDictionary)) return "map"; // 处理集合类型 if (type.IsGenericType) @@ -72,6 +80,8 @@ namespace JiShe.CollectBus.Cassandra.Extensions return $"list<{GetCassandraType(elementType)}>"; if (genericType == typeof(HashSet<>)) return $"set<{GetCassandraType(elementType)}>"; + if (genericType == typeof(Nullable<>)) + return GetCassandraType(elementType); if (genericType == typeof(Dictionary<,>)) { var keyType = type.GetGenericArguments()[0]; diff --git a/modules/JiShe.CollectBus.Cassandra/ICassandraRepository.cs b/modules/JiShe.CollectBus.Cassandra/ICassandraRepository.cs index 6dd474f..5f3b862 100644 --- a/modules/JiShe.CollectBus.Cassandra/ICassandraRepository.cs +++ b/modules/JiShe.CollectBus.Cassandra/ICassandraRepository.cs @@ -10,7 +10,10 @@ namespace JiShe.CollectBus.Cassandra public interface ICassandraRepository where TEntity : class { Task GetAsync(TKey id); - Task> GetListAsync(); + Task GetAsync(string cql, params object[] args); + Task FirstOrDefaultAsync(TKey id); + Task FirstOrDefaultAsync(string cql, params object[] args); + Task?> GetListAsync(string? cql = null, params object[] args); Task InsertAsync(TEntity entity); Task UpdateAsync(TEntity entity); Task DeleteAsync(TEntity entity); diff --git a/modules/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj b/modules/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj index edc303c..a3360e3 100644 --- a/modules/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj +++ b/modules/JiShe.CollectBus.Cassandra/JiShe.CollectBus.Cassandra.csproj @@ -15,8 +15,8 @@ - + diff --git a/modules/JiShe.CollectBus.IoTDB/Attribute/EntityTypeAttribute.cs b/modules/JiShe.CollectBus.IoTDB/Attribute/EntityTypeAttribute.cs new file mode 100644 index 0000000..3610c00 --- /dev/null +++ b/modules/JiShe.CollectBus.IoTDB/Attribute/EntityTypeAttribute.cs @@ -0,0 +1,19 @@ +using JiShe.CollectBus.IoTDB.Enums; + +namespace JiShe.CollectBus.IoTDB.Attribute +{ + /// + /// IoTDB实体类型特性 + /// + [AttributeUsage(AttributeTargets.Class)] + public class EntityTypeAttribute : System.Attribute + { + public EntityTypeEnum EntityType { get; } + + + public EntityTypeAttribute(EntityTypeEnum entityType) + { + EntityType = entityType; + } + } +} diff --git a/modules/JiShe.CollectBus.IoTDB/Attribute/SingleMeasuringAttribute.cs b/modules/JiShe.CollectBus.IoTDB/Attribute/SingleMeasuringAttribute.cs index cebb85a..5f0ca07 100644 --- a/modules/JiShe.CollectBus.IoTDB/Attribute/SingleMeasuringAttribute.cs +++ b/modules/JiShe.CollectBus.IoTDB/Attribute/SingleMeasuringAttribute.cs @@ -1,7 +1,7 @@ namespace JiShe.CollectBus.IoTDB.Attribute { /// - /// 用于标识当前实体为单侧点模式,单侧点模式只有一个Filed标识字段,类型是Tuple,Item1=>测点名称,Item2=>测点值,泛型 + /// 用于标识当前实体为单侧点模式,单侧点模式只有一个Filed标识字段,类型是Tuple,Item1=>测点名称,Item2=>测点值,泛型 /// [AttributeUsage(AttributeTargets.Property)] public class SingleMeasuringAttribute : System.Attribute diff --git a/modules/JiShe.CollectBus.IoTDB/Attribute/TableNameOrTreePathAttribute.cs b/modules/JiShe.CollectBus.IoTDB/Attribute/TableNameOrTreePathAttribute.cs new file mode 100644 index 0000000..1b4f4f0 --- /dev/null +++ b/modules/JiShe.CollectBus.IoTDB/Attribute/TableNameOrTreePathAttribute.cs @@ -0,0 +1,18 @@ +using JiShe.CollectBus.IoTDB.Enums; + +namespace JiShe.CollectBus.IoTDB.Attribute +{ + /// + /// IoTDB实体存储路径或表名称,一般用于已经明确的存储路径或表名称,例如日志存储 + /// + [AttributeUsage(AttributeTargets.Class)] + public class TableNameOrTreePathAttribute : System.Attribute + { + public string TableNameOrTreePath { get; } + + public TableNameOrTreePathAttribute(string tableNameOrTreePath) + { + TableNameOrTreePath = tableNameOrTreePath; + } + } +} diff --git a/modules/JiShe.CollectBus.IoTDB/CollectBusIoTDBModule.cs b/modules/JiShe.CollectBus.IoTDB/CollectBusIoTDBModule.cs index 93bbbfe..6d26bdc 100644 --- a/modules/JiShe.CollectBus.IoTDB/CollectBusIoTDBModule.cs +++ b/modules/JiShe.CollectBus.IoTDB/CollectBusIoTDBModule.cs @@ -1,33 +1,22 @@ 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 Volo.Abp.Modularity; -namespace JiShe.CollectBus.IoTDB +namespace JiShe.CollectBus.IoTDB; + +/// +/// CollectBusIoTDBModule +/// +public class CollectBusIoTDbModule : AbpModule { - public class CollectBusIoTDBModule : AbpModule + public override void ConfigureServices(ServiceConfigurationContext context) { - public override void ConfigureServices(ServiceConfigurationContext context) - { - - var configuration = context.Services.GetConfiguration(); - Configure(options => - { - configuration.GetSection(nameof(IoTDBOptions)).Bind(options); - }); + var configuration = context.Services.GetConfiguration(); + Configure(options => { configuration.GetSection(nameof(IoTDbOptions)).Bind(options); }); - // 注册上下文为Scoped - context.Services.AddScoped(); - - // 注册Session工厂 - context.Services.AddSingleton(); - - // 注册Provider - context.Services.AddScoped(); - - } + //// 注册上下文为Scoped + //context.Services.AddScoped(); } -} +} \ No newline at end of file diff --git a/modules/JiShe.CollectBus.IoTDB/Context/IoTDBRuntimeContext.cs b/modules/JiShe.CollectBus.IoTDB/Context/IoTDBRuntimeContext.cs index cd99b00..ef68325 100644 --- a/modules/JiShe.CollectBus.IoTDB/Context/IoTDBRuntimeContext.cs +++ b/modules/JiShe.CollectBus.IoTDB/Context/IoTDBRuntimeContext.cs @@ -1,23 +1,24 @@ using JiShe.CollectBus.IoTDB.Options; using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; namespace JiShe.CollectBus.IoTDB.Context { /// /// IoTDB SessionPool 运行时上下文 /// - public class IoTDBRuntimeContext + public class IoTDBRuntimeContext: IScopedDependency { private readonly bool _defaultValue; - public IoTDBRuntimeContext(IOptions options) + public IoTDBRuntimeContext(IOptions options) { _defaultValue = options.Value.UseTableSessionPoolByDefault; UseTableSessionPool = _defaultValue; } /// - /// 是否使用表模型存储, 默认false,使用tree模型存储 + /// 存储模型切换标识,是否使用table模型存储, 默认为false,标识tree模型存储 /// public bool UseTableSessionPool { get; set; } diff --git a/modules/JiShe.CollectBus.IoTDB/EnumInfo/EntityTypeEnum.cs b/modules/JiShe.CollectBus.IoTDB/EnumInfo/EntityTypeEnum.cs new file mode 100644 index 0000000..26c6645 --- /dev/null +++ b/modules/JiShe.CollectBus.IoTDB/EnumInfo/EntityTypeEnum.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDB.Enums +{ + /// + /// IoTDB实体类型枚举 + /// + public enum EntityTypeEnum + { + /// + /// 树模型 + /// + TreeModel = 1, + + /// + /// 表模型 + /// + TableModel = 2, + } +} diff --git a/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBProvider.cs b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBProvider.cs index bb47841..82a0d47 100644 --- a/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBProvider.cs +++ b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBProvider.cs @@ -1,4 +1,5 @@ using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.IoTDB.Model; using JiShe.CollectBus.IoTDB.Options; using JiShe.CollectBus.IoTDB.Provider; @@ -7,7 +8,7 @@ namespace JiShe.CollectBus.IoTDB.Interface /// /// IoTDB数据源,数据库能同时存多个时序模型,但数据是完全隔离的,不能跨时序模型查询,通过连接字符串配置 /// - public interface IIoTDBProvider + public interface IIoTDbProvider { ///// ///// 切换 SessionPool @@ -31,6 +32,15 @@ namespace JiShe.CollectBus.IoTDB.Interface /// Task BatchInsertAsync(IEnumerable entities) where T : IoTEntity; + /// + /// 批量插入数据 + /// + /// + /// 设备元数据 + /// + /// + Task BatchInsertAsync(DeviceMetadata deviceMetadata,IEnumerable entities) where T : IoTEntity; + /// /// 删除数据 @@ -38,7 +48,14 @@ namespace JiShe.CollectBus.IoTDB.Interface /// /// /// - Task DeleteAsync(QueryOptions options) where T : IoTEntity; + Task DeleteAsync(IoTDBQueryOptions options) where T : IoTEntity; + + /// + /// 获取设备元数据 + /// + /// + /// + Task GetMetadata() where T : IoTEntity; /// /// 查询数据 @@ -46,6 +63,6 @@ namespace JiShe.CollectBus.IoTDB.Interface /// /// /// - Task> QueryAsync(QueryOptions options) where T : IoTEntity, new(); + Task> QueryAsync(IoTDBQueryOptions options) where T : IoTEntity, new(); } } diff --git a/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionFactory.cs b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionFactory.cs index 03cd4a6..c2337ea 100644 --- a/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionFactory.cs +++ b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionFactory.cs @@ -3,8 +3,8 @@ /// /// Session 工厂接口 /// - public interface IIoTDBSessionFactory:IDisposable + public interface IIoTDbSessionFactory:IDisposable { - IIoTDBSessionPool GetSessionPool(bool useTableSession); + IIoTDbSessionPool GetSessionPool(bool useTableSession); } } diff --git a/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionPool.cs b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionPool.cs index 026a83a..6bcbc5c 100644 --- a/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionPool.cs +++ b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBSessionPool.cs @@ -5,7 +5,7 @@ namespace JiShe.CollectBus.IoTDB.Interface /// /// Session 连接池 /// - public interface IIoTDBSessionPool : IDisposable + public interface IIoTDbSessionPool : IDisposable { /// /// 打开连接池 @@ -13,6 +13,12 @@ namespace JiShe.CollectBus.IoTDB.Interface /// Task OpenAsync(); + /// + /// 关闭连接池 + /// + /// + Task CloseAsync(); + /// /// 插入数据 /// diff --git a/modules/JiShe.CollectBus.IoTDB/JiShe.CollectBus.IoTDB.csproj b/modules/JiShe.CollectBus.IoTDB/JiShe.CollectBus.IoTDB.csproj index dc8f2fb..8cc4b33 100644 --- a/modules/JiShe.CollectBus.IoTDB/JiShe.CollectBus.IoTDB.csproj +++ b/modules/JiShe.CollectBus.IoTDB/JiShe.CollectBus.IoTDB.csproj @@ -6,7 +6,6 @@ enable - diff --git a/modules/JiShe.CollectBus.IoTDB/Provider/IoTEntity.cs b/modules/JiShe.CollectBus.IoTDB/Model/IoTEntity.cs similarity index 66% rename from modules/JiShe.CollectBus.IoTDB/Provider/IoTEntity.cs rename to modules/JiShe.CollectBus.IoTDB/Model/IoTEntity.cs index 39a584d..a079bd4 100644 --- a/modules/JiShe.CollectBus.IoTDB/Provider/IoTEntity.cs +++ b/modules/JiShe.CollectBus.IoTDB/Model/IoTEntity.cs @@ -1,9 +1,9 @@ using JiShe.CollectBus.IoTDB.Attribute; -namespace JiShe.CollectBus.IoTDB.Provider +namespace JiShe.CollectBus.IoTDB.Model { /// - /// IoT实体基类 + /// IoT实体基类,此类适用于多个数据测点记录场景,单个测点请使用子类 SingleMeasuring /// public abstract class IoTEntity { @@ -17,7 +17,7 @@ namespace JiShe.CollectBus.IoTDB.Provider /// 项目编码 /// [TAGColumn] - public string ProjectCode { get; set; } + public string ProjectId { get; set; } /// /// 设备类型集中器、电表、水表、流量计、传感器等 @@ -26,13 +26,13 @@ namespace JiShe.CollectBus.IoTDB.Provider public string DeviceType { get; set; } /// - /// 设备ID + /// 设备ID,数据生成者,例如集中器ID,电表ID、水表ID、流量计ID、传感器ID等 /// [TAGColumn] public string DeviceId { get; set; } /// - /// 当前时间戳,单位毫秒 + /// 时标,也就是业务时间戳,单位毫秒,必须通过DateTimeOffset获取 /// public long Timestamps { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); } diff --git a/modules/JiShe.CollectBus.IoTDB/Model/TableModelSingleMeasuringEntity.cs b/modules/JiShe.CollectBus.IoTDB/Model/TableModelSingleMeasuringEntity.cs new file mode 100644 index 0000000..4308dae --- /dev/null +++ b/modules/JiShe.CollectBus.IoTDB/Model/TableModelSingleMeasuringEntity.cs @@ -0,0 +1,24 @@ +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.Enums; +using JiShe.CollectBus.IoTDB.Provider; + +namespace JiShe.CollectBus.IoTDB.Model +{ + /// + /// Table模型单项数据实体 + /// + [EntityType(EntityTypeEnum.TableModel)] + public class TableModelSingleMeasuringEntity : IoTEntity + { + /// + /// 单项数据键值对 + /// + [SingleMeasuring(nameof(SingleColumn))] + public required Tuple SingleColumn { get; set; } + } +} diff --git a/services/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs b/modules/JiShe.CollectBus.IoTDB/Model/TreeModelSingleMeasuringEntity.cs similarity index 51% rename from services/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs rename to modules/JiShe.CollectBus.IoTDB/Model/TreeModelSingleMeasuringEntity.cs index 0440c1d..9b3609c 100644 --- a/services/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs +++ b/modules/JiShe.CollectBus.IoTDB/Model/TreeModelSingleMeasuringEntity.cs @@ -4,19 +4,21 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using JiShe.CollectBus.IoTDB.Attribute; +using JiShe.CollectBus.IoTDB.Enums; using JiShe.CollectBus.IoTDB.Provider; -namespace JiShe.CollectBus.IotSystems.AFNEntity +namespace JiShe.CollectBus.IoTDB.Model { /// - /// AFN单项数据实体 + /// Tree模型单项数据实体 /// - public class SingleMeasuringAFNDataEntity : IoTEntity + [EntityType(EntityTypeEnum.TreeModel)] + public class TreeModelSingleMeasuringEntity : IoTEntity { /// - /// 单项数据对象 + /// 单项数据键值对 /// [SingleMeasuring(nameof(SingleMeasuring))] - public Tuple SingleMeasuring { get; set; } + public required Tuple SingleMeasuring { get; set; } } } diff --git a/modules/JiShe.CollectBus.IoTDB/Options/IoTDBOptions.cs b/modules/JiShe.CollectBus.IoTDB/Options/IoTDBOptions.cs index c9c0610..251e48b 100644 --- a/modules/JiShe.CollectBus.IoTDB/Options/IoTDBOptions.cs +++ b/modules/JiShe.CollectBus.IoTDB/Options/IoTDBOptions.cs @@ -3,7 +3,7 @@ /// /// IOTDB配置 /// - public class IoTDBOptions + public class IoTDbOptions { /// /// 数据库名称,表模型才有,树模型为空 @@ -42,5 +42,15 @@ /// 是否使用表模型存储, 默认false,使用tree模型存储 /// public bool UseTableSessionPoolByDefault { get; set; } = false; + + /// + /// 时区,默认为:"UTC+08:00" + /// + public string ZoneId { get; set; } = "UTC+08:00"; + + /// + /// 请求超时时间,单位毫秒,默认为:50000 + /// + public long Timeout { get; set; } = 50000; } } diff --git a/modules/JiShe.CollectBus.IoTDB/Options/QueryOptions.cs b/modules/JiShe.CollectBus.IoTDB/Options/IoTDBQueryOptions.cs similarity index 78% rename from modules/JiShe.CollectBus.IoTDB/Options/QueryOptions.cs rename to modules/JiShe.CollectBus.IoTDB/Options/IoTDBQueryOptions.cs index 90e44b2..bff4641 100644 --- a/modules/JiShe.CollectBus.IoTDB/Options/QueryOptions.cs +++ b/modules/JiShe.CollectBus.IoTDB/Options/IoTDBQueryOptions.cs @@ -3,7 +3,7 @@ /// /// 查询条件 /// - public class QueryOptions + public class IoTDBQueryOptions { /// /// 表模型的表名称或者树模型的设备路径 @@ -13,7 +13,7 @@ /// /// 分页 /// - public int Page { get; set; } + public int PageIndex { get; set; } /// /// 分页大小 @@ -23,6 +23,6 @@ /// /// 查询条件 /// - public List Conditions { get; } = new(); + public List Conditions { get; set; } = new(); } } diff --git a/modules/JiShe.CollectBus.IoTDB/Options/QueryCondition.cs b/modules/JiShe.CollectBus.IoTDB/Options/QueryCondition.cs index cf6d3a9..40dd443 100644 --- a/modules/JiShe.CollectBus.IoTDB/Options/QueryCondition.cs +++ b/modules/JiShe.CollectBus.IoTDB/Options/QueryCondition.cs @@ -9,10 +9,17 @@ /// 字段 /// public string Field { get; set; } + /// - /// 操作符 + /// 操作符,>,=,< /// public string Operator { get; set; } + + /// + /// 是否数值,如果是数值,则进行数值比较,否则进行字符串比较 + /// + public bool IsNumber { get; set; } = false; + /// /// 值 /// diff --git a/modules/JiShe.CollectBus.IoTDB/Provider/DeviceMetadata.cs b/modules/JiShe.CollectBus.IoTDB/Provider/DeviceMetadata.cs index 447f6ce..a093bb7 100644 --- a/modules/JiShe.CollectBus.IoTDB/Provider/DeviceMetadata.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/DeviceMetadata.cs @@ -1,4 +1,5 @@ using Apache.IoTDB; +using JiShe.CollectBus.IoTDB.Enums; namespace JiShe.CollectBus.IoTDB.Provider { @@ -7,6 +8,11 @@ namespace JiShe.CollectBus.IoTDB.Provider /// public class DeviceMetadata { + /// + /// IoTDB实体类型枚举 + /// + public EntityTypeEnum EntityType { get; set; } + /// /// 是否有单测量值 /// diff --git a/modules/JiShe.CollectBus.IoTDB/Provider/DevicePathBuilder.cs b/modules/JiShe.CollectBus.IoTDB/Provider/DevicePathBuilder.cs index 46ce091..6a1a596 100644 --- a/modules/JiShe.CollectBus.IoTDB/Provider/DevicePathBuilder.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/DevicePathBuilder.cs @@ -1,4 +1,6 @@ -namespace JiShe.CollectBus.IoTDB.Provider +using JiShe.CollectBus.IoTDB.Model; + +namespace JiShe.CollectBus.IoTDB.Provider { /// /// 设备路径构建器 @@ -13,7 +15,7 @@ /// public static string GetDevicePath(T entity) where T : IoTEntity { - return $"root.{entity.SystemName.ToLower()}.`{entity.ProjectCode}`.`{entity.DeviceId}`"; + return $"root.{entity.SystemName.ToLower()}.`{entity.ProjectId}`.`{entity.DeviceType}`.`{entity.DeviceId}`"; } @@ -28,6 +30,17 @@ var type = typeof(T); return $"{type.Name.ToLower()}"; } + + /// + /// 获取表名称,用作单侧点表模型特殊处理。 + /// + /// + /// + /// + public static string GetDeviceTableName(T entity) where T : IoTEntity + { + return $"{entity.SystemName.ToLower()}.`{entity.ProjectId}`.`{entity.DeviceType}`.`{entity.DeviceId}`"; + } } } diff --git a/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBProvider.cs b/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBProvider.cs index 9e18ac8..9c7c602 100644 --- a/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBProvider.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBProvider.cs @@ -1,33 +1,49 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; +using System.ComponentModel.DataAnnotations; using System.Reflection; +using System.Reflection.Metadata.Ecma335; using System.Text; +using System.Threading.Tasks; using Apache.IoTDB; using Apache.IoTDB.DataStructure; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IoTDB.Attribute; using JiShe.CollectBus.IoTDB.Context; using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Model; using JiShe.CollectBus.IoTDB.Options; using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities; namespace JiShe.CollectBus.IoTDB.Provider { /// /// IoTDB数据源 /// - public class IoTDBProvider : IIoTDBProvider + public class IoTDbProvider : IIoTDbProvider, ITransientDependency { - private static readonly ConcurrentDictionary _metadataCache = new(); - private readonly ILogger _logger; - private readonly IIoTDBSessionFactory _sessionFactory; + private static readonly ConcurrentDictionary MetadataCache = new(); + private readonly ILogger _logger; + private readonly IIoTDbSessionFactory _sessionFactory; private readonly IoTDBRuntimeContext _runtimeContext; - private IIoTDBSessionPool CurrentSession => + private IIoTDbSessionPool CurrentSession => _sessionFactory.GetSessionPool(_runtimeContext.UseTableSessionPool); - public IoTDBProvider( - ILogger logger, - IIoTDBSessionFactory sessionFactory, + /// + /// IoTDbProvider + /// + /// + /// + /// + public IoTDbProvider( + ILogger logger, + IIoTDbSessionFactory sessionFactory, IoTDBRuntimeContext runtimeContext) { _logger = logger; @@ -44,17 +60,19 @@ namespace JiShe.CollectBus.IoTDB.Provider /// public async Task InsertAsync(T entity) where T : IoTEntity { - var metadata = GetMetadata(); + try + { + var metadata = await GetMetadata(); - var tablet = BuildTablet(new[] { entity }, metadata); + var tablet = BuildTablet(new[] { entity }, metadata); - await CurrentSession.InsertAsync(tablet); - - //int result = await _currentSession.InsertAsync(tablet); - //if (result <= 0) - //{ - // _logger.LogError($"{typeof(T).Name}插入数据没有成功"); - //} + await CurrentSession.InsertAsync(tablet); + } + catch (Exception ex) + { + _logger.LogError(ex, $"{nameof(InsertAsync)} 插入数据时发生异常"); + throw; + } } /// @@ -64,20 +82,51 @@ namespace JiShe.CollectBus.IoTDB.Provider /// public async Task BatchInsertAsync(IEnumerable entities) where T : IoTEntity { - var metadata = GetMetadata(); - - var batchSize = 1000; - var batches = entities.Chunk(batchSize); - - foreach (var batch in batches) + try { - var tablet = BuildTablet(batch, metadata); - await CurrentSession.InsertAsync(tablet); - //var result = await _currentSession.InsertAsync(tablet); - //if (result <= 0) - //{ - // _logger.LogWarning($"{typeof(T).Name} 批量插入数据第{batch}批次没有成功,共{batches}批次。"); - //} + var metadata = await GetMetadata(); + + var batchSize = 1000; + var batches = entities.Chunk(batchSize); + + foreach (var batch in batches) + { + var tablet = BuildTablet(batch, metadata); + await CurrentSession.InsertAsync(tablet); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"{nameof(BatchInsertAsync)} 批量插入数据时发生异常"); + throw; + } + } + + /// + /// 批量插入数据 + /// + /// + /// 设备元数据 + /// + /// + public async Task BatchInsertAsync(DeviceMetadata deviceMetadata, IEnumerable entities) where T : IoTEntity + { + try + { + + var batchSize = 1000; + var batches = entities.Chunk(batchSize); + + foreach (var batch in batches) + { + var tablet = BuildTablet(batch, deviceMetadata); + await CurrentSession.InsertAsync(tablet); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"{nameof(BatchInsertAsync)} 批量插入数据时发生异常"); + throw; } } @@ -88,20 +137,54 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// /// - public async Task DeleteAsync(QueryOptions options) where T : IoTEntity + public async Task DeleteAsync(IoTDBQueryOptions options) where T : IoTEntity { - var query = BuildDeleteSQL(options); - var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query); - - if (!sessionDataSet.HasNext()) + try { - _logger.LogWarning($"{typeof(T).Name} 删除数据时,没有返回受影响记录数量。"); - return 0; - } + var query = await BuildDeleteSQL(options); + var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query); - //获取唯一结果行 - var row = sessionDataSet.Next(); - return row.Values[0]; + if (!sessionDataSet.HasNext()) + { + _logger.LogWarning($"{typeof(T).Name} 删除数据时,没有返回受影响记录数量。"); + return 0; + } + + //获取唯一结果行 + var row = sessionDataSet.Next(); + return row.Values[0]; + } + catch (Exception ex) + { + _logger.LogError(ex, $"{nameof(DeleteAsync)} 删除数据时发生异常"); + throw; + } + } + + /// + /// 获取设备元数据 + /// + /// + /// + public async Task GetMetadata() where T : IoTEntity + { + var columns = CollectColumnMetadata(typeof(T)); + var metadata = BuildDeviceMetadata(columns); + var metaData = 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 await Task.FromResult(metaData); } /// @@ -110,18 +193,33 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// /// - public async Task> QueryAsync(QueryOptions options) where T : IoTEntity, new() + public async Task> QueryAsync(IoTDBQueryOptions options) where T : IoTEntity, new() { - var query = BuildQuerySQL(options); - var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query); - - var result = new BusPagedResult + try { - TotalCount = await GetTotalCount(options), - Items = ParseResults(sessionDataSet, options.PageSize) - }; + var query = await BuildQuerySQL(options); + var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query); - return result; + + var result = new BusPagedResult + { + TotalCount = await GetTotalCount(options), + Items = await ParseResults(sessionDataSet, options.PageSize), + PageIndex = options.PageIndex, + PageSize = options.PageSize, + + }; + + result.HasNext = result.TotalCount > 0 ? result.TotalCount < result.PageSize : false; + + return result; + } + catch (Exception ex) + { + CurrentSession.Dispose(); + _logger.LogError(ex, $"{nameof(QueryAsync)} IoTDB查询数据时发生异常"); + throw; + } } /// @@ -139,10 +237,39 @@ namespace JiShe.CollectBus.IoTDB.Provider List tempColumnNames = new List(); tempColumnNames.AddRange(metadata.ColumnNames); + var entityTypeAttribute = typeof(T).GetCustomAttribute(); + + if (entityTypeAttribute == null) + { + throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 没有指定,属于异常情况,-101"); + } + + if (metadata.EntityType != entityTypeAttribute.EntityType) + { + throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 和{nameof(DeviceMetadata)}的 EntityType 不一致,属于异常情况,-102"); + } + + if (metadata.EntityType == Enums.EntityTypeEnum.TreeModel && _runtimeContext.UseTableSessionPool == true) + { + throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 tree模型不能使用table模型Session连接,属于异常情况,-103"); + } + else if (metadata.EntityType == Enums.EntityTypeEnum.TableModel && _runtimeContext.UseTableSessionPool == false) + { + throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 table模型不能使用tree模型Session连接,属于异常情况,-104"); + } + + string tableNameOrTreePath = string.Empty; + var tableNameOrTreePathAttribute = typeof(T).GetCustomAttribute(); + if (tableNameOrTreePathAttribute != null) + { + tableNameOrTreePath = tableNameOrTreePathAttribute.TableNameOrTreePath; + } + foreach (var entity in entities) { timestamps.Add(entity.Timestamps); var rowValues = new List(); + foreach (var measurement in tempColumnNames) { @@ -153,51 +280,79 @@ namespace JiShe.CollectBus.IoTDB.Provider } var value = propertyInfo.GetValue(entity); - if (propertyInfo.IsDefined(typeof(SingleMeasuringAttribute), false) && value != null)//表示当前对象是单测点模式 - { - Type tupleType = value.GetType(); - Type[] tupleArgs = tupleType.GetGenericArguments(); - Type item2Type = tupleArgs[1]; // T 的实际类型 - 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。"); - } - - var indexOf = metadata.ColumnNames.IndexOf(measurement); - metadata.ColumnNames[indexOf] = (string)item1!; - - rowValues.Add(item2); - - } - else + if (propertyInfo.IsDefined(typeof(SingleMeasuringAttribute), false) && metadata.IsSingleMeasuring == true)//表示当前对象是单测点模式 { if (value != null) { - rowValues.Add(value); + Type tupleType = value.GetType(); + Type[] tupleArgs = tupleType.GetGenericArguments(); + Type item2Type = tupleArgs[1]; // T 的实际类型 + 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。"); + } + + var indexOf = metadata.ColumnNames.IndexOf(measurement); + metadata.ColumnNames[indexOf] = (string)item1!; + + rowValues.Add(item2); } else { - //填充默认数据值 - DataTypeDefaultValueMap.TryGetValue(propertyInfo.PropertyType.Name, out object defaultValue); - - rowValues.Add(defaultValue); + rowValues.Add(null); } + + //同时如果是单测点模式,且是table模型存储,路径只能通过DevicePathBuilder.GetDeviceTableName(entity)获取 + if (_runtimeContext.UseTableSessionPool) + { + tableNameOrTreePath = DevicePathBuilder.GetDeviceTableName(entity); + } + } + else + { + + //需要根据value的类型,进行相应的值映射转换,例如datetime转换为long的时间戳值 + if (value != null) + { + Type tupleType = value.GetType(); + var tempValue = tupleType.Name.ToUpper() switch + { + "DATETIME" => Convert.ToDateTime(value).GetDateTimeOffset().ToUnixTimeNanoseconds(), + _ => value + }; + + rowValues.Add(tempValue); + } + else + { + rowValues.Add(value); + } + } } values.Add(rowValues); - if (!_runtimeContext.UseTableSessionPool)//树模型 + //如果指定了路径 + if (!string.IsNullOrWhiteSpace(tableNameOrTreePath)) { - devicePaths.Add(DevicePathBuilder.GetDevicePath(entity)); + devicePaths.Add(tableNameOrTreePath); } else { - devicePaths.Add(DevicePathBuilder.GetTableName()); + if (!_runtimeContext.UseTableSessionPool)//树模型 + { + devicePaths.Add(DevicePathBuilder.GetDevicePath(entity)); + } + else + { + devicePaths.Add(DevicePathBuilder.GetTableName()); + } } + } if (devicePaths.Count > 1) @@ -213,14 +368,15 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// 构建tree模型的Tablet /// - /// - /// - /// - /// + /// 已解析的设备数据元数据 + /// 设备路径 + /// 数据集合 + /// 时间戳集合 /// - private Tablet BuildSessionTablet(DeviceMetadata metadata, string devicePath, - List> values, List timestamps) + private Tablet BuildSessionTablet(DeviceMetadata metadata, string devicePath, List> values, List timestamps) { + //todo 树模型需要去掉TAG类型和ATTRIBUTE类型的字段,只需要保留FIELD类型字段即可 + return new Tablet( devicePath, metadata.ColumnNames, @@ -233,16 +389,15 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// 构建表模型的Tablet /// - /// - /// - /// - /// + /// 已解析的设备数据元数据 + /// 表名称 + /// 数据集合 + /// 时间戳集合 /// - private Tablet BuildTableSessionTablet(DeviceMetadata metadata, string devicePath, - List> values, List timestamps) + private Tablet BuildTableSessionTablet(DeviceMetadata metadata, string tableName, List> values, List timestamps) { var tablet = new Tablet( - devicePath, + tableName, metadata.ColumnNames, metadata.ColumnCategories, metadata.DataTypes, @@ -259,10 +414,10 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// /// - private string BuildQuerySQL(QueryOptions options) where T : IoTEntity + private async Task BuildQuerySQL(IoTDBQueryOptions options) where T : IoTEntity { - var metadata = GetMetadata(); - var sb = new StringBuilder("SELECT "); + var metadata = await GetMetadata(); + var sb = new StringBuilder("SELECT TIME as Timestamps,"); sb.AppendJoin(", ", metadata.ColumnNames); sb.Append($" FROM {options.TableNameOrTreePath}"); @@ -272,7 +427,7 @@ namespace JiShe.CollectBus.IoTDB.Provider sb.AppendJoin(" AND ", options.Conditions.Select(TranslateCondition)); } - sb.Append($" LIMIT {options.PageSize} OFFSET {options.Page * options.PageSize}"); + sb.Append($" LIMIT {options.PageSize} OFFSET {options.PageIndex * options.PageSize}"); return sb.ToString(); } @@ -282,9 +437,9 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// /// - private string BuildDeleteSQL(QueryOptions options) where T : IoTEntity + private async Task BuildDeleteSQL(IoTDBQueryOptions options) where T : IoTEntity { - var metadata = GetMetadata(); + var metadata = await GetMetadata(); var sb = new StringBuilder(); if (!_runtimeContext.UseTableSessionPool) @@ -319,10 +474,10 @@ namespace JiShe.CollectBus.IoTDB.Provider { return condition.Operator switch { - ">" => $"{condition.Field} > {condition.Value}", - "<" => $"{condition.Field} < {condition.Value}", - "=" => $"{condition.Field} = '{condition.Value}'", - _ => throw new NotSupportedException($"Operator {condition.Operator} not supported") + ">" => condition.IsNumber ? $"{condition.Field} > {condition.Value}" : $"{condition.Field} > '{condition.Value}'", + "<" => condition.IsNumber ? $"{condition.Field} < {condition.Value}" : $"{condition.Field} < '{condition.Value}'", + "=" => condition.IsNumber ? $"{condition.Field} = {condition.Value}" : $"{condition.Field} = '{condition.Value}'", + _ => throw new NotSupportedException($"{nameof(TranslateCondition)} 将查询条件转换为SQL语句时操作符 {condition.Operator} 属于异常情况") }; } @@ -332,7 +487,7 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// /// - private async Task GetTotalCount(QueryOptions options) where T : IoTEntity + private async Task GetTotalCount(IoTDBQueryOptions options) where T : IoTEntity { var countQuery = $"SELECT COUNT(*) FROM {options.TableNameOrTreePath}"; if (options.Conditions.Any()) @@ -341,7 +496,16 @@ namespace JiShe.CollectBus.IoTDB.Provider } var result = await CurrentSession.ExecuteQueryStatementAsync(countQuery); - return result.HasNext() ? Convert.ToInt32(result.Next().Values[0]) : 0; + if (result.HasNext()) + { + await result.Close(); + return 0; + } + + var count = Convert.ToInt32(result.Next().Values[0]); + await result.Close(); + + return count; } /// @@ -351,13 +515,20 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// /// - private IEnumerable ParseResults(SessionDataSet dataSet, int pageSize) where T : IoTEntity, new() + private async Task> ParseResults(SessionDataSet dataSet, int pageSize) where T : IoTEntity, new() { var results = new List(); - var metadata = GetMetadata(); + var metadata = await GetMetadata(); var properties = typeof(T).GetProperties(); + var columns = new List() { "Timestamps" }; + var dataTypes = new List() { TSDataType.TIMESTAMP }; + columns.AddRange(metadata.ColumnNames); + dataTypes.AddRange(metadata.DataTypes); + //metadata.ColumnNames.Insert(0, "Timestamps"); + //metadata.DataTypes.Insert(0, TSDataType.TIMESTAMP); + while (dataSet.HasNext() && results.Count < pageSize) { var record = dataSet.Next(); @@ -366,62 +537,37 @@ namespace JiShe.CollectBus.IoTDB.Provider Timestamps = record.Timestamps }; - - foreach (var measurement in metadata.ColumnNames) + foreach (var measurement in columns) { - var value = record.Values; + int indexOf = columns.IndexOf(measurement); + var value = record.Values[indexOf]; + TSDataType tSDataType = dataTypes[indexOf]; var prop = properties.FirstOrDefault(p => p.Name.Equals(measurement, StringComparison.OrdinalIgnoreCase)); - if (prop != null) + if (prop != null && !(value is System.DBNull)) { - typeof(T).GetProperty(measurement)?.SetValue(entity, value); + dynamic tempValue = GetTSDataValue(tSDataType, value); + + if (measurement.ToLower().EndsWith("time")) + { + typeof(T).GetProperty(measurement)?.SetValue(entity, TimestampHelper.ConvertToDateTime(tempValue, TimestampUnit.Nanoseconds)); + } + else + { + typeof(T).GetProperty(measurement)?.SetValue(entity, tempValue); + } } } results.Add(entity); + } + await dataSet.Close(); return results; } - /// - /// 获取设备元数据 - /// - /// - /// - private DeviceMetadata GetMetadata() where T : IoTEntity - { - - 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; - //}); - } - /// /// 获取设备元数据的列 /// @@ -433,21 +579,36 @@ namespace JiShe.CollectBus.IoTDB.Provider foreach (var prop in type.GetProperties()) { + + string typeName = string.Empty; + + Type declaredType = prop.PropertyType; + // 处理可空类型 + if (declaredType.IsGenericType && declaredType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + Type underlyingType = Nullable.GetUnderlyingType(declaredType); + typeName = underlyingType.Name; + } + else + { + typeName = declaredType.Name; + } + //先获取Tag标签和属性标签 ColumnInfo? column = prop.GetCustomAttribute() is not null ? new ColumnInfo( name: prop.Name, category: ColumnCategory.TAG, - dataType: GetDataTypeFromTypeName(prop.PropertyType.Name), + dataType: GetDataTypeFromTypeName(typeName), false ) : prop.GetCustomAttribute() is not null ? new ColumnInfo( prop.Name, ColumnCategory.ATTRIBUTE, - GetDataTypeFromTypeName(prop.PropertyType.Name), + GetDataTypeFromTypeName(typeName), false ) : prop.GetCustomAttribute() is not null ? new ColumnInfo( prop.Name, ColumnCategory.FIELD, - GetDataTypeFromTypeName(prop.PropertyType.Name), + GetDataTypeFromTypeName(typeName), false) : null; @@ -483,9 +644,10 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// 构建设备元数据 /// - /// + /// 待解析的类 + /// 已处理好的数据列 /// - private DeviceMetadata BuildDeviceMetadata(List columns) + private DeviceMetadata BuildDeviceMetadata(List columns) where T : IoTEntity { var metadata = new DeviceMetadata(); @@ -504,6 +666,15 @@ namespace JiShe.CollectBus.IoTDB.Provider ProcessCategory(groupedColumns, ColumnCategory.ATTRIBUTE, metadata); ProcessCategory(groupedColumns, ColumnCategory.FIELD, metadata); + var entityTypeAttribute = typeof(T).GetCustomAttribute(); + + if (entityTypeAttribute == null) + { + throw new ArgumentException($"{nameof(BuildDeviceMetadata)} 构建设备元数据时 {nameof(IoTEntity)} 的EntityType 没有指定,属于异常情况,-101"); + } + + metadata.EntityType = entityTypeAttribute.EntityType; + return metadata; } @@ -585,7 +756,7 @@ namespace JiShe.CollectBus.IoTDB.Provider ["DOUBLE"] = TSDataType.DOUBLE, ["TEXT"] = TSDataType.TEXT, ["NULLTYPE"] = TSDataType.NONE, - ["TIMESTAMP"] = TSDataType.TIMESTAMP, + ["DATETIME"] = TSDataType.TIMESTAMP, ["DATE"] = TSDataType.DATE, ["BLOB"] = TSDataType.BLOB, ["DECIMAL"] = TSDataType.STRING, @@ -605,11 +776,34 @@ namespace JiShe.CollectBus.IoTDB.Provider ["DOUBLE"] = 0.0d, ["TEXT"] = string.Empty, ["NULLTYPE"] = null, - ["TIMESTAMP"] = null, + ["DATETIME"] = null, ["DATE"] = null, ["BLOB"] = null, ["DECIMAL"] = "0.0", ["STRING"] = string.Empty }; + + /// + /// IoTDB 数据类型与.net类型映射 + /// + /// + /// + /// + private dynamic GetTSDataValue(TSDataType tSDataType, object value) => + tSDataType switch + { + TSDataType.BOOLEAN => Convert.ToBoolean(value), + TSDataType.INT32 => Convert.ToInt32(value), + TSDataType.INT64 => Convert.ToInt64(value), + TSDataType.FLOAT => Convert.ToDouble(value), + TSDataType.DOUBLE => Convert.ToDouble(value), + TSDataType.TEXT => Convert.ToString(value), + TSDataType.NONE => null, + TSDataType.TIMESTAMP => Convert.ToInt64(value), + TSDataType.DATE => Convert.ToDateTime(value), + TSDataType.BLOB => Convert.ToByte(value), + TSDataType.STRING => Convert.ToString(value), + _ => Convert.ToString(value) + }; } } diff --git a/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBSessionFactory.cs b/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBSessionFactory.cs index 572e93d..27b9200 100644 --- a/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBSessionFactory.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBSessionFactory.cs @@ -2,6 +2,7 @@ using JiShe.CollectBus.IoTDB.Interface; using JiShe.CollectBus.IoTDB.Options; using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; namespace JiShe.CollectBus.IoTDB.Provider { @@ -9,25 +10,29 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// 实现带缓存的Session工厂 /// - public class IoTDBSessionFactory : IIoTDBSessionFactory + public class IoTDbSessionFactory : IIoTDbSessionFactory, ISingletonDependency { - private readonly IoTDBOptions _options; - private readonly ConcurrentDictionary _pools = new(); + private readonly IoTDbOptions _options; + private readonly ConcurrentDictionary _pools = new(); private bool _disposed; - public IoTDBSessionFactory(IOptions options) + /// + /// IoTDbSessionFactory + /// + /// + public IoTDbSessionFactory(IOptions options) { _options = options.Value; } - public IIoTDBSessionPool GetSessionPool(bool useTableSession) + public IIoTDbSessionPool GetSessionPool(bool useTableSession) { - if (_disposed) throw new ObjectDisposedException(nameof(IoTDBSessionFactory)); + if (_disposed) throw new ObjectDisposedException(nameof(IoTDbSessionFactory)); return _pools.GetOrAdd(useTableSession, key => { var pool = key - ? (IIoTDBSessionPool)new TableSessionPoolAdapter(_options) + ? (IIoTDbSessionPool)new TableSessionPoolAdapter(_options) : new SessionPoolAdapter(_options); pool.OpenAsync().ConfigureAwait(false).GetAwaiter().GetResult(); ; diff --git a/modules/JiShe.CollectBus.IoTDB/Provider/SessionPoolAdapter.cs b/modules/JiShe.CollectBus.IoTDB/Provider/SessionPoolAdapter.cs index 44692bd..ee71cf1 100644 --- a/modules/JiShe.CollectBus.IoTDB/Provider/SessionPoolAdapter.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/SessionPoolAdapter.cs @@ -9,18 +9,23 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// 树模型连接池 /// - public class SessionPoolAdapter : IIoTDBSessionPool + public class SessionPoolAdapter : IIoTDbSessionPool { private readonly SessionPool _sessionPool; - private readonly IoTDBOptions _options; + private readonly IoTDbOptions _options; - public SessionPoolAdapter(IoTDBOptions options) + /// + /// SessionPoolAdapter + /// + /// + public SessionPoolAdapter(IoTDbOptions options) { _options = options; _sessionPool = new SessionPool.Builder() .SetNodeUrl(options.ClusterList) .SetUsername(options.UserName) .SetPassword(options.Password) + .SetZoneId(options.ZoneId) .SetFetchSize(options.FetchSize) .SetPoolSize(options.PoolSize) .Build(); @@ -42,6 +47,19 @@ namespace JiShe.CollectBus.IoTDB.Provider } } + /// + /// 关闭连接池 + /// + /// + public async Task CloseAsync() + { + if (_sessionPool == null) + { + return; + } + await _sessionPool.Close(); + } + /// /// 批量插入对齐时间序列数据 /// @@ -52,9 +70,9 @@ namespace JiShe.CollectBus.IoTDB.Provider var result = await _sessionPool.InsertAlignedTabletAsync(tablet); if (result != 0) { - throw new Exception($"{nameof(TableSessionPoolAdapter)} "); + throw new Exception($"{nameof(SessionPoolAdapter)} Tree模型数据入库没有成功,返回结果为:{result}"); } - + //await CloseAsync(); return result; } @@ -65,7 +83,10 @@ namespace JiShe.CollectBus.IoTDB.Provider /// public async Task ExecuteQueryStatementAsync(string sql) { - return await _sessionPool.ExecuteQueryStatementAsync(sql); + var result = await _sessionPool.ExecuteQueryStatementAsync(sql, _options.Timeout); + //await result.Close(); + //await CloseAsync(); + return result; } public void Dispose() diff --git a/modules/JiShe.CollectBus.IoTDB/Provider/TableSessionPoolAdapter.cs b/modules/JiShe.CollectBus.IoTDB/Provider/TableSessionPoolAdapter.cs index 1efd04f..dc4f0ee 100644 --- a/modules/JiShe.CollectBus.IoTDB/Provider/TableSessionPoolAdapter.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/TableSessionPoolAdapter.cs @@ -9,18 +9,23 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// 表模型Session连接池 /// - public class TableSessionPoolAdapter : IIoTDBSessionPool + public class TableSessionPoolAdapter : IIoTDbSessionPool { private readonly TableSessionPool _sessionPool; - private readonly IoTDBOptions _options; + private readonly IoTDbOptions _options; - public TableSessionPoolAdapter(IoTDBOptions options) + /// + /// TableSessionPoolAdapter + /// + /// + public TableSessionPoolAdapter(IoTDbOptions options) { _options = options; _sessionPool = new TableSessionPool.Builder() .SetNodeUrls(options.ClusterList) .SetUsername(options.UserName) .SetPassword(options.Password) + .SetZoneId(options.ZoneId) .SetFetchSize(options.FetchSize) .SetPoolSize(options.PoolSize) .SetDatabase(options.DataBaseName) @@ -40,6 +45,19 @@ namespace JiShe.CollectBus.IoTDB.Provider } } + /// + /// 关闭连接池 + /// + /// + public async Task CloseAsync() + { + if (_sessionPool == null) + { + return; + } + await _sessionPool.Close(); + } + /// /// 批量插入 /// @@ -50,9 +68,10 @@ namespace JiShe.CollectBus.IoTDB.Provider var result = await _sessionPool.InsertAsync(tablet); if (result != 0) { - throw new Exception($"{nameof(TableSessionPoolAdapter)} "); + throw new Exception($"{nameof(TableSessionPoolAdapter)} table模型数据入库没有成功,返回结果为:{result}"); } + //await CloseAsync(); return result; } @@ -63,7 +82,10 @@ namespace JiShe.CollectBus.IoTDB.Provider /// public async Task ExecuteQueryStatementAsync(string sql) { - return await _sessionPool.ExecuteQueryStatementAsync(sql); + var result = await _sessionPool.ExecuteQueryStatementAsync(sql,_options.Timeout); + //await result.Close(); + //await CloseAsync(); + return result; } public void Dispose() diff --git a/modules/JiShe.CollectBus.Kafka.Test/KafkaSubscribeTest.cs b/modules/JiShe.CollectBus.Kafka.Test/KafkaSubscribeTest.cs index 4c06e22..c7ad309 100644 --- a/modules/JiShe.CollectBus.Kafka.Test/KafkaSubscribeTest.cs +++ b/modules/JiShe.CollectBus.Kafka.Test/KafkaSubscribeTest.cs @@ -3,6 +3,7 @@ using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.Kafka.Attributes; +using JiShe.CollectBus.Kafka.Internal; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; @@ -16,53 +17,54 @@ namespace JiShe.CollectBus.Kafka.Test { public class KafkaSubscribeTest: IKafkaSubscribe { - [KafkaSubscribe(ProtocolConst.TESTTOPIC, EnableBatch=false,BatchSize=1000)] + [KafkaSubscribe(ProtocolConst.TESTTOPIC, EnableBatch = false, BatchSize = 10)] - public async Task KafkaSubscribeAsync(object obj) + public async Task KafkaSubscribeAsync(TestTopic obj) + //public async Task KafkaSubscribeAsync(IEnumerable 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.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.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.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.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(); - } + //[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/Program.cs b/modules/JiShe.CollectBus.Kafka.Test/Program.cs index a359e14..3c99810 100644 --- a/modules/JiShe.CollectBus.Kafka.Test/Program.cs +++ b/modules/JiShe.CollectBus.Kafka.Test/Program.cs @@ -7,6 +7,7 @@ using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Kafka; using JiShe.CollectBus.Kafka.AdminClient; using JiShe.CollectBus.Kafka.Consumer; +using JiShe.CollectBus.Kafka.Internal; using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Kafka.Test; using Microsoft.AspNetCore.Builder; @@ -62,6 +63,7 @@ var host = Host.CreateDefaultBuilder(args) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddTransient(); }) @@ -86,12 +88,13 @@ var logger = loggerFactory.CreateLogger(); logger.LogInformation("程序启动"); var adminClientService = host.Services.GetRequiredService(); var configuration = host.Services.GetRequiredService(); -string topic = "test-topic"; +string topic = ProtocolConst.TESTTOPIC; //await adminClientService.DeleteTopicAsync(topic); // 创建 topic //await adminClientService.CreateTopicAsync(topic, configuration.GetValue(CommonConst.NumPartitions), 3); var consumerService = host.Services.GetRequiredService(); +var producerService = host.Services.GetRequiredService(); //var kafkaOptions = host.Services.GetRequiredService>(); //await consumerService.SubscribeAsync(topic, (message) => //{ @@ -113,43 +116,49 @@ var consumerService = host.Services.GetRequiredService(); //for (int i = 0; i < 3; i++) //{ -// await consumerService.SubscribeBatchAsync(topic, (message) => +//await consumerService.SubscribeBatchAsync(topic, (message) => +//{ +// try // { -// try -// { -// int index = 0; -// logger.LogInformation($"消费消息_{index}消费总数:{message.Count()}:{JsonSerializer.Serialize(message)}"); -// return Task.FromResult(true); +// 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); -// }); +// } +// 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) + +int num = 1; +while (num <= 6) +{ + await producerService.ProduceAsync(topic, new TestTopic { Topic = topic, Val = num }); + num++; +} + +//int num = 2; +//while (num <= 4) //{ -// //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++; - } -}); +//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) { diff --git a/modules/JiShe.CollectBus.Kafka.Test/appsettings.json b/modules/JiShe.CollectBus.Kafka.Test/appsettings.json index b2579c6..9767dee 100644 --- a/modules/JiShe.CollectBus.Kafka.Test/appsettings.json +++ b/modules/JiShe.CollectBus.Kafka.Test/appsettings.json @@ -91,8 +91,8 @@ "SaslUserName": "lixiao", "SaslPassword": "lixiao1980", "KafkaReplicationFactor": 3, - "NumPartitions": 1, - "ServerTagName": "JiSheCollectBus2" + "NumPartitions": 30, + "ServerTagName": "JiSheCollectBus99" //"Topic": { // "ReplicationFactor": 3, // "NumPartitions": 1000 diff --git a/modules/JiShe.CollectBus.Kafka/AdminClient/AdminClientService.cs b/modules/JiShe.CollectBus.Kafka/AdminClient/AdminClientService.cs index 59e34fa..66643a5 100644 --- a/modules/JiShe.CollectBus.Kafka/AdminClient/AdminClientService.cs +++ b/modules/JiShe.CollectBus.Kafka/AdminClient/AdminClientService.cs @@ -1,204 +1,190 @@ 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.Configuration; using Microsoft.Extensions.Logging; using Volo.Abp.DependencyInjection; -namespace JiShe.CollectBus.Kafka.AdminClient +namespace JiShe.CollectBus.Kafka.AdminClient; + +public class AdminClientService : IAdminClientService, IDisposable, ISingletonDependency { - public class AdminClientService : IAdminClientService, IDisposable,ISingletonDependency + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public AdminClientService(IConfiguration configuration, ILogger logger) { + _logger = logger; + Instance = GetInstance(configuration); + } - private readonly ILogger _logger; + /// + /// Gets or sets the instance. + /// + /// + /// The instance. + /// + public IAdminClient Instance { get; set; } - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The logger. - public AdminClientService(IConfiguration configuration, ILogger logger) + /// + /// 创建Kafka主题 + /// + /// + /// + /// + /// + public async Task CreateTopicAsync(string topic, int numPartitions, short replicationFactor) + { + try { - _logger = logger; - GetInstance(configuration); - } + if (await CheckTopicAsync(topic)) return; - /// - /// Gets or sets the instance. - /// - /// - /// The instance. - /// - public IAdminClient Instance { get; set; } = default; - /// - /// Gets the instance. - /// - /// The configuration. - /// - public IAdminClient GetInstance(IConfiguration configuration) - { - ArgumentNullException.ThrowIfNullOrWhiteSpace(configuration["Kafka:EnableAuthorization"]); - var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]!); - var adminClientConfig = new AdminClientConfig() + await Instance.CreateTopicsAsync(new[] { - 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)); - } - - /// - /// 判断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) - { - - try - { - if (await CheckTopicAsync(topic)) return; - - - await Instance.CreateTopicsAsync(new[] + new TopicSpecification { - new TopicSpecification - { - Name = topic, - NumPartitions = numPartitions, - ReplicationFactor = replicationFactor - } - }); - } - catch (CreateTopicsException e) - { - if (e.Results[0].Error.Code != ErrorCode.TopicAlreadyExists) - { - throw; + Name = topic, + NumPartitions = numPartitions, + ReplicationFactor = replicationFactor } - } + }); } - - /// - /// 删除Kafka主题 - /// - /// - /// - public async Task DeleteTopicAsync(string topic) + catch (CreateTopicsException e) { - 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)); - 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 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(); + if (e.Results[0].Error.Code != ErrorCode.TopicAlreadyExists) throw; } } -} + + /// + /// 删除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)); + 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 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(); + } + + /// + /// Gets the instance. + /// + /// The configuration. + /// + public IAdminClient GetInstance(IConfiguration configuration) + { + ArgumentException.ThrowIfNullOrWhiteSpace(configuration["Kafka:EnableAuthorization"]); + 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"]; + } + return new AdminClientBuilder(adminClientConfig).Build(); + } + + /// + /// 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)); + } + + /// + /// 判断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)); + } +} \ No newline at end of file diff --git a/modules/JiShe.CollectBus.Kafka/Attributes/KafkaSubscribeAttribute.cs b/modules/JiShe.CollectBus.Kafka/Attributes/KafkaSubscribeAttribute.cs index c74aa2e..3b2cfae 100644 --- a/modules/JiShe.CollectBus.Kafka/Attributes/KafkaSubscribeAttribute.cs +++ b/modules/JiShe.CollectBus.Kafka/Attributes/KafkaSubscribeAttribute.cs @@ -1,68 +1,60 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace JiShe.CollectBus.Kafka.Attributes; -namespace JiShe.CollectBus.Kafka.Attributes +[AttributeUsage(AttributeTargets.Method)] +public class KafkaSubscribeAttribute : Attribute { - [AttributeUsage(AttributeTargets.Method)] - public class KafkaSubscribeAttribute : Attribute + /// + /// 订阅主题 + /// + /// + public KafkaSubscribeAttribute(string topic) { - /// - /// 订阅的主题 - /// - public string Topic { get; set; } = null!; - - /// - /// 分区 - /// - public int Partition { get; set; } = -1; - - /// - /// 消费者组 - /// - public string GroupId { get; set; } = "default"; - - /// - /// 任务数(默认是多少个分区多少个任务) - /// 如设置订阅指定Partition则任务数始终为1 - /// - public int TaskCount { get; set; } = -1; - - /// - /// 批量处理数量 - /// - public int BatchSize { get; set; } = 100; - - /// - /// 是否启用批量处理 - /// - public bool EnableBatch { get; set; } = false; - - /// - /// 批次超时时间 - /// 格式:("00:05:00") - /// - public TimeSpan? BatchTimeout { get; set; }=null; - - - /// - /// 订阅主题 - /// - /// - public KafkaSubscribeAttribute(string topic) - { - this.Topic = topic; - } - - /// - /// 订阅主题 - /// - public KafkaSubscribeAttribute(string topic, int partition) - { - this.Topic = topic; - this.Partition = partition; - } + Topic = topic; } -} + + /// + /// 订阅主题 + /// + public KafkaSubscribeAttribute(string topic, int partition) + { + Topic = topic; + Partition = partition; + } + + /// + /// 订阅的主题 + /// + public string Topic { get; set; } = null!; + + /// + /// 分区 + /// + public int Partition { get; set; } = -1; + + /// + /// 消费者组 + /// + public string? GroupId { get; set; } = null; //"default" + + /// + /// 任务数(默认是多少个分区多少个任务) + /// 如设置订阅指定Partition则任务数始终为1 + /// + public int TaskCount { get; set; } = -1; + + /// + /// 批量处理数量 + /// + public int BatchSize { get; set; } = 100; + + /// + /// 是否启用批量处理 + /// + public bool EnableBatch { get; set; } = false; + + /// + /// 批次超时时间 + /// 格式:("00:05:00") + /// + public TimeSpan? BatchTimeout { get; set; } = null; +} \ No newline at end of file diff --git a/modules/JiShe.CollectBus.Kafka/Attributes/TopicAttribute.cs b/modules/JiShe.CollectBus.Kafka/Attributes/TopicAttribute.cs index 4cb2fff..a023edb 100644 --- a/modules/JiShe.CollectBus.Kafka/Attributes/TopicAttribute.cs +++ b/modules/JiShe.CollectBus.Kafka/Attributes/TopicAttribute.cs @@ -1,29 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace JiShe.CollectBus.Kafka.Attributes; -namespace JiShe.CollectBus.Kafka.Attributes +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public class TopicAttribute : Attribute { - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - public class TopicAttribute: Attribute + /// + /// Initializes a new instance of the class. + /// + /// The name. + public TopicAttribute(string name = "Default") { - /// - /// 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; } + Name = name; } -} + + /// + /// Gets or sets the name. + /// + /// + /// The name. + /// + public string Name { get; set; } +} \ No newline at end of file diff --git a/modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs b/modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs index b467162..d31b9ed 100644 --- a/modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs +++ b/modules/JiShe.CollectBus.Kafka/CollectBusKafkaModule.cs @@ -1,6 +1,7 @@ using Confluent.Kafka; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Kafka.Consumer; +using JiShe.CollectBus.Kafka.Internal; using JiShe.CollectBus.Kafka.Producer; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; @@ -40,13 +41,25 @@ namespace JiShe.CollectBus.Kafka // 注册Consumer context.Services.AddSingleton(); + // 注册Polly + context.Services.AddSingleton(); + //context.Services.AddHostedService(); } + /// + /// 在初始化之前,初始化Kafka Topic + /// + /// + public override void OnPreApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + app.ApplicationServices.UseInitKafkaTopic(); + } + public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); - // 注册Subscriber app.ApplicationServices.UseKafkaSubscribe(); diff --git a/modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs b/modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs index 0ec5bd0..fc853e9 100644 --- a/modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs +++ b/modules/JiShe.CollectBus.Kafka/Consumer/ConsumerService.cs @@ -1,27 +1,51 @@ using Confluent.Kafka; +using JiShe.CollectBus.Common; using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Kafka.Internal; +using JiShe.CollectBus.Kafka.Serialization; +using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Collections.Concurrent; using System.Text; +using System.Text.RegularExpressions; +using System.Threading; namespace JiShe.CollectBus.Kafka.Consumer { public class ConsumerService : IConsumerService, IDisposable { private readonly ILogger _logger; - private readonly IConfiguration _configuration; - private readonly ConcurrentDictionary + /// + /// 消费者存储 + /// Key 格式:{groupId}_{topic}_{TKey}_{TValue} + /// + private readonly ConcurrentDictionary _consumerStore = new(); - private readonly KafkaOptionConfig _kafkaOptionConfig; - private class KafkaConsumer where TKey : notnull where TValue : class { } - public ConsumerService(IConfiguration configuration, ILogger logger, IOptions kafkaOptionConfig) + /// + /// 消费完或者无数据时的延迟时间 + /// + private TimeSpan DelayTime => TimeSpan.FromMilliseconds(100); + + private readonly KafkaOptionConfig _kafkaOptionConfig; + + private readonly ServerApplicationOptions _applicationOptions; + + private readonly KafkaPollyPipeline _kafkaPollyPipeline; + + /// + /// ConsumerService + /// + /// + /// + public ConsumerService(ILogger logger, IOptions kafkaOptionConfig, KafkaPollyPipeline kafkaPollyPipeline, IOptions applicationOptions) { - _configuration = configuration; _logger = logger; _kafkaOptionConfig = kafkaOptionConfig.Value; + _applicationOptions = applicationOptions.Value; + _kafkaPollyPipeline = kafkaPollyPipeline; } #region private 私有方法 @@ -47,11 +71,11 @@ namespace JiShe.CollectBus.Kafka.Consumer var config = new ConsumerConfig { BootstrapServers = _kafkaOptionConfig.BootstrapServers, - GroupId = groupId ?? "default", + GroupId = groupId ?? _applicationOptions.ServerTagName, AutoOffsetReset = AutoOffsetReset.Earliest, EnableAutoCommit = false, // 禁止AutoCommit EnablePartitionEof = true, // 启用分区末尾标记 - AllowAutoCreateTopics = true, // 启用自动创建 + //AllowAutoCreateTopics = true, // 启用自动创建 FetchMaxBytes = 1024 * 1024 * 50 // 增加拉取大小(50MB) }; @@ -90,7 +114,7 @@ namespace JiShe.CollectBus.Kafka.Consumer /// public async Task SubscribeAsync(string topic, Func> messageHandler, string? groupId = null) where TValue : class { - await SubscribeAsync(new[] { topic }, messageHandler,groupId); + await SubscribeAsync(new[] { topic }, messageHandler, groupId); } /// @@ -103,59 +127,75 @@ 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(KafkaConsumer); - var cts = new CancellationTokenSource(); - - //var consumer = _consumerStore.GetOrAdd(consumerKey, _ => - //( - // CreateConsumer(groupId), - // cts - //)).Consumer as IConsumer; - var consumer = CreateConsumer(groupId); - consumer!.Subscribe(topics); - - await Task.Run(async () => + await _kafkaPollyPipeline.KafkaPipeline.ExecuteAsync(async token => { - 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) - continue; - - if (result.IsPartitionEOF) + var consumerKey = $"{groupId}_{string.Join("_", topics)}_{typeof(TKey).Name}_{typeof(TValue).Name}"; + var cts = new CancellationTokenSource(); + + var consumer = _consumerStore.GetOrAdd(consumerKey, _ => + ( + CreateConsumer(groupId), + cts + )).Consumer as IConsumer; + + consumer!.Subscribe(topics); + + _ = Task.Run(async () => + { + while (!cts.IsCancellationRequested) + { + try { - _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); - await Task.Delay(TimeSpan.FromSeconds(1),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)} 开始拉取消息...."); + + var result = consumer.Consume(cts.Token); + if (result == null || result.Message == null || result.Message.Value == null) { - //consumer.Commit(result); // 提交偏移量 - // 跳过消息 + await Task.Delay(DelayTime, cts.Token); continue; } + if (result.IsPartitionEOF) + { +#if DEBUG + _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); +#endif + await Task.Delay(DelayTime, cts.Token); + continue; + } + if (_kafkaOptionConfig.EnableFilter) + { + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) } }; + // 检查 Header 是否符合条件 + if (!headersFilter.Match(result.Message.Headers)) + { + consumer.Commit(result); // 提交偏移量 + // 跳过消息 + continue; + } + } + bool sucess = await messageHandler(result.Message.Key, result.Message.Value); + if (sucess) + consumer.Commit(result); // 手动提交 } - bool sucess= await messageHandler(result.Message.Key, result.Message.Value); - if (sucess) + catch (ConsumeException ex) when (KafkaPollyPipeline.IsRecoverableError(ex)) { - consumer.Commit(result); // 手动提交 + _logger.LogError(ex, $"{string.Join("、", topics)}消息消费失败: {ex.Error.Reason}"); + throw; // 抛出异常,以便重试 + } + catch (OperationCanceledException) + { + //ignore + } + catch (Exception ex) + { + _logger.LogError(ex, "处理消息时发生未知错误"); } } - catch (ConsumeException ex) - { - _logger.LogError(ex, $"{string.Join("、", topics)}消息消费失败: {ex.Error.Reason}"); - } - } + }, cts.Token); + await Task.CompletedTask; }); - await Task.CompletedTask; + } @@ -163,27 +203,23 @@ namespace JiShe.CollectBus.Kafka.Consumer /// /// 订阅消息 /// - /// /// /// /// + /// /// public async Task SubscribeAsync(string[] topics, Func> messageHandler, string? groupId) where TValue : class { - try { - var consumerKey = typeof(KafkaConsumer); + await _kafkaPollyPipeline.KafkaPipeline.ExecuteAsync(async token => + { + var consumerKey = $"{groupId}_{string.Join("_", topics)}_{typeof(Ignore).Name}_{typeof(TValue).Name}"; var cts = new CancellationTokenSource(); - //if (topics.Contains(ProtocolConst.SubscriberLoginReceivedEventName)) - //{ - // string ssss = ""; - //} - //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); _ = Task.Run(async () => @@ -198,24 +234,25 @@ namespace JiShe.CollectBus.Kafka.Consumer var result = consumer.Consume(cts.Token); if (result == null || result.Message == null || result.Message.Value == null) { - await Task.Delay(500, cts.Token); + await Task.Delay(DelayTime, cts.Token); continue; } if (result.IsPartitionEOF) { +#if DEBUG _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); - await Task.Delay(100, cts.Token); +#endif + await Task.Delay(DelayTime, cts.Token); continue; } if (_kafkaOptionConfig.EnableFilter) { - var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } }; + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) } }; // 检查 Header 是否符合条件 if (!headersFilter.Match(result.Message.Headers)) { - await Task.Delay(500, cts.Token); - //consumer.Commit(result); // 提交偏移量 + consumer.Commit(result); // 提交偏移量 // 跳过消息 continue; } @@ -223,22 +260,26 @@ namespace JiShe.CollectBus.Kafka.Consumer bool sucess = await messageHandler(result.Message.Value); if (sucess) consumer.Commit(result); // 手动提交 - else - consumer.StoreOffset(result); + //else + // consumer.StoreOffset(result); } - catch (ConsumeException ex) + catch (ConsumeException ex) when (KafkaPollyPipeline.IsRecoverableError(ex)) { _logger.LogError(ex, $"{string.Join("、", topics)}消息消费失败: {ex.Error.Reason}"); + throw; // 抛出异常,以便重试 + } + catch (OperationCanceledException) + { + //ignore + } + catch (Exception ex) + { + _logger.LogError(ex, "处理消息时发生未知错误"); } } - }); - } catch (Exception ex) - { - _logger.LogWarning($"Kafka消费异常: {ex.Message}"); - - } - - await Task.CompletedTask; + }, cts.Token); + await Task.CompletedTask; + }); } @@ -252,7 +293,7 @@ namespace JiShe.CollectBus.Kafka.Consumer /// 消费组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 + 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); } @@ -267,109 +308,114 @@ namespace JiShe.CollectBus.Kafka.Consumer /// 消费组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 + 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 () => + await _kafkaPollyPipeline.KafkaPipeline.ExecuteAsync(async token => { - var messages = new List<(TValue Value, TopicPartitionOffset Offset)>(); - var startTime = DateTime.UtcNow; - while (!cts.IsCancellationRequested) + var consumerKey = $"{groupId}_{string.Join("_", topics)}_{typeof(TKey).Name}_{typeof(TValue).Name}"; + 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 () => { - try - { - // 非阻塞快速累积消息 - while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout) - { - var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用 + var messages = new List<(TValue Value, TopicPartitionOffset Offset)>(); + var startTime = DateTime.UtcNow; - if (result != null) + while (!cts.IsCancellationRequested) + { + try + { + // 非阻塞快速累积消息 + while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout) { - if (result.IsPartitionEOF) + var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用 + + if (result != null) { - _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); - await Task.Delay(TimeSpan.FromSeconds(1), cts.Token); - } - else if (result.Message.Value != null) - { - if (_kafkaOptionConfig.EnableFilter) + if (result.IsPartitionEOF) { - var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } }; - // 检查 Header 是否符合条件 - if (!headersFilter.Match(result.Message.Headers)) +#if DEBUG + _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); +#endif + await Task.Delay(DelayTime, cts.Token); + } + else if (result.Message.Value != null) + { + if (_kafkaOptionConfig.EnableFilter) { - //consumer.Commit(result); // 提交偏移量 - // 跳过消息 - continue; + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) } }; + // 检查 Header 是否符合条件 + if (!headersFilter.Match(result.Message.Headers)) + { + consumer.Commit(result); // 提交偏移量 + // 跳过消息 + continue; + } + } + messages.Add((result.Message.Value, result.TopicPartitionOffset)); + } + } + else + { + // 无消息时短暂等待 + await Task.Delay(DelayTime, cts.Token); + } + } + + // 处理批次 + if (messages.Count > 0) + { + bool success = await messageBatchHandler(messages.Select(m => m.Value).ToList()); + 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; } } - messages.Add((result.Message.Value, result.TopicPartitionOffset)); - //messages.Add(result.Message.Value); - } - } - else - { - // 无消息时短暂等待 - await Task.Delay(10, cts.Token); - } - } - // 处理批次 - if (messages.Count > 0) + 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) when (KafkaPollyPipeline.IsRecoverableError(ex)) { - 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(); + _logger.LogError(ex, $"{string.Join("、", topics)} 消息消费失败: {ex.Error.Reason}"); + throw; // 抛出异常,以便重试 } + catch (OperationCanceledException) + { + //ignore + } + catch (Exception ex) + { + _logger.LogError(ex, "处理批量消息时发生未知错误"); + } + } + }, cts.Token); - startTime = DateTime.UtcNow; - } - catch (ConsumeException ex) - { - _logger.LogError(ex, $"{string.Join("、", topics)} 消息消费失败: {ex.Error.Reason}"); - } - catch (OperationCanceledException) - { - // 任务取消,正常退出 - } - catch (Exception ex) - { - _logger.LogError(ex, "处理批量消息时发生未知错误"); - } - } - }, cts.Token); - - await Task.CompletedTask; + await Task.CompletedTask; + }); } @@ -383,9 +429,9 @@ namespace JiShe.CollectBus.Kafka.Consumer /// 批次大小 /// 批次超时时间 /// 消费等待时间 - public async Task SubscribeBatchAsync(string topic, Func, Task> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null, TimeSpan? consumeTimeout = null) where TValue : class + 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); + await SubscribeBatchAsync(new[] { topic }, messageBatchHandler, groupId, batchSize, batchTimeout, consumeTimeout); } @@ -400,110 +446,113 @@ 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 + 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 () => + await _kafkaPollyPipeline.KafkaPipeline.ExecuteAsync(async token => { - var messages = new List<(TValue Value, TopicPartitionOffset Offset)>(); - //var messages = new List>(); - var startTime = DateTime.UtcNow; - while (!cts.IsCancellationRequested) + var consumerKey = $"{groupId}_{string.Join("_", topics)}_{typeof(Ignore).Name}_{typeof(TValue).Name}"; + 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 () => { - try - { - // 非阻塞快速累积消息 - while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout) - { - var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用 + var messages = new List<(TValue Value, TopicPartitionOffset Offset)>(); + var startTime = DateTime.UtcNow; - if (result != null) + while (!cts.IsCancellationRequested) + { + try + { + // 非阻塞快速累积消息 + while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout) { - if (result.IsPartitionEOF) + var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用 + + if (result != null) { - _logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); - await Task.Delay(TimeSpan.FromSeconds(1), cts.Token); - } - else if (result.Message.Value != null) - { - if (_kafkaOptionConfig.EnableFilter) + if (result.IsPartitionEOF) { - var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } }; - // 检查 Header 是否符合条件 - if (!headersFilter.Match(result.Message.Headers)) + //_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition); + await Task.Delay(DelayTime, cts.Token); + } + else if (result.Message.Value != null) + { + if (_kafkaOptionConfig.EnableFilter) { - //consumer.Commit(result); // 提交偏移量 - // 跳过消息 - continue; + var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) } }; + // 检查 Header 是否符合条件 + if (!headersFilter.Match(result.Message.Headers)) + { + consumer.Commit(result); // 提交偏移量 + // 跳过消息 + continue; + } + } + messages.Add((result.Message.Value, result.TopicPartitionOffset)); + } + } + else + { + // 无消息时短暂等待 + await Task.Delay(DelayTime, cts.Token); + } + } + + // 处理批次 + if (messages.Count > 0) + { + bool success = await messageBatchHandler(messages.Select(m => m.Value).ToList()); + 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; } } - messages.Add((result.Message.Value, result.TopicPartitionOffset)); - //messages.Add(result.Message.Value); - } - } - else - { - // 无消息时短暂等待 - await Task.Delay(10, cts.Token); - } - } - // 处理批次 - if (messages.Count > 0) + 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) when (KafkaPollyPipeline.IsRecoverableError(ex)) { - 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(); + _logger.LogError(ex, $"消息消费失败: {ex.Error.Reason}"); + throw; // 抛出异常,以便重试 } + catch (OperationCanceledException) + { + //ignore + } + catch (Exception ex) + { + _logger.LogError(ex, "处理批量消息时发生未知错误"); + } + } + }, cts.Token); - 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; + await Task.CompletedTask; + }); } @@ -512,9 +561,9 @@ namespace JiShe.CollectBus.Kafka.Consumer /// /// /// - public void Unsubscribe() where TKey : notnull where TValue : class + public void Unsubscribe(string[] topics, string? groupId) where TKey : notnull where TValue : class { - var consumerKey = typeof((TKey, TValue)); + var consumerKey = $"{groupId}_{string.Join("_", topics)}_{typeof(TKey).Name}_{typeof(TValue).Name}"; if (_consumerStore.TryRemove(consumerKey, out var entry)) { entry.CTS.Cancel(); diff --git a/modules/JiShe.CollectBus.Kafka/Consumer/IConsumerService.cs b/modules/JiShe.CollectBus.Kafka/Consumer/IConsumerService.cs index d86dba8..b4b4274 100644 --- a/modules/JiShe.CollectBus.Kafka/Consumer/IConsumerService.cs +++ b/modules/JiShe.CollectBus.Kafka/Consumer/IConsumerService.cs @@ -1,46 +1,50 @@ -using Confluent.Kafka; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace JiShe.CollectBus.Kafka.Consumer; -namespace JiShe.CollectBus.Kafka.Consumer +public interface IConsumerService { - public interface IConsumerService - { - Task SubscribeAsync(string topic, Func> messageHandler, string? groupId=null) where TKey : notnull where TValue : class; + Task SubscribeAsync(string topic, Func> messageHandler, + string? groupId = null) where TKey : notnull where TValue : class; - /// - /// 订阅消息 - /// - /// - /// - /// - /// - Task SubscribeAsync(string topic, Func> messageHandler, string? groupId = null) where TValue : class; + /// + /// 订阅消息 + /// + /// + /// + /// + /// + 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, string? groupId) + where TKey : notnull where TValue : class; - /// - /// 订阅消息 - /// - /// - /// - /// - /// - /// - Task SubscribeAsync(string[] topics, Func> messageHandler, string? groupId = null) where TValue : class; + /// + /// 订阅消息 + /// + /// + /// + /// + /// + /// + 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[] 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 topic, 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, 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; + 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; - } -} + void Unsubscribe(string[] topics, string groupId) where TKey : notnull where TValue : class; +} \ No newline at end of file diff --git a/modules/JiShe.CollectBus.Kafka/HeadersFilter.cs b/modules/JiShe.CollectBus.Kafka/HeadersFilter.cs deleted file mode 100644 index 0790f9f..0000000 --- a/modules/JiShe.CollectBus.Kafka/HeadersFilter.cs +++ /dev/null @@ -1,30 +0,0 @@ -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/modules/JiShe.CollectBus.Kafka/IKafkaSubscribe.cs b/modules/JiShe.CollectBus.Kafka/IKafkaSubscribe.cs deleted file mode 100644 index 39e5789..0000000 --- a/modules/JiShe.CollectBus.Kafka/IKafkaSubscribe.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.Kafka -{ - /// - /// Kafka订阅者 - /// - /// 订阅者需要继承此接口并需要依赖注入,并使用标记 - /// - /// - public interface IKafkaSubscribe - { - } -} diff --git a/modules/JiShe.CollectBus.Kafka/ISubscribeAck.cs b/modules/JiShe.CollectBus.Kafka/ISubscribeAck.cs deleted file mode 100644 index ffb30ef..0000000 --- a/modules/JiShe.CollectBus.Kafka/ISubscribeAck.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.Kafka -{ - public interface ISubscribeAck - { - /// - /// 是否成功标记 - /// - bool Ack { get; set; } - - /// - /// 消息 - /// - string? Msg { get; set; } - } -} diff --git a/modules/JiShe.CollectBus.Kafka/Internal/HeadersFilter.cs b/modules/JiShe.CollectBus.Kafka/Internal/HeadersFilter.cs new file mode 100644 index 0000000..8bcdcdc --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka/Internal/HeadersFilter.cs @@ -0,0 +1,22 @@ +using Confluent.Kafka; + +namespace JiShe.CollectBus.Kafka.Internal; + +/// +/// 消息头过滤器 +/// +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; + } +} \ No newline at end of file diff --git a/modules/JiShe.CollectBus.Kafka/Internal/IKafkaSubscribe.cs b/modules/JiShe.CollectBus.Kafka/Internal/IKafkaSubscribe.cs new file mode 100644 index 0000000..5345f1b --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka/Internal/IKafkaSubscribe.cs @@ -0,0 +1,11 @@ +namespace JiShe.CollectBus.Kafka.Internal; + +/// +/// Kafka订阅者 +/// +/// 订阅者需要继承此接口并需要依赖注入,并使用标记 +/// +/// +public interface IKafkaSubscribe +{ +} \ No newline at end of file diff --git a/modules/JiShe.CollectBus.Kafka/Internal/ISubscribeAck.cs b/modules/JiShe.CollectBus.Kafka/Internal/ISubscribeAck.cs new file mode 100644 index 0000000..27512f7 --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka/Internal/ISubscribeAck.cs @@ -0,0 +1,14 @@ +namespace JiShe.CollectBus.Kafka.Internal; + +public interface ISubscribeAck +{ + /// + /// 是否成功标记 + /// + bool Ack { get; set; } + + /// + /// 消息 + /// + string? Msg { get; set; } +} \ No newline at end of file diff --git a/modules/JiShe.CollectBus.Kafka/Internal/KafkaOptionConfig.cs b/modules/JiShe.CollectBus.Kafka/Internal/KafkaOptionConfig.cs new file mode 100644 index 0000000..38c9482 --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka/Internal/KafkaOptionConfig.cs @@ -0,0 +1,52 @@ +using Confluent.Kafka; + +namespace JiShe.CollectBus.Kafka.Internal; + +public class KafkaOptionConfig +{ + /// + /// kafka地址 + /// + public string BootstrapServers { get; set; } = null!; + + /// + /// kafka主题副本数量 + /// + public short KafkaReplicationFactor { get; set; } + + /// + /// kafka主题分区数量 + /// + public int NumPartitions { get; set; } + + /// + /// 是否开启过滤器 + /// + 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; } + +} \ No newline at end of file diff --git a/modules/JiShe.CollectBus.Kafka/Internal/KafkaPollyPipeline.cs b/modules/JiShe.CollectBus.Kafka/Internal/KafkaPollyPipeline.cs new file mode 100644 index 0000000..c467921 --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka/Internal/KafkaPollyPipeline.cs @@ -0,0 +1,111 @@ +using Confluent.Kafka; +using Polly.CircuitBreaker; +using Polly.Retry; +using Polly; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Polly.Contrib.WaitAndRetry; +using Volo.Abp.DependencyInjection; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Kafka.Producer; + +namespace JiShe.CollectBus.Kafka.Internal +{ + public class KafkaPollyPipeline + { + + private readonly ILogger _logger; + public KafkaPollyPipeline(ILogger logger) + { + _logger= logger; + } + + /// + /// 判断是否可恢复的异常 + /// + /// + /// + public static bool IsRecoverableError(Exception ex) + { + var errorList= new List + { + ErrorCode.GroupLoadInProgress, + ErrorCode.Local_Retry, + ErrorCode.Local_MaxPollExceeded, + ErrorCode.RequestTimedOut, + ErrorCode.LeaderNotAvailable, + ErrorCode.NotLeaderForPartition, + ErrorCode.RebalanceInProgress, + ErrorCode.NotCoordinatorForGroup, + ErrorCode.NetworkException, + ErrorCode.GroupCoordinatorNotAvailable + }; + return ex switch + { + ConsumeException kafkaEx => errorList.Contains(kafkaEx.Error.Code), + KafkaException kafkaEx =>kafkaEx.Error.IsFatal && errorList.Contains(kafkaEx.Error.Code), + _ => false + }; + + } + + /// + /// 创建重试 + 断路器 + /// + /// + public ResiliencePipeline KafkaPipeline + { + get + { + // 组合重试 + 断路器 + ResiliencePipeline pipeline = new ResiliencePipelineBuilder() + .AddRetry(new RetryStrategyOptions + { + ShouldHandle = args => args.Outcome.Exception switch + { + not null when IsRecoverableError(args.Outcome.Exception) => + PredicateResult.True(), + _ => PredicateResult.False() + }, + Delay = TimeSpan.FromSeconds(2), + OnRetry = args => + { + _logger.LogWarning($"重试中... 第 {args.AttemptNumber} 次,原因: {args.Outcome.Exception?.Message}"); + return default; + } + }) + .AddCircuitBreaker(new CircuitBreakerStrategyOptions + { + ShouldHandle = args => args.Outcome.Exception switch + { + not null when IsRecoverableError(args.Outcome.Exception) => + PredicateResult.True(), + _ => PredicateResult.False() + }, + FailureRatio = 0.8, // 80% 失败触发熔断 + SamplingDuration = TimeSpan.FromSeconds(10), + MinimumThroughput = 4, // 至少4次调用才计算失败率 + BreakDuration = TimeSpan.FromSeconds(10), + OnOpened = args => + { + _logger.LogWarning($"熔断器开启,等待 {args.BreakDuration} 后重试"); + return default; + }, + OnClosed = _ => + { + _logger.LogWarning("熔断器关闭,再次开始重试"); + return default; + } + }) + .Build(); + return pipeline; + } + + } + + + } +} diff --git a/modules/JiShe.CollectBus.Kafka/Internal/ReflectionHelper.cs b/modules/JiShe.CollectBus.Kafka/Internal/ReflectionHelper.cs new file mode 100644 index 0000000..a39db81 --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka/Internal/ReflectionHelper.cs @@ -0,0 +1,103 @@ +using System.Collections; +using System.Reflection; + +namespace JiShe.CollectBus.Kafka.Internal; + +/// +/// 反射辅助类 +/// +public static class ReflectionHelper +{ + /// + /// 集合类型 + /// Item1:参数类型 + /// Item2:集合元素类型 + /// + public static Tuple GetParameterTypeInfo(this MethodInfo method, int parameterIndex = 0) + { + // 参数校验 + if (method == null) throw new ArgumentNullException(nameof(method)); + var parameters = method.GetParameters(); + if (parameterIndex < 0 || parameterIndex >= parameters.Length) + throw new ArgumentOutOfRangeException(nameof(parameterIndex)); + + var param = parameters[parameterIndex]; + var paramType = param.ParameterType; + Type? elementType = null; + + // 判断是否是集合类型(排除字符串) + if (paramType != typeof(string) && IsEnumerableType(paramType)) + elementType = GetEnumerableElementType(paramType); + + return Tuple.Create(paramType, elementType); + } + + /// + /// 判断是否是集合类型(排除字符串) + /// + public static bool IsEnumerableType(this Type type) + { + return type.IsArray + || (type.IsGenericType && type.GetInterfaces() + .Any(t => t.IsGenericType + && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + || type.GetInterfaces().Any(t => t == typeof(IEnumerable)); + } + + /// + /// 获取集合元素的类型 + /// + public static Type? GetEnumerableElementType(this Type type) + { + // 处理数组类型 + if (type.IsArray) + return type.GetElementType(); + + // 处理直接实现IEnumerable的类型(如IEnumerable本身) + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + return type.GetGenericArguments()[0]; + + // 处理通过接口实现IEnumerable的泛型集合(如List) + var genericEnumerable = type.GetInterfaces() + .FirstOrDefault(t => t.IsGenericType + && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + if (genericEnumerable != null) + return genericEnumerable.GetGenericArguments()[0]; + + // 处理非泛型集合类型(如 ArrayList) + if (typeof(IEnumerable).IsAssignableFrom(type) && type == typeof(ArrayList)) + return typeof(ArrayList); + // 返回null表示无法确定元素类型 + return null; + } + + + /// + /// 判断是否使用强转换 + /// + /// + /// + public static bool IsConvertType(this Type targetType) + { + // 处理可空类型 + var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; + // 情况1:值类型或基元类型(如 int、DateTime) + if (underlyingType.IsValueType || underlyingType.IsPrimitive) + return true; + // 情况2:字符串类型直接赋值 + if (underlyingType == typeof(string)) + return true; + + // 情况3:枚举类型处理 + //else if (underlyingType.IsEnum) + //{ + // if (Enum.IsDefined(underlyingType, msg)) + // { + // convertedValue = Enum.Parse(underlyingType, msg.ToString()); + // return true; + // } + // return false; + //} + return false; + } +} \ No newline at end of file diff --git a/modules/JiShe.CollectBus.Kafka/Internal/SubscribeResult.cs b/modules/JiShe.CollectBus.Kafka/Internal/SubscribeResult.cs new file mode 100644 index 0000000..d3fbcf6 --- /dev/null +++ b/modules/JiShe.CollectBus.Kafka/Internal/SubscribeResult.cs @@ -0,0 +1,62 @@ +namespace JiShe.CollectBus.Kafka.Internal; + +public class SubscribeResult : ISubscribeAck +{ + /// + /// 是否成功 + /// + public bool Ack { get; set; } + + /// + /// 消息 + /// + public string? Msg { get; set; } + + + /// + /// 成功 + /// + /// 消息 + public SubscribeResult Success(string? msg = null) + { + Ack = true; + Msg = msg; + return this; + } + + /// + /// 失败 + /// + /// + /// + public SubscribeResult Fail(string? msg = null) + { + Msg = msg; + Ack = false; + return this; + } +} + +public static class SubscribeAck +{ + /// + /// 成功 + /// + /// 消息 + /// + public static ISubscribeAck Success(string? msg = null) + { + return new SubscribeResult().Success(msg); + } + + + /// + /// 失败 + /// + /// 消息 + /// + public static ISubscribeAck Fail(string? msg = null) + { + return new SubscribeResult().Fail(msg); + } +} \ No newline at end of file diff --git a/modules/JiShe.CollectBus.Kafka/JiShe.CollectBus.Kafka.csproj b/modules/JiShe.CollectBus.Kafka/JiShe.CollectBus.Kafka.csproj index ce31120..a0fd2f7 100644 --- a/modules/JiShe.CollectBus.Kafka/JiShe.CollectBus.Kafka.csproj +++ b/modules/JiShe.CollectBus.Kafka/JiShe.CollectBus.Kafka.csproj @@ -8,6 +8,8 @@ + + diff --git a/modules/JiShe.CollectBus.Kafka/KafkaOptionConfig.cs b/modules/JiShe.CollectBus.Kafka/KafkaOptionConfig.cs deleted file mode 100644 index e592ea2..0000000 --- a/modules/JiShe.CollectBus.Kafka/KafkaOptionConfig.cs +++ /dev/null @@ -1,63 +0,0 @@ -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"; - - /// - /// kafka主题副本数量 - /// - public short KafkaReplicationFactor { get; set; } - - /// - /// kafka主题分区数量 - /// - public int NumPartitions { get; set; } - - /// - /// 是否开启过滤器 - /// - 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/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs b/modules/JiShe.CollectBus.Kafka/KafkaSubscribeExtensions.cs similarity index 61% rename from modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs rename to modules/JiShe.CollectBus.Kafka/KafkaSubscribeExtensions.cs index ff60130..3c50aae 100644 --- a/modules/JiShe.CollectBus.Kafka/KafkaSubcribesExtensions.cs +++ b/modules/JiShe.CollectBus.Kafka/KafkaSubscribeExtensions.cs @@ -5,46 +5,49 @@ using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Kafka.AdminClient; using JiShe.CollectBus.Kafka.Attributes; using JiShe.CollectBus.Kafka.Consumer; +using JiShe.CollectBus.Kafka.Internal; +using JiShe.CollectBus.Kafka.Serialization; 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 Newtonsoft.Json; -using System.Collections.Generic; +using System.Collections; +using System.ComponentModel; using System.Reflection; -using System.Threading.Tasks; namespace JiShe.CollectBus.Kafka { - public static class KafkaSubcribesExtensions + public static class KafkaSubscribeExtensions { - /// - /// 添加Kafka订阅 - /// - /// - /// - public static void UseKafkaSubscribe(this IServiceProvider provider) - { - var lifetime = provider.GetRequiredService(); + public static void UseInitKafkaTopic(this IServiceProvider provider) + { //初始化主题信息 var kafkaAdminClient = provider.GetRequiredService(); var kafkaOptions = provider.GetRequiredService>(); - List topics = ProtocolConstExtensions.GetAllTopicNamesByIssued(); + var 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(); } + } + + /// + /// 添加Kafka订阅 + /// + public static void UseKafkaSubscribe(this IServiceProvider provider) + { + var lifetime = provider.GetRequiredService(); + var kafkaOptions = provider.GetRequiredService>(); lifetime.ApplicationStarted.Register(() => { var logger = provider.GetRequiredService>(); - int threadCount = 0; - int topicCount = 0; + var threadCount = 0; + var topicCount = 0; var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); if (string.IsNullOrWhiteSpace(assemblyPath)) { @@ -62,7 +65,7 @@ namespace JiShe.CollectBus.Kafka // 实现IKafkaSubscribe接口 var subscribeTypes = assembly.GetTypes().Where(type => typeof(IKafkaSubscribe).IsAssignableFrom(type) && - !type.IsAbstract && !type.IsInterface).ToList(); ; + !type.IsAbstract && !type.IsInterface).ToList(); if (subscribeTypes.Count == 0) continue; @@ -84,21 +87,14 @@ namespace JiShe.CollectBus.Kafka }); } + /// + /// 添加Kafka订阅 + /// 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()); - - 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>(); @@ -130,9 +126,7 @@ namespace JiShe.CollectBus.Kafka /// /// 构建Kafka订阅 /// - /// - /// - private static Tuple BuildKafkaSubscribe(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() }) @@ -140,14 +134,14 @@ namespace JiShe.CollectBus.Kafka .ToArray(); //var configuration = provider.GetRequiredService(); int threadCount = 0; - + 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; + partitionCount = partitionCount > topicCount ? topicCount : partitionCount; #endif //int partitionCount = sub.Attribute!.TaskCount==-1?adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic) : sub.Attribute!.TaskCount; if (partitionCount <= 0) @@ -165,23 +159,18 @@ namespace JiShe.CollectBus.Kafka /// /// 启动后台消费线程 /// - /// - /// - /// - /// - /// - private static async Task StartConsumerAsync(IServiceProvider provider, KafkaSubscribeAttribute attr,MethodInfo method, object subscribe, ILogger logger) + private static async Task StartConsumerAsync(IServiceProvider provider, KafkaSubscribeAttribute attr, MethodInfo method, object subscribe, ILogger logger) { var consumerService = provider.GetRequiredService(); if (attr.EnableBatch) { - await consumerService.SubscribeBatchAsync(attr.Topic, async (message) => + await consumerService.SubscribeBatchAsync(attr.Topic, async (message) => { try { #if DEBUG - logger.LogInformation($"kafka批量消费消息:{message}"); + logger.LogInformation($"kafka批量消费消息:{message.Serialize()}"); #endif // 处理消息 return await ProcessMessageAsync(message.ToList(), method, subscribe); @@ -196,7 +185,7 @@ namespace JiShe.CollectBus.Kafka } else { - await consumerService.SubscribeAsync(attr.Topic, async (message) => + await consumerService.SubscribeAsync(attr.Topic, async (message) => { try { @@ -221,30 +210,112 @@ namespace JiShe.CollectBus.Kafka /// /// 处理消息 /// - /// - /// - /// - /// - private static async Task ProcessMessageAsync(List messages, 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; - List? messageObj = null; + object[]? executeParameters = null; + if (existParameters) { - messageObj = new List(); - var paramType = parameters[0].ParameterType; - foreach (var msg in messages) + IList? list = null; + Tuple tuple = method.GetParameterTypeInfo(); + bool isEnumerable = false; + if (tuple.Item2 != null) { - var data = paramType != typeof(string) ? msg?.ToString()?.Deserialize(paramType) : msg; - if (data != null) - messageObj.Add(data); + Type listType = typeof(List<>).MakeGenericType(tuple.Item2); + list = (IList)Activator.CreateInstance(listType)!; + isEnumerable = tuple.Item2.IsConvertType(); + } + else + { + isEnumerable = tuple.Item1.IsConvertType(); + } + #region 暂时 + //foreach (var msg in messages) + //{ + // if (tuple.Item2 != null) + // { + // if (isEnumerable) + // { + // var parameterType = parameters[0].ParameterType; + // var data=messages?.Serialize().Deserialize(parameterType); + // messageObj = data!=null? new[] { data }:null; + // break; + // } + // else + // { + // // 集合类型 + // var data = msg?.Serialize().Deserialize(tuple.Item2) /*isEnumerable ? Convert.ChangeType(msg, tuple.Item2) : msg?.Serialize().Deserialize(tuple.Item2)*/; + // if (data != null) + // list?.Add(data); + // } + + // } + // else + // { + // // (dynamic)Convert.ChangeType(msg, tuple.Item1) + // using (var stream = new MemoryStream(msg)) + // { + // var data1= System.Text.Json.JsonSerializer.Deserialize(stream, tuple.Item1); + // } + // var data = isEnumerable ? System.Text.Json.JsonSerializer.Deserialize(msg, tuple.Item1): msg?.ToString()?.Deserialize(tuple.Item1); + // if (data != null) + // messageObj = new[] { data }; + // } + //} + //if (tuple.Item2 != null && list != null && list.Count > 0) + //{ + // messageObj = new[] { list }; + //} + #endregion + var parameterDescriptors = method.GetParameters(); + executeParameters = new object?[parameterDescriptors.Length]; + for (var i = 0; i < parameterDescriptors.Length; i++) + { + foreach (var item in messages) + { + + object? tempParameter=null; + var parameterDescriptor = parameterDescriptors[i]; + if (KafkaSerialization.IsJsonType(item)) + { + tempParameter = KafkaSerialization.Deserialize(item, tuple.Item2 != null? tuple.Item2: parameterDescriptor.ParameterType); + } + else + { + + var converter = TypeDescriptor.GetConverter(parameterDescriptor.ParameterType); + if (converter.CanConvertFrom(item.GetType())) + { + tempParameter = converter.ConvertFrom(item); + } + else + { + if (parameterDescriptor.ParameterType.IsInstanceOfType(item)) + tempParameter = item; + else + tempParameter =Convert.ChangeType(item, parameterDescriptor.ParameterType); + } + } + if (tuple.Item2 == null) + { + executeParameters[i] = tempParameter; + } + else + { + list.Add(tempParameter); + } + + } + if(list!=null && list.Count>0) + executeParameters[i] = list; } } - var result = method.Invoke(subscribe, messageObj?.ToArray()); + var result = method.Invoke(subscribe, executeParameters); if (result is Task genericTask) { await genericTask.ConfigureAwait(false); @@ -261,6 +332,7 @@ namespace JiShe.CollectBus.Kafka } return false; } - } + + } diff --git a/modules/JiShe.CollectBus.Kafka/Producer/IProducerService.cs b/modules/JiShe.CollectBus.Kafka/Producer/IProducerService.cs index becea90..8cbe665 100644 --- a/modules/JiShe.CollectBus.Kafka/Producer/IProducerService.cs +++ b/modules/JiShe.CollectBus.Kafka/Producer/IProducerService.cs @@ -1,9 +1,4 @@ using Confluent.Kafka; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace JiShe.CollectBus.Kafka.Producer { diff --git a/modules/JiShe.CollectBus.Kafka/Producer/ProducerService.cs b/modules/JiShe.CollectBus.Kafka/Producer/ProducerService.cs index 529d293..50df423 100644 --- a/modules/JiShe.CollectBus.Kafka/Producer/ProducerService.cs +++ b/modules/JiShe.CollectBus.Kafka/Producer/ProducerService.cs @@ -5,7 +5,10 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Confluent.Kafka; +using JiShe.CollectBus.Common; using JiShe.CollectBus.Kafka.Consumer; +using JiShe.CollectBus.Kafka.Internal; +using JiShe.CollectBus.Kafka.Serialization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -21,11 +24,19 @@ 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, IOptions kafkaOptionConfig) + private readonly ServerApplicationOptions _applicationOptions; + /// + /// ProducerService + /// + /// + /// + /// + public ProducerService(IConfiguration configuration,ILogger logger, IOptions kafkaOptionConfig, IOptions applicationOptions) { _configuration = configuration; _logger = logger; _kafkaOptionConfig = kafkaOptionConfig.Value; + _applicationOptions = applicationOptions.Value; } #region private 私有方法 @@ -56,7 +67,7 @@ namespace JiShe.CollectBus.Kafka.Producer var config = new ProducerConfig { BootstrapServers = _kafkaOptionConfig.BootstrapServers, - AllowAutoCreateTopics = true, + //AllowAutoCreateTopics = true, QueueBufferingMaxKbytes = 2_097_151, // 修改缓冲区最大为2GB,默认为1GB CompressionType = CompressionType.Lz4, // 配置使用压缩算法LZ4,其他:gzip/snappy/zstd BatchSize = 32_768, // 修改批次大小为32K @@ -108,9 +119,9 @@ namespace JiShe.CollectBus.Kafka.Producer var message = new Message { Key = key, - Value = value, + Value = value, Headers = new Headers{ - { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } + { "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) } } }; await producer.ProduceAsync(topic, message); @@ -132,7 +143,7 @@ namespace JiShe.CollectBus.Kafka.Producer //Key= _kafkaOptionConfig.ServerTagName, Value = value, Headers = new Headers{ - { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } + { "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) } } }; await producer.ProduceAsync(topic, message); @@ -156,7 +167,7 @@ namespace JiShe.CollectBus.Kafka.Producer Key = key, Value = value, Headers = new Headers{ - { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } + { "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) } } }; var typeKey = typeof(KafkaProducer); @@ -191,7 +202,7 @@ namespace JiShe.CollectBus.Kafka.Producer //Key = _kafkaOptionConfig.ServerTagName, Value = value, Headers = new Headers{ - { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } + { "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) } } }; var typeKey = typeof(KafkaProducer); diff --git a/modules/JiShe.CollectBus.Kafka/JsonSerializer.cs b/modules/JiShe.CollectBus.Kafka/Serialization/JsonSerializer.cs similarity index 61% rename from modules/JiShe.CollectBus.Kafka/JsonSerializer.cs rename to modules/JiShe.CollectBus.Kafka/Serialization/JsonSerializer.cs index 83f58a3..8034954 100644 --- a/modules/JiShe.CollectBus.Kafka/JsonSerializer.cs +++ b/modules/JiShe.CollectBus.Kafka/Serialization/JsonSerializer.cs @@ -8,7 +8,7 @@ using Confluent.Kafka; using System.Text.Json.Serialization; using System.Text.Encodings.Web; -namespace JiShe.CollectBus.Kafka +namespace JiShe.CollectBus.Kafka.Serialization { /// /// JSON 序列化器(支持泛型) @@ -49,10 +49,11 @@ namespace JiShe.CollectBus.Kafka { if (isNull) return default; - try { - return JsonSerializer.Deserialize(data, _options); + if (data.IsEmpty) + return default; + return JsonSerializer.Deserialize(data, _options)!; } catch (Exception ex) { @@ -85,4 +86,40 @@ namespace JiShe.CollectBus.Kafka writer.WriteStringValue(value.ToString(_dateFormatString)); } } + + + public static class KafkaSerialization + { + + /// + /// 判断是否是json类型 + /// + /// + /// + public static bool IsJsonType(this object jsonObject) + { + return jsonObject is JsonElement; + } + public static object? Deserialize(object value, Type valueType) + { + var _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 (value is JsonElement jsonElement) return jsonElement.Deserialize(valueType, _jsonSerializerOptions); + + throw new NotSupportedException("Type is not of type JsonElement"); + } + } } diff --git a/modules/JiShe.CollectBus.Kafka/SubscribeResult.cs b/modules/JiShe.CollectBus.Kafka/SubscribeResult.cs deleted file mode 100644 index 83eaa49..0000000 --- a/modules/JiShe.CollectBus.Kafka/SubscribeResult.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Confluent.Kafka; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using static System.Runtime.InteropServices.JavaScript.JSType; - -namespace JiShe.CollectBus.Kafka -{ - public class SubscribeResult: ISubscribeAck - { - /// - /// 是否成功 - /// - public bool Ack { get; set; } - - /// - /// 消息 - /// - public string? Msg { get; set; } - - - /// - /// 成功 - /// - /// 消息 - public SubscribeResult Success(string? msg = null) - { - Ack = true; - Msg = msg; - return this; - } - - /// - /// 失败 - /// - /// - /// - /// - /// - public SubscribeResult Fail(string? msg = null) - { - Msg = msg; - Ack = false; - return this; - } - } - - public static partial class SubscribeAck - { - - /// - /// 成功 - /// - /// 消息 - /// - public static ISubscribeAck Success(string? msg = null) - { - return new SubscribeResult().Success(msg); - } - - - /// - /// 失败 - /// - /// 消息 - /// - public static ISubscribeAck Fail(string? msg = null) - { - return new SubscribeResult().Fail(msg); - } - } - -} diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs deleted file mode 100644 index 2f48cd2..0000000 --- a/protocols/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Threading.Tasks; -using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.IotSystems.MessageReceiveds; -using JiShe.CollectBus.IotSystems.Protocols; -using JiShe.CollectBus.Protocol.Contracts.Models; -using TouchSocket.Sockets; - -namespace JiShe.CollectBus.Protocol.Contracts.Interfaces -{ - public interface IProtocolPlugin - { - Task GetAsync(); - - Task AddAsync(); - - Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) where T : TB3761; - - Task LoginAsync(MessageReceivedLogin messageReceived); - - Task HeartbeatAsync(MessageReceivedHeartbeat messageReceived); - } -} diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj b/protocols/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj deleted file mode 100644 index cb60dbd..0000000 --- a/protocols/JiShe.CollectBus.Protocol.Contracts/JiShe.CollectBus.Protocol.Contracts.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net8.0 - enable - enable - - - - - - - - - - - - - - - - - - - - - - - diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/Models/TB3761.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Models/TB3761.cs deleted file mode 100644 index ecc1f88..0000000 --- a/protocols/JiShe.CollectBus.Protocol.Contracts/Models/TB3761.cs +++ /dev/null @@ -1,55 +0,0 @@ -using JiShe.CollectBus.Common.Enums; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.Protocol.Contracts.Models -{ - public class TB3761 - { - public int Id { get; set; } - - public AFN Afn { get; set; } - - public List FnList { get; set; } - - } - - public class TB3761FN - { - public int Id { get; set; } - - public int Fn { get; set; } - - public string Text { get; set; } - - public List UpList { get; set; } - - } - - public class TB3761UP - { - public int Id { get; set; } - - public string Name { get; set; } - - public string? Value { get; set; } - - public string DataType { get; set; } - - public int DataIndex { get; set; } - - //public int DataIndex { get; set; } - - public int DataCount { get; set; } - - //public int ParentId { get; set; } - - public int Sort { get; set; } - - public List Tb3761UpChildlList { get; set; } - } - -} diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/ProtocolPools/IPluginContainer.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/ProtocolPools/IPluginContainer.cs new file mode 100644 index 0000000..c34a1eb --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.Contracts/ProtocolPools/IPluginContainer.cs @@ -0,0 +1,6 @@ +namespace JiShe.CollectBus.Protocol.Contracts.ProtocolPools +{ + public interface IPluginContainer + { + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/ProtocolPools/PluginContainer.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/ProtocolPools/PluginContainer.cs new file mode 100644 index 0000000..116eccc --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.Contracts/ProtocolPools/PluginContainer.cs @@ -0,0 +1,26 @@ +using JiShe.CollectBus.Interfaces; + +namespace JiShe.CollectBus.Protocol.Contracts.ProtocolPools +{ + public class PluginContainer: IPluginContainer + { + public Dictionary ProtocolPools; + + public PluginContainer() + { + + + } + + + } + + public static class ServiceProviderKeyedServiceExtensions + { + //public static Task AddKeyedSingleton(this IServiceProvider provider, string key) + //{ + // //var aa = Activator.CreateInstance(); + + //} + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T1882018/CJT 188-2018 户用计量仪表数据传输技术条件.pdf b/protocols/JiShe.CollectBus.Protocol.T1882018/CJT 188-2018 户用计量仪表数据传输技术条件.pdf new file mode 100644 index 0000000..ce3fb6a Binary files /dev/null and b/protocols/JiShe.CollectBus.Protocol.T1882018/CJT 188-2018 户用计量仪表数据传输技术条件.pdf differ diff --git a/protocols/JiShe.CollectBus.Protocol.T1882018/JiShe.CollectBus.Protocol.T1882018.csproj b/protocols/JiShe.CollectBus.Protocol.T1882018/JiShe.CollectBus.Protocol.T1882018.csproj new file mode 100644 index 0000000..4866985 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T1882018/JiShe.CollectBus.Protocol.T1882018.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/protocols/JiShe.CollectBus.Protocol.T1882018/JiSheCollectBusProtocolT1882018Module.cs b/protocols/JiShe.CollectBus.Protocol.T1882018/JiSheCollectBusProtocolT1882018Module.cs new file mode 100644 index 0000000..101eec1 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T1882018/JiSheCollectBusProtocolT1882018Module.cs @@ -0,0 +1,28 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Modularity; + +namespace JiShe.CollectBus.Protocol.T1882018 +{ + public class JiSheCollectBusProtocolT1882018Module : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddKeyedSingleton(nameof(T1882018ProtocolPlugin)); + } + + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + Console.WriteLine($"{nameof(T1882018ProtocolPlugin)} OnApplicationInitializationAsync"); + var standardProtocol = context.ServiceProvider.GetRequiredKeyedService(nameof(T1882018ProtocolPlugin)); + await standardProtocol.LoadAsync(); + } + + public override void OnApplicationShutdown(ApplicationShutdownContext context) + { + Console.WriteLine($"{nameof(T1882018ProtocolPlugin)} OnApplicationShutdown"); + base.OnApplicationShutdown(context); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T1882018/SendData/Telemetry1882018PacketBuilder.cs b/protocols/JiShe.CollectBus.Protocol.T1882018/SendData/Telemetry1882018PacketBuilder.cs new file mode 100644 index 0000000..e5511f2 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T1882018/SendData/Telemetry1882018PacketBuilder.cs @@ -0,0 +1,75 @@ +using System.Reflection; +using JiShe.CollectBus.Common.BuildSendDatas; + +namespace JiShe.CollectBus.Protocol.T1882018.SendData +{ + /// + /// 构建188-2018下发报文 + /// + public static class Telemetry1882018PacketBuilder + { + /// + /// 构建报文的委托 + /// + /// + /// + public delegate Telemetry1882018PacketResponse T1882018Delegate(Telemetry1882018PacketRequest request); + + /// + /// 编码与方法的映射表 + /// + public static readonly Dictionary T1882018ControlHandlers = new(); + + static Telemetry1882018PacketBuilder() + { + // 初始化时自动注册所有符合命名规则的方法 + var methods = typeof(Telemetry1882018PacketBuilder).GetMethods(BindingFlags.Static | BindingFlags.Public); + foreach (var method in methods) + { + if (method.Name.StartsWith("CTR") && method.Name.EndsWith("_Send")) + { + string code = method.Name; + var delegateInstance = (T1882018Delegate)Delegate.CreateDelegate(typeof(T1882018Delegate), method); + T1882018ControlHandlers[code] = delegateInstance; + } + } + } + + #region 读数据 + + /// + /// 读取计量数据 + /// + /// + /// + public static Telemetry1882018PacketResponse CTR_01_Send(Telemetry1882018PacketRequest request) + { + var itemCodeArr = request.ItemCode.Split('_'); + var c_data = itemCodeArr[0];//01 + var d_data = itemCodeArr[1];//91 或者 90 + var dataUnit = new List() { "1F", d_data, "00" }; + var dataList = Build188SendData.Build188SendCommand(request.MeterAddress, c_data, dataUnit); + + return new Telemetry1882018PacketResponse() { Data = dataList }; + } + #endregion + + #region 写数据 + /// + /// 阀控 + /// + /// + /// + public static Telemetry1882018PacketResponse CTR_04_Send(Telemetry1882018PacketRequest request) + { + var itemCodeArr = request.ItemCode.Split('_'); + var c_data = itemCodeArr[0];//01 + var d_data = itemCodeArr[1];//55 或者 99 + var dataUnit = new List() { "A0", "17", "00", d_data }; + var dataList = Build188SendData.Build188SendCommand(request.MeterAddress, c_data, dataUnit); + + return new Telemetry1882018PacketResponse() { Data = dataList }; + } + #endregion + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T1882018/SendData/Telemetry1882018PacketRequest.cs b/protocols/JiShe.CollectBus.Protocol.T1882018/SendData/Telemetry1882018PacketRequest.cs new file mode 100644 index 0000000..d3c9638 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T1882018/SendData/Telemetry1882018PacketRequest.cs @@ -0,0 +1,23 @@ +namespace JiShe.CollectBus.Protocol.T1882018.SendData +{ + /// + /// 构建645报文参数 + /// + public class Telemetry1882018PacketRequest + { + /// + /// 表地址 + /// + public required string MeterAddress { get; set; } + + /// + /// 密码 + /// + public required string Password { get; set; } + + /// + /// 操作码 + /// + public required string ItemCode { get; set; } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T1882018/SendData/Telemetry1882018PacketResponse.cs b/protocols/JiShe.CollectBus.Protocol.T1882018/SendData/Telemetry1882018PacketResponse.cs new file mode 100644 index 0000000..9cde217 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T1882018/SendData/Telemetry1882018PacketResponse.cs @@ -0,0 +1,13 @@ +namespace JiShe.CollectBus.Protocol.T1882018.SendData +{ + /// + /// 返回645报文结果 + /// + public class Telemetry1882018PacketResponse + { + /// + /// 报文体 + /// + public List Data { get; set; } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T1882018/T1882018ProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol.T1882018/T1882018ProtocolPlugin.cs new file mode 100644 index 0000000..5aa1b1f --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T1882018/T1882018ProtocolPlugin.cs @@ -0,0 +1,140 @@ +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Protocols; +using JiShe.CollectBus.Protocol.Models; +using JiShe.CollectBus.Protocol.T1882018.SendData; +using JiShe.CollectBus.Protocol.T37612012; +using JiShe.CollectBus.Protocol.T37612012.SendData; +using Mapster; +using Microsoft.Extensions.Logging; +using TouchSocket.Sockets; + +namespace JiShe.CollectBus.Protocol.T1882018 +{ + /// + /// T1882018协议插件 + /// + public class T1882018ProtocolPlugin : T37612012ProtocolPlugin + { + private readonly ILogger _logger; + + public readonly Dictionary T188ControlHandlers; + + /// + /// Initializes a new instance of the class. + /// + /// The service provider. + public T1882018ProtocolPlugin(IServiceProvider serviceProvider, ILogger logger, ITcpService tcpService) : base(serviceProvider, logger, tcpService) + { + _logger = logger; + T188ControlHandlers = Telemetry1882018PacketBuilder.T1882018ControlHandlers; + } + + public sealed override ProtocolInfo Info => new(nameof(T1882018ProtocolPlugin), "376.1/188-2018", "TCP", "376.1/188-2018协议", "HJ-LXS-15 DN15"); + + public override async Task AnalyzeAsync(ITcpSessionClient client, string messageReceived, Action? sendAction = null) + { + //TB3761? tB3761 = Analysis3761(messageReceived); + //if (tB3761 != null) + //{ + // if (tB3761.AFN_FC?.AFN == (int)AFN.链路接口检测) + // { + // if (tB3761.A == null || tB3761.A.Code.IsNullOrWhiteSpace() || tB3761.A.A3?.D1_D7 == null || tB3761.SEQ?.PSEQ == null) + // { + // _logger.LogError($"解析AFN.链路接口检测报文失败,报文:{messageReceived},TB3761:{tB3761.Serialize()}"); + // } + // else + // { + // if (tB3761.DT?.Fn == (int)FN.登录) + // { + // // 登录回复 + // if (tB3761.SEQ.CON == (int)CON.需要对该帧进行确认) + // await LoginAsync(client, messageReceived, tB3761.A.Code, tB3761.A.A3?.D1_D7, tB3761.SEQ?.PSEQ); + // } + // else if (tB3761.DT?.Fn == (int)FN.心跳) + // { + // // 心跳回复 + // //心跳帧有两种情况: + // //1. 集中器先有登录帧,再有心跳帧 + // //2. 集中器没有登录帧,只有心跳帧 + // await HeartbeatAsync(client, messageReceived, tB3761.A.Code, tB3761.A.A3?.D1_D7, tB3761.SEQ?.PSEQ); + // } + // } + + // } + // await OnTcpNormalReceived(client, tB3761); + //} + //return (tB3761 as T)!; + + return null; + } + + /// + /// 组装报文 + /// + /// 报文构建参数 + /// + public override async Task BuildAsync(ProtocolBuildRequest request) + { + if (request == null) + { + _logger.LogError($"{nameof(T1882018ProtocolPlugin)} 报文构建失败,参数为空"); + return new ProtocolBuildResponse(); + } + var itemCodeArr = request.ItemCode.Split('_'); + var aFNStr = itemCodeArr[0]; + var aFN = (AFN)aFNStr.HexToDec(); + var fn = int.Parse(itemCodeArr[1]); + + Telemetry3761PacketResponse builderResponse = null; + + List dataUnit = new List(); + //数据转发场景 10H_F1 + if (aFNStr == "10" && request.SubProtocolRequest != null && string.IsNullOrWhiteSpace(request.SubProtocolRequest.ItemCode) == false) + { + var t188PacketHandlerName = $"{T1882018PacketItemCodeConst.BasicT1882018}_{request.SubProtocolRequest.ItemCode}_Send"; + Telemetry1882018PacketResponse t645PacketResponse = null; + + if (T188ControlHandlers != null && T188ControlHandlers.TryGetValue(t188PacketHandlerName + , out var t645PacketHandler)) + { + t645PacketResponse = t645PacketHandler(new Telemetry1882018PacketRequest() + { + MeterAddress = request.SubProtocolRequest.MeterAddress, + Password = request.SubProtocolRequest.Password, + ItemCode = request.SubProtocolRequest.ItemCode, + }); + } + + if (t645PacketResponse != null) + { + dataUnit = t645PacketResponse.Data; + } + } + + string afnMethonCode = $"AFN{aFNStr}_Fn_Send"; + if (base.T3761AFNHandlers != null && base.T3761AFNHandlers.TryGetValue(afnMethonCode + , out var handler)) + { + builderResponse = handler(new Telemetry3761PacketRequest() + { + FocusAddress = request.FocusAddress, + Fn = fn, + Pn = request.Pn, + DataUnit = dataUnit, + }); + } + + if (builderResponse == null) + { + return new ProtocolBuildResponse(); + } + + var result = builderResponse.Adapt(); + result.IsSuccess = true; + + return await Task.FromResult(result); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_00H/AFN0_F1_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_00H/AFN0_F1_Analysis.cs new file mode 100644 index 0000000..63ab925 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_00H/AFN0_F1_Analysis.cs @@ -0,0 +1,43 @@ +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_00H +{ + /// + /// 5.1.3.1 F1:全部确认:对收到报文中的全部数据单元标识进行确认 + /// + public class AFN0_F1_Analysis: IAnalysisStrategy> + { + private readonly ILogger _logger; + + public AFN0_F1_Analysis(ILogger logger) + { + _logger = logger; + } + + public Task> ExecuteAsync(TB3761 tB3761) + { + try + { + ArgumentNullException.ThrowIfNull(nameof(tB3761)); + UnitDataAnalysis dto = new UnitDataAnalysis + { + Code = tB3761.A.Code, + AFN = tB3761.AFN_FC.AFN, + Fn = tB3761.DT.Fn, + Pn = tB3761.DA.Pn, + Data= true + }; + return Task.FromResult(dto); + } + catch (Exception ex) + { + _logger.LogError(ex, $"00_1解析失败:{tB3761.A.Code}-{tB3761.DT.Fn}-{tB3761.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + } + +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_00H/AFN0_F2_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_00H/AFN0_F2_Analysis.cs new file mode 100644 index 0000000..5ae0b45 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_00H/AFN0_F2_Analysis.cs @@ -0,0 +1,41 @@ +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_00H +{ + /// + /// 5.1.3.2 F2:全部否认 + /// + public class AFN0_F2_Analysis : IAnalysisStrategy> + { + private readonly ILogger _logger; + + public AFN0_F2_Analysis(ILogger logger) + { + _logger = logger; + } + public Task> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + UnitDataAnalysis dto = new UnitDataAnalysis + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data = false, + }; + return Task.FromResult(dto); + } + catch (Exception ex) + { + _logger.LogError(ex, $"00_2解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_09H/AFN9_F1_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_09H/AFN9_F1_Analysis.cs new file mode 100644 index 0000000..fd93dab --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_09H/AFN9_F1_Analysis.cs @@ -0,0 +1,66 @@ +using System.Text; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.AnalysisData.AFN_09H +{ + /// + /// 5.9.1.2 F1:终端版本信息 + /// + public class AFN9_F1_Analysis : IAnalysisStrategy> + { + private readonly ILogger _logger; + + public AFN9_F1_Analysis(ILogger logger) + { + _logger = logger; + } + + public Task> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData.HexMessageList); + var data = AnalysisDataUnit(input.UnitData.HexMessageList); + data.AreaCode = input.A.Code?.Substring(0, 4); + data.Address = input.A.Code?.Substring(4, 5); + UnitDataAnalysis dto = new UnitDataAnalysis + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data= data + }; + return Task.FromResult(dto); + } + catch (Exception ex) + { + _logger.LogError(ex, $"09_1解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}"); + return null; + } + } + + private AFN9_F1_AnalysisDto AnalysisDataUnit(List hexMessageList) + { + AFN9_F1_AnalysisDto version = new AFN9_F1_AnalysisDto(); + version.MakerNo = Encoding.ASCII.GetString(string.Join("", hexMessageList.Skip(4).Take(4).ToList()).HexToByte());//厂商代号 + version.DeviceNo = Encoding.ASCII.GetString(string.Join("", hexMessageList.Skip(8).Take(8).ToList()).HexToByte()).Replace("\0", "");//设备编号 + version.SoftwareVersion = Encoding.ASCII.GetString(string.Join("", hexMessageList.Skip(16).Take(4).ToList()).HexToByte());//终端软件版本号 + version.HardwareVersion = Encoding.ASCII.GetString(string.Join("", hexMessageList.Skip(38).Take(4).ToList()).HexToByte());//终端硬件版本号 + version.AddDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + var dateArr = hexMessageList.Skip(20).Take(3).ToList(); + var dateArr2 = hexMessageList.Skip(42).Take(3).ToList(); + dateArr.Reverse(); + dateArr2.Reverse(); + version.SoftwareReleaseDate = $"{DateTime.Now.Year.ToString().Substring(0, 2)}{string.Join("-", dateArr)}";//终端软件发布日期:日月年 + version.HardwareReleaseDate = $"{DateTime.Now.Year.ToString().Substring(0, 2)}{string.Join("-", dateArr2)}";//终端硬件发布日期:日月年 + return version; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_09H/AFN9_F9_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_09H/AFN9_F9_Analysis.cs new file mode 100644 index 0000000..5d7f9f7 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_09H/AFN9_F9_Analysis.cs @@ -0,0 +1,45 @@ +using System.Text; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_09H +{ + /// + /// 5.9.2.4.9 F9:远程通信模块版本信息(只读解析SIM卡号) + /// + public class AFN9_F9_Analysis : IAnalysisStrategy> + { + private readonly ILogger _logger; + + public AFN9_F9_Analysis(ILogger logger) + { + _logger = logger; + } + + public Task> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData.HexMessageList); + UnitDataAnalysis dto = new UnitDataAnalysis + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data = Encoding.ASCII.GetString(string.Join("", input.UnitData.HexMessageList.Skip(30).Take(20).ToList()).HexToByte()).Replace("\0", "") //SIM卡 + }; + return Task.FromResult(dto); + } + catch (Exception ex) + { + _logger.LogError(ex, $"00_1解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}"); + return null; + } + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0AH/AFN10_F10_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0AH/AFN10_F10_Analysis.cs new file mode 100644 index 0000000..b6d190f --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0AH/AFN10_F10_Analysis.cs @@ -0,0 +1,158 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0AH +{ + /// + /// 5.10.1.3.1 F10:终端电能表/交流采样装置配置参数 + /// + internal class AFN10_F10_Analysis : IAnalysisStrategy> + { + private readonly ILogger _logger; + + public AFN10_F10_Analysis(ILogger logger) + { + _logger = logger; + } + + public Task> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData.HexMessageList); + Tuple> tuple = AFN10F10EntityAnalysis(input.UnitData.HexMessageList); + UnitDataAnalysis dto = new UnitDataAnalysis + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data=new AFN10_F10_AnalysisDto() + { + AFN10F10Entitys = tuple.Item2, + ConfigNum = tuple.Item1 + } + }; + return Task.FromResult(dto); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0A_10解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}"); + return null; + } + } + + + + public Tuple> AFN10F10EntityAnalysis(List hexMessageList) + { + List> meterList = new List>(); + int total = $"{hexMessageList[5]}{hexMessageList[4]}".HexToDec(); + List aFN10F10Entitys = new List(); + for (int i = 0; i < total; i++) + { + List sArray = hexMessageList.GetRange(6 + 27 * i, 27); + AFN10F10Entity aFN10F10Entity = new AFN10F10Entity() + { + SerialNum = $"{sArray[1]}{sArray[0]}".HexToDec(), + Point = $"{sArray[3]}{sArray[2]}".HexToDec(), + RuleType= GetProtocol(sArray[5]), + ComAddress= $"{sArray[11]}{sArray[10]}{sArray[9]}{sArray[8]}{sArray[7]}{sArray[6]}", + ComPwd= $"{sArray[17]}{sArray[16]}{sArray[15]}{sArray[14]}{sArray[13]}{sArray[12]}".Substring(6, 6), + ElectricityRatesNum= sArray[18].HexToBin().Substring(2, 6).BinToDec(), + CollectorAddress = $"{sArray[25]}{sArray[24]}{sArray[23]}{sArray[22]}{sArray[21]}{sArray[20]}", + + }; + + string baudPort = sArray[4].HexToBin().PadLeft(8, '0'); //波特率和端口号放在一个字节内 + aFN10F10Entity.BaudRate = GetBaudrate(baudPort.Substring(0, 3)); + aFN10F10Entity.Port = baudPort.Substring(3, 5).BinToDec(); + + string dataDigit = sArray[19].HexToBin().PadLeft(8, '0'); //有功电能示值整数位及小数位个数 + aFN10F10Entity.IntegerBitsNum = dataDigit.Substring(4, 2).BinToDec() + 4; + aFN10F10Entity.DecimalPlacesNum = dataDigit.Substring(6, 2).BinToDec() + 1; + + string classNo = sArray[26].HexToBin().PadLeft(8, '0');//用户大类号及用户小类号 + aFN10F10Entity.UserCategoryNum = classNo.Substring(0, 4).BinToDec() + 1; + aFN10F10Entity.UserCategoryNum = classNo.Substring(4, 4).BinToDec() + 1; + aFN10F10Entitys.Add(aFN10F10Entity); + } + return Tuple.Create(total, aFN10F10Entitys); + } + + /// + /// 获取波特率 + /// + /// + /// + private int GetBaudrate(string binBaud) + { + int baudRate = 0; + switch (binBaud) + { + case "001": + baudRate = 600; + break; + case "010": + baudRate = 1200; + break; + case "011": + baudRate = 2400; + break; + case "100": + baudRate = 4800; + break; + case "101": + baudRate = 7200; + break; + case "110": + baudRate = 9600; + break; + case "111": + baudRate = 19200; + break; + default: + baudRate = 0; + break; + } + return baudRate; + } + + /// + /// 获取通信协议文本说明 + /// + /// + /// + private string GetProtocol(string protocol) + { + int dataUnit = protocol.HexToDec(); + + if (dataUnit == 1) + { + return "DL/T 645—1997"; + } + if (dataUnit == 2) + { + return "交流采样装置通信协议"; + } + if (dataUnit == 30) + { + return "DL/T 645—2007"; + } + if (dataUnit == 31) + { + return "串行接口连接窄带低压载波通信模块"; + } + if (dataUnit == 32) + { + return "CJ/T 188—2018协议"; + } + return "其他协议"; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0AH/AFN10_F66_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0AH/AFN10_F66_Analysis.cs new file mode 100644 index 0000000..c197509 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0AH/AFN10_F66_Analysis.cs @@ -0,0 +1,82 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0AH +{ + /// + /// 5.5.1.3.53 F66:定时上报 2 类数据任务设置 + /// + public class AFN10_F66_Analysis : IAnalysisStrategy> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public AFN10_F66_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext= analysisStrategyContext; + } + + public async Task> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData.HexMessageList); + var data = await GenerateFinalResult(input.UnitData.HexMessageList); + data.Pn = input.DA.Pn; + UnitDataAnalysis dto = new UnitDataAnalysis + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data= data + }; + return await Task.FromResult(dto); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0A_66解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}"); + return null; + } + } + + public async Task GenerateFinalResult(List hexMessageList) + { + AFN10_F66_AnalysisDto entity = new AFN10_F66_AnalysisDto(); + var cycleBin = hexMessageList[4].HexToBin().PadLeft(8, '0'); + var cycleUnitBin = cycleBin.Substring(0, 2); + var cycleValueBin = cycleBin.Substring(2, 6); + + entity.Cycle = cycleValueBin.BinToDec();//定时发送周期 + entity.Unit = cycleUnitBin.BinToDec();//定时发送周期(单位) + //TODO:发送基准时间 + var arrBaseTime = hexMessageList.GetRange(5, 6); + + var baseTimeArrStr = await _analysisStrategyContext.ExecuteAsync, string>("Appendix_A1", arrBaseTime); + var baseTimeArr= baseTimeArrStr.Split('_'); + //entity.BaseTime = DateTime.Parse($"{DateTime.Now.Year.ToString().Substring(0, 2)}{arrBaseTime[0]}-{arrBaseTime[1]}-{arrBaseTime[2]} {arrBaseTime[3]}:{arrBaseTime[4]}:{arrBaseTime[5]}"); + + entity.BaseTime = Convert.ToDateTime(baseTimeArr[0]); + entity.CurveRatio = hexMessageList[11].HexToDec(); + var count = hexMessageList[12].HexToDec(); + var dataArr = hexMessageList.GetRange(13, 4 * count); + for (int i = 0; i < count; i++) + { + var pnfnArr = dataArr.GetRange(0, 4); + var tempPn = T37612012ProtocolPlugin.CalculatePn(pnfnArr[0], pnfnArr[1]); + var tempFn = T37612012ProtocolPlugin.CalculateFn(pnfnArr[2], pnfnArr[3]); + entity.Details.Add(new SetAutoItemCodeDetails() { Fn = tempFn, Pn = tempPn }); + dataArr.RemoveRange(0, 4); + } + + return entity; + } + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0AH/AFN10_F68_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0AH/AFN10_F68_Analysis.cs new file mode 100644 index 0000000..f9358e2 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0AH/AFN10_F68_Analysis.cs @@ -0,0 +1,43 @@ +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0AH +{ + /// + /// 5.5.1.3.55 F68:定时上报 2 类数据任务启动/停止设置 + /// + public class AFN10_F68_Analysis : IAnalysisStrategy> + { + private readonly ILogger _logger; + + public AFN10_F68_Analysis(ILogger logger) + { + _logger = logger; + } + public Task> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData.HexMessageList); + UnitDataAnalysis dto = new UnitDataAnalysis + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data = input.UnitData.HexMessageList[4].Equals("55") + }; + + return Task.FromResult(dto); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0A_68解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}"); + return null; + } + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F129_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F129_Analysis.cs new file mode 100644 index 0000000..248317d --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F129_Analysis.cs @@ -0,0 +1,105 @@ +using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0CH +{ + /// + /// 5.12.2.4.85 F129:当前正向有功电能示值(总、费率1~M) + /// + public class AFN12_F129_Analysis : IAnalysisStrategy>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly IIoTDbProvider _dbProvider; + + public AFN12_F129_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, IIoTDbProvider dbProvider) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dbProvider= dbProvider; + } + + public async Task>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + ArgumentNullException.ThrowIfNull(input.A.A3?.D1_D7); + UnitDataAnalysis> unitDataAnalysis = new UnitDataAnalysis> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA= input.A.A3.D1_D7, + PSEQ= input.SEQ.PSEQ, + }; + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + List list = GenerateFinalResult(2, datas, "正向有功电能示值", input.AFN_FC.AFN, input.DT.Fn); + unitDataAnalysis.Data= list; + + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0C_129解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + #region 单元数据值解析 + /// + /// 单元数据值解析 + /// + /// + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.Add(hexMessageList.GetReadTime(4, 5)); + int rationgCount = hexMessageList.GetRatingCount(9, 1); + values.Add(rationgCount.ToString()); + for (int i = 0; i < rationgCount + 1; i++) + { + var arr = hexMessageList.GetRange(10 + (i * 5), 5); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A14), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + #endregion + + public List GenerateFinalResult(int index, List data, string remark = "", int afn = 0, int fn = 0) + { + List list = new List(); + for (int i = index; i < data.Count; i++) + { + AFN12_F129_AnalysisDto meter = new AFN12_F129_AnalysisDto(); + + decimal value = 0; + var errorCode = data[i].CheckErrorCode(); + if (errorCode != null) + meter.ValidData = false; + else + decimal.TryParse(data[i], out value); + meter.DataValue = value; + + meter.DataType = $"{afn.ToString().PadLeft(2, '0')}_{fn}_{i - index}"; + meter.ReadTime = Convert.ToDateTime($"{data[0].Substring(0, 4)}-{data[0].Substring(4, 2)}-{data[0].Substring(6, 2)} {data[0].Substring(8, 2)}:{data[0].Substring(10, 2)}:00"); + list.Add(meter); + } + return list; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F130_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F130_Analysis.cs new file mode 100644 index 0000000..009e9d2 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F130_Analysis.cs @@ -0,0 +1,93 @@ +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0CH +{ + /// + /// 5.12.2.4.86 F130:当前正向无功(组合无功 1)电能示值(总、费率 1~M) + /// + public class AFN12_F130_Analysis : IAnalysisStrategy>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public AFN12_F130_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public async Task>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + List list = GenerateFinalResult(2, datas, "正向无功电能示值", input.AFN_FC.AFN, input.DT.Fn); + UnitDataAnalysis> unitDataAnalysis = new UnitDataAnalysis> + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data = list + }; + + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0C_130解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.Add(hexMessageList.GetReadTime(4, 5)); + int ratingCount = hexMessageList.GetRatingCount(9, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(10 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + public List GenerateFinalResult(int index, List data, string remark = "", int afn = 0, int fn = 0) + { + List list = new List(); + for (int i = index; i < data.Count; i++) + { + AFN12_F130_AnalysisDto meter = new AFN12_F130_AnalysisDto(); + decimal value = 0; + var errorCode = data[i].CheckErrorCode(); + if (errorCode != null) + meter.ValidData = false; + else + decimal.TryParse(data[i], out value); + meter.DataValue = value; + meter.DataType = $"{afn.ToString().PadLeft(2, '0')}_{fn}_{i - index}"; + meter.ReadTime = Convert.ToDateTime($"{data[0].Substring(0, 4)}-{data[0].Substring(4, 2)}-{data[0].Substring(6, 2)} {data[0].Substring(8, 2)}:{data[0].Substring(10, 2)}:00"); + list.Add(meter); + } + return list; + } + + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F131_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F131_Analysis.cs new file mode 100644 index 0000000..36492fb --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F131_Analysis.cs @@ -0,0 +1,93 @@ +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0CH +{ + /// + /// 5.12.2.4.87 F131:当前反向有功电能示值(总、费率 1~M) + /// + public class AFN12_F131_Analysis : IAnalysisStrategy>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public AFN12_F131_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public async Task>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + List list = GenerateFinalResult(2, datas, "反向有功总电能示值", input.AFN_FC.AFN, input.DT.Fn); + UnitDataAnalysis> unitDataAnalysis = new UnitDataAnalysis> + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data = list + }; + + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0C_131解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.Add(hexMessageList.GetReadTime(4, 5)); + int ratingCount = hexMessageList.GetRatingCount(9, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(10 + (i * 5), 5); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A14), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + public List GenerateFinalResult(int index, List data, string remark = "", int afn = 0, int fn = 0) + { + List list = new List(); + for (int i = index; i < data.Count; i++) + { + AFN12_F131_AnalysisDto meter = new AFN12_F131_AnalysisDto(); + decimal value = 0; + var errorCode = data[i].CheckErrorCode(); + if (errorCode != null) + meter.ValidData = false; + else + decimal.TryParse(data[i], out value); + meter.DataValue = value; + meter.DataType = $"{afn.ToString().PadLeft(2, '0')}_{fn}_{i - index}"; + meter.ReadTime = Convert.ToDateTime($"{data[0].Substring(0, 4)}-{data[0].Substring(4, 2)}-{data[0].Substring(6, 2)} {data[0].Substring(8, 2)}:{data[0].Substring(10, 2)}:00"); + list.Add(meter); + } + return list; + } + + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F132_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F132_Analysis.cs new file mode 100644 index 0000000..59a41fb --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F132_Analysis.cs @@ -0,0 +1,94 @@ +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0CH +{ + /// + /// 5.12.2.4.88 F132:当前反向无功(组合无功 2)电能示值(总、费率 1~M) + /// + public class AFN12_F132_Analysis : IAnalysisStrategy>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public AFN12_F132_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + + public async Task>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + List list = GenerateFinalResult(2, datas, "反向无功电能示值", (int)input.AFN_FC.AFN!, (int)input.DT.Fn!); + UnitDataAnalysis> unitDataAnalysis = new UnitDataAnalysis> + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn , + Pn = input.DA.Pn, + Data = list + }; + + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0C_132解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + + } + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.Add(hexMessageList.GetReadTime(4, 5)); + int ratingCount = hexMessageList.GetRatingCount(9, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(10 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加4个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + public List GenerateFinalResult(int index, List data, string remark = "",int afn=0,int fn=0) + { + List list = new List(); + for (int i = index; i < data.Count; i++) + { + AFN12_F132_AnalysisDto meter = new AFN12_F132_AnalysisDto(); + decimal value = 0; + var errorCode = data[i].CheckErrorCode(); + if (errorCode != null) + meter.ValidData = false; + else + decimal.TryParse(data[i], out value); + meter.DataValue = value; + meter.DataType = $"{afn.ToString().PadLeft(2, '0')}_{fn}_{i - index}"; + meter.ReadTime = Convert.ToDateTime($"{data[0].Substring(0, 4)}-{data[0].Substring(4, 2)}-{data[0].Substring(6, 2)} {data[0].Substring(8, 2)}:{data[0].Substring(10, 2)}:00"); + list.Add(meter); + } + return list; + } + + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F145_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F145_Analysis.cs new file mode 100644 index 0000000..ec72e1a --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F145_Analysis.cs @@ -0,0 +1,107 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0CH +{ + /// + /// 5.12.2.4.101 F145:当月正向有功最大需量及发生时间(总、费率 1~M) + /// + public class AFN12_F145_Analysis : IAnalysisStrategy> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public AFN12_F145_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + + public async Task> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + AFN12_F145_AnalysisDto data = GenerateFinalResult(2, datas, "当月正向有功最大需量及发生时间"); + data.DataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + UnitDataAnalysis unitDataAnalysis = new UnitDataAnalysis + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data = data + }; + + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0C_145解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.Add(hexMessageList.GetReadTime(4, 5)); + int ratingCount = hexMessageList.GetRatingCount(9, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(10 + i * 7, 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A23), arr); + values.Add(value.ToString());//正向有功总最大需量 + } + arr = hexMessageList.GetRange(10 + i * 7 + 3, 4); + + errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, string>(nameof(Appendix_A17), arr); + values.Add(value.ToString());//正向有功总最大需量发生时间 + } + } + return values; + } + + public AFN12_F145_AnalysisDto GenerateFinalResult(int index, List data, string remark = "") + { + AFN12_F145_AnalysisDto dto = new AFN12_F145_AnalysisDto(); + + decimal value = 0; + var errorCode = data[2].CheckErrorCode(); + if (errorCode != null) + dto.ValidData = false; + else + decimal.TryParse(data[2], out value); + + dto.DataValue = value; + dto.TimeSpan = $"{DateTime.Now.Year}-{data[3].Substring(0, 2)}-{data[3].Substring(2, 2)} {data[3].Substring(4, 2)}:{data[3].Substring(6, 2)}:00";//$"{data[0].Substring(0, 4)}-{data[0].Substring(4, 2)}-{data[0].Substring(6, 2)}"; + if (DateTime.TryParse(dto.TimeSpan, out DateTime readingDate)) + { + dto.ReadingDate = readingDate; + } + return dto; + } + + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F149_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F149_Analysis.cs new file mode 100644 index 0000000..07480f0 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F149_Analysis.cs @@ -0,0 +1,148 @@ +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.AnalysisData; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AFN_0CH +{ + /// + /// 5.12.2.4.105 F149:上月(上一结算日)正向有功最大需量及发生时间(总、费率 1~M) + /// + public class AFN12_F149_Analysis : IAnalysisStrategy>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN12_F149_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage= dataStorage; + } + + + public async Task>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + List datas = await AnalysisDataUnit(input.UnitData.HexMessageList); + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + AnalysisBaseDto data = GenerateFinalResult(datas, dataType,"上月(上一结算日)正向有功最大需量及发生时间"); + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data.MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ProjectId = ammeterInfo.ProjectID; + data.MeterId = ammeterInfo.MeterId; + data.DatabaseBusiID=ammeterInfo.DatabaseBusiID; + data.MeterAddress= ammeterInfo.AmmerterAddress; + } + UnitDataAnalysis> unitDataAnalysis = new UnitDataAnalysis> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA=input.A.A3!.D1_D7!, + PSEQ=input.SEQ.PSEQ, + Data = data, + HexMessage=input.BaseHexMessage.HexMessageString, + MessageId=input.MessageId, + TimeDensity=1, + DensityUnit = DensityUnit.Month + }; + await _dataStorage.SaveDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0C_149解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + + } + private async Task> AnalysisDataUnit(List hexMessageList) + { + List values = new List(); + values.Add(hexMessageList.GetReadTime(4, 5)); + int ratingCount = hexMessageList.GetRatingCount(9, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(10 + i * 7, 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode!=null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A23), arr); + values.Add(value.ToString());//正向有功总最大需量 + } + + arr = hexMessageList.GetRange(13 + i * 7, 4); + + errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, string>(nameof(Appendix_A17), arr);//正向有功总最大需量发生时间 + values.Add(value);//正向有功总最大需量 + } + } + return values; + } + public AnalysisBaseDto GenerateFinalResult(List data,string dataType, string filedDesc = "") + { + AnalysisBaseDto dto = new AnalysisBaseDto(); + + var errorCodeInfo = data[2].CheckErrorCode(); + if (errorCodeInfo != null) + { + dto.ValidData = false; + dto.ErrorCodeMsg= errorCodeInfo.Item2; + } + decimal.TryParse(data[2], out decimal value); + dto.DataValue = value; + dto.MeterType= MeterTypeEnum.Ammeter; + //TODO:最大需量发生时间 + errorCodeInfo = data[3].CheckErrorCode(); + if (data[3].Length != 8 && errorCodeInfo != null) + { + dto.ValidData = false; + dto.ErrorCodeMsg = errorCodeInfo.Item2; + } + else + { + string timeSpan = $"{DateTime.Now.Year}-{data[3].Substring(0, 2)}-{data[3].Substring(2, 2)} {data[3].Substring(4, 2)}:{data[3].Substring(6, 2)}:00"; + + //TODO:时间标 + if (!DateTime.TryParse(timeSpan, out DateTime dataTime)) + dto.ValidData = false; + dto.DataTime = dataTime; + } + if (DateTime.Now.Month.Equals(1))//如果为1月份,则日期减去1年 + { + dto.DataTime = dto.DataTime.AddYears(-1); + dto.TimeSpan= dto.DataTime; + } + + dto.FiledDesc = "上月(上一结算日)正向有功最大需量及发生时间"; + dto.FiledName = dataType.GetDataFieldByGatherDataType() ?? string.Empty; + return dto; + } + + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F188_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F188_Analysis.cs new file mode 100644 index 0000000..e3eaee7 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F188_Analysis.cs @@ -0,0 +1,72 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AFN_0CH +{ + /// + /// 水表抄读取 + /// + public class AFN12_F188_Analysis : IAnalysisStrategy> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public AFN12_F188_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public async Task> ExecuteAsync(TB3761 input) + { + + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + ArgumentNullException.ThrowIfNull(input.AFN_FC.AFN); + ArgumentNullException.ThrowIfNull(input.DT.Fn); + var data = GenerateFinalResult(input.UnitData.HexMessageList); + data.DataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + UnitDataAnalysis dto = new UnitDataAnalysis + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data = data + }; + return await Task.FromResult(dto); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0C_188解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage?.HexMessageString},{ex.Message}"); + return null; + } + } + public AFN12_F149_AnalysisDto GenerateFinalResult(List hexMessageList) + { + AFN12_F149_AnalysisDto dto = new AFN12_F149_AnalysisDto(); + decimal value = 0; + var arr = hexMessageList.GetRange(11, 4); + var errorCodeInfo = arr.CheckErrorCode(); + if (errorCodeInfo != null) + { + dto.ValidData = false; + dto.ErrorCodeMsg = errorCodeInfo.Item2; + } + else + decimal.TryParse($"{arr[11]}{arr[12]}{arr[13]}.{arr[14]}", out value); + + dto.DataValue = value; + //dto.ReadTime = Convert.ToDateTime($"{data[0].Substring(0, 4)}-{data[0].Substring(4, 2)}-{data[0].Substring(6, 2)} {data[0].Substring(8, 2)}:{data[0].Substring(10, 2)}:00"); + return dto; + } + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F25_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F25_Analysis.cs new file mode 100644 index 0000000..5b3cb9e --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F25_Analysis.cs @@ -0,0 +1,113 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AFN_0CH +{ + /// + /// 5.12.2.4.22 F25:当前三相及总有/无功功率、功率因数,三相电压、电流、零序电流、视在功率 + /// + public class AFN12_F25_Analysis : IAnalysisStrategy>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public AFN12_F25_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public List DataType { get; set; } = new List() { "YGGL", "YGGL_A", "YGGL_B", "YGGL_C", "WGGL", "WGGL_A", "WGGL_B", "WGGL_C", "GLYS", "GLYS_A", "GLYS_B", "GLYS_C", "DY_A", "DY_B", "DY_C", "DL_A", "DL_B", "DL_C", "LXDL", "SZGL", "SZGL_A", "SZGL_B", "SZGL_C" }; + + public async Task>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + List remarks = new List() { "当前总有功功率", "当前A相有功功率", "当前B相有功功率", "当前C相有功功率", "当前总无功功率", "当前A相无功功率", "当前B相无功功率", "当前C相无功功率", "当前总功率因数", "当前A相功率因数", "当前B相功率因数", "当前C相功率因数", "当前A相电压", "当前B相电压", "当前C相电压", "当前A相电流", "当前C相电流", "当前 C相电流", "当前零序电流", "当前总视在功率", "当前A相视在功率", "当前B相视在功率", "当前C相视在功率" }; + List list = new List(); + List data = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + + for (int i = 1; i < data.Count; i++) + { + AFN12_F25_AnalysisDto dto = new AFN12_F25_AnalysisDto(); + decimal value = 0; + var errorCodeInfo = data[i].CheckErrorCode(); + if (errorCodeInfo != null) + { + dto.ValidData = false; + dto.ErrorCodeMsg = errorCodeInfo.Item2; + } + else + decimal.TryParse(data[i], out value); + dto.DataValue = value; + dto.DataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}_{DataType[i-1]}"; + dto.FiledName = DataType[i - 1]; + dto.ReadTime = Convert.ToDateTime($"{data[0].Substring(0, 4)}-{data[0].Substring(4, 2)}-{data[0].Substring(6, 2)} {data[0].Substring(8, 2)}:{data[0].Substring(10, 2)}:00"); + dto.FiledDesc = remarks[i - 1]; + list.Add(dto); + } + UnitDataAnalysis> unitDataAnalysis = new UnitDataAnalysis> + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data = list + }; + + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0C_25解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.Add(hexMessageList.GetReadTime(4, 5)); + values.AddRange(await CutOutAsync(9, 8, 3, nameof(Appendix_A9), hexMessageList));//解析 总/ABC相有功/无功功率 + values.AddRange(await CutOutAsync(33, 4, 2, nameof(Appendix_A5), hexMessageList));//解析 总/ABC相功率因数 + values.AddRange(await CutOutAsync(41, 3, 2, nameof(Appendix_A7), hexMessageList));//ABC相电压 + values.AddRange(await CutOutAsync(47, 4, 3, nameof(Appendix_A25), hexMessageList));//ABC相/零序电流 + values.AddRange(await CutOutAsync(59, 4, 3, nameof(Appendix_A9), hexMessageList));//总视\ABC相视在功率 + return values; + } + /// + /// 截取值 + /// + /// 开始位置 + /// 总项数 + /// 每项字节数 + /// 解析方式 + /// 解析数据 + private async Task> CutOutAsync(int Index, int count, int byteCount, string appendixName, List hexMessageList) + { + List values = new List(); + var temp = hexMessageList.GetRange(Index, count * byteCount);//截取位置(9),解析项*截取字节数(8*3) + for (int i = 0; i < count; i++) + { + var arr = temp.GetRange(i * byteCount, byteCount); + var errorCode = arr.CheckErrorCode(); + if (errorCode!=null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(appendixName, arr); + values.Add(value.ToString()); + } + } + return values; + } + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F2_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F2_Analysis.cs new file mode 100644 index 0000000..44c94a7 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F2_Analysis.cs @@ -0,0 +1,71 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AFN_0CH +{ + /// + /// 5.12.2.4.1 F2:终端日历时钟 + /// + public class AFN12_F2_Analysis : IAnalysisStrategy> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public AFN12_F2_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public async Task> ExecuteAsync(TB3761 input) + { + + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + var data= await GenerateFinalResultAsync(input.UnitData.HexMessageList); + data.DataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + UnitDataAnalysis dto = new UnitDataAnalysis + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data = data + }; + return await Task.FromResult(dto); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0C_2解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}"); + return null; + } + } + + public async Task GenerateFinalResultAsync(List hexMessageList) + { + AFN12_F2_AnalysisDto dto = new AFN12_F2_AnalysisDto(); + var arr = hexMessageList.GetRange(4, 6); + var errorCodeInfo = arr.CheckErrorCode(); + if (errorCodeInfo != null) + { + dto.ValidData = false; + dto.ErrorCodeMsg = errorCodeInfo.Item2; + } + else + dto.DataValue = await _analysisStrategyContext.ExecuteAsync, string>(nameof(Appendix_A1), arr); + + dto.FiledName = "TerminalTime"; + dto.FiledDesc = "召读终端时间"; + return await Task.FromResult(dto); + } + + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F33_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F33_Analysis.cs new file mode 100644 index 0000000..50bcfd8 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F33_Analysis.cs @@ -0,0 +1,121 @@ +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AFN_0CH +{ + /// + /// 5.12.2.4.30 F33:当前正向有/无功电能示值、一/四象限无功电能示值(总、费率 1~M,1≤M≤12) + /// + public class AFN12_F33_Analysis : IAnalysisStrategy> + { + private List DataUnitHexList { get; set; }=new List(); + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public AFN12_F33_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + + public async Task> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData.HexMessageList); + int rationgCount = input.UnitData.HexMessageList!.GetRatingCount(23, 1); + + DataUnitHexList = input.UnitData.HexMessageList.GetRange(24, (5 * (rationgCount + 1)) + (4 * (rationgCount + 1)) * 3); + GetDataUnitHexString(input.UnitData.HexMessageList, rationgCount); + UnitDataAnalysis unitDataAnalysis = new UnitDataAnalysis + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data = await AnalysisDataUnit(input.UnitData.HexMessageList, rationgCount) + }; + + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0C_33解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}"); + return null; + } + } + + #region 获取当前正向有/无功电能示值数据单元格式 private void GetDataUnitHexString(int rationgCount) + private void GetDataUnitHexString(List hexMessageList, int rationgCount) + { + //(5*(x+1))+(4*(x+1))*3 + var lenght = (5 * (rationgCount + 1)) + (4 * (rationgCount + 1)) * 3;//(rationgCount * 5 + 5) + (4 * 3) + (rationgCount * 4) * 2;//费率数(R):4 公式:5+5+(费率数*5)+ (4+(费率数*4))*2 + DataUnitHexList = hexMessageList.GetRange(24, lenght); + } + #endregion + + #region 单元数据值解析 private F33Entity AnalysisDataUnit() + private async Task AnalysisDataUnit(List hexMessageList, int rationgCount) + { + AFN12_F33_AnalysisDto f33Entity = new AFN12_F33_AnalysisDto(); + f33Entity.ReadTime = hexMessageList.GetReadTime(18, 5); // 终端抄表时间 + f33Entity.RatingCount = hexMessageList.GetRatingCount(23, 1); // 费率数 M(1≤M≤12) + f33Entity.F_A_Kwh = await GetDataAsync(f33Entity.RatingCount, 0, 5, nameof(Appendix_A14));//当前正向有功总电能示值 + f33Entity.R_R_Kvarh = await GetDataAsync(f33Entity.RatingCount, 25, 4, nameof(Appendix_A11));//当前正向无功(组合无功1)总电能示值 0+5*5 + f33Entity.Q1_R_Kvarh = await GetDataAsync(f33Entity.RatingCount, 45, 4, nameof(Appendix_A11));//当前一象限无功总电能示值 25+4*5 + f33Entity.Q4_R_Kvarh = await GetDataAsync(f33Entity.RatingCount, 65, 4, nameof(Appendix_A11));//当前四象限无功总电能示值 45+4*5 + return f33Entity; + } + #region 有功/无功、费率总电能示值 + /// + /// 有功/无功、费率总电能示值 + /// + /// 费率数 + /// 开始位置 + /// 长度 + /// + private async Task GetDataAsync(int ratingCount, int startIndex, int len, string appendxName) + { + ParentNodes parent = new ParentNodes(); + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(appendxName, DataUnitHexList.GetRange(startIndex, len)); + parent.Total_Value = value; + parent.childNodes = await GetChildDataAsync(ratingCount, startIndex, len, appendxName); + return parent; + } + + /// + /// 费率总电能示值 + /// + /// + /// + /// + /// + /// + private async Task> GetChildDataAsync(int RatingCount, int startIndex, int len, string appendxName) + { + List children = new List(); + for (int i = 0; i < RatingCount; i++) + { + ChildNodes model = new ChildNodes() + { + Value = await _analysisStrategyContext.ExecuteAsync, decimal>(appendxName, DataUnitHexList.GetRange((i * len) + (startIndex + len), len)) //29,4 33,4 37,4 41,4 + }; + children.Add(model); + } + return children; + } + + + #endregion + + + #endregion + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F49_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F49_Analysis.cs new file mode 100644 index 0000000..3e6f45a --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0CH/AFN12_F49_Analysis.cs @@ -0,0 +1,94 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AFN_0CH +{ + /// + /// 5.12.2.4.46 F49:当前电压、电流相位角 + /// + public class AFN12_F49_Analysis : IAnalysisStrategy>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public AFN12_F49_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public List DataType { get; set; } = new List() { "Uab_Ua", "Ub", "Ucb_Uc", "Ia", "Ib", "Ic" }; + + public async Task>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + + List data = await AnalysisDataUnitAsync(input.UnitData?.HexMessageList!); + List remarks = new List() { "Uab/Ua 相位角", "Ub 相位角", "Ucb/Uc 相位角", "Ia 相位角", "Ib 相位角", "Ic 相位角" }; + List list = new List(); + for (int i = 0; i < data.Count; i++) + { + AFN12_F49_AnalysisDto dto = new AFN12_F49_AnalysisDto(); + decimal value = 0; + var errorCodeInfo = data[i].CheckErrorCode(); + if (errorCodeInfo != null) + { + dto.ValidData = false; + dto.ErrorCodeMsg = errorCodeInfo.Item2; + } + else + decimal.TryParse(data[i], out value); + dto.DataValue = value; + + dto.DataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}_{DataType[i]}"; + dto.FiledName = DataType[i]; + dto.FiledDesc= remarks[i]; + list.Add(dto); + } + UnitDataAnalysis> unitDataAnalysis = new UnitDataAnalysis> + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data= list + }; + + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0C_49解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + for (int i = 0; i < 6; i++) + { + var arr = hexMessageList.GetRange(4 + (i * 2), 2); + var errorCode = arr.CheckErrorCode(); + if (errorCode!=null) + values.Add(errorCode.Item1); + else + { + var value= await _analysisStrategyContext.ExecuteAsync,decimal>(nameof(Appendix_A5), arr); + values.Add(value.ToString()); + } + + } + return values; + } + + + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F100_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F100_Analysis.cs new file mode 100644 index 0000000..80af3c8 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F100_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.78 F100:测量点反向无功总电能量曲线 + /// + public class AFN13_F100_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F100_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "反向无功总电能量"); + + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_100解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A13), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F101_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F101_Analysis.cs new file mode 100644 index 0000000..4a81d20 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F101_Analysis.cs @@ -0,0 +1,99 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.79 F101:测量点正向有功总电能示值曲线 + /// + public class AFN13_F101_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F101_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "正向有功总电能示值"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_101解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F102_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F102_Analysis.cs new file mode 100644 index 0000000..7f9d101 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F102_Analysis.cs @@ -0,0 +1,100 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.80 F102:测量点正向无功总电能示值曲线 + /// + public class AFN13_F102_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F102_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "正向无功总电能示值"); + + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_102解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F103_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F103_Analysis.cs new file mode 100644 index 0000000..0882271 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F103_Analysis.cs @@ -0,0 +1,100 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.81 F103:测量点反向有功总电能示值曲线 + /// + public class AFN13_F103_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F103_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "反向有功总电能示值"); + + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_103解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F104_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F104_Analysis.cs new file mode 100644 index 0000000..408a5af --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F104_Analysis.cs @@ -0,0 +1,100 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.82 F104:测量点反向无功总电能示值曲线 + /// + public class AFN13_F104_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F104_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "反向无功总电能示值"); + + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_104解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F105_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F105_Analysis.cs new file mode 100644 index 0000000..1d1fd4c --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F105_Analysis.cs @@ -0,0 +1,101 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.83 F105:测量点功率因数曲线 + /// + public class AFN13_F105_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F105_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "功率因数"); + + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_105解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 2), 2); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A5), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F106_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F106_Analysis.cs new file mode 100644 index 0000000..5dbd7a3 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F106_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.84 F106:测量点 A相功率因数曲线 + /// + public class AFN13_F106_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F106_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "A相功率因数"); + + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_106解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 2), 2); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A5), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F107_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F107_Analysis.cs new file mode 100644 index 0000000..d5b1584 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F107_Analysis.cs @@ -0,0 +1,100 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.84 F107:测量点 B相功率因数曲线 + /// + public class AFN13_F107_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F107_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "B相功率因数"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_107解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 2), 2); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A5), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F108_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F108_Analysis.cs new file mode 100644 index 0000000..3a54a06 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F108_Analysis.cs @@ -0,0 +1,99 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.84 F108:测量点 C相功率因数曲线 + /// + public class AFN13_F108_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F108_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "C相功率因数"); + + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_107解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 2), 2); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A5), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F11_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F11_Analysis.cs new file mode 100644 index 0000000..495f0dc --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F11_Analysis.cs @@ -0,0 +1,155 @@ +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.11 F11:抄表日冻结电能表正向有/无功最大需量及发生时间(总、费率 1~M,1≤M≤12) + /// + public class AFN13_F11_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F11_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + List> data = GenerateFinalResult(datas,3, dataType, "抄表日冻结电能表正向有功最大需量及发生时间"); + + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_11解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.Add(hexMessageList.GetReadTime(4, 3));//日冻结类数据时标 Td_d + values.Add(hexMessageList.GetReadTime(7, 5));//终端抄表时间 + int ratingCount = hexMessageList.GetRatingCount(12, 1);//费率数 M(1≤M≤12) + values.Add(ratingCount.ToString()); + int handlerNum = 13; + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 3 * (ratingCount + 1)), ratingCount, 3, nameof(Appendix_A23)));//正向有功总最大需量 + handlerNum += 3 * (ratingCount + 1);//12 + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 4 * (ratingCount + 1)), ratingCount, 4, nameof(Appendix_A17)));//正向有功总最大需量发生时间 + handlerNum += 4 * (ratingCount + 1);//28 + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 3 * (ratingCount + 1)), ratingCount, 3, nameof(Appendix_A23)));//正向无功总最大需量 + handlerNum += 3 * (ratingCount + 1);//48 + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 4 * (ratingCount + 1)), ratingCount, 4, nameof(Appendix_A17)));//正向无功总最大需量发生时间 + handlerNum += 4 * (ratingCount + 1); + return values; + } + + private async Task> GetDataAsync(List data, int ratingCount, int len, string appendixName) + { + List values = new List(); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = data.GetRange(0 + (i * len), len); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, T>(nameof(Appendix_A5), arr); + values.Add(value.ToString()); + } + } + return values; + } + + public List> GenerateFinalResult(List data, int index, string dataType, string filedDesc = "") + { + List> list = new List>(); + int fCount = Convert.ToInt32(data[2]) + 1; + for (int i = 1; i <= 2; i++) + { + AnalysisBaseDto meter = new AnalysisBaseDto + { + MeterType = MeterTypeEnum.Ammeter + }; + int startIndex = i == 1 ? 3 : (3 + i * fCount); + + decimal value = 0; + var errorCode = data[startIndex].CheckErrorCode(); + if (errorCode != null) + { + meter.ValidData = false; + meter.ErrorCodeMsg = errorCode.Item2; + } + else + decimal.TryParse(data[startIndex], out value); + + meter.DataValue = value; + string timeSpan = $"{DateTime.Now.Year}-{data[startIndex + fCount].Substring(0, 2)}-{data[startIndex + fCount].Substring(2, 2)} {data[startIndex + fCount].Substring(4, 2)}:{data[startIndex + fCount].Substring(6, 2)}:00"; + DateTime readingDate = DateTime.Now; + if (DateTime.TryParse(timeSpan, out readingDate)) + { + meter.TimeSpan = readingDate; + } + meter.DataType = i == 1 ? dataType : $"{dataType}_{ i - 1}"; + filedDesc = i == 1 ? filedDesc : filedDesc.Replace("有功", "无功"); + meter.FiledDesc = filedDesc; + meter.FiledName = meter.DataType.GetDataFieldByGatherDataType() ?? string.Empty; + + list.Add(meter); + } + return list; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F145_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F145_Analysis.cs new file mode 100644 index 0000000..125ef90 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F145_Analysis.cs @@ -0,0 +1,100 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.104 F145:测量点一象限无功总电能示值曲线 + /// + public class AFN13_F145_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F145_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "一象限无功电能示值"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_145解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F146_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F146_Analysis.cs new file mode 100644 index 0000000..3113c9b --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F146_Analysis.cs @@ -0,0 +1,100 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.105 F146:测量点四象限无功总电能示值曲线 + /// + public class AFN13_F146_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F146_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "四象限无功电能示值"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_146解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F147_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F147_Analysis.cs new file mode 100644 index 0000000..cdc58cc --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F147_Analysis.cs @@ -0,0 +1,100 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.106 F147:测量点二象限无功总电能示值曲线 + /// + public class AFN13_F147_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F147_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "二象限无功电能示值"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_147解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F148_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F148_Analysis.cs new file mode 100644 index 0000000..dc978e3 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F148_Analysis.cs @@ -0,0 +1,100 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.107 F148:测量点三象限无功总电能示值曲线 + /// + public class AFN13_F148_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F148_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "三象限无功电能示值"); + + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_148解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F161_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F161_Analysis.cs new file mode 100644 index 0000000..a5c7c04 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F161_Analysis.cs @@ -0,0 +1,105 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.116 F161:日冻结正向有功电能示值(总、费率 1~M) + /// + public class AFN13_F161_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F161_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-{datas[0].Substring(6, 2)} 00:00:00"; + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_d(3, dataType,timeSpan, "正向有功总电能示值").IsValidData(new List() { "0D_161", "0D_161_1", "0D_161_2", "0D_161_3", "0D_161_4" }); + + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_161解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 3), + hexMessageList.GetReadTime(7, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(12, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(13 + (i * 5), 5); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A14), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F162_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F162_Analysis.cs new file mode 100644 index 0000000..3a3853d --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F162_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.117 F162:日冻结正向无功(组合无功 1)电能示值(总、费率 1~M) + /// + public class AFN13_F162_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F162_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-{datas[0].Substring(6, 2)} 00:00:00"; + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_d(3, dataType, timeSpan, "正向无功(组合无功 1)电能示值").IsValidData(new List() { "0D_162", "0D_162_1", "0D_162_2", "0D_162_3", "0D_162_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_162解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 3), + hexMessageList.GetReadTime(7, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(12, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(13 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F163_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F163_Analysis.cs new file mode 100644 index 0000000..7748948 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F163_Analysis.cs @@ -0,0 +1,103 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.118 F163:日冻结反向有功电能示值(总、费率 1~M) + /// + public class AFN13_F163_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F163_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-{datas[0].Substring(6, 2)} 00:00:00"; + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_d(3, dataType, timeSpan, "反向有功总电能示值").IsValidData(new List() { "0D_163", "0D_163_1", "0D_163_2", "0D_163_3", "0D_163_4" }); + + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_163解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 3), + hexMessageList.GetReadTime(7, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(12, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(13 + (i * 5), 5); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A14), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F164_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F164_Analysis.cs new file mode 100644 index 0000000..81cd07f --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F164_Analysis.cs @@ -0,0 +1,104 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.119 F164:日冻结反向无功(组合无功 1)电能示值(总、费率 1~M) + /// + public class AFN13_F164_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F164_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-{datas[0].Substring(6, 2)} 00:00:00"; + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_d(3, dataType, timeSpan, "日冻结反向无功(组合无功 1)电能示值").IsValidData(new List() { "0D_164", "0D_164_1", "0D_164_2", "0D_164_3", "0D_164_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_164解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 3), + hexMessageList.GetReadTime(7, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(12, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(13 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F165_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F165_Analysis.cs new file mode 100644 index 0000000..d7a821c --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F165_Analysis.cs @@ -0,0 +1,104 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.120 F165:日冻结一象限无功电能示值(总、费率 1~M) + /// + public class AFN13_F165_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F165_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-{datas[0].Substring(6, 2)} 00:00:00"; + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_d(3, dataType, timeSpan, "日冻结一象限无功电能示值").IsValidData(new List() { "0D_165", "0D_165_1", "0D_165_2", "0D_165_3", "0D_165_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_165解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 3), + hexMessageList.GetReadTime(7, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(12, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(13 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F166_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F166_Analysis.cs new file mode 100644 index 0000000..cce8772 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F166_Analysis.cs @@ -0,0 +1,103 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.121 F166:日冻结二象限无功电能示值(总、费率 1~M) + /// + public class AFN13_F166_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F166_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-{datas[0].Substring(6, 2)} 00:00:00"; + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_d(3, dataType, timeSpan, "日冻结二象限无功电能示值").IsValidData(new List() { "0D_166", "0D_166_1", "0D_166_2", "0D_166_3", "0D_166_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_166解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 3), + hexMessageList.GetReadTime(7, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(12, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(13 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F167_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F167_Analysis.cs new file mode 100644 index 0000000..2f1fc28 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F167_Analysis.cs @@ -0,0 +1,103 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.122 F167:日冻结三象限无功电能示值(总、费率 1~M) + /// + public class AFN13_F167_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F167_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-{datas[0].Substring(6, 2)} 00:00:00"; + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_d(3, dataType, timeSpan, "日冻结三象限无功电能示值").IsValidData(new List() { "0D_167", "0D_167_1", "0D_167_2", "0D_167_3", "0D_167_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_167解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 3), + hexMessageList.GetReadTime(7, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(12, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(13 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F168_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F168_Analysis.cs new file mode 100644 index 0000000..2553c0b --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F168_Analysis.cs @@ -0,0 +1,103 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.123 F168:日冻结四象限无功电能示值(总、费率 1~M) + /// + public class AFN13_F168_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F168_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-{datas[0].Substring(6, 2)} 00:00:00"; + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_d(3, dataType, timeSpan, "日冻结四象限无功电能示值").IsValidData(new List() { "0D_168", "0D_168_1", "0D_168_2", "0D_168_3", "0D_168_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_168解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 3), + hexMessageList.GetReadTime(7, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(12, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(13 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F177_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F177_Analysis.cs new file mode 100644 index 0000000..624f2c6 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F177_Analysis.cs @@ -0,0 +1,104 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.132 F177:月冻结正向有功电能示值(总、费率 1~M) + /// + public class AFN13_F177_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F177_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-01 00:00:00"; + List> data = datas.GenerateFinalResultTd_m(3, dataType, timeSpan, "月冻结正向有功电能示值").IsValidData(new List() { "0D_177", "0D_177_1", "0D_177_2", "0D_177_3", "0D_177_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Month + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_177解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 2), + hexMessageList.GetReadTime(6, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(11, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(12 + (i * 5), 5); + + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A14), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F178_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F178_Analysis.cs new file mode 100644 index 0000000..dc882c4 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F178_Analysis.cs @@ -0,0 +1,105 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// F178:月冻结正向无功(组合无功 1)电能示值(总、费率 1~M) + /// + public class AFN13_F178_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F178_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-01 00:00:00"; + List> data = datas.GenerateFinalResultTd_m(3, dataType, timeSpan, "月冻结正向无功(组合无功 1)电能示值").IsValidData(new List() { "0D_178", "0D_178_1", "0D_178_2", "0D_178_3", "0D_178_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Month + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_178解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 2), + hexMessageList.GetReadTime(6, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(11, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(12 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + + + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F179_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F179_Analysis.cs new file mode 100644 index 0000000..07ff2b2 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F179_Analysis.cs @@ -0,0 +1,104 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.134 F179:月冻结反向有功电能示值(总、费率 1~M) + /// + public class AFN13_F179_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F179_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-01 00:00:00"; + List> data = datas.GenerateFinalResultTd_m(3, dataType, timeSpan, "反向有功总电能示值").IsValidData(new List() { "0D_179", "0D_179_1", "0D_179_2", "0D_179_3", "0D_179_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Month + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_179解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 2), + hexMessageList.GetReadTime(6, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(11, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(12 + (i * 5), 5); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A14), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F180_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F180_Analysis.cs new file mode 100644 index 0000000..0fed8cb --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F180_Analysis.cs @@ -0,0 +1,103 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.135 F180:月冻结反向无功(组合无功 1)电能示值(总、费率 1~M) + /// + public class AFN13_F180_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F180_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-01 00:00:00"; + List> data = datas.GenerateFinalResultTd_m(3, dataType, timeSpan, "月冻结反向无功(组合无功 1)电能示值").IsValidData(new List() { "0D_180", "0D_180_1", "0D_180_2", "0D_180_3", "0D_180_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Month + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_180解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 2), + hexMessageList.GetReadTime(6, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(11, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(12 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F181_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F181_Analysis.cs new file mode 100644 index 0000000..466da30 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F181_Analysis.cs @@ -0,0 +1,104 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.136 F181:月冻结一象限无功电能示值(总、费率 1~M) + /// + public class AFN13_F181_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F181_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-01 00:00:00"; + List> data = datas.GenerateFinalResultTd_m(3, dataType, timeSpan, "月冻结一象限无功电能示值(总、费率 1~M)").IsValidData(new List() { "0D_181", "0D_181_1", "0D_181_2", "0D_181_3", "0D_181_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Month + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_181解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 2), + hexMessageList.GetReadTime(6, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(11, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(12 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F182_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F182_Analysis.cs new file mode 100644 index 0000000..0833b9f --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F182_Analysis.cs @@ -0,0 +1,103 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.137 F182:月冻结二象限无功电能示值(总、费率 1~M) + /// + public class AFN13_F182_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F182_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-01 00:00:00"; + List> data = datas.GenerateFinalResultTd_m(3, dataType, timeSpan, "月冻结二象限无功电能示值(总、费率 1~M)").IsValidData(new List() { "0D_182", "0D_182_1", "0D_182_2", "0D_182_3", "0D_182_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Month + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_182解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 2), + hexMessageList.GetReadTime(6, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(11, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(12 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F183_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F183_Analysis.cs new file mode 100644 index 0000000..fc73291 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F183_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.138 F183:月冻结三象限无功电能示值(总、费率 1~M) + /// + public class AFN13_F183_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F183_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-01 00:00:00"; + List> data = datas.GenerateFinalResultTd_m(3, dataType, timeSpan, "月冻结三象限无功电能示值(总、费率 1~M)").IsValidData(new List() { "0D_183", "0D_183_1", "0D_183_2", "0D_183_3", "0D_183_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Month + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_183解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 2), + hexMessageList.GetReadTime(6, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(11, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(12 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F184_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F184_Analysis.cs new file mode 100644 index 0000000..103d49c --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F184_Analysis.cs @@ -0,0 +1,103 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.139 F184:月冻结四象限无功电能示值(总、费率 1~M) + /// + public class AFN13_F184_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F184_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-01 00:00:00"; + List> data = datas.GenerateFinalResultTd_m(3, dataType, timeSpan, "月冻结四象限无功电能示值(总、费率 1~M)").IsValidData(new List() { "0D_184", "0D_184_1", "0D_184_2", "0D_184_3", "0D_184_4" }); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Month + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_184解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 2), + hexMessageList.GetReadTime(6, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(11, 1); + values.Add(ratingCount.ToString()); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = hexMessageList.GetRange(12 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A11), arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F189_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F189_Analysis.cs new file mode 100644 index 0000000..1fba574 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F189_Analysis.cs @@ -0,0 +1,113 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.144 F189:抄表日冻结正向有功最大需量及发生时间(总、费率 1~M) + /// + public class AFN13_F189_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F189_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string timeSpan = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-{datas[0].Substring(6, 2)} 00:00:00"; + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_d(3, dataType, timeSpan, "抄表日冻结正向有功最大需量及发生时间"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_189解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.Add(hexMessageList.GetReadTime(4, 3)); + values.Add(hexMessageList.GetReadTime(7, 5)); + int ratingCount = hexMessageList.GetRatingCount(12, 1); + values.Add(ratingCount.ToString()); + int handlerNum = 13; + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 3), ratingCount, 3, nameof(Appendix_A23)));//正向有功总最大需量 + handlerNum += 3; + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 4), ratingCount, 4, nameof(Appendix_A17)));//正向有功总最大需量发生时间 + return values; + } + + private async Task> GetDataAsync(List data, int ratingCount, int len, string appendixName) + { + List values = new List(); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = data.GetRange(0 + (i * len), len); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, T>(appendixName, arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F190_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F190_Analysis.cs new file mode 100644 index 0000000..956eb11 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F190_Analysis.cs @@ -0,0 +1,141 @@ +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.145 F190:抄表日冻结正向无功最大需量及发生时间(总、费率 1~M) + /// + public class AFN13_F190_Analysis : IAnalysisStrategy>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F190_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + + AnalysisBaseDto data = GenerateFinalResult(datas, "抄表日冻结正向无功最大需量及发生时间"); + data.DataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data.MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ProjectId = ammeterInfo.ProjectID; + data.MeterId = ammeterInfo.MeterId; + data.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + data.MeterAddress = ammeterInfo.AmmerterAddress; + } + UnitDataAnalysis> unitDataAnalysis = new UnitDataAnalysis> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_190解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.Add(hexMessageList.GetReadTime(4, 3)); + values.Add(hexMessageList.GetReadTime(7, 5)); + int ratingCount = hexMessageList.GetRatingCount(12, 1); + values.Add(ratingCount.ToString()); + int handlerNum = 13; + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 3 * (ratingCount + 1)), ratingCount, 3, nameof(Appendix_A23)));//正向有功总最大需量 + handlerNum += 3 * (ratingCount + 1);//12 + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 4 * (ratingCount + 1)), ratingCount, 4, nameof(Appendix_A17)));//正向有功总最大需量发生时间 + handlerNum += 4 * (ratingCount + 1);//28 + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 3 * (ratingCount + 1)), ratingCount, 3, nameof(Appendix_A23)));//正向无功总最大需量 + handlerNum += 3 * (ratingCount + 1);//48 + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 4 * (ratingCount + 1)), ratingCount, 4, nameof(Appendix_A17)));//正向无功总最大需量发生时间 + handlerNum += 4 * (ratingCount + 1); + return values; + } + + private async Task> GetDataAsync(List data, int ratingCount, int len, string appendixName) + { + List values = new List(); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = data.GetRange(0 + (i * len), len); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, T>(appendixName, arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + + public AnalysisBaseDto GenerateFinalResult(List data, string filedDesc = "") + { + List> list = new List>(); + AnalysisBaseDto meter = new AnalysisBaseDto + { + MeterType = MeterTypeEnum.Ammeter + }; + + decimal value = 0; + var errorCode = data[3].CheckErrorCode(); + if (errorCode != null) + { + meter.ValidData = false; + meter.ErrorCodeMsg = errorCode.Item2; + } + else + decimal.TryParse(data[3], out value); + meter.DataValue = value; + + string timeSpan = $"{DateTime.Now.Year}-{data[4].Substring(0, 2)}-{data[4].Substring(2, 2)} {data[4].Substring(4, 2)}:{data[4].Substring(6, 2)}:00";//$"{data[0].Substring(0, 4)}-{data[0].Substring(4, 2)}-{data[0].Substring(6, 2)}"; + DateTime readingDate = DateTime.Now; + if (DateTime.TryParse(timeSpan, out readingDate)) + { + meter.TimeSpan = readingDate; + } + meter.FiledDesc = filedDesc; + meter.FiledName = meter.DataType.GetDataFieldByGatherDataType() ?? string.Empty; + return meter; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F193_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F193_Analysis.cs new file mode 100644 index 0000000..2e0c48d --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F193_Analysis.cs @@ -0,0 +1,132 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Common.Consts; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.148 F193:月冻结正向有功最大需量及发生时间(总、费率 1~M) + /// + public class AFN13_F193_Analysis : IAnalysisStrategy>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F193_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + + AnalysisBaseDto data = GenerateFinalResult(datas, "月冻结正向有功最大需量及发生时间"); + data.DataType= $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data.MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ProjectId = ammeterInfo.ProjectID; + data.MeterId = ammeterInfo.MeterId; + data.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + data.MeterAddress = ammeterInfo.AmmerterAddress; + } + UnitDataAnalysis> unitDataAnalysis = new UnitDataAnalysis> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Month + }; + await _dataStorage.SaveDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_193解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 2), + hexMessageList.GetReadTime(6, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(11, 1); + values.Add(ratingCount.ToString()); + int handlerNum = 12; + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 3), 3, nameof(Appendix_A23)));//正向有功总最大需量 + handlerNum += 3; + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 4), 4, nameof(Appendix_A17)));//正向有功总最大需量发生时间 + return values; + } + + private async Task> GetDataAsync(List data, int len, string appendixName) + { + List values = new List(); + var errorCode = data.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, T>(appendixName, data); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + return values; + } + public AnalysisBaseDto GenerateFinalResult(List data, string filedDesc = "") + { + AnalysisBaseDto meter = new AnalysisBaseDto + { + MeterType = MeterTypeEnum.Ammeter, + }; + + decimal value = 0; + var errorCode = data[3].CheckErrorCode(); + if (errorCode != null) + { + meter.ValidData = false; + meter.ErrorCodeMsg = errorCode.Item2; + } + else + decimal.TryParse(data[3], out value); + meter.DataValue = value; + + string timeSpan = $"{data[0].Substring(0, 4)}-{data[4].Substring(0, 2)}-{data[4].Substring(2, 2)} {data[4].Substring(4, 2)}:{data[4].Substring(6, 2)}:00";//$"{data[0].Substring(0, 4)}-{data[0].Substring(4, 2)}-{data[0].Substring(6, 2)}"; + DateTime readingDate = DateTime.Now; + if (DateTime.TryParse(timeSpan, out readingDate)) + { + meter.TimeSpan = readingDate; + } + meter.FiledDesc = filedDesc; + meter.FiledName = meter.DataType.GetDataFieldByGatherDataType() ?? string.Empty; + return meter; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F195_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F195_Analysis.cs new file mode 100644 index 0000000..c7ffe04 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F195_Analysis.cs @@ -0,0 +1,132 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.150 F195:月冻结反向有功最大需量及发生时间(总、费率 1~M) + /// + public class AFN13_F195_Analysis : IAnalysisStrategy>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F195_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + + AnalysisBaseDto data = GenerateFinalResult(datas, "月冻结反向有功最大需量及发生时间"); + data.DataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data.MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ProjectId = ammeterInfo.ProjectID; + data.MeterId = ammeterInfo.MeterId; + data.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + data.MeterAddress = ammeterInfo.AmmerterAddress; + } + UnitDataAnalysis> unitDataAnalysis = new UnitDataAnalysis> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Month + }; + await _dataStorage.SaveDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_195解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 2), + hexMessageList.GetReadTime(6, 5) + }; + int ratingCount = hexMessageList.GetRatingCount(11, 1); + values.Add(ratingCount.ToString()); + int handlerNum = 12; + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 3), 3,nameof(Appendix_A23)));//正向有功总最大需量 + handlerNum += 3; + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 4), 4, nameof(Appendix_A17)));//正向有功总最大需量发生时间 + return values; + } + + private async Task> GetDataAsync(List data, int len, string appendixName) + { + List values = new List(); + var errorCode = data.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, T>(appendixName, data); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + return values; + } + public AnalysisBaseDto GenerateFinalResult(List data, string filedDesc = "") + { + AnalysisBaseDto meter = new AnalysisBaseDto + { + MeterType = MeterTypeEnum.Ammeter + }; + + decimal value = 0; + var errorCode = data[3].CheckErrorCode(); + if (errorCode != null) + { + meter.ValidData = false; + meter.ErrorCodeMsg = errorCode.Item2; + } + else + decimal.TryParse(data[3], out value); + meter.DataValue = value; + + string timeSpan = $"{data[0].Substring(0, 4)}-{data[4].Substring(0, 2)}-{data[4].Substring(2, 2)} {data[4].Substring(4, 2)}:{data[4].Substring(6, 2)}:00";//$"{data[0].Substring(0, 4)}-{data[0].Substring(4, 2)}-{data[0].Substring(6, 2)}"; + DateTime readingDate = DateTime.Now; + if (DateTime.TryParse(timeSpan, out readingDate)) + { + meter.TimeSpan = readingDate; + } + meter.FiledDesc = filedDesc; + meter.FiledName = meter.DataType.GetDataFieldByGatherDataType() ?? string.Empty; + return meter; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F19_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F19_Analysis.cs new file mode 100644 index 0000000..5cda5b3 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F19_Analysis.cs @@ -0,0 +1,153 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using System.Data; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.15 F19:月冻结电能表正向有/无功最大需量及发生时间(总、费率 1~M,1≤M≤12) + /// + public class AFN13_F19_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F19_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + List> data = GenerateFinalResult(datas, dataType, "月冻结电能表正向有功最大需量及发生时间"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_19解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.Add(hexMessageList.GetReadTime(4, 2));//月冻结类数据时标 Td_m + values.Add(hexMessageList.GetReadTime(6, 5));// + int ratingCount = hexMessageList.GetRatingCount(11, 1); + values.Add(ratingCount.ToString()); + int handlerNum = 12; + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 3 * (ratingCount + 1)), ratingCount, 3, nameof(Appendix_A23)));//正向有功总最大需量 + handlerNum += 3 * (ratingCount + 1); + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 4 * (ratingCount + 1)), ratingCount, 4, nameof(Appendix_A17)));//正向有功总最大需量发生时间 + handlerNum += 4 * (ratingCount + 1); + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 3 * (ratingCount + 1)), ratingCount, 3, nameof(Appendix_A23)));//正向无功总最大需量 + handlerNum += 3 * (ratingCount + 1); + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 4 * (ratingCount + 1)), ratingCount, 4, nameof(Appendix_A17)));//正向无功总最大需量发生时间 + handlerNum += 4 * (ratingCount + 1); + return values; + } + + private async Task> GetDataAsync(List data, int ratingCount, int len, string appendixName) + { + List values = new List(); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = data.GetRange(0 + (i * len), len); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, T>(appendixName, arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + + public List> GenerateFinalResult(List data, string dataType,string filedDesc = "") + { + List> list = new List>(); + int fCount = Convert.ToInt32(data[2]) + 1; + for (int i = 1; i <= 2; i++) + { + AnalysisBaseDto meter = new AnalysisBaseDto(); + meter.MeterType= MeterTypeEnum.Ammeter; + int startIndex = i == 1 ? 3 : (3 + i * fCount); + decimal value = 0; + var errorCode = data[startIndex].CheckErrorCode(); + if (errorCode != null) + { + meter.ValidData = false; + meter.ErrorCodeMsg = errorCode.Item2; + } + else + decimal.TryParse(data[startIndex], out value); + meter.DataValue = value; + //2021 01 15 + string timeSpan = $"{DateTime.Now.Year}-{data[startIndex + fCount].Substring(0, 2)}-{data[startIndex + fCount].Substring(2, 2)} {data[startIndex + fCount].Substring(4, 2)}:{data[startIndex + fCount].Substring(6, 2)}:00"; + DateTime readingDate = DateTime.Now; + if (DateTime.TryParse(timeSpan, out readingDate)) + { + meter.TimeSpan = readingDate; + } + meter.DataType = i == 1 ? dataType : $"{dataType}_{ i - 1}"; + + filedDesc = i == 1 ? filedDesc : filedDesc.Replace("有功", "无功"); + meter.FiledDesc = filedDesc; + meter.FiledName = meter.DataType.GetDataFieldByGatherDataType() ?? string.Empty; + list.Add(meter); + } + return list; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F3_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F3_Analysis.cs new file mode 100644 index 0000000..f53b936 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F3_Analysis.cs @@ -0,0 +1,180 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using System.Data; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using Volo.Abp.Domain.Entities; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Common.Consts; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.3 F3:日冻结正向有/无功最大需量及发生时间(总、费率 1~M,1≤M≤12) + /// + public class AFN13_F3_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F3_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + //冻结数据时标 + var time = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-{datas[0].Substring(6, 2)} 00:00:00"; + + List> data = GenerateFinalResult(datas,dataType, "日冻结正向有/无功最大需量及发生时间"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_3解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 3),//日冻结类数据时标 Td_d + hexMessageList.GetReadTime(7, 5)//终端抄表时间 + }; + int ratingCount = hexMessageList.GetRatingCount(12, 1);//费率数 M(1≤M≤12) + values.Add(ratingCount.ToString()); + int handlerNum = 13; + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 3 * (ratingCount + 1)), ratingCount, 3, nameof(Appendix_A23)));//正向有功总最大需量 + handlerNum += 3 * (ratingCount + 1);//12 + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 4 * (ratingCount + 1)), ratingCount, 4, nameof(Appendix_A17)));//正向有功总最大需量发生时间 + handlerNum += 4 * (ratingCount + 1);//28 + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 3 * (ratingCount + 1)), ratingCount, 3, nameof(Appendix_A23)));//正向无功总最大需量 + handlerNum += 3 * (ratingCount + 1);//48 + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 4 * (ratingCount + 1)), ratingCount, 4, nameof(Appendix_A17)));//正向无功总最大需量发生时间 + handlerNum += 4 * (ratingCount + 1); + return values; + } + + private async Task> GetDataAsync(List data, int ratingCount, int len, string appendixName) + { + List values = new List(); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = data.GetRange(0 + (i * len), len); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, T>(appendixName, arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + + public List> GenerateFinalResult(List data,string dataType, string filedDesc = "") + { + List> list = new List>(); + int fCount = Convert.ToInt32(data[2]) + 1; + for (int i = 1; i <= 2; i++) + { + try + { + AnalysisBaseDto meter = new AnalysisBaseDto + { + MeterType = MeterTypeEnum.Ammeter + }; + int startIndex = i == 1 ? 3 : (3 + i * fCount); + + var errorCode = data[startIndex].CheckErrorCode(); + if (errorCode != null) + meter.ValidData = false; + decimal.TryParse(data[startIndex], out decimal value);//decimal.TryParse(data[startIndex], out value); + meter.DataValue = value; + + //TODO:日冻结类数据时标Td 20210824 + var dataTime = string.Empty; + errorCode = data[0].CheckErrorCode(); + if (data[0].Length == 8 && errorCode is null) + dataTime = $"{data[0].Substring(0, 4)}-{data[0].Substring(4, 2)}-{data[0].Substring(6, 2)} 00:00:00"; + if (!DateTime.TryParse(dataTime, out DateTime readingDate)) + meter.ValidData = false; + meter.TimeSpan = readingDate;// + + //TODO:最大需量发生时间 + errorCode = data[startIndex + fCount].CheckErrorCode(); + if (errorCode != null && data[startIndex + fCount].Length != 8) + meter.ValidData = false; + else + { + string timeSpan = $"{data[0].Substring(0, 4)}-{data[startIndex + fCount].Substring(0, 2)}-{data[startIndex + fCount].Substring(2, 2)} {data[startIndex + fCount].Substring(4, 2)}:{data[startIndex + fCount].Substring(6, 2)}:00"; + if (!DateTime.TryParse(timeSpan, out DateTime tsField)) + meter.ValidData = false; + else + meter.TimeSpan=tsField; + } + + meter.DataType = i == 1 ? dataType : $"{dataType}_{ i - 1}"; + filedDesc = i == 1 ? filedDesc : filedDesc.Replace("有功", "无功"); + meter.FiledDesc = filedDesc; + meter.FiledName = meter.DataType.GetDataFieldByGatherDataType() ?? string.Empty; + list.Add(meter); + } + catch + { + } + } + if (list.Count == 0) + throw new Exception("错误数据"); + + return list; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F4_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F4_Analysis.cs new file mode 100644 index 0000000..7af6f21 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F4_Analysis.cs @@ -0,0 +1,173 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using AutoMapper.Internal.Mappers; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Common.Consts; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.4 F4:日冻结反向有/无功最大需量及发生时间(总、费率 1~M,1≤M≤12) + /// + public class AFN13_F4_Analysis : IAnalysisStrategy>>> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F4_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + //冻结数据时标 + var time = $"{datas[0].Substring(0, 4)}-{datas[0].Substring(4, 2)}-{datas[0].Substring(6, 2)} 00:00:00"; + + List> data = GenerateFinalResult(datas, dataType, "日冻结反向有/无功最大需量及发生时间"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔, + DensityUnit = DensityUnit.Day + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_4解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List + { + hexMessageList.GetReadTime(4, 3),//日冻结类数据时标 Td_d + hexMessageList.GetReadTime(7, 5)//终端抄表时间 + }; + int ratingCount = hexMessageList.GetRatingCount(12, 1);//费率数 M(1≤M≤12) + values.Add(ratingCount.ToString()); + int handlerNum = 13; + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 3 * (ratingCount + 1)), ratingCount, 3, nameof(Appendix_A23)));//正向有功总最大需量 + handlerNum += 3 * (ratingCount + 1);//12 + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 4 * (ratingCount + 1)), ratingCount, 4, nameof(Appendix_A17)));//正向有功总最大需量发生时间 + handlerNum += 4 * (ratingCount + 1);//28 + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 3 * (ratingCount + 1)), ratingCount, 3, nameof(Appendix_A23)));//正向无功总最大需量 + handlerNum += 3 * (ratingCount + 1);//48 + values.AddRange(await GetDataAsync(hexMessageList.GetRange(handlerNum, 4 * (ratingCount + 1)), ratingCount, 4, nameof(Appendix_A17)));//正向无功总最大需量发生时间 + handlerNum += 4 * (ratingCount + 1); + return values; + } + + private async Task> GetDataAsync(List data, int ratingCount, int len, string appendixName) + { + List values = new List(); + for (int i = 0; i < ratingCount + 1; i++) + { + var arr = data.GetRange(0 + (i * len), len); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, T>(appendixName, arr); //从第10个开始,每加5个字节为下一个值的开始 + values.Add(value.ToString()); + } + } + return values; + } + + public List> GenerateFinalResult(List data,string dataType, string filedDesc = "") + { + List> list = new List>(); + int fCount = Convert.ToInt32(data[2]) + 1; + for (int i = 1; i <= 2; i++) + { + AnalysisBaseDto meter = new AnalysisBaseDto + { + MeterType = MeterTypeEnum.Ammeter + }; + int startIndex = i == 1 ? 3 : (3 + i * fCount); + + var errorCode = data[startIndex].CheckErrorCode(); + if (errorCode != null) + { + meter.ValidData = false; + meter.ErrorCodeMsg = errorCode.Item2; + } + decimal.TryParse(data[startIndex], out decimal value);//decimal.TryParse(data[startIndex], out value); + meter.DataValue = value; + + //TODO:日冻结类数据时标Td 20210824 + var dataTime = string.Empty; + errorCode = data[0].CheckErrorCode(); + if (data[0].Length == 8 && errorCode is null) + dataTime = $"{data[0].Substring(0, 4)}-{data[0].Substring(4, 2)}-{data[0].Substring(6, 2)} 00:00:00"; + if (!DateTime.TryParse(dataTime, out DateTime readingDate)) + meter.ValidData = false; + meter.TimeSpan = readingDate; + + //TODO:最大需量发生时间 + errorCode = data[startIndex + fCount].CheckErrorCode(); + if (errorCode != null && data[startIndex + fCount].Length != 8) + meter.ValidData = false; + else + { + string timeSpan = $"{data[0].Substring(0, 4)}-{data[startIndex + fCount].Substring(0, 2)}-{data[startIndex + fCount].Substring(2, 2)} {data[startIndex + fCount].Substring(4, 2)}:{data[startIndex + fCount].Substring(6, 2)}:00"; + if (!DateTime.TryParse(timeSpan, out DateTime tsField)) + meter.ValidData = false; + else + meter.TimeSpan = tsField; + } + meter.DataType = i == 1 ? dataType : $"{dataType}_{ i - 1}"; + filedDesc = i == 1 ? filedDesc : filedDesc.Replace("有功", "无功"); + meter.FiledDesc = filedDesc; + meter.FiledName = meter.DataType.GetDataFieldByGatherDataType() ?? string.Empty; + list.Add(meter); + + } + return list; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F81_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F81_Analysis.cs new file mode 100644 index 0000000..fd9eaf5 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F81_Analysis.cs @@ -0,0 +1,101 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.60 F81:测量点有功功率曲线 + /// + public class AFN13_F81_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F81_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "有功功率曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_81解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 3), 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A9), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F82_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F82_Analysis.cs new file mode 100644 index 0000000..724cad2 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F82_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.61 F82:测量点 A相有功功率曲线 + /// + public class AFN13_F82_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F82_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "A相有功功率曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_82解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 3), 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A9), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F83_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F83_Analysis.cs new file mode 100644 index 0000000..2ffbd12 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F83_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.61 F83:测量点 B相有功功率曲线 + /// + public class AFN13_F83_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F83_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "B相有功功率曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_83解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 3), 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A9), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F84_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F84_Analysis.cs new file mode 100644 index 0000000..36bc660 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F84_Analysis.cs @@ -0,0 +1,101 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.61 F84:测量点 C相有功功率曲线 + /// + public class AFN13_F84_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F84_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "C相有功功率曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_84解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 3), 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A9), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F85_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F85_Analysis.cs new file mode 100644 index 0000000..1437d2f --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F85_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.64 F85:测量点无功功率曲线 + /// + public class AFN13_F85_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F85_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "无功功率曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_85解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 3), 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A9), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F86_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F86_Analysis.cs new file mode 100644 index 0000000..09c5a4a --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F86_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.65 F86:测量点 A相无功功率曲线 + /// + public class AFN13_F86_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F86_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "A相无功功率曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_86解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 3), 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A9), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F87_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F87_Analysis.cs new file mode 100644 index 0000000..d2b8dc1 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F87_Analysis.cs @@ -0,0 +1,101 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.66 F87:测量点 B相无功功率曲线 + /// + public class AFN13_F87_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F87_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "B相无功功率曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_87解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 3), 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A9), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F88_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F88_Analysis.cs new file mode 100644 index 0000000..3eb020c --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F88_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.67 F88:测量点 C相无功功率曲线 + /// + public class AFN13_F88_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F88_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "C相无功功率曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_88解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 3), 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A9), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F89_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F89_Analysis.cs new file mode 100644 index 0000000..d9863e4 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F89_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.68 F89:测量点 A相电压曲线 + /// + public class AFN13_F89_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F89_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "A相电压曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_89解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 2), 2); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A7), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F90_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F90_Analysis.cs new file mode 100644 index 0000000..a11a586 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F90_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.68 F89:测量点 B相电压曲线 + /// + public class AFN13_F90_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F90_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "B相电压曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_90解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 2), 2); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A7), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F91_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F91_Analysis.cs new file mode 100644 index 0000000..740aa7f --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F91_Analysis.cs @@ -0,0 +1,101 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.68 F89:测量点 C相电压曲线 + /// + public class AFN13_F91_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F91_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "C相电压曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_91解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 2), 2); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A7), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F92_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F92_Analysis.cs new file mode 100644 index 0000000..83a1aca --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F92_Analysis.cs @@ -0,0 +1,101 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.71 F92:测量点 A相电流曲线 + /// + public class AFN13_F92_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F92_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "A相电流曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_92解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 3), 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A25), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F93_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F93_Analysis.cs new file mode 100644 index 0000000..085c209 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F93_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.72 F93:测量点 B相电流曲线 + /// + public class AFN13_F93_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F93_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "B相电流曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_93解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 3), 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A25), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F94_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F94_Analysis.cs new file mode 100644 index 0000000..b701b5c --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F94_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.73 F94:测量点 C相电流曲线 + /// + public class AFN13_F94_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F94_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "C相电流曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_94解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 3), 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A25), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F95_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F95_Analysis.cs new file mode 100644 index 0000000..1021cf9 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F95_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.74 F95:测量点零序电流曲线 + /// + public class AFN13_F95_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F95_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "测量点零序电流曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_95解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 3), 3); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A25), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F97_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F97_Analysis.cs new file mode 100644 index 0000000..44a27e6 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F97_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.75 F97:测量点正向有功总电能量曲线 + /// + public class AFN13_F97_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F97_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "正向有功总电能量曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_97解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A13), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F98_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F98_Analysis.cs new file mode 100644 index 0000000..5fad048 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F98_Analysis.cs @@ -0,0 +1,101 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.76 F98:测量点正向无功总电能量曲线 + /// + public class AFN13_F98_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F98_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "正向无功总电能量曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_98解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A13), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F99_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F99_Analysis.cs new file mode 100644 index 0000000..974740b --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0DH/AFN13_F99_Analysis.cs @@ -0,0 +1,102 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using JiShe.CollectBus.Protocol.T37612012.Appendix; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Ammeters; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0DH +{ + /// + /// 5.13.2.4.77 F99:测量点反向有功总电能量曲线 + /// + public class AFN13_F99_Analysis : IAnalysisStrategy>>> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN13_F99_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task>>> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = await AnalysisDataUnitAsync(input.UnitData.HexMessageList); + int density = Convert.ToInt32(Enum.GetName(typeof(DensityEnums), Convert.ToInt32(datas[1]))!.Split('_')[1]);//密度-间隔分钟数, + string dataType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"; + + List> data = datas.GenerateFinalResultTd_c(3, density, dataType, "反向有功总电能量曲线"); + if (data.Count > 0) + { + // 查询电表信息 + AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data[0].MeterType.ToString(), "15"); + if (ammeterInfo != null) + { + data.ForEach(item => + { + item.ProjectId = ammeterInfo.ProjectID; + item.MeterId = ammeterInfo.MeterId; + item.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + item.MeterAddress = ammeterInfo.AmmerterAddress; + }); + } + } + UnitDataAnalysis>> unitDataAnalysis = new UnitDataAnalysis>> + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = data, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = density,//密度-间隔分钟数, + DensityUnit = DensityUnit.Minute + }; + await _dataStorage.SaveMultipleDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0D_99解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private async Task> AnalysisDataUnitAsync(List hexMessageList) + { + List values = new List(); + values.AddRange(hexMessageList.GetReadTimeTd_c(4, 7)); + int.TryParse(values[values.Count - 1], out int n); + + for (int i = 0; i < n; i++) + { + var arr = hexMessageList.GetRange(11 + (i * 4), 4); + var errorCode = arr.CheckErrorCode(); + if (errorCode != null) + values.Add(errorCode.Item1); + else + { + var value = await _analysisStrategyContext.ExecuteAsync, decimal>(nameof(Appendix_A13), arr); + values.Add(value.ToString()); + } + } + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0EH/AFN14_F1_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0EH/AFN14_F1_Analysis.cs new file mode 100644 index 0000000..7ca9c21 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_0EH/AFN14_F1_Analysis.cs @@ -0,0 +1,79 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0EH +{ + /// + /// 请求重要事件 + /// Item1=停电事件 + /// Item2=上电事件 + /// + public class AFN14_F1_Analysis : IAnalysisStrategy>> + { + private readonly ILogger _logger; + + public AFN14_F1_Analysis(ILogger logger) + { + _logger = logger; + } + + public Task>> ExecuteAsync(TB3761 tB3761) + { + try + { + ArgumentNullException.ThrowIfNull(nameof(tB3761)); + ArgumentNullException.ThrowIfNull(nameof(tB3761.UnitData.HexMessageList)); + int erc = tB3761.UnitData.HexMessageList![8].HexToDec(); + bool isOnOffPower = erc.Equals(14) ? true : false; + if (!isOnOffPower) + { + throw new Exception($"ERC{erc}非上掉电事件"); + } + UnitDataAnalysis> dto = new UnitDataAnalysis> + { + Code = tB3761.A.Code!, + AFN = tB3761.AFN_FC.AFN, + Fn = tB3761.DT.Fn, + Pn = tB3761.DA.Pn, + Data = AnalysisDataUnit(tB3761.UnitData.HexMessageList) + }; + // meterData.DataType = "0E_1"; + return Task.FromResult(dto); + } + catch (Exception ex) + { + _logger.LogError(ex, $"0E_1解析失败:{tB3761.A.Code}-{tB3761.DT.Fn}-{tB3761.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private Tuple AnalysisDataUnit(List hexMessageList) + { + /// Item1=停电事件 + /// Item2=上电事件 + return Tuple.Create(HandlerTime(hexMessageList.GetRange(10, 5)), HandlerTime(hexMessageList.GetRange(15, 5))); + } + /// + /// /解析时间 + /// + /// + /// 时间验证失败是否默认为当前时间 + /// + private string HandlerTime(List times) + { + var time = string.Empty; + try + { + times.Reverse(); + time = $"{DateTime.Now.ToString("yyyy").Substring(0, 2)}{times[0]}-{times[1]}-{times[2]} {times[3]}:{times[4]}:00"; + + } + catch { } + return time; + } + } +} + diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_10H/AFN16_F101_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_10H/AFN16_F101_Analysis.cs new file mode 100644 index 0000000..ce5a7a3 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_10H/AFN16_F101_Analysis.cs @@ -0,0 +1,55 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace GatherService.WattMeter.AnalysisData.AFN_10H +{ + /// + /// 5.16.1.2.1 F1:透明转发 读取SIM卡信息 + /// + public class AFN16_F101_Analysis : IAnalysisStrategy> + { + private readonly ILogger _logger; + + public AFN16_F101_Analysis(ILogger logger) + { + _logger = logger; + } + + public Task> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData.HexMessageList); + UnitDataAnalysis dto = new UnitDataAnalysis + { + Code = input.A.Code, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data = AnalysisDataUnit(input.UnitData.HexMessageList) //SIM卡 + }; + return Task.FromResult(dto); + } + catch (Exception ex) + { + _logger.LogError(ex, $"10_101解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}"); + return null; + } + } + private string AnalysisDataUnit(List hexMessageList) + { + //TODO:转发内容 + var dataArray = hexMessageList.Skip(18).ToList(); + var contentLengthArr = dataArray.Skip(1).Take(2).ToList(); + contentLengthArr.Reverse(); + int contentLength = string.Join("", contentLengthArr).HexToDec(); + var dataField = dataArray.Skip(3).Take(contentLength).ToList(); + var sim = string.Join("", dataField).TrimStart('0'); + return sim.Length != 20 ? "" : sim; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_10H/AFN16_F1_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_10H/AFN16_F1_Analysis.cs new file mode 100644 index 0000000..9f9b120 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_10H/AFN16_F1_Analysis.cs @@ -0,0 +1,68 @@ +using GatherService.WattMeter.AnalysisData.AFN_10H; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TouchSocket.Core; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_10H +{ + /// + /// 5.16 数据转发(AFN=10H) + /// + public class AFN16_F1_Analysis : IAnalysisStrategy> + { + private readonly ILogger _logger; + + public AFN16_F1_Analysis(ILogger logger) + { + _logger = logger; + } + + public Task> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData.HexMessageList); + UnitDataAnalysis dto = new UnitDataAnalysis + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + Data = AnalysisDataUnit(input.UnitData.HexMessageList) + }; + return Task.FromResult(dto); + } + catch (Exception ex) + { + _logger.LogError(ex, $"10_101解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}"); + return null; + } + } + + + private string AnalysisDataUnit(List hexMessageList) + { + // TODO: 待定,等确认如何匹配规则 + string value = string.Join(" ", hexMessageList); + if (value.Contains(F10TranspondMatch.ReadNormal)) + { + if (value.Contains(F10TranspondMatch.PowerHz))//电网频率 + return "AFN16_F97_Analysis"; + //else if (value.Contains(F10TranspondMatch.ReadData))//读取电表地址 + // result = "AFN16_F105_Analysis"; + } + return ""; + } + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_10H/AFN16_F98_Analysis.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_10H/AFN16_F98_Analysis.cs new file mode 100644 index 0000000..a0e719d --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/AFN_10H/AFN16_F98_Analysis.cs @@ -0,0 +1,104 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol; +using JiShe.CollectBus.Protocol.Dto; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.T37612012.AnalysisData; +using JiShe.CollectBus.Protocol3761; +using Microsoft.Extensions.Logging; + +namespace GatherService.WattMeter.AnalysisData.AFN_10H +{ + /// + /// 透明转发---跳合闸 + /// + public class AFN16_F98_Analysis : IAnalysisStrategy> + { + + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + private readonly DataStorage _dataStorage; + public AFN16_F98_Analysis(ILogger logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + _dataStorage = dataStorage; + } + + public async Task> ExecuteAsync(TB3761 input) + { + try + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(input.UnitData?.HexMessageList); + + List datas = AnalysisDataUnit(input.UnitData.HexMessageList); + + // 查询电表信息 + //AmmeterInfo ammeterInfo = await _dataStorage.GetMeterInfoAsync(data.MeterType.ToString(), "15"); + //if (ammeterInfo != null) + //{ + // data.ProjectId = ammeterInfo.ProjectID; + // data.MeterId = ammeterInfo.MeterId; + // data.DatabaseBusiID = ammeterInfo.DatabaseBusiID; + // data.MeterAddress = ammeterInfo.AmmerterAddress; + //} + UnitDataAnalysis unitDataAnalysis = new UnitDataAnalysis + { + Code = input.A.Code!, + AFN = input.AFN_FC.AFN, + Fn = input.DT.Fn, + Pn = input.DA.Pn, + MSA = input.A.A3!.D1_D7!, + PSEQ = input.SEQ.PSEQ, + Data = (datas[2].Equals("9C") || datas[2].Equals("94")) ? true : false, + HexMessage = input.BaseHexMessage.HexMessageString, + MessageId = input.MessageId, + TimeDensity = 1,//密度-间隔分钟数, + DensityUnit = DensityUnit.Second + }; + //await _dataStorage.SaveDataToIotDbAsync(unitDataAnalysis); + return await Task.FromResult(unitDataAnalysis); + } + catch (Exception ex) + { + _logger.LogError(ex, $"10_98跳合闸解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}"); + return null; + } + } + + private List AnalysisDataUnit(List hexMessageList) + { + List values = new List(); + values.Add(hexMessageList.GetRange(4, 1)[0].HexToDec().ToString());//端口号 + var contentLengthArr = hexMessageList.GetRange(5, 2); + contentLengthArr.Reverse(); + int contentLength = string.Join("", contentLengthArr).HexToDec(); + //TODO:转发内容 + List contentArr = hexMessageList.GetRange(7, contentLength); + //TODO:表地址 + var addressList = contentArr.GetRange(1, 6); + addressList.Reverse(); + var address = string.Join("", addressList); + values.Add(address); + //TODO:控制码 + var controlCode = contentArr.GetRange(8, 1)[0]; + values.Add(controlCode); + //TODO:长度 + //var len = contentArr.GetRange(9, 1)[0].DataConvert(16); + //values.Add(len.ToString()); + //TODO:数据域 + //var dataField = contentArr.GetRange(10, len); + //if (dataField.Count > 0) + //{ + // string dataMark = string.Join("", CalculateErrControl(dataField.GetRange(0, 4))); + // values.Add(dataMark);//数据标识 + // var readValue = CalculateErrControl(dataField.GetRange(4, len - 4));//值 + // var valueData = GetValue>(readValue, (AppendixEnums)Enum.Parse(typeof(AppendixEnums), dataMark)); + // values.AddRange(valueData); + //} + + return values; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/DataStorage.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/DataStorage.cs new file mode 100644 index 0000000..6914c3c --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/DataStorage.cs @@ -0,0 +1,247 @@ +using JiShe.CollectBus.Common; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Encrypt; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.IoTDB.Context; +using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Model; +using JiShe.CollectBus.IoTDB.Options; +using JiShe.CollectBus.IoTDB.Provider; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.IotSystems.MeterReadingRecords; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using JiShe.CollectBus.Protocol.Dto; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData +{ + public class DataStorage:ITransientDependency + { + private readonly IGuidGenerator _guidGenerator; + private readonly IIoTDbProvider _dbProvider; + private readonly ServerApplicationOptions _applicationOptions; + private readonly IoTDBRuntimeContext _runtimeContext; + public DataStorage(IIoTDbProvider dbProvider, IOptions applicationOptions, IGuidGenerator guidGenerator, IoTDBRuntimeContext runtimeContext) + { + _dbProvider= dbProvider; + _applicationOptions = applicationOptions.Value; + _guidGenerator= guidGenerator; + _runtimeContext= runtimeContext; + } + + /// + /// 获取缓存电表信息 + /// + /// + /// + /// + public async Task GetMeterInfoAsync(string meterType,string timeDensity="15") + { + var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, _applicationOptions.SystemType, _applicationOptions.ServerTagName, meterType, timeDensity)}"; + return await Task.FromResult(new AmmeterInfo()); + } + + /// + /// 保存数据到IotDb + /// + /// + /// + /// + public async Task SaveDataToIotDbAsync(UnitDataAnalysis> analysisBaseDto) + { + var data = analysisBaseDto.Data!; + + string taskMark = CommonHelper.GetTaskMark(analysisBaseDto.AFN, analysisBaseDto.Fn, analysisBaseDto.Pn, analysisBaseDto.MSA, analysisBaseDto.PSEQ); + string scoreValue = $"{analysisBaseDto.Code}.{taskMark}".Md5Fun(); + + var conditions = new List(); + conditions.Add(new QueryCondition() + { + Field = "ScoreValue", + Operator = "=", + IsNumber = false, + Value = scoreValue + }); + conditions.Add(new QueryCondition() + { + Field = "TaskMark", + Operator = "=", + IsNumber = false, + Value = taskMark + }); + conditions.Add(new QueryCondition() + { + Field = "IsReceived", + Operator = "=", + IsNumber = false, + Value = false + }); + var meter = new TreeModelSingleMeasuringEntity() + { + SystemName = _applicationOptions.SystemType, + DeviceId = $"{data.MeterId}", + DeviceType = $"{data.MeterType}", + ProjectId = $"{data.ProjectId}", + Timestamps = data.TimeSpan.GetDateTimeOffset().ToUnixTimeMilliseconds(), + SingleMeasuring = new Tuple(data.FiledName ?? string.Empty, data.DataValue ?? default) + }; + _runtimeContext.UseTableSessionPool = true; // 使用表模型池 + var taskSendInfo = await _dbProvider.QueryAsync(new IoTDBQueryOptions() { TableNameOrTreePath = DevicePathBuilder.GetTableName(), Conditions = conditions, PageIndex = 0, PageSize = 1 }); + var taskData = taskSendInfo?.Items.FirstOrDefault(); + if (taskData != null) + { + // 更新 + meter.Timestamps = taskData.Timestamps; + taskData.IsReceived=true; + taskData.ReceivedMessageHexString= analysisBaseDto.HexMessage; + taskData.ReceivedMessageId= analysisBaseDto.MessageId ?? string.Empty; + } + else + { + // 新建 + var currentTime = DateTime.Now; + taskData = new MeterReadingTelemetryPacketInfo() + { + SystemName = _applicationOptions.SystemType, + ProjectId = $"{data.ProjectId}", + DeviceType = $"{data.MeterType}", + DeviceId = $"{data.MeterId}", + Timestamps = DateTime.Now.CheckTimePoint().GetDateTimeOffset().ToUnixTimeNanoseconds(), + DatabaseBusiID = data.DatabaseBusiID, + PendingCopyReadTime = data.DataTime.CheckTimePoint(), + CreationTime = currentTime, + MeterAddress = data.MeterAddress, + AFN = analysisBaseDto.AFN, + Fn = analysisBaseDto.Fn, + Seq = analysisBaseDto.PSEQ, + MSA = analysisBaseDto.MSA, + ItemCode = data.DataType, + TaskMark = taskMark, + IsSend = false, + ManualOrNot = false, + Pn = analysisBaseDto.Pn, + ReceivedMessageId = analysisBaseDto.MessageId?? string.Empty, + ReceivedMessageHexString = analysisBaseDto.HexMessage, + IsReceived = true, + ReceivedRemark = data.ErrorCodeMsg ?? string.Empty, + ScoreValue = $"{analysisBaseDto.Code}.{taskMark}".Md5Fun(), + }; + } + + await _dbProvider.InsertAsync(taskData); + //如果无字段名,则不保存数据 + if (string.IsNullOrWhiteSpace(data.FiledName)) + { + _runtimeContext.UseTableSessionPool = false; // 使树模型池 + await _dbProvider.InsertAsync(meter); + } + return await Task.FromResult(true); + } + + + /// + /// 保存数据到IotDb + /// + /// + /// + /// + public async Task SaveMultipleDataToIotDbAsync(UnitDataAnalysis>> analysisBaseDto) + { + var data = analysisBaseDto.Data!; + foreach (var item in data) + { + string taskMark = CommonHelper.GetTaskMark(analysisBaseDto.AFN, analysisBaseDto.Fn, analysisBaseDto.Pn, analysisBaseDto.MSA, analysisBaseDto.PSEQ); + string scoreValue = $"{analysisBaseDto.Code}.{taskMark}".Md5Fun(); + + var conditions = new List(); + conditions.Add(new QueryCondition() + { + Field = "ScoreValue", + Operator = "=", + IsNumber = false, + Value = scoreValue + }); + conditions.Add(new QueryCondition() + { + Field = "TaskMark", + Operator = "=", + IsNumber = false, + Value = taskMark + }); + conditions.Add(new QueryCondition() + { + Field = "IsReceived", + Operator = "=", + IsNumber = false, + Value = false + }); + var meter = new TreeModelSingleMeasuringEntity() + { + SystemName = _applicationOptions.SystemType, + DeviceId = $"{item.MeterId}", + DeviceType = $"{item.MeterType}", + ProjectId = $"{item.ProjectId}", + Timestamps = item.TimeSpan.GetDateTimeOffset().ToUnixTimeMilliseconds(), + SingleMeasuring = new Tuple(item.FiledName ?? string.Empty, item.DataValue ?? default) + }; + _runtimeContext.UseTableSessionPool = true; // 使用表模型池 + var taskSendInfo = await _dbProvider.QueryAsync(new IoTDBQueryOptions() { TableNameOrTreePath = DevicePathBuilder.GetTableName(), Conditions = conditions, PageIndex = 0, PageSize = 1 }); + var taskData = taskSendInfo?.Items.FirstOrDefault(); + if (taskData != null) + { + // 更新 + meter.Timestamps = taskData.Timestamps; + taskData.IsReceived = true; + taskData.ReceivedMessageHexString = analysisBaseDto.HexMessage; + taskData.ReceivedMessageId = analysisBaseDto.MessageId ?? string.Empty; + } + else + { + // 新建 + var currentTime = DateTime.Now; + taskData = new MeterReadingTelemetryPacketInfo() + { + SystemName = _applicationOptions.SystemType, + ProjectId = $"{item.ProjectId}", + DeviceType = $"{item.MeterType}", + DeviceId = $"{item.MeterId}", + Timestamps = DateTime.Now.CheckTimePoint().GetDateTimeOffset().ToUnixTimeNanoseconds(), + DatabaseBusiID = item.DatabaseBusiID, + PendingCopyReadTime = item.DataTime.CheckTimePoint(), + CreationTime = currentTime, + MeterAddress = item.MeterAddress, + AFN = analysisBaseDto.AFN, + Fn = analysisBaseDto.Fn, + Seq = analysisBaseDto.PSEQ, + MSA = analysisBaseDto.MSA, + ItemCode = item.DataType, + TaskMark = taskMark, + IsSend = false, + ManualOrNot = false, + Pn = analysisBaseDto.Pn, + ReceivedMessageId = analysisBaseDto.MessageId ?? string.Empty, + ReceivedMessageHexString = analysisBaseDto.HexMessage, + IsReceived = true, + ReceivedRemark = item.ErrorCodeMsg ?? string.Empty, + ScoreValue = $"{analysisBaseDto.Code}.{taskMark}".Md5Fun(), + }; + } + + await _dbProvider.InsertAsync(taskData); + //如果无字段名,则不保存数据 + if (string.IsNullOrWhiteSpace(item.FiledName)) + { + _runtimeContext.UseTableSessionPool = false; // 使树模型池 + await _dbProvider.InsertAsync(meter); + } + } + return await Task.FromResult(true); + } + + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/F10TranspondMatch.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/F10TranspondMatch.cs new file mode 100644 index 0000000..f7b1176 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/AnalysisData/F10TranspondMatch.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData +{ + public class F10TranspondMatch + { + /// + /// 188协议良禾水表透传解析 + /// + public const string WaterLiangHe188 = "1F 90 00"; + /// + /// 188协议良禾水表透传解析 + /// + public const string WaterLiangHe188_1 = "90 1F 00"; + /// + /// 电网频率 + /// + public const string PowerHz = "35 33 B3 35"; + + /// + /// 读取数据 + /// + public const string ReadData = "34 37 33 37"; + + /// + /// 跳合闸 正常应答帧 + /// + public const string JumpClosingNormal = "68 9C"; + /// + /// 跳合闸 异常应答帧 + /// + public const string JumpClosingError = "68 DC"; + /// + /// 写数据正常应答帧 + /// + public const string WriteNormal = "68 94 00"; + /// + /// 写数据异常应答帧 + /// + public const string WriteError = "68 D4 01"; + + /// + /// 读数据异常应答帧 + /// + public const string ReadError = "68 D1"; + + /// + /// 读数据正常应答帧 + /// + public const string ReadNormal = "68 91"; + /// + /// 捷先电动阀门(DN50) + /// + public const string CJT_188_2018 = "84 05 A0 17 00"; + + public const string DLT_645_1997 = "68 84 00"; + /// + /// ES190_DC 4G水表 + /// + public const string ES190_DC = "FF D5"; + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_02800002.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_02800002.cs new file mode 100644 index 0000000..7245d70 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_02800002.cs @@ -0,0 +1,35 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 第一套第 2 日时段表数据 + /// + public class Appendix_02800002 : IAnalysisStrategy, List> + { + private readonly ILogger _logger; + + public Appendix_02800002(ILogger logger) + { + _logger = logger; + } + + public Task> ExecuteAsync(List data) + { + try + { + if (data.Count != 2) + return null; + var value = string.Join(".", data); + var values= new List() { "电网频率", "10_97", "1", value }; + return Task.FromResult(values); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Appendix_02800002解析失败:{ex.Message}"); + return null; + } + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04000201.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04000201.cs new file mode 100644 index 0000000..7c2db74 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04000201.cs @@ -0,0 +1,33 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 年时区数 p≤14 + /// + public class Appendix_04000201 : IAnalysisStrategy, List> + { + private int valueByteCount = 1; + private readonly ILogger _logger; + + public Appendix_04000201(ILogger logger) + { + _logger = logger; + } + + public async Task> ExecuteAsync(List data) + { + List values = new List(); + values.Insert(0, "10_94"); + values.Insert(0, "年时区数"); + values.Add((data.Count / valueByteCount).ToString());//值总数 + foreach (var item in data) + { + values.Add(item.HexToDec().ToString()); + } + return await Task.FromResult(values); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04000202.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04000202.cs new file mode 100644 index 0000000..20870cd --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04000202.cs @@ -0,0 +1,27 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 日时段表数 q≤8 + /// + public class Appendix_04000202 : IAnalysisStrategy, List> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public Appendix_04000202(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public async Task> ExecuteAsync(List data) + { + var values = await _analysisStrategyContext.ExecuteAsync, List>("Appendix_04000201", data); + values[0] = "日时段表数"; + return await Task.FromResult(values); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04000203.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04000203.cs new file mode 100644 index 0000000..cf0c882 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04000203.cs @@ -0,0 +1,27 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 日时段数(每日切换数) m≤14 + /// + public class Appendix_04000203 : IAnalysisStrategy, List> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public Appendix_04000203(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public async Task> ExecuteAsync(List data) + { + var values = await _analysisStrategyContext.ExecuteAsync, List>("Appendix_04000201", data); + values[0] = "日时段数(每日切换数)"; + return await Task.FromResult(values); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04000204.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04000204.cs new file mode 100644 index 0000000..56ab772 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04000204.cs @@ -0,0 +1,27 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 费率数 k≤63 + /// + public class Appendix_04000204 : IAnalysisStrategy, List> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public Appendix_04000204(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public async Task> ExecuteAsync(List data) + { + var values = await _analysisStrategyContext.ExecuteAsync, List>("Appendix_04000201", data); + values[0] = "费率数"; + return await Task.FromResult(values); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010000.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010000.cs new file mode 100644 index 0000000..4ce52fc --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010000.cs @@ -0,0 +1,38 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 第一套时区表数据 + /// + public class Appendix_04010000 : IAnalysisStrategy, List> + { + /// + /// 每个值字节数 + /// + private const int valueByteCount = 3; + private readonly ILogger _logger; + + public Appendix_04010000(ILogger logger) + { + _logger = logger; + } + + public async Task> ExecuteAsync(List data) + { + List values = new List(); + int valueCount = data.Count / valueByteCount; + values.Insert(0, "10_94"); + values.Insert(0, "第一套第{0}时区起始日期及日时段表号"); + values.Add(valueCount.ToString());//值总数 11 11 11 22 22 22 33 33 33 + for (int i = 0; i < valueCount; i++) + { + var value = data.GetRange(i * 3, valueByteCount);//MMDDNN + values.Add($"{value[0]}:{value[1]}");//MM:DD 或hhmm + values.Add(value[2]);//NN 费率号 + } + return await Task.FromResult(values); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010001.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010001.cs new file mode 100644 index 0000000..deb25fe --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010001.cs @@ -0,0 +1,27 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 第一套第 1 日时段表数据: + /// + public class Appendix_04010001 : IAnalysisStrategy, List> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public Appendix_04010001(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public async Task> ExecuteAsync(List data) + { + var values = await _analysisStrategyContext.ExecuteAsync, List>("Appendix_04010000", data); + values[0] = "第一套第1日第{0}时段起始时间及费率号"; + return await Task.FromResult(values); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010002.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010002.cs new file mode 100644 index 0000000..3dee4da --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010002.cs @@ -0,0 +1,27 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 第一套第 2 日时段表数据 + /// + public class Appendix_04010002 : IAnalysisStrategy, List> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public Appendix_04010002(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public async Task> ExecuteAsync(List data) + { + var values = await _analysisStrategyContext.ExecuteAsync, List>("Appendix_04010000", data); + values[0] = "第一套第2日第{0}时段起始时间及费率号"; + return await Task.FromResult(values); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010003.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010003.cs new file mode 100644 index 0000000..d3693c2 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010003.cs @@ -0,0 +1,27 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 第一套第 3 日时段表数据 + /// + public class Appendix_04010003 : IAnalysisStrategy, List> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public Appendix_04010003(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public async Task> ExecuteAsync(List data) + { + var values = await _analysisStrategyContext.ExecuteAsync, List>("Appendix_04010000", data); + values[0] = "第一套第3日第{0}时段起始时间及费率号"; + return await Task.FromResult(values); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010004.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010004.cs new file mode 100644 index 0000000..68faa82 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010004.cs @@ -0,0 +1,26 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 第一套第 4 日时段表数据 + /// + public class Appendix_04010004 : IAnalysisStrategy, List> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public Appendix_04010004(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + public async Task> ExecuteAsync(List data) + { + var values = await _analysisStrategyContext.ExecuteAsync, List> ("Appendix_04010000", data); + values[0] = "第一套第4日第{0}时段起始时间及费率号"; + return await Task.FromResult(values); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010005.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010005.cs new file mode 100644 index 0000000..9ef615f --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010005.cs @@ -0,0 +1,27 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 第一套第 5 日时段表数据 + /// + public class Appendix_04010005 : IAnalysisStrategy, List> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public Appendix_04010005(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public async Task> ExecuteAsync(List data) + { + var values = await _analysisStrategyContext.ExecuteAsync, List>("Appendix_04010000", data); + values[0] = "第一套第5日第{0}时段起始时间及费率号"; + return await Task.FromResult(values); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010006.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010006.cs new file mode 100644 index 0000000..a31ae33 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010006.cs @@ -0,0 +1,27 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 第一套第 6 日时段表数据 + /// + public class Appendix_04010006 : IAnalysisStrategy, List> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public Appendix_04010006(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public async Task> ExecuteAsync(List data) + { + var values = await _analysisStrategyContext.ExecuteAsync, List>("Appendix_04010000", data); + values[0] = "第一套第6日第{0}时段起始时间及费率号"; + return await Task.FromResult(values); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010007.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010007.cs new file mode 100644 index 0000000..5142cf7 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010007.cs @@ -0,0 +1,26 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 第一套第 7 日时段表数据 + /// + public class Appendix_04010007 : IAnalysisStrategy, List> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public Appendix_04010007(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + public async Task> ExecuteAsync(List data) + { + var values = await _analysisStrategyContext.ExecuteAsync, List>("Appendix_04010000", data); + values[0] = "第一套第7日第{0}时段起始时间及费率号"; + return await Task.FromResult(values); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010008.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010008.cs new file mode 100644 index 0000000..0074290 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_04010008.cs @@ -0,0 +1,27 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 第一套第 8 日时段表数据 + /// + public class Appendix_04010008 : IAnalysisStrategy, List> + { + private readonly ILogger _logger; + private readonly AnalysisStrategyContext _analysisStrategyContext; + + public Appendix_04010008(ILogger logger, AnalysisStrategyContext analysisStrategyContext) + { + _logger = logger; + _analysisStrategyContext = analysisStrategyContext; + } + + public async Task> ExecuteAsync(List data) + { + var values = await _analysisStrategyContext.ExecuteAsync, List>("Appendix_04010000", data); + values[0] = "第一套第8日第{0}时段起始时间及费率号"; + return await Task.FromResult(values); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A1.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A1.cs new file mode 100644 index 0000000..86d2149 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A1.cs @@ -0,0 +1,32 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + public class Appendix_A1 : IAnalysisStrategy, string> + { + private readonly ILogger _logger; + + public Appendix_A1(ILogger logger) + { + _logger = logger; + } + + public async Task ExecuteAsync(List data) + { + var seconds = data[0]; + var minutes = data[1]; + var hours = data[2]; + var day = data[3]; + string binString = data[4].HexTo4BinZero(); + var months = (binString.Substring(3, 1).BinToDec() * 10) + Convert.ToInt32(binString.Substring(4, 4).BinToHex()); + var week = binString.Substring(0, 3).HexTo4BinZero(); + var year = $"{DateTime.Now.ToString("yyyy").Substring(0, 2)}{data[5]}"; + string date= $"{year}-{months.ToString().PadLeft(2, '0')}-{day} {hours}:{minutes}:{seconds}_{week}"; + return await Task.FromResult(date); + } + + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A11.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A11.cs new file mode 100644 index 0000000..2713adb --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A11.cs @@ -0,0 +1,57 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 附录A.11数据格式 + /// + class Appendix_A11 : IAnalysisStrategy, decimal> + { + private readonly ILogger _logger; + + public Appendix_A11(ILogger logger) + { + _logger = logger; + } + + public async Task ExecuteAsync(List data) + { + decimal value = 0.00M; + List decimalDCBList = new List(); + decimalDCBList.Add(new decimal[2] { 10M, 100M }); + decimalDCBList.Add(new decimal[2] { 10M, 1M }); + decimalDCBList.Add(new decimal[2] { 1000M, 100M }); + decimalDCBList.Add(new decimal[2] { 100000M, 10000M }); + for (int i = 0; i < data.Count; i++) + { + int CalcType = i > 0 ? 2 : 1; + value += GetByteDCD(data[i].HexTo4BinZero(), CalcType, decimalDCBList[i]); + } + return await Task.FromResult(value); + } + + /// + /// 附录A.14数据格式计算规则方法 + /// + /// 二进制字符串 + /// //计算类型,1为除法,2为乘法 + /// 除数数组 + /// + private decimal GetByteDCD(string binString, int calcType, decimal[] bcd) + { + decimal result = 0.00000M; + switch (calcType) + { + case 1: + result = (binString.Substring(0, 4).BinToDec() / bcd[0]) + (binString.Substring(4, 4).BinToDec() / bcd[1]); + break; + case 2: + result = (binString.Substring(0, 4).BinToDec() * bcd[0]) + (binString.Substring(4, 4).BinToDec() * bcd[1]); + break; + } + return result; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A13.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A13.cs new file mode 100644 index 0000000..51d73ab --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A13.cs @@ -0,0 +1,56 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 附录A.13数据格式 + /// + class Appendix_A13 : IAnalysisStrategy, decimal> + { + private readonly ILogger _logger; + + public Appendix_A13(ILogger logger) + { + _logger = logger; + } + + public async Task ExecuteAsync(List data) + { + decimal value = 0.00M; + List decimalDCBList = new List(); + decimalDCBList.Add(new decimal[2] { 1000M, 10000M }); + decimalDCBList.Add(new decimal[2] { 10M, 100M }); + decimalDCBList.Add(new decimal[2] { 10M, 1M }); + decimalDCBList.Add(new decimal[2] { 1000M, 100M }); + for (int i = 0; i < data.Count; i++) + { + int CalcType = i > 1 ? 2 : 1; + value += GetByteDCD(data[i].HexTo4BinZero(), CalcType, decimalDCBList[i]); + } + return await Task.FromResult(value); + } + /// + /// 附录A.13数据格式计算规则方法 + /// + /// 二进制字符串 + /// //计算类型,1为除法,2为乘法 + /// 除数数组 + /// + private decimal GetByteDCD(string binString, int calcType, decimal[] bcd) + { + decimal result = 0.00000M; + switch (calcType) + { + case 1: + result = (binString.Substring(0, 4).BinToDec() / bcd[0]) + (binString.Substring(4, 4).BinToDec() / bcd[1]); + break; + case 2: + result = (binString.Substring(0, 4).BinToDec() * bcd[0]) + (binString.Substring(4, 4).BinToDec() * bcd[1]); + break; + } + return result; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A14.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A14.cs new file mode 100644 index 0000000..5d21ee8 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A14.cs @@ -0,0 +1,57 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + /// + /// 附录A.14数据格式 + /// + class Appendix_A14 : IAnalysisStrategy, decimal> + { + private readonly ILogger _logger; + + public Appendix_A14(ILogger logger) + { + _logger = logger; + } + public async Task ExecuteAsync(List data) + { + decimal value = 0.0000M; + List decimalDCBList = new List(); + decimalDCBList.Add(new decimal[2] { 1000M, 10000M }); + decimalDCBList.Add(new decimal[2] { 10M, 100M }); + decimalDCBList.Add(new decimal[2] { 10M, 1M }); + decimalDCBList.Add(new decimal[2] { 1000M, 100M }); + decimalDCBList.Add(new decimal[2] { 100000M, 10000M }); + for (int i = 0; i < data.Count; i++) + { + int CalcType = i > 1 ? 2 : 1; + value += GetByteDCD(data[i].HexTo4BinZero(), CalcType, decimalDCBList[i]); + } + return await Task.FromResult(value); + } + + /// + /// 附录A.14数据格式计算规则方法 + /// + /// 二进制字符串 + /// //计算类型,1为除法,2为乘法 + /// 除数数组 + /// + private decimal GetByteDCD(string binString, int calcType, decimal[] bcd) + { + decimal result = 0.00000M; + switch (calcType) + { + case 1: + result = (binString.Substring(0, 4).BinToDec() / bcd[0]) + (binString.Substring(4, 4).BinToDec() / bcd[1]); + break; + case 2: + result = (binString.Substring(0, 4).BinToDec() * bcd[0]) + (binString.Substring(4, 4).BinToDec() * bcd[1]); + break; + } + return result; + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A15.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A15.cs new file mode 100644 index 0000000..284c3f6 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A15.cs @@ -0,0 +1,23 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.Logging; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + public class Appendix_A15 : IAnalysisStrategy, string> + { + private readonly ILogger _logger; + + public Appendix_A15(ILogger logger) + { + _logger = logger; + } + + public async Task ExecuteAsync(List data) + { + data.Reverse(); + data.Insert(0, DateTime.Now.ToString("yyyy").Substring(0, 2)); + string date= string.Join("", data); + return await Task.FromResult(date); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A17.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A17.cs new file mode 100644 index 0000000..942fc95 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A17.cs @@ -0,0 +1,14 @@ +using JiShe.CollectBus.Protocol.Interfaces; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + public class Appendix_A17 : IAnalysisStrategy, string> + { + public async Task ExecuteAsync(List data) + { + data.Reverse(); + string datasStr= string.Join("", data); + return await Task.FromResult(datasStr); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A23.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A23.cs new file mode 100644 index 0000000..1a6efb5 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A23.cs @@ -0,0 +1,50 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Interfaces; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + public class Appendix_A23 : IAnalysisStrategy, decimal> + { + + public async Task ExecuteAsync(List data) + { + decimal value = 0.000M; + if (data[0].Check3761Byte()) + value += GetValue(0, 4, 1000M, 4, 4, 10000M, data[0], 1); + if (data[1].Check3761Byte()) + value += GetValue(0, 4, 10M, 4, 4, 100M, data[1], 1); + if (data[2].Check3761Byte()) + value += GetValue(0, 4, 10M, 4, 4, 1M, data[2], 0); + return await Task.FromResult(value); + } + /// + /// 解析值 + /// + /// 二进制低位字符串截取位置 + /// 截取长度 + /// 分位值,1000M + /// 二进制高位字符串截取位置 + /// 截取长度 + /// 分位值,100M + /// 数据 + /// 计算类型,0:乘法,1:除法 + /// + private decimal GetValue(int startIndex, int startLength, decimal startQuantile, int endIndex, int endLength, decimal endQuantile, string data, int calType = 0) + { + var value = 0M; + string binString = data.HexTo4BinZero(); + switch (calType) + { + case 0: + value = (binString.Substring(startIndex, startLength).BinToDec() * startQuantile) + (binString.Substring(endIndex, endLength).BinToDec() * endQuantile); + break; + case 1: + value = (binString.Substring(startIndex, startLength).BinToDec() / startQuantile) + (binString.Substring(endIndex, endLength).BinToDec() / endQuantile); + break; + } + return value; + } + + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A25.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A25.cs new file mode 100644 index 0000000..3cda672 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A25.cs @@ -0,0 +1,33 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Interfaces; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + public class Appendix_A25 : IAnalysisStrategy, decimal> + { + public async Task ExecuteAsync(List data) + { + decimal value = 0.000M; + string binString = string.Empty; + if (data[0].Check3761Byte()) + { + binString = data[0].HexTo4BinZero(); + value += (binString.Substring(0, 4).BinToDec() / 100M) + (binString.Substring(4, 4).BinToDec() / 1000M); + } + if (data[1].Check3761Byte()) + { + binString = data[1].HexTo4BinZero(); + value += (binString.Substring(0, 4).BinToDec() / 1M) + (binString.Substring(4, 4).BinToDec() / 10M); + } + if (data[2].Check3761Byte()) + { + binString = data[2].HexTo4BinZero(); + value += (binString.Substring(1, 3).BinToDec() * 100M) + (binString.Substring(4, 4).BinToDec() * 10M); + int sign = Convert.ToInt32(binString.Substring(0, 1));//正负值标记 + value = sign == 0 ? value * 1 : value * -1; + } + return await Task.FromResult(value); + } + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A5.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A5.cs new file mode 100644 index 0000000..3815e85 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A5.cs @@ -0,0 +1,27 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Interfaces; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + public class Appendix_A5 : IAnalysisStrategy, decimal> + { + public async Task ExecuteAsync(List data) + { + decimal value = 0.0M; + string binString = string.Empty; + if (data[0].Check3761Byte()) + { + binString = data[0].HexTo4BinZero(); + value += (binString.Substring(0, 4).BinToDec() / 1M) + (binString.Substring(4, 4).BinToDec() / 10M); + } + if (data[1].Check3761Byte()) + { + binString = data[1].HexTo4BinZero(); + value += (binString.Substring(1, 3).BinToDec() * 100M) + (binString.Substring(4, 4).BinToDec() * 10M); + int sign = Convert.ToInt32(binString.Substring(0, 1));//正负值标记 + value = sign == 0 ? value * 1 : value * -1; + } + return await Task.FromResult(value); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A7.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A7.cs new file mode 100644 index 0000000..b75d733 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A7.cs @@ -0,0 +1,27 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Interfaces; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + public class Appendix_A7 : IAnalysisStrategy, decimal> + { + + public async Task ExecuteAsync(List data) + { + decimal value = 0.0M; + string binString = string.Empty; + if (data[0].Check3761Byte()) + { + binString = data[0].HexTo4BinZero(); + value += (binString.Substring(0, 4).BinToDec() / 1M) + (binString.Substring(4, 4).BinToDec() / 10M); + } + if (data[1].Check3761Byte()) + { + binString = data[1].HexTo4BinZero(); + if (binString.Check3761Byte()) + value += (binString.Substring(0, 4).BinToDec() * 100M) + (binString.Substring(4, 4).BinToDec() * 10M); + } + return await Task.FromResult(value); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A9.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A9.cs new file mode 100644 index 0000000..a3a0b38 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Appendix/Appendix_A9.cs @@ -0,0 +1,36 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Interfaces; + +namespace JiShe.CollectBus.Protocol.T37612012.Appendix +{ + public class Appendix_A9 : IAnalysisStrategy, decimal> + { + + public async Task ExecuteAsync(List data) + { + decimal value = 0.0000M; + string binString = string.Empty; + if (data[0].Check3761Byte()) + { + binString = data[0].HexTo4BinZero(); + value += (binString.Substring(0, 4).BinToDec() / 1000M) + (binString.Substring(4, 4).BinToDec() / 10000M); + } + + if (data[1].Check3761Byte()) + { + binString = data[1].HexTo4BinZero(); + value += (binString.Substring(0, 4).BinToDec() / 10M) + (binString.Substring(4, 4).BinToDec() / 100M); + } + + if (data[2].Check3761Byte()) + { + binString = data[2].HexTo4BinZero(); + value += (binString.Substring(1, 3).BinToDec() * 10M) + (binString.Substring(4, 4).BinToDec() * 1M); + int sign = Convert.ToInt32(binString.Substring(0, 1));//正负值标记 + value = sign == 0 ? value * 1 : value * -1; + } + return await Task.FromResult(value); + } + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/CollectBusProtocolT37612012Module.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/CollectBusProtocolT37612012Module.cs new file mode 100644 index 0000000..53ec062 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/CollectBusProtocolT37612012Module.cs @@ -0,0 +1,105 @@ +using System.Reflection; +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Volo.Abp; +using Volo.Abp.Modularity; + +namespace JiShe.CollectBus.Protocol.T37612012 +{ + public class CollectBusProtocolT37612012Module : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddKeyedSingleton(nameof(T37612012ProtocolPlugin)); + //RegisterProtocolAnalysis(context.Services); + LoadAnalysisStrategy(context.Services); + } + + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + Console.WriteLine($"{nameof(T37612012ProtocolPlugin)} OnApplicationInitializationAsync"); + var standardProtocol = context.ServiceProvider.GetRequiredKeyedService(nameof(T37612012ProtocolPlugin)); + await standardProtocol.LoadAsync(); + } + + public override void OnApplicationShutdown(ApplicationShutdownContext context) + { + Console.WriteLine($"{nameof(T37612012ProtocolPlugin)} OnApplicationShutdown"); + base.OnApplicationShutdown(context); + } + + public void LoadAnalysisStrategy(IServiceCollection services) + { + var assembly = Assembly.GetExecutingAssembly(); + var analysisStrategyTypes = assembly.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAnalysisStrategy<,>))); + foreach (var analysisStrategyType in analysisStrategyTypes) + { + var service = analysisStrategyType.GetInterfaces().First(); + services.AddKeyedSingleton(service, analysisStrategyType.Name, analysisStrategyType); + } + } + + public void RegisterProtocolAnalysis(IServiceCollection services) + { + // 扫描并注册所有策略 + var strategyMetadata = new Dictionary<(string, Type, Type), Type>(); + services.AddTransient(); + + // 批量注册 + var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); + if (string.IsNullOrWhiteSpace(assemblyPath)) + { + return; + } + var dllFiles = Directory.GetFiles(Path.Combine(assemblyPath, "Plugins"), "*.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); + // 实现IAnalysisStrategy接口 + var analysisStrategyTypes = assembly.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAnalysisStrategy<,>))); + if (!analysisStrategyTypes.Any()) + continue; + foreach (var analysisStrategyType in analysisStrategyTypes) + { + // 通过反射获取静态元数据 + var strategyType = analysisStrategyType.Name; + var genericArgs = analysisStrategyType.GetInterface($"IAnalysisStrategy`2")!.GetGenericArguments(); + var inputType = genericArgs[0]; + var resultType = genericArgs[1]; + // 注册策略实现 + services.AddTransient(analysisStrategyType); + strategyMetadata[(strategyType, inputType, resultType)] = analysisStrategyType; + } + } + + // 注册元数据字典 + services.AddSingleton(strategyMetadata); + + // 注册策略解析工厂 + services.AddTransient>(provider => (name, inputType, resultType) => + { + var metadata = provider.GetRequiredService>(); + if (metadata.TryGetValue((name, inputType, resultType), out var strategyType)) + { + return provider.GetRequiredService(strategyType); + } + else + { + var logger = provider.GetRequiredService>(); + logger.LogWarning($"未能找到解析策略:{name}-{inputType}-{resultType}"); + return null; + } + }); + + + } + } +} + + + diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/JiShe.CollectBus.Protocol.T37612012.csproj b/protocols/JiShe.CollectBus.Protocol.T37612012/JiShe.CollectBus.Protocol.T37612012.csproj new file mode 100644 index 0000000..e83c165 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/JiShe.CollectBus.Protocol.T37612012.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/Protocol3761Extensions.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/Protocol3761Extensions.cs new file mode 100644 index 0000000..2045a06 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/Protocol3761Extensions.cs @@ -0,0 +1,358 @@ +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Contracts.Protocol.Dto; +using NUglify.JavaScript.Syntax; +using System.Text.RegularExpressions; + +namespace JiShe.CollectBus.Protocol.T37612012 +{ + public static class Protocol3761Extensions + { + + /// + /// 3761协议数据字节校验 + /// + /// + /// + public static bool Check3761Byte(this string value) + { + if (!value.ToUpper().Equals("FF") && !value.ToUpper().Equals("EE")) + return true; + return false; + } + + /// + /// 字符串中是否包含字母 + /// + /// + /// + public static bool IsLetterExists(this string value) + { + return Regex.Matches(value, "[a-zA-Z]").Count > 0; + } + + /// + /// 判断错误代码 + /// + public static Tuple? CheckErrorCode(this List data) + { + var value = string.Join("", data); + if (value.IsLetterExists()) + { + var code = ErrorCodes().Where(f => value.Contains(f.Key)).FirstOrDefault(); + if (code.Key != null) + return Tuple.Create(code.Key,code.Value); + else + return Tuple.Create("EE", "未知数据错误"); + } + return null; + } + + /// + /// 判断错误代码 + /// + public static Tuple? CheckErrorCode(this string value) + { + if (value.IsLetterExists()) + { + var code = ErrorCodes().Where(f => value.Contains(f.Key)).FirstOrDefault(); + if (code.Key != null) + return Tuple.Create(code.Key, code.Value); + else + return Tuple.Create("EE", "未知数据错误"); + } + return null; + } + + /// + /// 错误信息 + /// + /// + public static Dictionary ErrorCodes() + { + return new Dictionary() + { + { "FF", "电表无此数据项" }, + { "EE", "未知数据错误" }, + { "E1", "数据点缺少(停电)" }, + { "E2", "通讯异常" }, + { "E3", "集中器未配置数据项" }, + { "E4", "电表档案无效" }, + { "E5", "电表无此数据项" }, + { "E6", "电表时间异常" }, + { "E7","暂停抄表" } + }; + } + + + /// + /// 费率数 + /// + /// + /// + /// + public static int GetRatingCount(this List hexMessageList ,int index, int len) + { + var list = hexMessageList.GetRange(index, len); + return list.Count > 0 ? list[0].HexToDec() : 0; + } + + + /// + /// 抄表时间 + /// + /// + /// + /// + public static string GetReadTime(this List hexMessageList, int index, int len) + { + var list = hexMessageList.GetRange(index, len); + return list.GetReadTime(); + } + + /// + /// 抄表时间 + /// + /// + /// + public static string GetReadTime(this List data) + { + data.Reverse(); + data.Insert(0, DateTime.Now.ToString("yyyy").Substring(0, 2)); + return string.Join("", data); + } + + + /// + /// 判断当前时间所在时标(15分钟) + /// + /// + /// + public static DateTime CheckTimePoint(this DateTime curTime) + { + var curMinute = Convert.ToInt32(curTime.ToString("mm")); + string dataTime; + if (curMinute >= 0 && curMinute < 15) + dataTime = curTime.ToString("yyyy-MM-dd HH:00:00"); + else if (curMinute >= 15 && curMinute < 30) + dataTime = curTime.ToString("yyyy-MM-dd HH:15:00"); + else if (curMinute >= 30 && curMinute < 45) + dataTime = curTime.ToString("yyyy-MM-dd HH:30:00"); + else + dataTime = curTime.ToString("yyyy-MM-dd HH:45:00"); + return DateTime.Parse(dataTime); + } + + public static DateTime CheckTimeFivePoint(this DateTime curTime) + { + string dataTime; + var curMinute = Convert.ToInt32(curTime.ToString("mm")); + if (curMinute >= 0 && curMinute < 5) + dataTime = curTime.ToString("yyyy-MM-dd HH:00:00"); + else if (curMinute >= 5 && curMinute < 10) + dataTime = curTime.ToString("yyyy-MM-dd HH:5:00"); + else if (curMinute >= 10 && curMinute < 15) + dataTime = curTime.ToString("yyyy-MM-dd HH:10:00"); + else if (curMinute >= 15 && curMinute < 20) + dataTime = curTime.ToString("yyyy-MM-dd HH:15:00"); + else if (curMinute >= 20 && curMinute < 25) + dataTime = curTime.ToString("yyyy-MM-dd HH:20:00"); + else if (curMinute >= 25 && curMinute < 30) + dataTime = curTime.ToString("yyyy-MM-dd HH:25:00"); + else if (curMinute >= 30 && curMinute < 35) + dataTime = curTime.ToString("yyyy-MM-dd HH:30:00"); + else if (curMinute >= 35 && curMinute < 40) + dataTime = curTime.ToString("yyyy-MM-dd HH:35:00"); + else if (curMinute >= 40 && curMinute < 45) + dataTime = curTime.ToString("yyyy-MM-dd HH:40:00"); + else if (curMinute >= 45 && curMinute < 50) + dataTime = curTime.ToString("yyyy-MM-dd HH:45:00"); + else if (curMinute >= 50 && curMinute < 55) + dataTime = curTime.ToString("yyyy-MM-dd HH:50:00"); + else + dataTime = curTime.ToString("yyyy-MM-dd HH:55:00"); + return DateTime.Parse(dataTime); + } + + /// + /// 数据时标 + /// + /// + /// + /// + /// + /// + public static List GetReadTimeTd_c(this List hexMessageList, int index, int len) + { + if (len != 7) + throw new ArgumentException("数据时标 Td_c参数为不标准长度"); + var list = hexMessageList.GetRange(index, len); + List values = new List + { + GetReadTime(list.GetRange(0, 5)), + list[5].HexToDec().ToString(),//密度 + list[6].HexToDec().ToString()//数据点数 + }; + return values; + } + + + + /// + /// 数据时标型(曲线类型) + /// + /// + /// + public static List> GenerateFinalResultTd_c(this List data, int index,int density,string dataType, string filedDesc = "") + { + List> list = new List>(); + for (int i = index; i < data.Count; i++) + { + AnalysisBaseDto meter = new AnalysisBaseDto(); + meter.MeterType= MeterTypeEnum.Ammeter; + + decimal value = 0; + var errorCode = data[i].CheckErrorCode(); + if (errorCode != null) + { + meter.ValidData = false; + meter.ErrorCodeMsg = errorCode.Item2; + } + else + decimal.TryParse(data[i], out value); + meter.DataValue = value; + meter.DataType = dataType; + meter.FiledDesc = filedDesc; + meter.FiledName = meter.DataType.GetDataFieldByGatherDataType() ?? string.Empty; + if (DateTime.TryParse(CalculateTimeSpan(i - 3, data[0], density), out DateTime readingDate)) + meter.TimeSpan = readingDate; + list.Add(meter); + } + return list; + } + + /// + /// 月冻结 + /// + /// + /// + /// + /// + /// + /// + public static List> GenerateFinalResultTd_m(this List data, int index,string dataType,string timeSpan, string filedDesc = "") + { + List> list = new List>(); + for (int i = index; i < data.Count; i++) + { + AnalysisBaseDto meter = new AnalysisBaseDto(); + meter.MeterType = MeterTypeEnum.Ammeter; + decimal value = 0; + var errorCode = data[i].CheckErrorCode(); + if (errorCode != null) + { + meter.ValidData = false; + meter.ErrorCodeMsg = errorCode.Item2; + } + else + decimal.TryParse(data[i], out value); + meter.DataValue = value; + if (DateTime.TryParse(timeSpan, out DateTime readingDate)) + { + meter.TimeSpan = readingDate; + } + meter.DataType = i - index == 0 ? dataType : $"{dataType}_{i - index}"; + meter.FiledDesc = filedDesc; + meter.FiledName = meter.DataType.GetDataFieldByGatherDataType() ?? string.Empty; + list.Add(meter); + } + return list; + } + + /// + /// 日冻结 + /// + /// + /// + /// + /// + public static List> GenerateFinalResultTd_d(this List data, int index,string dataType, string timeSpan, string filedDesc = "") + { + List> list = new List>(); + int typeIndex = 0; + for (int i = index; i < data.Count; i++) + { + AnalysisBaseDto meter = new AnalysisBaseDto + { + MeterType = MeterTypeEnum.Ammeter + }; + decimal value = 0; + var errorCode = data[i].CheckErrorCode(); + if (errorCode != null) + { + meter.ValidData = false; + meter.ErrorCodeMsg = errorCode.Item2; + } + else + decimal.TryParse(data[i], out value); + meter.DataValue = value; + if (DateTime.TryParse(timeSpan, out DateTime readingDate)) + { + meter.TimeSpan = readingDate; + } + meter.DataType = i - index == 0 ? dataType : $"{dataType}_{i - index}"; + meter.FiledDesc = filedDesc; + meter.FiledName = meter.DataType.GetDataFieldByGatherDataType() ?? string.Empty; + list.Add(meter); + typeIndex++; + } + return list; + } + + /// + /// 计算时标 + /// + /// + /// + /// + /// + private static string CalculateTimeSpan(int index, string time, int intervalTime) + { + var startTime = Convert.ToDateTime($"{time.Substring(0, 4)}-{time.Substring(4, 2)}-{time.Substring(6, 2)} {time.Substring(8, 2)}:{time.Substring(10, 2)}"); + return startTime.AddMinutes(index * intervalTime).ToString("yyyy-MM-dd HH:mm:ss"); + } + + /// + /// 校验数据值精度 + /// + /// + /// + /// + public static List> IsValidData(this List> meterDatas, List mark) + { + bool isUpload = false; + var jfpgSum = 0M; + var totalItem = meterDatas.Find(f => f.DataType.Equals(mark[0]));//meterDatas.Find(f => f.DataType.Equals(mark[0])); + for (int i = 1; i <= meterDatas.Count - 1; i++) + { + var value = meterDatas[i];//meterDatas.Find(f => f.DataType.Equals(mark[i])); + if (value.ValidData) + jfpgSum += value.DataValue; + } + var floatingNum = (jfpgSum * 5 / 100);//上下浮动数据 + var minjfpgSum = jfpgSum - floatingNum;//100-(100*5/100); + var maxjfpgSum = jfpgSum + floatingNum; + if (totalItem.DataValue <= maxjfpgSum || totalItem.DataValue >= minjfpgSum)//总值,在JFPG之和的浮动范围内 + isUpload = true; + else + isUpload = false; + if (!isUpload) + meterDatas.ForEach(f => + { + f.ValidData = false; + }); + return meterDatas; + } + } +} diff --git a/shared/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/SendData/Telemetry3761PacketBuilder.cs similarity index 73% rename from shared/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs rename to protocols/JiShe.CollectBus.Protocol.T37612012/SendData/Telemetry3761PacketBuilder.cs index 8ad2a39..ab43cc8 100644 --- a/shared/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketBuilder.cs +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/SendData/Telemetry3761PacketBuilder.cs @@ -1,51 +1,42 @@ -using JiShe.CollectBus.Common.Enums; +using System.Reflection; +using JiShe.CollectBus.Common.BuildSendDatas; +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 +namespace JiShe.CollectBus.Protocol.T37612012.SendData { /// - /// 构建下发报文,只适用与定时抄读 + /// 构建3761下发报文 /// - public static class TelemetryPacketBuilder + public static class Telemetry3761PacketBuilder { /// /// 构建报文的委托 - /// - /// - /// - /// - public delegate byte[] AFNDelegate(TelemetryPacketRequest request); + /// + public delegate Telemetry3761PacketResponse T3761Delegate(Telemetry3761PacketRequest request); /// /// 编码与方法的映射表 /// - public static readonly Dictionary AFNHandlersDictionary = new(); + public static readonly Dictionary T3761AFNHandlers = new(); - static TelemetryPacketBuilder() + static Telemetry3761PacketBuilder() { // 初始化时自动注册所有符合命名规则的方法 - var methods = typeof(TelemetryPacketBuilder).GetMethods(BindingFlags.Static | BindingFlags.Public); + var methods = typeof(Telemetry3761PacketBuilder).GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (var method in methods) { if (method.Name.StartsWith("AFN") && method.Name.EndsWith("_Fn_Send")) { string code = method.Name; - var delegateInstance = (AFNDelegate)Delegate.CreateDelegate(typeof(AFNDelegate), method); - AFNHandlersDictionary[code] = delegateInstance; + var delegateInstance = (T3761Delegate)Delegate.CreateDelegate(typeof(T3761Delegate), method); + T3761AFNHandlers[code] = delegateInstance; } } } #region AFN_00H 确认∕否认 - public static byte[] AFN00_Fn_Send(TelemetryPacketRequest request) + public static Telemetry3761PacketResponse AFN00_Fn_Send(Telemetry3761PacketRequest request) { var reqParameter = new ReqParameter2() { @@ -64,13 +55,13 @@ namespace JiShe.CollectBus.Common.BuildSendDatas Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; + return new Telemetry3761PacketResponse() { Seq = reqParameter.Seq.PRSEQ, Data = bytes, MSA = reqParameter.MSA, }; } #endregion #region AFN_01H 复位命令 - public static byte[] AFN01_Fn_Send(TelemetryPacketRequest request) + public static Telemetry3761PacketResponse AFN01_Fn_Send(Telemetry3761PacketRequest request) { var reqParameter = new ReqParameter2() { @@ -89,13 +80,13 @@ namespace JiShe.CollectBus.Common.BuildSendDatas Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; + return new Telemetry3761PacketResponse() { Seq = reqParameter.Seq.PRSEQ, Data = bytes, MSA = reqParameter.MSA, }; } #endregion #region AFN_02H 链路接口检测 - public static byte[] AFN02_Fn_Send(TelemetryPacketRequest request) + public static Telemetry3761PacketResponse AFN02_Fn_Send(Telemetry3761PacketRequest request) { var reqParameter = new ReqParameter2() { @@ -114,12 +105,12 @@ namespace JiShe.CollectBus.Common.BuildSendDatas Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; + return new Telemetry3761PacketResponse() { Seq = reqParameter.Seq.PRSEQ, Data = bytes, MSA = reqParameter.MSA, }; } #endregion #region AFN_04H 设置参数 - public static byte[] AFN04_Fn_Send(TelemetryPacketRequest request) + public static Telemetry3761PacketResponse AFN04_Fn_Send(Telemetry3761PacketRequest request) { var reqParameter = new ReqParameter2() { @@ -138,13 +129,13 @@ namespace JiShe.CollectBus.Common.BuildSendDatas Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; + return new Telemetry3761PacketResponse() { Seq = reqParameter.Seq.PRSEQ, Data = bytes, MSA = reqParameter.MSA, }; } #endregion #region AFN_05H 控制命令 - public static byte[] AFN05_Fn_Send(TelemetryPacketRequest request) + public static Telemetry3761PacketResponse AFN05_Fn_Send(Telemetry3761PacketRequest request) { var reqParameter = new ReqParameter2() { @@ -163,12 +154,12 @@ namespace JiShe.CollectBus.Common.BuildSendDatas Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; + return new Telemetry3761PacketResponse() { Seq = reqParameter.Seq.PRSEQ, Data = bytes, MSA = reqParameter.MSA, }; } #endregion #region AFN_09H 请求终端配置及信息 - public static byte[] AFN09_Fn_Send(TelemetryPacketRequest request) + public static Telemetry3761PacketResponse AFN09_Fn_Send(Telemetry3761PacketRequest request) { var reqParameter = new ReqParameter2() { @@ -187,13 +178,13 @@ namespace JiShe.CollectBus.Common.BuildSendDatas Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; + return new Telemetry3761PacketResponse() { Seq = reqParameter.Seq.PRSEQ, Data = bytes, MSA = reqParameter.MSA, }; } #endregion #region AFN_0AH 查询参数 - public static byte[] AFN0A_Fn_Send(TelemetryPacketRequest request) + public static Telemetry3761PacketResponse AFN0A_Fn_Send(Telemetry3761PacketRequest request) { var reqParameter = new ReqParameter2() { @@ -212,12 +203,12 @@ namespace JiShe.CollectBus.Common.BuildSendDatas Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; + return new Telemetry3761PacketResponse() { Seq = reqParameter.Seq.PRSEQ, Data = bytes, MSA = reqParameter.MSA, }; } #endregion #region AFN_0CH 请求一类数据 - public static byte[] AFN0C_Fn_Send(TelemetryPacketRequest request) + public static Telemetry3761PacketResponse AFN0C_Fn_Send(Telemetry3761PacketRequest request) { var reqParameter = new ReqParameter2() { @@ -236,12 +227,12 @@ namespace JiShe.CollectBus.Common.BuildSendDatas Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; + return new Telemetry3761PacketResponse() { Seq = reqParameter.Seq.PRSEQ, Data = bytes, MSA = reqParameter.MSA, }; } #endregion #region AFN_0DH 请求二类数据 - public static byte[] AFN0D_Fn_Send(TelemetryPacketRequest request) + public static Telemetry3761PacketResponse AFN0D_Fn_Send(Telemetry3761PacketRequest request) { var reqParameter = new ReqParameter2() { @@ -260,12 +251,12 @@ namespace JiShe.CollectBus.Common.BuildSendDatas Fn = request.Fn }; var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter); - return bytes; + return new Telemetry3761PacketResponse() { Seq = reqParameter.Seq.PRSEQ, Data = bytes, MSA = reqParameter.MSA, }; } #endregion #region AFN10H 数据转发 - public static byte[] AFN10_Fn_Send(TelemetryPacketRequest request) + public static Telemetry3761PacketResponse AFN10_Fn_Send(Telemetry3761PacketRequest request) { var reqParameter = new ReqParameter2() { @@ -283,8 +274,8 @@ namespace JiShe.CollectBus.Common.BuildSendDatas Pn = request.Pn, Fn = request.Fn }; - var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter,request.DataUnit); - return bytes; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParameter, request.DataUnit); + return new Telemetry3761PacketResponse() { Seq = reqParameter.Seq.PRSEQ, Data = bytes, MSA = reqParameter.MSA, }; } #region SpecialAmmeter 特殊电表转发 diff --git a/shared/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketRequest.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/SendData/Telemetry3761PacketRequest.cs similarity index 59% rename from shared/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketRequest.cs rename to protocols/JiShe.CollectBus.Protocol.T37612012/SendData/Telemetry3761PacketRequest.cs index d22f923..f02efc9 100644 --- a/shared/JiShe.CollectBus.Common/BuildSendDatas/TelemetryPacketRequest.cs +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/SendData/Telemetry3761PacketRequest.cs @@ -1,20 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace JiShe.CollectBus.Common.BuildSendDatas +namespace JiShe.CollectBus.Protocol.T37612012.SendData { /// - /// 报文构建参数 + /// 构建3761报文参数 /// - public class TelemetryPacketRequest + public class Telemetry3761PacketRequest { /// /// 集中器地址 /// - public string FocusAddress { get; set; } + public required string FocusAddress { get; set; } /// /// 抄读功能码 @@ -24,7 +18,7 @@ namespace JiShe.CollectBus.Common.BuildSendDatas /// /// 抄读计量点,也就是终端电表对应端口 /// - public int Pn { get; set; } + public int Pn { get; set; } /// /// 透明转发单元 diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/SendData/Telemetry3761PacketResponse.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/SendData/Telemetry3761PacketResponse.cs new file mode 100644 index 0000000..cd4ed6e --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/SendData/Telemetry3761PacketResponse.cs @@ -0,0 +1,23 @@ +namespace JiShe.CollectBus.Protocol.T37612012.SendData +{ + /// + /// 返回3761报文结果 + /// + public class Telemetry3761PacketResponse + { + /// + /// 帧序列域SEQ + /// + public int Seq { get; set; } + + /// + /// 地址域A3的主站地址MSA + /// + public int MSA { get; set; } + + /// + /// 报文体 + /// + public byte[] Data { get; set; } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T37612012/T37612012ProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol.T37612012/T37612012ProtocolPlugin.cs new file mode 100644 index 0000000..1fc8d2a --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T37612012/T37612012ProtocolPlugin.cs @@ -0,0 +1,837 @@ +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.IotSystems.Devices; +using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.Protocols; +using JiShe.CollectBus.Kafka.Producer; +using JiShe.CollectBus.Protocol.Abstracts; +using JiShe.CollectBus.Protocol.Models; +using JiShe.CollectBus.Protocol.T37612012.SendData; +using JiShe.CollectBus.Protocol3761; +using Mapster; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using TouchSocket.Sockets; +using Volo.Abp.Domain.Repositories; + +namespace JiShe.CollectBus.Protocol.T37612012 +{ + public class T37612012ProtocolPlugin : ProtocolPlugin + { + private readonly ILogger _logger; + + private readonly IProducerService _producerService; + + private readonly IRepository _deviceRepository; + private readonly ITcpService _tcpService; + + public readonly Dictionary T3761AFNHandlers; + + /// + /// Initializes a new instance of the class. + /// + /// The service provider. + public T37612012ProtocolPlugin(IServiceProvider serviceProvider, ILogger logger, ITcpService tcpService) : base(serviceProvider, logger) + { + _logger = logger; + //_logger = serviceProvider.GetRequiredService>(); + _producerService = serviceProvider.GetRequiredService(); + _deviceRepository = serviceProvider.GetRequiredService>(); + _tcpService = tcpService; + T3761AFNHandlers = Telemetry3761PacketBuilder.T3761AFNHandlers; + } + + public override ProtocolInfo Info => new(nameof(T37612012ProtocolPlugin), "376.1", "TCP", "376.1协议", "DTS1980"); + + public override async Task AnalyzeAsync(ITcpSessionClient client, string messageReceived, Action? sendAction = null) + { + TB3761? tB3761 = Analysis3761(messageReceived); + if (tB3761 != null) + { + if (tB3761.AFN_FC?.AFN == (int)AFN.链路接口检测) + { + if (tB3761.A == null || tB3761.A.Code.IsNullOrWhiteSpace() || tB3761.A.A3?.D1_D7 == null || tB3761.SEQ?.PSEQ == null) + { + _logger.LogError($"解析AFN.链路接口检测报文失败,报文:{messageReceived},TB3761:{tB3761.Serialize()}"); + } + else + { + if (tB3761.DT?.Fn == (int)FN.登录) + { + // 登录回复 + if (tB3761.SEQ.CON == (int)CON.需要对该帧进行确认) + await LoginAsync(client, messageReceived, tB3761.A.Code, tB3761.A.A3?.D1_D7, tB3761.SEQ?.PSEQ); + } + else if (tB3761.DT?.Fn == (int)FN.心跳) + { + // 心跳回复 + //心跳帧有两种情况: + //1. 集中器先有登录帧,再有心跳帧 + //2. 集中器没有登录帧,只有心跳帧 + await HeartbeatAsync(client, messageReceived, tB3761.A.Code, tB3761.A.A3?.D1_D7, tB3761.SEQ?.PSEQ); + } + } + + } + await OnTcpNormalReceived(client, tB3761); + } + return (tB3761 as T)!; + } + + /// + /// 正常帧处理,将不同的AFN进行分发 + /// + /// + /// + /// + /// + private async Task OnTcpNormalReceived(ITcpSessionClient tcpSessionClient, TB3761 tB3761) + { + //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() + //}); + + if (tB3761.AFN_FC.BaseHexMessage == null || tB3761.DT.BaseHexMessage == null || tB3761.BaseHexMessage.HexMessageString==null) + { + _logger.LogError("376.1协议解析AFN失败"); + return; + } + // 登录心跳已做了处理,故需要忽略登录和心跳帧 + if (tB3761.DT.Fn == (int)FN.登录 || tB3761.DT.Fn == (int)FN.心跳) + return; + //TODO:根据AFN进行分流推送到kafka + string topicName = string.Format(ProtocolConst.AFNTopicNameFormat, tB3761.AFN_FC.AFN.ToString().PadLeft(2, '0')); + tB3761.MessageId = Guid.NewGuid().ToString(); + MessageProtocolAnalysis messageReceivedAnalysis = new MessageProtocolAnalysis() + { + ClientId = tcpSessionClient.Id, + ClientIp = tcpSessionClient.IP, + ClientPort = tcpSessionClient.Port, + MessageHexString = tB3761.BaseHexMessage.HexMessageString!, + DeviceNo = tB3761.A.Code!, + MessageId = tB3761.MessageId, + ReceivedTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), + Data = tB3761 + }; + + List topics = ProtocolConstExtensions.GetAllTopicNamesByReceived(); + if (topics.Contains(topicName)) + await _producerService.ProduceAsync(topicName, messageReceivedAnalysis); + else + { + _logger.LogError($"不支持的上报kafka主题:{topicName}"); + await _producerService.ProduceAsync(ProtocolConst.SubscriberReceivedEventName, messageReceivedAnalysis); + } + + } + + + /// + /// 登录回复 + /// + /// + /// + /// + /// + /// + public async Task LoginAsync(ITcpSessionClient client, string messageReceived, string code, int? msa, int? pseq) + { + string oldClientId = $"{client.Id}"; + await client.ResetIdAsync(code); + var deviceInfoList = await _deviceRepository.GetListAsync(a => a.Number == code); + if (deviceInfoList != null && deviceInfoList.Count > 1) + { + //todo 推送集中器编号重复预警 + _logger.LogError($"集中器编号:{code},存在多个集中器,请检查集中器编号是否重复"); + return; + } + + var entity = deviceInfoList?.FirstOrDefault(a => a.Number == code); + if (entity == null) + { + await _deviceRepository.InsertAsync(new Device(code, oldClientId, DateTime.Now, DateTime.Now, DeviceStatus.Online)); + } + else + { + entity.UpdateByLoginAndHeartbeat(oldClientId); + await _deviceRepository.UpdateAsync(entity); + } + + var messageReceivedLoginEvent = new MessageReceivedLogin + { + ClientId = code, + ClientIp = client.IP, + ClientPort = client.Port, + MessageHexString = messageReceived, + DeviceNo = code, + MessageId = Guid.NewGuid().ToString(), + ReceivedTime=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + }; + await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent); + var reqParam = new ReqParameter2 + { + AFN = AFN.确认或否认, + FunCode = (int)CFromStationFunCode.链路数据, + PRM = PRM.从动站报文, + A = code, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.需要对该帧进行确认, + PRSEQ = pseq!.Value + }, + MSA = msa!.Value, + Pn = 0, + Fn = 1 + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); + var issuedEventMessage = new IssuedEventMessage + { + ClientId = messageReceivedLoginEvent.ClientId, + DeviceNo = messageReceivedLoginEvent.DeviceNo, + Message = bytes, + Type = IssuedEventType.Login, + MessageId = messageReceivedLoginEvent.MessageId + }; + if (_tcpService.ClientExists(issuedEventMessage.ClientId)) + { + await _tcpService.SendAsync(issuedEventMessage.ClientId, issuedEventMessage.Message); + _logger.LogInformation($"集中器地址{issuedEventMessage.ClientId} 登录回复下发内容:{Convert.ToHexString(bytes)}"); + await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginIssuedEventName, issuedEventMessage); + } + } + + /// + /// 心跳帧解析 + /// + /// + /// + /// + /// + /// + public async Task HeartbeatAsync(ITcpSessionClient client, string messageReceived, string code, int? msa, int? pseq) + { + + string clientId = code; + string oldClientId = $"{client.Id}"; + var deviceInfoList = await _deviceRepository.GetListAsync(a => a.Number == code); + if (deviceInfoList != null && deviceInfoList.Count > 1) + { + //todo 推送集中器编号重复预警 + _logger.LogError($"集中器编号:{code},存在多个集中器,请检查集中器编号是否重复"); + return; + } + + var entity = deviceInfoList?.FirstOrDefault(a => a.Number == code); + if (entity == null) //没有登录帧的设备,只有心跳帧 + { + await client.ResetIdAsync(clientId); + await _deviceRepository.InsertAsync(new Device(code, 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 = clientId, + ClientIp = client.IP, + ClientPort = client.Port, + MessageHexString = messageReceived, + DeviceNo = code, + MessageId = Guid.NewGuid().ToString(), + ReceivedTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + }; + await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent); + + var reqParam = new ReqParameter2() + { + AFN = AFN.确认或否认, + FunCode = (int)CFromStationFunCode.链路数据, + PRM = PRM.从动站报文, + A = code, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = pseq!.Value, + }, + MSA = msa!.Value, + Pn = 0, + Fn = 1 + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); + + IssuedEventMessage issuedEventMessage = new IssuedEventMessage + { + ClientId = messageReceivedHeartbeatEvent.ClientId, + DeviceNo = messageReceivedHeartbeatEvent.DeviceNo, + Message = bytes, + Type = IssuedEventType.Heartbeat, + MessageId = messageReceivedHeartbeatEvent.MessageId + }; + if (_tcpService.ClientExists(issuedEventMessage.ClientId)) + { + await _tcpService.SendAsync(issuedEventMessage.ClientId, bytes); + _logger.LogWarning($"集中器地址{issuedEventMessage.ClientId} 心跳回复下发内容:{Convert.ToHexString(bytes)}"); + await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, issuedEventMessage); + } + + + } + + /// + /// 组装报文 + /// + /// 报文构建参数 + /// + public override async Task BuildAsync(ProtocolBuildRequest request) + { + if (request == null) + { + throw new Exception($"{nameof(T37612012ProtocolPlugin)} 报文构建失败,参数为空"); + } + var itemCodeArr = request.ItemCode.Split('_'); + var aFNStr = itemCodeArr[0]; + var aFN = (AFN)aFNStr.HexToDec(); + var fn = int.Parse(itemCodeArr[1]); + Telemetry3761PacketResponse builderResponse = null; + + List dataUnit = new List(); + //数据转发场景 10H_F1_1CH + if (aFNStr == "10" && request.SubProtocolRequest != null && string.IsNullOrWhiteSpace(request.SubProtocolRequest.ItemCode) == false) + { + //var t645PacketHandlerName = $"C{request.SubProtocolRequest.ItemCode}_Send"; + //Telemetry645PacketResponse t645PacketResponse = null; + + //if (T645ControlHandlers != null && T645ControlHandlers.TryGetValue(t645PacketHandlerName + // , out var t645PacketHandler)) + //{ + // t645PacketResponse = t645PacketHandler(new Telemetry645PacketRequest() + // { + // MeterAddress = request.SubProtocolRequest.MeterAddress, + // Password = request.SubProtocolRequest.Password, + // ItemCode = request.SubProtocolRequest.ItemCode, + // }); + //} + + //if (t645PacketResponse != null) + //{ + // dataUnit = t645PacketResponse.Data; + //} + } + + string afnMethonCode = $"AFN{aFNStr}_Fn_Send"; + if (T3761AFNHandlers != null && T3761AFNHandlers.TryGetValue(afnMethonCode + , out var handler)) + { + builderResponse = handler(new Telemetry3761PacketRequest() + { + FocusAddress = request.FocusAddress, + Fn = fn, + Pn = request.Pn, + DataUnit = dataUnit, + }); + } + + if (builderResponse == null) + { + return new ProtocolBuildResponse(); + } + + var result = builderResponse.Adapt(); + result.IsSuccess = true; + + return await Task.FromResult(result); + } + + + + + /// + /// 解析376.1帧 + /// + /// + /// + public virtual TB3761? Analysis3761(string messageReceived) + { + try + { + var hexStringList = messageReceived.StringToPairs(); + // 初步校验 + if (hexStringList.Count < 6 || hexStringList.FirstOrDefault() != "68" || hexStringList.Skip(5).Take(1).FirstOrDefault() != "68" || hexStringList.Count < 18 || hexStringList.LastOrDefault() != "16") + { + _logger.LogError($"解析Analysis3761校验不通过,报文:{messageReceived}"); + } + else + { + TB3761 tB3761 = new TB3761 + { + BaseHexMessage = new BaseHexMessage + { + HexMessageString = messageReceived, + HexMessageList = hexStringList + }, + C = Analysis_C(hexStringList), + A = Analysis_A(hexStringList), + AFN_FC = Analysis_AFN_FC(hexStringList), + SEQ = Analysis_SEQ(hexStringList), + UnitData = Analysis_UnitData(hexStringList), + DA = Analysis_DA(hexStringList), + DT = Analysis_DT(hexStringList) + }; + return tB3761; + } + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis3761错误,报文:{messageReceived},异常:{ex.Message}"); + } + return null; + } + + /// + /// 控制域C解析 + /// + /// + public virtual C Analysis_C(List hexStringList) + { + C c = new C(); + try + { + if (hexStringList.Count > 6) + { + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexStringList.GetRange(6, 1) // 控制域 1字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + if (baseHexMessage.HexMessageList.Count == 0) + return null; + string binStr = baseHexMessage.HexMessageString.HexTo4BinZero(); + c = new C + { + BaseHexMessage = baseHexMessage, + FC = binStr.Substring(binStr.Length - 4, 4).BinToDec(), + FCV = binStr.Substring(3, 1).BinToDec(), + FCB = binStr.Substring(2, 1).BinToDec(), + PRM = binStr.Substring(1, 1).BinToDec(), + DIR = binStr.Substring(0, 1).BinToDec() + }; + return c; + } + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_C错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + + return c; + } + + /// + /// 地址域A解析 + /// + /// + /// + public virtual A Analysis_A(List hexStringList) + { + A a = new A(); + try + { + if (hexStringList.Count > 7) + { + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexStringList.GetRange(7, 5) // 地址域 5个字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + a = new A + { + BaseHexMessage = baseHexMessage, + A1 = baseHexMessage.HexMessageList.ListReverseToStr(0, 2),//.DataConvert(10);//行政区划码A1 + A2 = baseHexMessage.HexMessageList.ListReverseToStr(2, 2).PadLeft(5, '0').HexToDec(),//终端地址A2 + A3 = Analysis_A3(baseHexMessage.HexMessageList) //主站地址和组地址标志A3 + }; + a.Code = $"{a.A1.PadLeft(4, '0')}{a.A2.ToString()!.PadLeft(5, '0')}"; + + } + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_A错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + + return a; + + } + + /// + /// 站地址和组地址标志A3 + /// + /// 地址域A集合 + /// + public virtual A3 Analysis_A3(List hexAList) + { + A3 a3 = new A3(); + try + { + if (hexAList.Count != 0) + { + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexAList.GetRange(4, 1) // 站地址和组地址标志A3 1个字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + var binStr = baseHexMessage.HexMessageString.HexTo4BinZero(); + a3 = new A3 + { + BaseHexMessage = baseHexMessage, + D0 = binStr.Substring(binStr.Length - 1, 1).BinToDec(), + D1_D7 = binStr.Substring(0, binStr.Length - 1).BinToDec() + }; + } + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_A3错误,报文:{string.Join("", hexAList)},异常:{ex.Message}"); + } + + return a3; + } + + /// + /// AFN_FC功能码 + /// + /// + public virtual AFN_FC Analysis_AFN_FC(List hexStringList) + { + AFN_FC aFN_FC = new AFN_FC(); + try + { + if (hexStringList.Count == 0) + { + + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexStringList.GetRange(12, 1) //AFN功能码 1个字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + + aFN_FC = new AFN_FC + { + BaseHexMessage = baseHexMessage, + AFN = baseHexMessage.HexMessageString.HexToDec(), + }; + } + + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_AFN_FC错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + + return aFN_FC; + } + + /// + /// 解析帧序列域SEQ + /// + /// + public virtual SEQ Analysis_SEQ(List hexStringList) + { + SEQ seq = new SEQ(); + try + { + if (hexStringList.Count != 0) + { + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexStringList.GetRange(13, 1) //帧序列域 SEQ 1个字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + var binStr = baseHexMessage.HexMessageString.HexTo4BinZero(); + seq = new SEQ + { + PSEQ = binStr.Substring(binStr.Length - 4, 4).BinToDec(), + CON = binStr.Substring(3, 1).BinToDec(), + FIN = binStr.Substring(2, 1).BinToDec(), + FIR = binStr.Substring(1, 1).BinToDec(), + TpV = binStr.Substring(0, 1).BinToDec() + }; + } + + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_SEQ错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + return seq; + } + + + /// + /// 数据单元标识及数据单元数据 + /// + public virtual UnitData Analysis_UnitData(List hexStringList) + { + UnitData unitData = new UnitData(); + try + { + if (hexStringList.Count != 0) + { + unitData = new UnitData + { + HexMessageList = hexStringList.GetRange(14, hexStringList.Count - 14 - 2) //总数字节数-固定长度报文头-控制域C-地址域A-校验和CS-结束字符(16H) + }; + unitData.HexMessageString = string.Join("", unitData.HexMessageList); + } + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_UnitData错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + return unitData; + } + + /// + /// 信息点DA Pn + /// + /// + public virtual DA Analysis_DA(List hexStringList) + { + DA da = new DA(); + try + { + if (hexStringList.Count != 0) + { + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexStringList.GetRange(14, 2) //信息点DA Pn 2个字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + var da1 = baseHexMessage.HexMessageList[0]; + var da2 = baseHexMessage.HexMessageList[1]; + da = new DA() + { + BaseHexMessage = baseHexMessage, + Pn = CalculatePn(da1, da2) + }; + } + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_DA错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + return da; + } + + /// + /// 信息类DT Fn + /// + /// + public virtual DT Analysis_DT(List hexStringList) + { + DT dt = new DT(); + try + { + if (hexStringList.Count != 0) + { + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexStringList.GetRange(16, 2) //信息类DT Fn 2个字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + var dt1 = baseHexMessage.HexMessageList[0]; + var dt2 = baseHexMessage.HexMessageList[1]; + dt = new DT() + { + BaseHexMessage = baseHexMessage, + Fn = CalculateFn(dt1, dt2) + }; + } + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_DT错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + + return dt; + + } + + + /// + /// 计算Pn + /// + /// + /// + /// + public static int CalculatePn(string da1, string da2) => (da2.HexToDec() - 1) * 8 + (8 - da1.HexTo4BinZero().IndexOf(da1.Equals("00") ? "0" : "1")); + + + /// + /// 计算Fn + /// + /// + /// + /// + public static int CalculateFn(string dt1, string dt2) => dt2.HexToDec() * 8 + (8 - dt1.HexTo4BinZero().IndexOf("1")); + + + + #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 + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T6452007/JiShe.CollectBus.Protocol.T6452007.csproj b/protocols/JiShe.CollectBus.Protocol.T6452007/JiShe.CollectBus.Protocol.T6452007.csproj new file mode 100644 index 0000000..e19a7e0 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T6452007/JiShe.CollectBus.Protocol.T6452007.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + preview + + + + + + + + + + + + + + + + + + + diff --git a/protocols/JiShe.CollectBus.Protocol.T6452007/JiSheCollectBusProtocolT6452007Module.cs b/protocols/JiShe.CollectBus.Protocol.T6452007/JiSheCollectBusProtocolT6452007Module.cs new file mode 100644 index 0000000..cc20361 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T6452007/JiSheCollectBusProtocolT6452007Module.cs @@ -0,0 +1,28 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Modularity; + +namespace JiShe.CollectBus.Protocol.T6452007 +{ + public class JiSheCollectBusProtocolT6452007Module : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddKeyedSingleton(nameof(T6452007ProtocolPlugin)); + } + + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + Console.WriteLine($"{nameof(T6452007ProtocolPlugin)} OnApplicationInitializationAsync"); + var standardProtocol = context.ServiceProvider.GetRequiredKeyedService(nameof(T6452007ProtocolPlugin)); + await standardProtocol.LoadAsync(); + } + + public override void OnApplicationShutdown(ApplicationShutdownContext context) + { + Console.WriteLine($"{nameof(T6452007ProtocolPlugin)} OnApplicationShutdown"); + base.OnApplicationShutdown(context); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T6452007/SendData/Telemetry6452007PacketBuilder.cs b/protocols/JiShe.CollectBus.Protocol.T6452007/SendData/Telemetry6452007PacketBuilder.cs new file mode 100644 index 0000000..2d20272 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T6452007/SendData/Telemetry6452007PacketBuilder.cs @@ -0,0 +1,103 @@ +using System.Reflection; +using JiShe.CollectBus.Common.BuildSendDatas; +using JiShe.CollectBus.Common.Extensions; + +namespace JiShe.CollectBus.Protocol.T6452007.SendData +{ + /// + /// 构建645-2007下发报文 + /// + public static class Telemetry6452007PacketBuilder + { + /// + /// 构建报文的委托 + /// + /// + /// + public delegate Telemetry6452007PacketResponse T6452007Delegate(Telemetry6452007PacketRequest request); + + /// + /// 编码与方法的映射表 + /// + public static readonly Dictionary T645ControlHandlers = new(); + + static Telemetry6452007PacketBuilder() + { + // 初始化时自动注册所有符合命名规则的方法 + var methods = typeof(Telemetry6452007PacketBuilder).GetMethods(BindingFlags.Static | BindingFlags.Public); + foreach (var method in methods) + { + if (method.Name.StartsWith("C") && method.Name.EndsWith("_Send")) + { + string code = method.Name; + var delegateInstance = (T6452007Delegate)Delegate.CreateDelegate(typeof(T6452007Delegate), method); + T645ControlHandlers[code] = delegateInstance; + } + } + } + + #region 1CH 跳合闸、报警、保电 + + /// + /// 1CH 跳合闸 + /// + /// + /// + public static Telemetry6452007PacketResponse C1C_01_Send(Telemetry6452007PacketRequest request) + { + var itemCodeArr = request.ItemCode.Split('_'); + var c_data = itemCodeArr[0]; + var n_data = itemCodeArr[1]; + string password = request.Password; + string pwdLevel = "02"; + + if (!string.IsNullOrWhiteSpace(password) && password.Contains("|")) + { + var sp = password.Split('|'); + password = sp[0]; + pwdLevel = sp[1]; + } + + var strDate = DateTime.Now.AddYears(3).ToString("000012ddMMyy").StrAddSpan();//命令有效截止时间 + + var strP = password.StrAddSpan().StrReverseOrder(); + var strSJY = " " + pwdLevel + " " + strP + " 01 00 00 00 " + n_data + " 00 " + strDate; + var dataUnit = strSJY.Replace(" ", "").StringToPairs(); + + var dataList = Build645SendData.Build645SendCommand(request.MeterAddress, c_data, dataUnit); + return new Telemetry6452007PacketResponse() { Data = dataList }; + } + + + /// + /// 1CH 保电 + /// + /// + /// + public static Telemetry6452007PacketResponse C1C_03_Send(Telemetry6452007PacketRequest request) + { + var itemCodeArr = request.ItemCode.Split('_'); + var c_data = itemCodeArr[0]; + var n_data = itemCodeArr[1]; + string password = request.Password; + + if (!string.IsNullOrWhiteSpace(password) && password.Contains("|")) + { + var sp = password.Split('|'); + password = sp[0]; + } + + var strDate = (n_data + DateTime.Now.AddDays(1).ToString("00000012ddMMyy")).StrAddSpan(); + + var strP = password.StrAddSpan().StrReverseOrder(); + + var strSJY = " 02 " + strP + " 01 00 00 00 " + strDate; + + var dataUnit = strSJY.Replace(" ", "").StringToPairs(); + + var dataList = Build645SendData.Build645SendCommand(request.MeterAddress, c_data, dataUnit); + return new Telemetry6452007PacketResponse() { Data = dataList }; + } + #endregion + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T6452007/SendData/Telemetry6452007PacketRequest.cs b/protocols/JiShe.CollectBus.Protocol.T6452007/SendData/Telemetry6452007PacketRequest.cs new file mode 100644 index 0000000..c367206 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T6452007/SendData/Telemetry6452007PacketRequest.cs @@ -0,0 +1,23 @@ +namespace JiShe.CollectBus.Protocol.T6452007.SendData +{ + /// + /// 构建645报文参数 + /// + public class Telemetry6452007PacketRequest + { + /// + /// 表地址 + /// + public required string MeterAddress { get; set; } + + /// + /// 密码 + /// + public required string Password { get; set; } + + /// + /// 操作码 + /// + public required string ItemCode { get; set; } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T6452007/SendData/Telemetry6452007PacketResponse.cs b/protocols/JiShe.CollectBus.Protocol.T6452007/SendData/Telemetry6452007PacketResponse.cs new file mode 100644 index 0000000..d37c7c7 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T6452007/SendData/Telemetry6452007PacketResponse.cs @@ -0,0 +1,13 @@ +namespace JiShe.CollectBus.Protocol.T6452007.SendData +{ + /// + /// 返回645报文结果 + /// + public class Telemetry6452007PacketResponse + { + /// + /// 报文体 + /// + public List Data { get; set; } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.T6452007/T6452007ProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol.T6452007/T6452007ProtocolPlugin.cs new file mode 100644 index 0000000..7e3b620 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.T6452007/T6452007ProtocolPlugin.cs @@ -0,0 +1,269 @@ +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.IotSystems.Protocols; +using JiShe.CollectBus.Protocol.Models; +using JiShe.CollectBus.Protocol.T37612012; +using JiShe.CollectBus.Protocol.T37612012.SendData; +using JiShe.CollectBus.Protocol.T6452007.SendData; +using Mapster; +using Microsoft.Extensions.Logging; +using TouchSocket.Sockets; + +namespace JiShe.CollectBus.Protocol.T6452007 +{ + /// + /// T6452007协议插件 + /// + public class T6452007ProtocolPlugin : T37612012ProtocolPlugin + { + private readonly ILogger _logger; + + public readonly Dictionary T645ControlHandlers; + + /// + /// Initializes a new instance of the class. + /// + /// The service provider. + public T6452007ProtocolPlugin(IServiceProvider serviceProvider, ILogger logger, ITcpService tcpService) : base(serviceProvider, logger, tcpService) + { + _logger = logger; + T645ControlHandlers = Telemetry6452007PacketBuilder.T645ControlHandlers; + } + + public sealed override ProtocolInfo Info => new(nameof(T6452007ProtocolPlugin), "376.1/645-2007", "TCP", "376.1/645-2007协议", "DTS1980"); + + public override async Task AnalyzeAsync(ITcpSessionClient client, string messageReceived, Action? sendAction = null) + { + //TODO:645解析报文 + //TB3761? tB3761 = Analysis3761(messageReceived); + //if (tB3761 != null) + //{ + // if (tB3761.AFN_FC?.AFN == (int)AFN.链路接口检测) + // { + // if (tB3761.A == null || tB3761.A.Code.IsNullOrWhiteSpace() || tB3761.A.A3?.D1_D7 == null || tB3761.SEQ?.PSEQ == null) + // { + // _logger.LogError($"解析AFN.链路接口检测报文失败,报文:{messageReceived},TB3761:{tB3761.Serialize()}"); + // } + // else + // { + // if (tB3761.DT?.Fn == (int)FN.登录) + // { + // // 登录回复 + // if (tB3761.SEQ.CON == (int)CON.需要对该帧进行确认) + // await LoginAsync(client, messageReceived, tB3761.A.Code, tB3761.A.A3?.D1_D7, tB3761.SEQ?.PSEQ); + // } + // else if (tB3761.DT?.Fn == (int)FN.心跳) + // { + // // 心跳回复 + // //心跳帧有两种情况: + // //1. 集中器先有登录帧,再有心跳帧 + // //2. 集中器没有登录帧,只有心跳帧 + // await HeartbeatAsync(client, messageReceived, tB3761.A.Code, tB3761.A.A3?.D1_D7, tB3761.SEQ?.PSEQ); + // } + // } + + // } + // await OnTcpNormalReceived(client, tB3761); + //} + //return (tB3761 as T)!; + return null; + } + + /// + /// 组装报文 + /// + /// 报文构建参数 + /// + public override async Task BuildAsync(ProtocolBuildRequest request) + { + if (request == null) + { + _logger.LogError($"{nameof(ProtocolBuildResponse)} 报文构建失败,参数为空"); + return new ProtocolBuildResponse(); + } + var itemCodeArr = request.ItemCode.Split('_'); + var aFNStr = itemCodeArr[0]; + var aFN = (AFN)aFNStr.HexToDec(); + var fn = int.Parse(itemCodeArr[1]); + Telemetry3761PacketResponse builderResponse = null; + + List dataUnit = new List(); + //数据转发场景 10H_F1 + if (request.ItemCode == T37612012PacketItemCodeConst.AFN10HFN01H && request.SubProtocolRequest != null && string.IsNullOrWhiteSpace(request.SubProtocolRequest.ItemCode) == false) + { + var t645PacketHandlerName = $"C{request.SubProtocolRequest.ItemCode}_Send"; + Telemetry6452007PacketResponse t645PacketResponse = null; + + if (T645ControlHandlers != null && T645ControlHandlers.TryGetValue(t645PacketHandlerName + , out var t645PacketHandler)) + { + t645PacketResponse = t645PacketHandler(new Telemetry6452007PacketRequest() + { + MeterAddress = request.SubProtocolRequest.MeterAddress, + Password = request.SubProtocolRequest.Password, + ItemCode = request.SubProtocolRequest.ItemCode, + }); + } + + if (t645PacketResponse != null) + { + dataUnit = t645PacketResponse.Data; + } + } + + string afnMethonCode = $"AFN{aFNStr}_Fn_Send"; + if (base.T3761AFNHandlers != null && base.T3761AFNHandlers.TryGetValue(afnMethonCode + , out var handler)) + { + builderResponse = handler(new Telemetry3761PacketRequest() + { + FocusAddress = request.FocusAddress, + Fn = fn, + Pn = request.Pn, + DataUnit = dataUnit, + }); + } + + if (builderResponse == null) + { + return new ProtocolBuildResponse(); + } + + var result = builderResponse.Adapt(); + result.IsSuccess = true; + + return await Task.FromResult(result); + } + + + #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 + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj b/protocols/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj index 899ebad..e798bdb 100644 --- a/protocols/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj +++ b/protocols/JiShe.CollectBus.Protocol.Test/JiShe.CollectBus.Protocol.Test.csproj @@ -10,7 +10,6 @@ - @@ -18,7 +17,7 @@ - + diff --git a/protocols/JiShe.CollectBus.Protocol.Test/JiSheCollectBusProtocolModule.cs b/protocols/JiShe.CollectBus.Protocol.Test/JiSheCollectBusProtocolModule.cs deleted file mode 100644 index 4abc95b..0000000 --- a/protocols/JiShe.CollectBus.Protocol.Test/JiSheCollectBusProtocolModule.cs +++ /dev/null @@ -1,21 +0,0 @@ -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/protocols/JiShe.CollectBus.Protocol.Test/JiSheCollectBusTestProtocolModule.cs b/protocols/JiShe.CollectBus.Protocol.Test/JiSheCollectBusTestProtocolModule.cs new file mode 100644 index 0000000..6c3ebdc --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.Test/JiSheCollectBusTestProtocolModule.cs @@ -0,0 +1,29 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Modularity; + +namespace JiShe.CollectBus.Protocol.Test +{ + public class JiSheCollectBusTestProtocolModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Console.WriteLine("TestProtocolPlugin ConfigureServices"); + context.Services.AddKeyedSingleton(nameof(TestProtocolPlugin)); + } + + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + Console.WriteLine("TestProtocolPlugin OnApplicationInitializationAsync"); + var protocol = context.ServiceProvider.GetRequiredKeyedService(nameof(TestProtocolPlugin)); + await protocol.LoadAsync(); + } + + public override void OnApplicationShutdown(ApplicationShutdownContext context) + { + Console.WriteLine("TestProtocolPlugin OnApplicationShutdown"); + base.OnApplicationShutdown(context); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs index 2573ab7..549f00a 100644 --- a/protocols/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs +++ b/protocols/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs @@ -1,155 +1,32 @@ -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; +using JiShe.CollectBus.IotSystems.Protocols; +using JiShe.CollectBus.Protocol.Models; +using JiShe.CollectBus.Protocol.T37612012; +using Microsoft.Extensions.Logging; +using TouchSocket.Sockets; namespace JiShe.CollectBus.Protocol.Test { - public class TestProtocolPlugin : BaseProtocolPlugin + public class TestProtocolPlugin : T37612012ProtocolPlugin { + private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// The service provider. - public TestProtocolPlugin(IServiceProvider serviceProvider) : base(serviceProvider) + public TestProtocolPlugin(IServiceProvider serviceProvider, ILogger logger, ITcpService tcpService) : base(serviceProvider, logger, tcpService) { } - public sealed override ProtocolInfo Info => new(nameof(TestProtocolPlugin), "Test", "TCP", "Test协议", "DTS1980-Test"); + public override ProtocolInfo Info => new(nameof(TestProtocolPlugin), "Test", "TCP", "Test协议", "DTS1980-TEST"); - public override async Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) + public override Task AnalyzeAsync(ITcpSessionClient client, string messageReceived, Action? receivedAction = 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) + public override Task BuildAsync(ProtocolBuildRequest request) { - 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; + throw new NotImplementedException(); } - - /// - /// 解析地址 - /// - /// - /// - 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 } } diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol/Abstracts/BaseProtocolPlugin_bak.cs similarity index 88% rename from protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs rename to protocols/JiShe.CollectBus.Protocol/Abstracts/BaseProtocolPlugin_bak.cs index bc066fe..923f9ba 100644 --- a/protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/protocols/JiShe.CollectBus.Protocol/Abstracts/BaseProtocolPlugin_bak.cs @@ -1,4 +1,5 @@ -using JiShe.CollectBus.Common.Enums; +using FreeRedis; +using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.Protocol.Contracts.Interfaces; @@ -10,19 +11,18 @@ using JiShe.CollectBus.Protocol.Contracts.AnalysisData; using Microsoft.Extensions.DependencyInjection; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.Protocols; -using MassTransit; -using DotNetCore.CAP; using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.FreeRedis; namespace JiShe.CollectBus.Protocol.Contracts.Abstracts { - public abstract class BaseProtocolPlugin : IProtocolPlugin + public abstract class BaseProtocolPlugin_bak //: IProtocolPlugin { - private readonly ICapPublisher _producerBus; private readonly IProducerService _producerService; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IRepository _protocolInfoRepository; + private readonly IFreeRedisProvider _redisProvider; //头部字节长度 public const int hearderLen = 6; @@ -32,23 +32,23 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts public const string errorData = "EE"; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The service provider. - protected BaseProtocolPlugin(IServiceProvider serviceProvider) + protected BaseProtocolPlugin_bak(IServiceProvider serviceProvider) { - _logger = serviceProvider.GetRequiredService>(); + _logger = serviceProvider.GetRequiredService>(); _protocolInfoRepository = serviceProvider.GetRequiredService>(); _producerService = serviceProvider.GetRequiredService(); - _producerBus = serviceProvider.GetRequiredService(); + _redisProvider = serviceProvider.GetRequiredService(); } public abstract ProtocolInfo Info { get; } public virtual async Task GetAsync() => await Task.FromResult(Info); - public virtual async Task AddAsync() + public virtual async Task LoadAsync() { if (Info == null) { @@ -57,7 +57,8 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts await _protocolInfoRepository.DeleteDirectAsync(a => a.Name == Info.Name); await _protocolInfoRepository.InsertAsync(Info); - //await _protocolInfoCache.Get() + await _redisProvider.Instance.HDelAsync($"{RedisConst.ProtocolKey}", Info.Name); + await _redisProvider.Instance.HSetAsync($"{RedisConst.ProtocolKey}", Info.Name, Info); } public abstract Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) where T : TB3761; @@ -607,149 +608,149 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts var time = Appendix.Appendix_A1(hexDatas.Take(6).ToList()); } - /// - /// 通用解析 - /// - /// - /// - /// - public virtual TB3761 AnalyzeReadingDataAsync(MessageReceived messageReceived, - Action? sendAction = null) - { - var hexStringList = messageReceived.MessageHexString.StringToPairs(); - var afn = (AFN)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN); - var fn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN); + ///// + ///// 通用解析 + ///// + ///// + ///// + ///// + //public virtual TB3761 AnalyzeReadingDataAsync(MessageReceived messageReceived, + // Action? sendAction = null) + //{ + // var hexStringList = messageReceived.MessageHexString.StringToPairs(); + // var afn = (AFN)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN); + // var fn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN); - var tb3761 = QGDW3761Config.CommandList.FirstOrDefault(it => it.Afn == afn); - if (tb3761 == null) return null; + // var tb3761 = QGDW3761Config.CommandList.FirstOrDefault(it => it.Afn == afn); + // if (tb3761 == null) return null; - var tb3761Fn = tb3761.FnList.FirstOrDefault(it => it.Fn == fn); - if (tb3761Fn == null) return null; + // var tb3761Fn = tb3761.FnList.FirstOrDefault(it => it.Fn == fn); + // if (tb3761Fn == null) return null; - var analyzeValue = (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 = analyzeValue.Skip(rateNumberUp.DataIndex).Take(rateNumberUp.DataCount).FirstOrDefault(); - m = Convert.ToInt32(rateNumber); - rateNumberUpSort = rateNumberUp.Sort; - } + // var m = 0; + // var rateNumberUpSort = -1; + // var rateNumberUp = tb3761Fn.UpList.FirstOrDefault(it => it.Name.Contains("费率数M")); + // if (rateNumberUp != null) + // { + // var rateNumber = analyzeValue.Skip(rateNumberUp.DataIndex).Take(rateNumberUp.DataCount).FirstOrDefault(); + // m = Convert.ToInt32(rateNumber); + // rateNumberUpSort = rateNumberUp.Sort; + // } - foreach (var up in tb3761Fn.UpList) - { - var dataIndex = up.DataIndex; - if (dataIndex == 0 && up.Sort > rateNumberUpSort) - { - var sum1 = tb3761Fn.UpList.Where(it => it.Sort < up.Sort) - .Sum(it => it.DataCount); - var sum2 = tb3761Fn.UpList.Where(it => it.Sort < up.Sort && it.Tb3761UpChildlList.Count > 0) - .Sum(it => it.Tb3761UpChildlList.Sum(c=> m * c.DataCount)); - dataIndex = sum1 + sum2; - } + // foreach (var up in tb3761Fn.UpList) + // { + // var dataIndex = up.DataIndex; + // if (dataIndex == 0 && up.Sort > rateNumberUpSort) + // { + // var sum1 = tb3761Fn.UpList.Where(it => it.Sort < up.Sort) + // .Sum(it => it.DataCount); + // var sum2 = tb3761Fn.UpList.Where(it => it.Sort < up.Sort && it.Tb3761UpChildlList.Count > 0) + // .Sum(it => it.Tb3761UpChildlList.Sum(c=> m * c.DataCount)); + // dataIndex = sum1 + sum2; + // } - var value = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, up.DataCount, up.DataType); - if (value != null) - { - up.Value = value.ToString(); - } - if (up.Tb3761UpChildlList.Count > 0) //复费率根据费率数来解析 - { - var repeatCount = m; - foreach (var upChild in up.Tb3761UpChildlList) - { - for (var j = 0; j < repeatCount; j++) - { - var val = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, upChild.DataCount, upChild.DataType); - if (val != null) - { - upChild.Name = string.Format(upChild.Name, j + 1); - upChild.Value = val.ToString(); - } - dataIndex += upChild.DataCount; - } - } + // var value = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, up.DataCount, up.DataType); + // if (value != null) + // { + // up.Value = value.ToString(); + // } + // if (up.Tb3761UpChildlList.Count > 0) //复费率根据费率数来解析 + // { + // var repeatCount = m; + // foreach (var upChild in up.Tb3761UpChildlList) + // { + // for (var j = 0; j < repeatCount; j++) + // { + // var val = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, upChild.DataCount, upChild.DataType); + // if (val != null) + // { + // upChild.Name = string.Format(upChild.Name, j + 1); + // upChild.Value = val.ToString(); + // } + // dataIndex += upChild.DataCount; + // } + // } - } - } + // } + // } - return tb3761; - } + // return tb3761; + //} - /// - /// 通用解析 日冻结曲线类 - /// - /// - /// - /// - public virtual TB3761 AnalyzeReadingTdcDataAsync(MessageReceived messageReceived, - Action? sendAction = null) - { + ///// + ///// 通用解析 日冻结曲线类 + ///// + ///// + ///// + ///// + //public virtual TB3761 AnalyzeReadingTdcDataAsync(MessageReceived messageReceived, + // Action? sendAction = null) + //{ - var hexStringList = messageReceived.MessageHexString.StringToPairs(); - var afn = (AFN)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN); - var fn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN); + // var hexStringList = messageReceived.MessageHexString.StringToPairs(); + // var afn = (AFN)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN); + // var fn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN); - var tb3761 = QGDW3761Config.CommandTdcList.FirstOrDefault(it => it.Afn == afn); - if (tb3761 == null) return null; + // var tb3761 = QGDW3761Config.CommandTdcList.FirstOrDefault(it => it.Afn == afn); + // if (tb3761 == null) return null; - var tb3761Fn = tb3761.FnList.FirstOrDefault(it => it.Fn == fn); - if (tb3761Fn == null) return null; + // var tb3761Fn = tb3761.FnList.FirstOrDefault(it => it.Fn == fn); + // if (tb3761Fn == null) return null; - var analyzeValue = (List)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data); + // var analyzeValue = (List)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data); - foreach (var up in tb3761Fn.UpList) - { - var value = AnalyzeDataAccordingDataType(analyzeValue, up.DataIndex, up.DataCount, up.DataType); - if (value != null) - { - up.Value = value.ToString(); + // foreach (var up in tb3761Fn.UpList) + // { + // var value = AnalyzeDataAccordingDataType(analyzeValue, up.DataIndex, up.DataCount, up.DataType); + // if (value != null) + // { + // up.Value = value.ToString(); - if (up.Tb3761UpChildlList.Count > 0) - { - var dataIndex = up.DataIndex; - var repeatCount = (int)value; - foreach (var upChild in up.Tb3761UpChildlList) - { - for (var j = 0; j < repeatCount; j++) - { - var val = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, upChild.DataCount, upChild.DataType); - if (val != null) - { - upChild.Value = val.ToString(); - upChild.Name = string.Format(upChild.Name, j + 1); - } - dataIndex += upChild.DataCount; - } - } - } - } - } + // if (up.Tb3761UpChildlList.Count > 0) + // { + // var dataIndex = up.DataIndex; + // var repeatCount = (int)value; + // foreach (var upChild in up.Tb3761UpChildlList) + // { + // for (var j = 0; j < repeatCount; j++) + // { + // var val = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, upChild.DataCount, upChild.DataType); + // if (val != null) + // { + // upChild.Value = val.ToString(); + // upChild.Name = string.Format(upChild.Name, j + 1); + // } + // dataIndex += upChild.DataCount; + // } + // } + // } + // } + // } - return tb3761; - //var freezeDensity = (FreezeDensity)Convert.ToInt32(hexDatas.Skip(5).Take(1)); - //var addMinute = 0; - //switch (freezeDensity) - //{ - // case FreezeDensity.No:break; - // case FreezeDensity.Min15: - // addMinute = 15; - // break; - // case FreezeDensity.Min30: - // addMinute = 30; - // break; - // case FreezeDensity.Min60: - // addMinute = 60; - // break; - // case FreezeDensity.Min5: break; - // addMinute = 5; - // case FreezeDensity.Min1: - // addMinute = 1; - // break; - // } - } + // return tb3761; + // //var freezeDensity = (FreezeDensity)Convert.ToInt32(hexDatas.Skip(5).Take(1)); + // //var addMinute = 0; + // //switch (freezeDensity) + // //{ + // // case FreezeDensity.No:break; + // // case FreezeDensity.Min15: + // // addMinute = 15; + // // break; + // // case FreezeDensity.Min30: + // // addMinute = 30; + // // break; + // // case FreezeDensity.Min60: + // // addMinute = 60; + // // break; + // // case FreezeDensity.Min5: break; + // // addMinute = 5; + // // case FreezeDensity.Min1: + // // addMinute = 1; + // // break; + // // } + //} private object? AnalyzeDataAccordingDataType(List analyzeValue, int dataIndex,int dataCount,string dataType) { @@ -1172,6 +1173,5 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts } #endregion - } } diff --git a/protocols/JiShe.CollectBus.Protocol/Abstracts/ProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol/Abstracts/ProtocolPlugin.cs new file mode 100644 index 0000000..c6779e5 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol/Abstracts/ProtocolPlugin.cs @@ -0,0 +1,67 @@ +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.FreeRedis; +using JiShe.CollectBus.IotSystems.Protocols; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol.Models; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using TouchSocket.Sockets; +using Volo.Abp.Domain.Repositories; + +namespace JiShe.CollectBus.Protocol.Abstracts +{ + public abstract class ProtocolPlugin : IProtocolPlugin + { + //头部字节长度 + public const int hearderLen = 6; + + public const int tPLen = 6; + + public const string errorData = "EE"; + + private readonly ILogger _logger; + private readonly IRepository _protocolInfoRepository; + private readonly IFreeRedisProvider _redisProvider; + + public ProtocolPlugin(IServiceProvider serviceProvider, ILogger logger) + { + _logger = logger; + _protocolInfoRepository = serviceProvider.GetRequiredService>(); + _redisProvider = serviceProvider.GetRequiredService(); + } + + + public abstract ProtocolInfo Info { get; } + + public virtual async Task GetAsync() => await Task.FromResult(Info); + + public virtual async Task LoadAsync() + { + if (Info == null) + { + throw new ArgumentNullException(nameof(Info)); + } + + await _protocolInfoRepository.DeleteDirectAsync(a => a.Name == Info.Name); + await _protocolInfoRepository.InsertAsync(Info); + await _redisProvider.Instance.HDelAsync($"{RedisConst.ProtocolKey}", Info.Name); + await _redisProvider.Instance.HSetAsync($"{RedisConst.ProtocolKey}", Info.Name, Info); + } + + public abstract Task AnalyzeAsync(ITcpSessionClient client, string messageReceived, Action? receivedAction = null) where T : class; + + #region 下行命令构建 + + /// + /// 组装报文 + /// + /// + /// 设备数据实体 + /// 映射读取执行方法的Code,例如10_1,表示 10H_F1_00000,10H_F1_00001,统一英文下划线分隔 + /// + public abstract Task BuildAsync(ProtocolBuildRequest request); + + #endregion + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/Adapters/StandardFixedHeaderDataHandlingAdapter.cs b/protocols/JiShe.CollectBus.Protocol/Adapters/StandardFixedHeaderDataHandlingAdapter.cs similarity index 71% rename from protocols/JiShe.CollectBus.Protocol.Contracts/Adapters/StandardFixedHeaderDataHandlingAdapter.cs rename to protocols/JiShe.CollectBus.Protocol/Adapters/StandardFixedHeaderDataHandlingAdapter.cs index 0984a8f..2f2e992 100644 --- a/protocols/JiShe.CollectBus.Protocol.Contracts/Adapters/StandardFixedHeaderDataHandlingAdapter.cs +++ b/protocols/JiShe.CollectBus.Protocol/Adapters/StandardFixedHeaderDataHandlingAdapter.cs @@ -1,12 +1,7 @@ -using JiShe.CollectBus.Protocol.Contracts.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using JiShe.CollectBus.Protocol.Models; using TouchSocket.Core; -namespace JiShe.CollectBus.Protocol.Contracts.Adapters +namespace JiShe.CollectBus.Protocol.Adapters { public class StandardFixedHeaderDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter { diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/AnalysisData/Appendix.cs b/protocols/JiShe.CollectBus.Protocol/AnalysisData/Appendix.cs similarity index 79% rename from protocols/JiShe.CollectBus.Protocol.Contracts/AnalysisData/Appendix.cs rename to protocols/JiShe.CollectBus.Protocol/AnalysisData/Appendix.cs index f68268c..6075363 100644 --- a/protocols/JiShe.CollectBus.Protocol.Contracts/AnalysisData/Appendix.cs +++ b/protocols/JiShe.CollectBus.Protocol/AnalysisData/Appendix.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Extensions; -namespace JiShe.CollectBus.Protocol.Contracts.AnalysisData +namespace JiShe.CollectBus.Protocol.AnalysisData { /// /// 附录 diff --git a/protocols/JiShe.CollectBus.Protocol/AnalysisStrategyContext.cs b/protocols/JiShe.CollectBus.Protocol/AnalysisStrategyContext.cs new file mode 100644 index 0000000..cb66bcf --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol/AnalysisStrategyContext.cs @@ -0,0 +1,28 @@ +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.DependencyInjection; + +namespace JiShe.CollectBus.Protocol +{ + + public class AnalysisStrategyContext(IServiceProvider provider) + { + private readonly IServiceProvider _provider = provider; + + /// + /// 执行策略 + /// + /// + /// + /// + /// + /// + public Task ExecuteAsync(string type, TInput input) + { + var factory = _provider.GetRequiredService>(); + var strategy = (IAnalysisStrategy)factory(type, typeof(TInput), typeof(TResult)); + return strategy.ExecuteAsync(input); + } + } + + +} diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/Attributes/ProtocolNameAttribute.cs b/protocols/JiShe.CollectBus.Protocol/Attributes/ProtocolNameAttribute.cs similarity index 69% rename from protocols/JiShe.CollectBus.Protocol.Contracts/Attributes/ProtocolNameAttribute.cs rename to protocols/JiShe.CollectBus.Protocol/Attributes/ProtocolNameAttribute.cs index a04af71..4fa46d5 100644 --- a/protocols/JiShe.CollectBus.Protocol.Contracts/Attributes/ProtocolNameAttribute.cs +++ b/protocols/JiShe.CollectBus.Protocol/Attributes/ProtocolNameAttribute.cs @@ -1,6 +1,4 @@ -using System; - -namespace JiShe.CollectBus.Protocol.Contracts.Attributes +namespace JiShe.CollectBus.Protocol.Attributes { [AttributeUsage(AttributeTargets.Class)] public class ProtocolNameAttribute(string name) : Attribute diff --git a/protocols/JiShe.CollectBus.Protocol/CollectBusProtocolModule.cs b/protocols/JiShe.CollectBus.Protocol/CollectBusProtocolModule.cs new file mode 100644 index 0000000..f044539 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol/CollectBusProtocolModule.cs @@ -0,0 +1,15 @@ +using Volo.Abp.Modularity; + +namespace JiShe.CollectBus.Protocol +{ + public class CollectBusProtocolModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + + } + } +} + + + diff --git a/protocols/JiShe.CollectBus.Protocol/Interfaces/IAnalysisStrategy.cs b/protocols/JiShe.CollectBus.Protocol/Interfaces/IAnalysisStrategy.cs new file mode 100644 index 0000000..0066c19 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol/Interfaces/IAnalysisStrategy.cs @@ -0,0 +1,7 @@ +namespace JiShe.CollectBus.Protocol.Interfaces +{ + public interface IAnalysisStrategy + { + Task ExecuteAsync(TInput input); + } +} diff --git a/protocols/JiShe.CollectBus.Protocol/Interfaces/IProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol/Interfaces/IProtocolPlugin.cs new file mode 100644 index 0000000..b28f24b --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol/Interfaces/IProtocolPlugin.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; +using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.IotSystems.Protocols; +using JiShe.CollectBus.Protocol.Models; +using TouchSocket.Sockets; + +namespace JiShe.CollectBus.Protocol.Interfaces +{ + public interface IProtocolPlugin + { + Task GetAsync(); + + Task LoadAsync(); + + Task AnalyzeAsync(ITcpSessionClient client, string messageReceived, Action? sendAction = null) where T : class; + + /// + /// 组装报文 + /// + /// 是否需要转发的扩展协议入参对象 + /// 映射读取执行方法的Code,例如10_1,表示10H_F1 + /// + Task BuildAsync(ProtocolBuildRequest request); + } +} diff --git a/protocols/JiShe.CollectBus.Protocol/Interfaces/IProtocolService.cs b/protocols/JiShe.CollectBus.Protocol/Interfaces/IProtocolService.cs new file mode 100644 index 0000000..fd42f76 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol/Interfaces/IProtocolService.cs @@ -0,0 +1,30 @@ +using JiShe.CollectBus.IotSystems.Protocols; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp; + +namespace JiShe.CollectBus.Protocol.Interfaces +{ + public interface IProtocolService + { + /// + /// 通过仪器设备型号获取协议信息 + /// + /// + /// + /// + /// + Task FirstOrDefaultByDeviceAsync(string deviceCode, bool isSpecial = false); + + /// + /// 获取协议池服务 + /// + /// + /// + /// + Task GetProtocolServiceAsync(string deviceCode, bool isSpecial = false); + } +} diff --git a/protocols/JiShe.CollectBus.Protocol/JiShe.CollectBus.Protocol.csproj b/protocols/JiShe.CollectBus.Protocol/JiShe.CollectBus.Protocol.csproj index 1497183..f75b5dd 100644 --- a/protocols/JiShe.CollectBus.Protocol/JiShe.CollectBus.Protocol.csproj +++ b/protocols/JiShe.CollectBus.Protocol/JiShe.CollectBus.Protocol.csproj @@ -4,25 +4,30 @@ net8.0 enable enable - - preview - - - - + + + - - - + + - - - + + + + + + + + + + + + diff --git a/protocols/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs b/protocols/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs deleted file mode 100644 index 5cda0c8..0000000 --- a/protocols/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs +++ /dev/null @@ -1,21 +0,0 @@ -using JiShe.CollectBus.Protocol.Contracts.Interfaces; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp; -using Volo.Abp.Modularity; - -namespace JiShe.CollectBus.Protocol -{ - public class JiSheCollectBusProtocolModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddKeyedSingleton(nameof(StandardProtocolPlugin)); - } - - public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) - { - var standardProtocol = context.ServiceProvider.GetRequiredKeyedService(nameof(StandardProtocolPlugin)); - await standardProtocol.AddAsync(); - } - } -} diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/Models/CustomFixedHeaderRequestInfo.cs b/protocols/JiShe.CollectBus.Protocol/Models/CustomFixedHeaderRequestInfo.cs similarity index 85% rename from protocols/JiShe.CollectBus.Protocol.Contracts/Models/CustomFixedHeaderRequestInfo.cs rename to protocols/JiShe.CollectBus.Protocol/Models/CustomFixedHeaderRequestInfo.cs index 61ea887..def0753 100644 --- a/protocols/JiShe.CollectBus.Protocol.Contracts/Models/CustomFixedHeaderRequestInfo.cs +++ b/protocols/JiShe.CollectBus.Protocol/Models/CustomFixedHeaderRequestInfo.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Core; +using TouchSocket.Core; -namespace JiShe.CollectBus.Protocol.Contracts.Models +namespace JiShe.CollectBus.Protocol.Models { public class CustomFixedHeaderRequestInfo : IFixedHeaderRequestInfo { diff --git a/protocols/JiShe.CollectBus.Protocol/Models/ProtocolBuildRequest.cs b/protocols/JiShe.CollectBus.Protocol/Models/ProtocolBuildRequest.cs new file mode 100644 index 0000000..9936dc6 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol/Models/ProtocolBuildRequest.cs @@ -0,0 +1,28 @@ +namespace JiShe.CollectBus.Protocol.Models +{ + /// + /// 报文构建参数 + /// + public class ProtocolBuildRequest + { + /// + /// 集中器地址 + /// + public required string FocusAddress { get; set; } + + /// + /// 抄读计量点,也就是终端电表对应端口 + /// + public int Pn { get; set; } + + /// + /// 3761协议构建组合功能码 + /// + public required string ItemCode { get; set; } + + /// + /// 集中器转发协议构建构建参数 + /// + public SubProtocolBuildRequest SubProtocolRequest { get; set; } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol/Models/ProtocolBuildResponse.cs b/protocols/JiShe.CollectBus.Protocol/Models/ProtocolBuildResponse.cs new file mode 100644 index 0000000..b371460 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol/Models/ProtocolBuildResponse.cs @@ -0,0 +1,38 @@ +namespace JiShe.CollectBus.Protocol.Models +{ + /// + /// 报文构建返回结果 + /// + public class ProtocolBuildResponse + { + /// + /// 是否成功 + /// + public bool IsSuccess { get; set; } = false; + + /// + /// 帧功能域AFN + /// + public int AFn { get; set;} + + /// + /// 帧功能域FN + /// + public int Fn { get; set; } + + /// + /// 帧序列域SEQ + /// + public int Seq { get; set; } + + /// + /// 地址域A3的主站地址MSA + /// + public int MSA { get; set; } + + /// + /// 报文体 + /// + public byte[] Data { get; set; } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol/Models/SubProtocolBuildRequest.cs b/protocols/JiShe.CollectBus.Protocol/Models/SubProtocolBuildRequest.cs new file mode 100644 index 0000000..09a5c55 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol/Models/SubProtocolBuildRequest.cs @@ -0,0 +1,23 @@ +namespace JiShe.CollectBus.Protocol.Models +{ + /// + /// 子协议构建参数 + /// + public class SubProtocolBuildRequest + { + /// + /// 表地址 + /// + public required string MeterAddress { get; set; } + + /// + /// 密码 + /// + public required string Password { get; set; } + + /// + /// 操作码 + /// + public required string ItemCode { get; set; } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/QGDW3761Config.cs b/protocols/JiShe.CollectBus.Protocol/QGDW3761Config.cs similarity index 100% rename from protocols/JiShe.CollectBus.Protocol.Contracts/QGDW3761Config.cs rename to protocols/JiShe.CollectBus.Protocol/QGDW3761Config.cs diff --git a/protocols/JiShe.CollectBus.Protocol/Services/ProtocolService.cs b/protocols/JiShe.CollectBus.Protocol/Services/ProtocolService.cs new file mode 100644 index 0000000..0368151 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol/Services/ProtocolService.cs @@ -0,0 +1,73 @@ +using System.Text.RegularExpressions; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.FreeRedis; +using JiShe.CollectBus.IotSystems.Protocols; +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Volo.Abp; +using Volo.Abp.DependencyInjection; + +namespace JiShe.CollectBus.Protocol.Services +{ + public class ProtocolService : IProtocolService, ISingletonDependency + { + private readonly IFreeRedisProvider _freeRedisProvider; + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + + public ProtocolService(IFreeRedisProvider freeRedisProvider, IServiceProvider serviceProvider, ILogger logger) + { + _freeRedisProvider = freeRedisProvider; + _serviceProvider = serviceProvider; + _logger= logger; + } + + /// + /// 通过仪器设备型号获取协议信息 + /// + /// + /// + /// + /// + public async Task FirstOrDefaultByDeviceAsync(string deviceCode, bool isSpecial = false) + { + var protocols = await _freeRedisProvider.Instance.HGetAllAsync(RedisConst.ProtocolKey); + var keyValuePair = protocols.FirstOrDefault(a => ContainsExactPartRegex(deviceCode, a.Value.RegularExpression)); + if (!keyValuePair.Key.IsNullOrWhiteSpace() || keyValuePair.Value != null) return keyValuePair.Value; + if (isSpecial) throw new UserFriendlyException("The device protocol plugin does not exist!", ExceptionCode.NotFound); + var hasStandardProtocolPlugin = protocols.TryGetValue("StandardProtocolPlugin", out var protocolInfo); + if (!hasStandardProtocolPlugin) throw new UserFriendlyException("Standard protocol plugin does not exist!", ExceptionCode.NotFound); + return protocolInfo; + } + + /// + /// 获取协议池服务 + /// + /// + /// + /// + public async Task GetProtocolServiceAsync(string deviceCode, bool isSpecial = false) + { + try + { + ProtocolInfo protocolInfo= await FirstOrDefaultByDeviceAsync(deviceCode, isSpecial); + if(protocolInfo==null) + return null; + return _serviceProvider.GetKeyedService(protocolInfo.Name); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取协议失败"); + return null; + } + } + + private static bool ContainsExactPartRegex(string searchPattern, string fullString) + { + // 构建正则表达式 - 匹配以逗号或开头为边界,以逗号或结尾为边界的部分 + var pattern = $"(^|,)\\s*{Regex.Escape(searchPattern)}\\s*(,|$)"; + return Regex.IsMatch(fullString, pattern, RegexOptions.IgnoreCase); + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs deleted file mode 100644 index a28cd2d..0000000 --- a/protocols/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs +++ /dev/null @@ -1,188 +0,0 @@ -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; -using JiShe.CollectBus.Protocol.Contracts.Models; -using Newtonsoft.Json.Linq; - -namespace JiShe.CollectBus.Protocol -{ - public class StandardProtocolPlugin : BaseProtocolPlugin - { - /// - /// Initializes a new instance of the class. - /// - /// The service provider. - public StandardProtocolPlugin(IServiceProvider serviceProvider) : base(serviceProvider) - { - } - - public sealed override ProtocolInfo Info => new(nameof(StandardProtocolPlugin), "376.1", "TCP", "376.1协议", "DTS1980"); - - 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.确认或否认: - AnalyzeAnswerDataAsync(messageReceived, sendAction); - break; - case AFN.设置参数: break; - case AFN.查询参数: break; - case AFN.请求实时数据: - if (Enum.IsDefined(typeof(ATypeOfDataItems), fn)) - { - analyze = (T?)AnalyzeReadingDataAsync(messageReceived, sendAction); - } - break; - case AFN.请求历史数据: - if (Enum.IsDefined(typeof(IIdataTypeItems), fn)) - { - analyze = (T?)AnalyzeReadingTdcDataAsync(messageReceived, sendAction); - } - break; - case AFN.数据转发: - AnalyzeTransparentForwardingAnswerAsync(messageReceived, sendAction); - break; - } - - return await Task.FromResult(analyze); - } - - #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 - } -} diff --git a/services/JiShe.CollectBus.Application.Contracts/Samples/SampleDto.cs b/services/JiShe.CollectBus.Application.Contracts/Samples/SampleDto.cs index 6211273..12f46d1 100644 --- a/services/JiShe.CollectBus.Application.Contracts/Samples/SampleDto.cs +++ b/services/JiShe.CollectBus.Application.Contracts/Samples/SampleDto.cs @@ -4,14 +4,12 @@ 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/services/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs b/services/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs index 0f04005..5650a57 100644 --- a/services/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs +++ b/services/JiShe.CollectBus.Application.Contracts/ScheduledMeterReading/IScheduledMeterReadingService.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.GatherItem; +using JiShe.CollectBus.IotSystems.Ammeters; using JiShe.CollectBus.IotSystems.Watermeter; using Volo.Abp.Application.Services; @@ -58,8 +60,27 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// Task AmmeterScheduledMeterFifteenMinuteReading(); - #endregion + /// + /// 获取电表阀控配置 + /// + /// 阀控的时间 + /// + Task> GetAmmeterAutoValveControlSetting(string currentTime); + /// + /// 电表自动阀控 + /// + /// + Task AmmeterScheduledAutoValveControl(); + + /// + /// 电表自动校时 + /// + /// 采集频率 + /// + Task AmmeterScheduledAutomaticVerificationTime(int timeDensity, AmmeterInfo ammeterInfo, int groupIndex, DateTime timestamps); + + #endregion #region 水表采集处理 /// @@ -81,7 +102,30 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// /// Task WatermeterScheduledMeterAutoReading(); - + + #endregion + + #region 集中器处理 + + /// + /// 自动获取终端版 + /// + /// 采集频率 + /// 电表信息 + /// 集中器所在分组 + /// 采集频率对应的时间戳 + /// + Task ConcentratorScheduledAutomaticGetTerminalVersion(int timeDensity, AmmeterInfo ammeterInfo, int groupIndex, DateTime timestamps); + + /// + /// 自动获取远程通信模块(SIM)版本信息 + /// + /// 采集频率 + /// 电表信息 + /// 集中器所在分组 + /// 采集频率对应的时间戳 + /// + Task ConcentratorScheduledAutomaticGetTelematicsModule(int timeDensity, AmmeterInfo ammeterInfo, int groupIndex, DateTime timestamps); #endregion diff --git a/services/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAnalysisAppService.cs b/services/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAnalysisAppService.cs new file mode 100644 index 0000000..ec12ab6 --- /dev/null +++ b/services/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAnalysisAppService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Subscribers +{ + public interface ISubscriberAnalysisAppService + { + + } +} diff --git a/services/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs b/services/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs index 658ff29..63594c8 100644 --- a/services/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs +++ b/services/JiShe.CollectBus.Application.Contracts/Subscribers/ISubscriberAppService.cs @@ -1,17 +1,19 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IotSystems.MessageReceiveds; -using JiShe.CollectBus.Kafka; +using JiShe.CollectBus.Kafka.Internal; +using JiShe.CollectBus.Protocol3761; 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(List issuedEventMessage); + Task HeartbeatIssuedEvent(List issuedEventMessage); + Task ReceivedEvent(MessageProtocolAnalysis receivedMessage); + Task ReceivedHeartbeatEvent(List receivedHeartbeatMessage); + Task ReceivedLoginEvent(List receivedLoginMessage); } } diff --git a/services/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs b/services/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs index 9a37167..950f4d9 100644 --- a/services/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs +++ b/services/JiShe.CollectBus.Application.Contracts/Subscribers/IWorkerSubscriberAppService.cs @@ -1,7 +1,7 @@ using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; -using JiShe.CollectBus.Kafka; +using JiShe.CollectBus.Kafka.Internal; using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Services; @@ -20,19 +20,25 @@ namespace JiShe.CollectBus.Subscribers /// 1分钟采集电表数据下行消息消费订阅 /// /// - Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); + Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(MeterReadingTelemetryPacketInfo issuedEventMessage); /// /// 5分钟采集电表数据下行消息消费订阅 /// /// - Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); + Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(MeterReadingTelemetryPacketInfo issuedEventMessage); /// /// 15分钟采集电表数据下行消息消费订阅 /// /// - Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); + Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(MeterReadingTelemetryPacketInfo issuedEventMessage); + + /// + /// 电表自动阀控下行消息消费订阅 + /// + /// + Task AmmeterScheduledAutoValveControl(MeterReadingTelemetryPacketInfo issuedEventMessage); #endregion #region 水表消息采集 @@ -40,7 +46,7 @@ namespace JiShe.CollectBus.Subscribers /// 1分钟采集水表数据下行消息消费订阅 /// /// - Task WatermeterSubscriberWorkerAutoReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage issuedEventMessage); + Task WatermeterSubscriberWorkerAutoReadingIssuedEvent(MeterReadingTelemetryPacketInfo issuedEventMessage); #endregion } diff --git a/services/JiShe.CollectBus.Application/CollectBusAppService.cs b/services/JiShe.CollectBus.Application/CollectBusAppService.cs index e155c65..7c66b95 100644 --- a/services/JiShe.CollectBus.Application/CollectBusAppService.cs +++ b/services/JiShe.CollectBus.Application/CollectBusAppService.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading.Tasks; using JiShe.CollectBus.FreeRedis; using Volo.Abp.Application.Services; +using Volo.Abp.Timing; namespace JiShe.CollectBus; @@ -20,7 +21,6 @@ public abstract class CollectBusAppService : ApplicationService public IFreeSqlProvider SqlProvider => LazyServiceProvider.LazyGetRequiredService(); protected IFreeRedisProvider FreeRedisProvider => LazyServiceProvider.LazyGetService()!; - protected CollectBusAppService() { LocalizationResource = typeof(CollectBusResource); diff --git a/services/JiShe.CollectBus.Application/CollectBusApplicationModule.cs b/services/JiShe.CollectBus.Application/CollectBusApplicationModule.cs index e5077cc..ce2e5aa 100644 --- a/services/JiShe.CollectBus.Application/CollectBusApplicationModule.cs +++ b/services/JiShe.CollectBus.Application/CollectBusApplicationModule.cs @@ -1,28 +1,25 @@ -using JiShe.CollectBus.Common.Consts; -using JiShe.CollectBus.Common.Extensions; -using JiShe.CollectBus.FreeSql; -using JiShe.CollectBus.Kafka; -using JiShe.CollectBus.Kafka.AdminClient; -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.Linq; using System.Reflection; using System.Threading.Tasks; +using Cassandra.Mapping; using JiShe.CollectBus.Cassandra; using JiShe.CollectBus.FreeRedis; +using JiShe.CollectBus.FreeSql; +using JiShe.CollectBus.Interceptors; using JiShe.CollectBus.IoTDB; +using JiShe.CollectBus.Kafka; +using JiShe.CollectBus.Mappers; +using JiShe.CollectBus.Protocol; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.ScheduledMeterReading; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp; using Volo.Abp.Application; using Volo.Abp.Autofac; using Volo.Abp.AutoMapper; using Volo.Abp.BackgroundWorkers; using Volo.Abp.BackgroundWorkers.Hangfire; -using Volo.Abp.EventBus; using Volo.Abp.Modularity; -using Microsoft.Extensions.Options; namespace JiShe.CollectBus; @@ -36,9 +33,10 @@ namespace JiShe.CollectBus; typeof(CollectBusFreeRedisModule), typeof(CollectBusFreeSqlModule), typeof(CollectBusKafkaModule), - typeof(CollectBusIoTDBModule), - typeof(CollectBusCassandraModule) - )] + typeof(CollectBusIoTDbModule), + typeof(CollectBusCassandraModule), + typeof(CollectBusProtocolModule) +)] public class CollectBusApplicationModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) @@ -46,39 +44,34 @@ public class CollectBusApplicationModule : AbpModule var configuration = context.Services.GetConfiguration(); context.Services.AddAutoMapperObjectMapper(); - Configure(options => + Configure(options => { options.AddMaps(true); }); + + context.Services.AddSingleton(new MappingConfiguration() + .Define(new CollectBusMapping())); + + // 注册拦截器 + context.Services.OnRegistered(ctx => { - options.AddMaps(validate: true); - }); + var methods = ctx.ImplementationType.GetMethods(); + var any = methods.Any(a => a.GetCustomAttribute() != null); + if (any) ctx.Interceptors.TryAdd(); + }); } 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) + var types = assembly.GetTypes().Where(t => typeof(ICollectWorker).IsAssignableFrom(t) && !t.IsInterface) + .ToList(); + foreach (var type in types) await context.AddBackgroundWorkerAsync(type); + + Task.Run(() => { - await context.AddBackgroundWorkerAsync(type); - } - - //默认初始化表计信息 - var dbContext = context.ServiceProvider.GetRequiredService(); - //await dbContext.InitAmmeterCacheData(); - //await dbContext.InitWatermeterCacheData(); - - //初始化主题信息 - 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, kafkaOptions.Value.NumPartitions, kafkaOptions.Value.KafkaReplicationFactor); - } + //默认初始化表计信息 + var dbContext = context.ServiceProvider.GetRequiredService(); + dbContext.InitAmmeterCacheData(); + //await dbContext.InitWatermeterCacheData(); + }).ConfigureAwait(false); } - -} +} \ No newline at end of file diff --git a/services/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs deleted file mode 100644 index b1041f2..0000000 --- a/services/JiShe.CollectBus.Application/Consumers/IssuedConsumer.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Threading.Tasks; -using JiShe.CollectBus.Common.Enums; -using JiShe.CollectBus.IotSystems.MessageIssueds; -using JiShe.CollectBus.IotSystems.MessageReceiveds; -using MassTransit; -using Microsoft.Extensions.Logging; -using TouchSocket.Sockets; -using Volo.Abp.Domain.Repositories; - -namespace JiShe.CollectBus.Consumers -{ - public class IssuedConsumer: IConsumer - { - private readonly ILogger _logger; - private readonly ITcpService _tcpService; - private readonly IRepository _messageReceivedLoginEventRepository; - private readonly IRepository _messageReceivedHeartbeatEventRepository; - - /// - /// IssuedConsumer - /// - /// - /// - /// - /// - public IssuedConsumer(ILogger logger, - ITcpService tcpService, - IRepository messageReceivedLoginEventRepository, - IRepository messageReceivedHeartbeatEventRepository) - { - _logger = logger; - _tcpService = tcpService; - _messageReceivedLoginEventRepository = messageReceivedLoginEventRepository; - _messageReceivedHeartbeatEventRepository = messageReceivedHeartbeatEventRepository; - } - - - public async Task Consume(ConsumeContext context) - { - switch (context.Message.Type) - { - case IssuedEventType.Heartbeat: - _logger.LogInformation($"IssuedEvent:{context.Message.MessageId}"); - var heartbeatEntity = await _messageReceivedHeartbeatEventRepository.GetAsync(a => a.MessageId == context.Message.MessageId); - heartbeatEntity.AckTime = DateTime.Now; - heartbeatEntity.IsAck = true; - await _messageReceivedHeartbeatEventRepository.UpdateAsync(heartbeatEntity); - break; - case IssuedEventType.Login: - var loginEntity = await _messageReceivedLoginEventRepository.GetAsync(a => a.MessageId == context.Message.MessageId); - loginEntity.AckTime = DateTime.Now; - loginEntity.IsAck = true; - await _messageReceivedLoginEventRepository.UpdateAsync(loginEntity); - break; - case IssuedEventType.Data: - break; - default: - throw new ArgumentOutOfRangeException(); - } - await _tcpService.SendAsync(context.Message.ClientId, context.Message.Message); - } - } -} diff --git a/services/JiShe.CollectBus.Application/Consumers/IssuedFaultConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/IssuedFaultConsumer.cs deleted file mode 100644 index 9bc9983..0000000 --- a/services/JiShe.CollectBus.Application/Consumers/IssuedFaultConsumer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Threading.Tasks; -using JiShe.CollectBus.IotSystems.MessageIssueds; -using MassTransit; - -namespace JiShe.CollectBus.Consumers -{ - public class IssuedFaultConsumer : IConsumer> - { - public Task Consume(ConsumeContext> context) - { - throw new NotImplementedException(); - } - } -} diff --git a/services/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs deleted file mode 100644 index 4f7b5eb..0000000 --- a/services/JiShe.CollectBus.Application/Consumers/ReceivedConsumer.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -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; -using Volo.Abp.Domain.Repositories; - -namespace JiShe.CollectBus.Consumers -{ - /// - /// Batch 一次最多 100 个,最多 10 个并发批次 - /// - public class ReceivedConsumer: IConsumer> - { - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; - private readonly IRepository _messageReceivedEventRepository; - - /// - /// MessageReceivedConsumer - /// - /// - /// - /// - public ReceivedConsumer(ILogger logger, - IServiceProvider serviceProvider, - IRepository messageReceivedEventRepository) - { - _logger = logger; - _serviceProvider = serviceProvider; - _messageReceivedEventRepository = messageReceivedEventRepository; - } - - - public async Task Consume(ConsumeContext> context) - { - const string protocolType = "Standard"; - var protocolPlugin = _serviceProvider.GetKeyedService(protocolType); - if (protocolPlugin == null) - { - _logger.LogError("协议不存在!"); - } - else - { - var list = new List(); - foreach (var contextItem in context.Message) - { - await protocolPlugin.AnalyzeAsync(contextItem.Message); - list.Add(contextItem.Message); - } - await _messageReceivedEventRepository.InsertManyAsync(list); - } - } - } -} diff --git a/services/JiShe.CollectBus.Application/Consumers/ReceivedFaultConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/ReceivedFaultConsumer.cs deleted file mode 100644 index 60bbdfc..0000000 --- a/services/JiShe.CollectBus.Application/Consumers/ReceivedFaultConsumer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Threading.Tasks; -using JiShe.CollectBus.IotSystems.MessageReceiveds; -using MassTransit; - -namespace JiShe.CollectBus.Consumers -{ - public class ReceivedFaultConsumer: IConsumer>> - { - public Task Consume(ConsumeContext>> context) - { - throw new NotImplementedException(); - } - } -} diff --git a/services/JiShe.CollectBus.Application/Consumers/ReceivedHeartbeatConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/ReceivedHeartbeatConsumer.cs deleted file mode 100644 index c2317c6..0000000 --- a/services/JiShe.CollectBus.Application/Consumers/ReceivedHeartbeatConsumer.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.IotSystems.MessageReceiveds; -using JiShe.CollectBus.Protocol.Contracts.Interfaces; -using MassTransit; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace JiShe.CollectBus.Consumers -{ - public class ReceivedHeartbeatConsumer:IConsumer - { - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; - - /// - /// - /// - /// - /// - public ReceivedHeartbeatConsumer(IServiceProvider serviceProvider, ILogger logger) - { - this._serviceProvider = serviceProvider; - this._logger = logger; - } - - public async Task Consume(ConsumeContext context) - { - _logger.LogInformation("心跳消费队列开始处理"); - var protocolPlugin = _serviceProvider.GetKeyedService("Standard"); - if (protocolPlugin == null) - { - _logger.LogError("【心跳消费队列开始处理】协议不存在!"); - } - else - { - await protocolPlugin.HeartbeatAsync(context.Message); - _logger.LogInformation("心跳消费队列完成处理"); - } - } - } -} diff --git a/services/JiShe.CollectBus.Application/Consumers/ReceivedLoginConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/ReceivedLoginConsumer.cs deleted file mode 100644 index ce67886..0000000 --- a/services/JiShe.CollectBus.Application/Consumers/ReceivedLoginConsumer.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.IotSystems.MessageReceiveds; -using JiShe.CollectBus.Protocol.Contracts.Interfaces; -using MassTransit; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace JiShe.CollectBus.Consumers -{ - public class ReceivedLoginConsumer : IConsumer - { - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; - - /// - /// - /// - /// - /// - public ReceivedLoginConsumer(ILogger logger, IServiceProvider serviceProvider) - { - _logger = logger; - _serviceProvider = serviceProvider; - } - - public async Task Consume(ConsumeContext context) - { - _logger.LogInformation("登录消费队列开始处理"); - var protocolPlugin = _serviceProvider.GetKeyedService("Standard"); - if (protocolPlugin == null) - { - _logger.LogError("【登录消费队列开始处理】协议不存在!"); - } - else - { - await protocolPlugin.LoginAsync(context.Message); - _logger.LogInformation("登录消费队列完成处理"); - } - await Task.CompletedTask; - } - } -} diff --git a/services/JiShe.CollectBus.Application/Consumers/ScheduledMeterReadingConsumer.cs b/services/JiShe.CollectBus.Application/Consumers/ScheduledMeterReadingConsumer.cs deleted file mode 100644 index cdc731a..0000000 --- a/services/JiShe.CollectBus.Application/Consumers/ScheduledMeterReadingConsumer.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -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; -using Volo.Abp.Domain.Repositories; - -namespace JiShe.CollectBus.Consumers -{ - /// - /// 定时抄读任务消费者 - /// - public class ScheduledMeterReadingConsumer : IConsumer - { - private readonly ILogger _logger; - private readonly ITcpService _tcpService; - - /// - /// WorkerConsumer - /// - /// - /// - public ScheduledMeterReadingConsumer(ILogger logger, - ITcpService tcpService) - { - _logger = logger; - _tcpService = tcpService; - } - - - public async Task Consume(ConsumeContext context) - { - _logger.LogError($"{nameof(ScheduledMeterReadingConsumer)} 集中器的消息消费{context.Message.FocusAddress}"); - await _tcpService.SendAsync(context.Message.FocusAddress, context.Message.MessageHexString); - } - } -} diff --git a/services/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs b/services/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs index d4ace87..6e2034a 100644 --- a/services/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs +++ b/services/JiShe.CollectBus.Application/EnergySystem/CacheAppService.cs @@ -18,7 +18,7 @@ namespace JiShe.CollectBus.EnergySystem { public class CacheAppService : CollectBusAppService, ICacheAppService { - public async Task SetHashByKey(string key) + public async Task SetHashByKey() { var data = await SqlProvider.Instance.Change(DbEnum.EnergyDB).Select().ToListAsync(); diff --git a/services/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs b/services/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs index 9fc8dd4..19a1296 100644 --- a/services/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs +++ b/services/JiShe.CollectBus.Application/EnergySystem/EnergySystemAppService.cs @@ -5,7 +5,6 @@ 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.Consts; using JiShe.CollectBus.Common.Enums; @@ -18,7 +17,6 @@ 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; using Newtonsoft.Json; using Volo.Abp.Domain.Repositories; @@ -32,16 +30,14 @@ 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, ICapPublisher capBus) + IRepository conrOnlineRecordRepository, IProducerService producerService) { _focusRecordRepository = focusRecordRepository; _csqRecordRepository = csqRecordRepository; _conrOnlineRecordRepository = conrOnlineRecordRepository; _producerService = producerService; - _capBus = capBus; } /// @@ -81,7 +77,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage @@ -90,7 +86,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); result.Status = true; result.Msg = "操作成功"; @@ -127,7 +123,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage @@ -136,7 +132,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); } @@ -178,7 +174,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { @@ -186,7 +182,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); result.Status = true; result.Msg = "操作成功"; @@ -216,7 +212,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { @@ -224,7 +220,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); } @@ -307,7 +303,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage @@ -316,7 +312,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); if (isManual) @@ -375,7 +371,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage @@ -384,7 +380,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); result.Status = true; @@ -412,7 +408,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { @@ -420,7 +416,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); result.Status = true; @@ -448,7 +444,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { @@ -456,7 +452,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); result.Status = true; @@ -483,7 +479,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { @@ -491,7 +487,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); result.Status = true; @@ -519,7 +515,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { @@ -527,7 +523,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); result.Status = true; result.Msg = "操作成功"; @@ -576,7 +572,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage @@ -585,7 +581,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); } @@ -654,7 +650,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { @@ -662,7 +658,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); } result.Status = true; @@ -691,7 +687,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { @@ -699,7 +695,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); } result.Status = true; @@ -727,7 +723,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { @@ -735,7 +731,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); } result.Status = true; @@ -761,7 +757,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { @@ -769,7 +765,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); result.Status = true; result.Msg = "操作成功"; @@ -796,7 +792,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { @@ -804,7 +800,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); } result.Status = true; @@ -859,7 +855,7 @@ namespace JiShe.CollectBus.EnergySystem // DeviceNo = address, // Message = bytes, // Type = IssuedEventType.Data, - // MessageId = NewId.NextGuid().ToString() + // MessageId = Guid.NewGuid().ToString() //}); await _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerManualValveReadingIssuedEventName, new IssuedEventMessage { @@ -867,7 +863,7 @@ namespace JiShe.CollectBus.EnergySystem DeviceNo = address, Message = bytes, Type = IssuedEventType.Data, - MessageId = NewId.NextGuid().ToString() + MessageId = Guid.NewGuid().ToString() }); } diff --git a/services/JiShe.CollectBus.Application/Interceptors/LogInterceptAttribute.cs b/services/JiShe.CollectBus.Application/Interceptors/LogInterceptAttribute.cs new file mode 100644 index 0000000..86ddb49 --- /dev/null +++ b/services/JiShe.CollectBus.Application/Interceptors/LogInterceptAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace JiShe.CollectBus.Interceptors +{ + [AttributeUsage(AttributeTargets.Method)] + public class LogInterceptAttribute : Attribute + { + + } +} diff --git a/services/JiShe.CollectBus.Application/Interceptors/LogInterceptor.cs b/services/JiShe.CollectBus.Application/Interceptors/LogInterceptor.cs new file mode 100644 index 0000000..a796aaf --- /dev/null +++ b/services/JiShe.CollectBus.Application/Interceptors/LogInterceptor.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; + +namespace JiShe.CollectBus.Interceptors +{ + public class LogInterceptor : AbpInterceptor, ITransientDependency + { + public override async Task InterceptAsync(IAbpMethodInvocation invocation) + { + // 方法执行前的逻辑(如果需要) + + try + { + // 执行原始方法 + await invocation.ProceedAsync(); + + // 方法执行成功后,返回前的逻辑 + await OnSuccessAsync(invocation); + } + catch (Exception ex) + { + // 出现异常时的逻辑 + await OnExceptionAsync(invocation, ex); + throw; + } + finally + { + // 方法结束前一定会执行的逻辑 + await OnCompleteAsync(invocation); + } + } + + private Task OnSuccessAsync(IAbpMethodInvocation invocation) + { + // 方法执行成功后的逻辑 + // 可以访问 invocation.ReturnValue 获取返回值 + Console.WriteLine($"方法 {invocation.Method.Name} 成功执行,返回值:{invocation.ReturnValue}"); + return Task.CompletedTask; + } + + private Task OnExceptionAsync(IAbpMethodInvocation invocation, Exception ex) + { + // 方法执行异常时的逻辑 + Console.WriteLine($"方法 {invocation.Method.Name} 执行异常:{ex.Message}"); + return Task.CompletedTask; + } + + private Task OnCompleteAsync(IAbpMethodInvocation invocation) + { + // 无论成功失败,方法执行完毕后的逻辑 + Console.WriteLine($"方法 {invocation.Method.Name} 执行完毕"); + return Task.CompletedTask; + } + } +} diff --git a/services/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj b/services/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj index b473dea..3ab9831 100644 --- a/services/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj +++ b/services/JiShe.CollectBus.Application/JiShe.CollectBus.Application.csproj @@ -15,9 +15,6 @@ - - - @@ -25,7 +22,7 @@ - + diff --git a/modules/JiShe.CollectBus.Cassandra/Mappers/CollectBusMapping.cs b/services/JiShe.CollectBus.Application/Mappers/CollectBusMapping.cs similarity index 61% rename from modules/JiShe.CollectBus.Cassandra/Mappers/CollectBusMapping.cs rename to services/JiShe.CollectBus.Application/Mappers/CollectBusMapping.cs index d07f8ea..a4d4ef4 100644 --- a/modules/JiShe.CollectBus.Cassandra/Mappers/CollectBusMapping.cs +++ b/services/JiShe.CollectBus.Application/Mappers/CollectBusMapping.cs @@ -1,13 +1,8 @@ using Cassandra.Mapping; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; -using static Cassandra.QueryTrace; -namespace JiShe.CollectBus.Cassandra.Mappers +namespace JiShe.CollectBus.Mappers { public class CollectBusMapping: Mappings { @@ -15,6 +10,8 @@ namespace JiShe.CollectBus.Cassandra.Mappers { For() .Column(e => e.Type, cm => cm.WithName("type").WithDbType()); + For() + .Column(e => e.Status, cm => cm.WithName("status").WithDbType()); } } } diff --git a/services/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/services/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index c2bd026..6eadb0a 100644 --- a/services/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/services/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -1,39 +1,27 @@ -using System; -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.Consts; -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.Ammeters; using JiShe.CollectBus.IotSystems.Devices; -using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.Kafka.Producer; -using JiShe.CollectBus.Protocol.Contracts; -using MassTransit; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; using TouchSocket.Core; using TouchSocket.Sockets; 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 { 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; + private readonly IServiceProvider _serviceProvider; + private readonly IProtocolService _protocolService; /// /// @@ -42,57 +30,37 @@ namespace JiShe.CollectBus.Plugins /// /// /// - public TcpMonitor(ICapPublisher producerBus, IProducerService producerService, + /// + public TcpMonitor(IProducerService producerService, ILogger logger, IRepository deviceRepository, - IDistributedCache ammeterInfoCache) + IDistributedCache ammeterInfoCache, IServiceProvider serviceProvider, IProtocolService protocolService) { - _producerBus = producerBus; _producerService = producerService; _logger = logger; _deviceRepository = deviceRepository; _ammeterInfoCache = ammeterInfoCache; + _serviceProvider= serviceProvider; + _protocolService = protocolService; + + } public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) { var messageHexString = Convert.ToHexString(e.ByteBlock.Span); - var hexStringList = messageHexString.StringToPairs(); - 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 && aTuple != null && !string.IsNullOrWhiteSpace(aTuple.Item1)) + var protocolPlugin = await _protocolService.GetProtocolServiceAsync("376.1"); + if (protocolPlugin == null) { - var tcpSessionClient = (ITcpSessionClient)client; - - if ((AFN)aFn == AFN.链路接口检测) - { - switch (fn) - { - case 1: - await OnTcpLoginReceived(tcpSessionClient, messageHexString, aTuple.Item1); - break; - case 3: - //心跳帧有两种情况: - //1. 集中器先有登录帧,再有心跳帧 - //2. 集中器没有登录帧,只有心跳帧 - await OnTcpHeartbeatReceived(tcpSessionClient, messageHexString, aTuple.Item1); - break; - default: - _logger.LogError($"指令初步解析失败,指令内容:{messageHexString}"); - break; - } - } - else - { - await OnTcpNormalReceived(tcpSessionClient, messageHexString, aTuple.Item1,aFn.ToString()!.PadLeft(2,'0')); - } + _logger.LogError("协议不存在!"); } - else + var tcpSessionClient = (ITcpSessionClient)client; + TB3761? tB3761 = await protocolPlugin!.AnalyzeAsync(tcpSessionClient, messageHexString); + if (tB3761 == null) { + // TODO: 暂时不处理,后续再处理 _logger.LogError($"指令初步解析失败,指令内容:{messageHexString}"); } - await e.InvokeNext(); } @@ -134,145 +102,5 @@ namespace JiShe.CollectBus.Plugins await e.InvokeNext(); } - /// - /// 登录帧处理 - /// - /// - /// - /// 集中器编号 - /// - private async Task OnTcpLoginReceived(ITcpSessionClient client, string messageHexString, string deviceNo) - { - string oldClientId = $"{client.Id}"; - - await client.ResetIdAsync(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)); - } - else - { - entity.UpdateByLoginAndHeartbeat(oldClientId); - await _deviceRepository.UpdateAsync(entity); - } - - var messageReceivedLoginEvent = new MessageReceivedLogin - { - ClientId = deviceNo, - ClientIp = client.IP, - ClientPort = client.Port, - MessageHexString = messageHexString, - DeviceNo = deviceNo, - MessageId = NewId.NextGuid().ToString() - }; - - //await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent); - - - await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent); - - //await _producerBus.Publish( messageReceivedLoginEvent); - } - - private async Task OnTcpHeartbeatReceived(ITcpSessionClient client, string messageHexString, string deviceNo) - { - string clientId = deviceNo; - string oldClientId = $"{client.Id}"; - - 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); - 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 = clientId, - ClientIp = client.IP, - ClientPort = client.Port, - MessageHexString = messageHexString, - DeviceNo = deviceNo, - MessageId = NewId.NextGuid().ToString() - }; - //await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent); - - await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent); - //await _producerBus.Publish(messageReceivedHeartbeatEvent); - } - - /// - /// 正常帧处理,将不同的AFN进行分发 - /// - /// - /// - /// - /// - /// - private async Task OnTcpNormalReceived(ITcpSessionClient client, string messageHexString, string deviceNo,string aFn) - { - //await _producerBus.Publish(new MessageReceived - //{ - // ClientId = client.Id, - // ClientIp = client.IP, - // ClientPort = client.Port, - // MessageHexString = messageHexString, - // DeviceNo = deviceNo, - // 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, - ClientIp = client.IP, - ClientPort = client.Port, - MessageHexString = messageHexString, - DeviceNo = deviceNo, - MessageId = NewId.NextGuid().ToString() - }); - } } } diff --git a/services/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs b/services/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs index 1a056a7..625c1e6 100644 --- a/services/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs +++ b/services/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs @@ -42,6 +42,8 @@ namespace JiShe.CollectBus.RedisDataCache Instance = _freeRedisProvider.Instance; } + //todo 单个数据查询 + /// /// 单个添加数据 /// diff --git a/services/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/services/JiShe.CollectBus.Application/Samples/SampleAppService.cs index f0aa541..31a3494 100644 --- a/services/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/services/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -1,44 +1,41 @@ -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.IotSystems.PrepayModel; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.Logging; -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; -using JiShe.CollectBus.Kafka.Attributes; -using System.Text.Json; -using JiShe.CollectBus.Kafka; +using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Application.Contracts; -using JiShe.CollectBus.Common.Models; -using System.Diagnostics; +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.FreeSql; using JiShe.CollectBus.IoTDB.Context; using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Model; using JiShe.CollectBus.IoTDB.Options; +using JiShe.CollectBus.IotSystems.Ammeters; +using JiShe.CollectBus.IotSystems.PrepayModel; +using JiShe.CollectBus.Kafka.Attributes; +using JiShe.CollectBus.Kafka.Internal; +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; namespace JiShe.CollectBus.Samples; public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaSubscribe { private readonly ILogger _logger; - private readonly IIoTDBProvider _iotDBProvider; + private readonly IIoTDbProvider _iotDBProvider; private readonly IoTDBRuntimeContext _dbContext; - private readonly IoTDBOptions _options; + private readonly IoTDbOptions _options; private readonly IRedisDataCacheService _redisDataCacheService; - public SampleAppService(IIoTDBProvider iotDBProvider, IOptions options, + public SampleAppService(IIoTDbProvider iotDBProvider, IOptions options, IoTDBRuntimeContext dbContext, ILogger logger, IRedisDataCacheService redisDataCacheService) { _iotDBProvider = iotDBProvider; @@ -51,33 +48,23 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS /// /// 测试 UseSessionPool /// - /// + /// /// [HttpGet] - public async Task UseSessionPool(long timestamps) + public async Task UseSessionPool(long testTime) { - string? messageHexString = null; - if (timestamps == 0) - { - timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - _logger.LogError($"timestamps_{timestamps}"); - } - else - { - messageHexString = messageHexString + timestamps; - } - ElectricityMeter meter = new ElectricityMeter() + ElectricityMeterTreeModel meter = new ElectricityMeterTreeModel() { SystemName = "energy", - DeviceId = "402440506", + DeviceId = "402440506s", DeviceType = "Ammeter", Current = 10, MeterModel = "DDZY-1980", - ProjectCode = "10059", + ProjectId = "10059", Voltage = 10, - IssuedMessageHexString = messageHexString, - Timestamps = timestamps, + IssuedMessageHexString = "messageHexString", + Timestamps = testTime// DateTimeOffset.UtcNow.ToUnixTimeNanoseconds()//testTime.GetDateTimeOffset().ToUnixTimeNanoseconds(), }; await _iotDBProvider.InsertAsync(meter); } @@ -87,18 +74,19 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS /// /// [HttpGet] - public async Task UseTableSessionPool() + public async Task UseTableSessionPool(DateTime time) { - ElectricityMeter meter2 = new ElectricityMeter() + var testTime = time; + ElectricityMeterTreeModel meter2 = new ElectricityMeterTreeModel() { SystemName = "energy", DeviceId = "402440506", DeviceType = "Ammeter", Current = 10, MeterModel = "DDZY-1980", - ProjectCode = "10059", + ProjectId = "10059", Voltage = 10, - Timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + Timestamps = DateTimeOffset.Now.ToUnixTimeNanoseconds(), }; await _iotDBProvider.InsertAsync(meter2); @@ -112,11 +100,142 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS DeviceType = "Ammeter", Current = 10, MeterModel = "DDZY-1980", - ProjectCode = "10059", + ProjectId = "10059", Voltage = 10, - Timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + Timestamps = DateTimeOffset.Now.ToUnixTimeNanoseconds(), }; await _iotDBProvider.InsertAsync(meter); + + } + + /// + /// 测试Session切换3 + /// + /// + [HttpGet] + public async Task UseTableSessionPool3(DateTime time) + { + var testTime = time; + ElectricityMeterTreeModel meter2 = new ElectricityMeterTreeModel() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + Current = 10, + MeterModel = "DDZY-1980", + ProjectId = "10059", + Voltage = 10, + IssuedMessageHexString = "dsdfsfd", + Timestamps = DateTimeOffset.UtcNow.ToUnixTimeNanoseconds(), + + }; + + await _iotDBProvider.InsertAsync(meter2); + + _dbContext.UseTableSessionPool = true; + + + ElectricityMeter meter3 = new ElectricityMeter() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + Current = 10, + MeterModel = "DDZY-1980", + ProjectId = "10059", + Voltage = 10, + Currentd = 22, + IssuedMessageHexString = "dsdfsfd", + Timestamps = DateTimeOffset.Now.ToUnixTimeNanoseconds(), + }; + + //var dd = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + //var dd3 = DateTimeOffset.Now.ToUnixTimeMicroseconds(); + //var dd2 = DateTimeOffset.Now.ToUnixTimeNanoseconds(); + + await _iotDBProvider.InsertAsync(meter3); + } + + /// + /// 测试树模型单个测点数据项 + /// + /// + /// + [HttpGet] + public async Task TestTreeModelSingleMeasuringEntity(string measuring, string value, DateTime time) + { + var meter = new TreeModelSingleMeasuringEntity() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "1", + ProjectId = "10059", + Timestamps = time.GetDateTimeOffset().ToUnixTimeMilliseconds(), + SingleMeasuring = new Tuple(measuring, value) + }; + await _iotDBProvider.InsertAsync(meter); + } + + /// + /// 测试树模型单个测点数据项2 + /// + /// + /// + [HttpGet] + public async Task TestTreeModelSingleMeasuringEntity2(string measuring, int value, DateTime time) + { + var meter = new TreeModelSingleMeasuringEntity() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + ProjectId = "10059", + Timestamps = time.GetDateTimeOffset().ToUnixTimeMilliseconds(), + SingleMeasuring = new Tuple(measuring, value) + }; + await _iotDBProvider.InsertAsync(meter); + } + + /// + /// 测试表模型单个测点数据项 + /// + /// + /// + [HttpGet] + public async Task TestTableModelSingleMeasuringEntity(string measuring, string value, DateTime time) + { + var meter = new TableModelSingleMeasuringEntity() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + ProjectId = "10059", + Timestamps = time.GetDateTimeOffset().ToUnixTimeMilliseconds(), + SingleColumn = new Tuple(measuring, value) + }; + _dbContext.UseTableSessionPool = true; + await _iotDBProvider.InsertAsync(meter); + } + + /// + /// 测试表模型单个测点数据项2 + /// + /// + /// + [HttpGet] + public async Task TestTableModelSingleMeasuringEntity2(string measuring, int value, DateTime time) + { + var meter = new TableModelSingleMeasuringEntity() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + ProjectId = "10059", + Timestamps = time.GetDateTimeOffset().ToUnixTimeMilliseconds(), + SingleColumn = new Tuple(measuring, value) + }; + _dbContext.UseTableSessionPool = true; + await _iotDBProvider.InsertAsync(meter); } /// @@ -169,27 +288,7 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS await Task.CompletedTask; } - - - /// - /// 测试单个测点数据项 - /// - /// - /// - [HttpGet] - public async Task TestSingleMeasuringAFNData(string measuring, string value) - { - var meter = new SingleMeasuringAFNDataEntity() - { - SystemName = "energy", - DeviceId = "402440506", - DeviceType = "Ammeter", - ProjectCode = "10059", - Timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), - SingleMeasuring = new Tuple(measuring, value) - }; - await _iotDBProvider.InsertAsync(meter); - } + /// /// 测试Redis批量读取10万条数据性能 @@ -243,6 +342,19 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS } + /// + /// 下一个采集时间点验证 + /// + /// + [HttpGet] + public async Task TestCalculateNextCollectionTime(string time, int timeDensity) + { + DateTime nextTaskTime = Convert.ToDateTime(time); + + return await Task.FromResult(nextTaskTime.CalculateNextCollectionTime(timeDensity)); + } + + public Task GetAsync() { return Task.FromResult( diff --git a/services/JiShe.CollectBus.Application/Samples/TestAppService.cs b/services/JiShe.CollectBus.Application/Samples/TestAppService.cs index 2de458f..1975d5a 100644 --- a/services/JiShe.CollectBus.Application/Samples/TestAppService.cs +++ b/services/JiShe.CollectBus.Application/Samples/TestAppService.cs @@ -1,30 +1,20 @@ -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.IotSystems.PrepayModel; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -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 Cassandra; using JiShe.CollectBus.Cassandra; using JiShe.CollectBus.Common.Enums; -using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.DynamicModule; +using JiShe.CollectBus.Interceptors; using JiShe.CollectBus.IotSystems.MessageIssueds; -using Volo.Abp.Application.Services; -using JiShe.CollectBus.IotSystems.MessageReceiveds; -using Volo.Abp.Domain.Repositories; +using JiShe.CollectBus.IotSystems.Protocols; +using JiShe.CollectBus.Protocol.Interfaces; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using Cassandra; +using System.Reflection; +using System.Threading.Tasks; +using Volo.Abp.Modularity; namespace JiShe.CollectBus.Samples; @@ -34,16 +24,22 @@ public class TestAppService : CollectBusAppService private readonly ILogger _logger; private readonly ICassandraRepository _messageReceivedCassandraRepository; private readonly ICassandraProvider _cassandraProvider; - + private readonly IProtocolService _protocolService; + private readonly IServiceProvider _serviceProvider; + private readonly IDynamicModuleManager _dynamicModuleManager; public TestAppService( ILogger logger, - ICassandraRepository messageReceivedCassandraRepository, ICassandraProvider cassandraProvider) + ICassandraRepository messageReceivedCassandraRepository, + ICassandraProvider cassandraProvider, IProtocolService protocolService,IServiceProvider serviceProvider, IDynamicModuleManager dynamicModuleManager) { _logger = logger; _messageReceivedCassandraRepository = messageReceivedCassandraRepository; _cassandraProvider = cassandraProvider; + _protocolService = protocolService; + _serviceProvider = serviceProvider; + _dynamicModuleManager = dynamicModuleManager; } public async Task AddMessageOfCassandra() { @@ -122,4 +118,26 @@ public class TestAppService : CollectBusAppService // 等待所有批处理完成 await Task.WhenAll(tasks); } + + [LogIntercept] + public virtual Task LogInterceptorTest(string str) + { + _logger.LogWarning(str); + return Task.FromResult(str) ; + } + + public virtual async Task GetProtocol(string deviceCode, bool isSpecial = false) + { + var protocol = await _protocolService.FirstOrDefaultByDeviceAsync(deviceCode, isSpecial); + return protocol; + } + + // 重新加载插件方法 + public async Task ReloadPluginsAsync() + { + var aa = Assembly.LoadFile( + @"D:\Codes\CollectBusV5\JiShe.CollectBus\web\JiShe.CollectBus.Host\bin\Debug\net8.0\Plugins\JiShe.CollectBus.Protocol.Test.dll"); + var module = aa.GetTypes().First(a=> typeof(IAbpModule).IsAssignableFrom(a)); + await _dynamicModuleManager.ReinitializeModuleAsync(module); + } } diff --git a/services/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/services/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 42f0f1c..a159326 100644 --- a/services/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/services/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -1,33 +1,32 @@ -using DotNetCore.CAP; -using JiShe.CollectBus.Ammeters; -using JiShe.CollectBus.Application.Contracts; -using JiShe.CollectBus.Common.BuildSendDatas; +using JiShe.CollectBus.Application.Contracts; +using JiShe.CollectBus.Common; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.DeviceBalanceControl; +using JiShe.CollectBus.Common.Encrypt; 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.MessageIssueds; +using JiShe.CollectBus.IoTDB.Context; +using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Model; +using JiShe.CollectBus.IoTDB.Options; +using JiShe.CollectBus.IoTDB.Provider; +using JiShe.CollectBus.IotSystems.Ammeters; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; -using JiShe.CollectBus.Kafka; +using JiShe.CollectBus.Kafka.Internal; using JiShe.CollectBus.Kafka.Producer; -using JiShe.CollectBus.Protocol.Contracts; -using JiShe.CollectBus.RedisDataCache; -using JiShe.CollectBus.Repository.MeterReadingRecord; -using Mapster; +using JiShe.CollectBus.Protocol.Interfaces; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading; using System.Threading.Tasks; -using JiShe.CollectBus.IoTDB.Interface; -using static FreeSql.Internal.GlobalFilter; +using JiShe.CollectBus.Protocol.Models; namespace JiShe.CollectBus.ScheduledMeterReading { @@ -37,26 +36,36 @@ namespace JiShe.CollectBus.ScheduledMeterReading public abstract class BasicScheduledMeterReadingService : CollectBusAppService, IScheduledMeterReadingService { private readonly ILogger _logger; - private readonly IIoTDBProvider _dbProvider; - private readonly IMeterReadingRecordRepository _meterReadingRecordRepository; + private readonly IIoTDbProvider _dbProvider; private readonly IProducerService _producerService; private readonly IRedisDataCacheService _redisDataCacheService; private readonly KafkaOptionConfig _kafkaOptions; + private readonly ServerApplicationOptions _applicationOptions; + private readonly IoTDBRuntimeContext _runtimeContext; + private readonly IProtocolService _protocolService; + + int pageSize = 3000; public BasicScheduledMeterReadingService( ILogger logger, - IMeterReadingRecordRepository meterReadingRecordRepository, IProducerService producerService, IRedisDataCacheService redisDataCacheService, - IIoTDBProvider dbProvider, - IOptions kafkaOptions) + IIoTDbProvider dbProvider, + IoTDBRuntimeContext runtimeContext, + IProtocolService protocolService, + IOptions kafkaOptions, + IOptions applicationOptions) { _logger = logger; _dbProvider = dbProvider; - _meterReadingRecordRepository = meterReadingRecordRepository; + _runtimeContext = runtimeContext; _producerService = producerService; _redisDataCacheService = redisDataCacheService; _kafkaOptions = kafkaOptions.Value; + _applicationOptions = applicationOptions.Value; + _protocolService = protocolService; + + _runtimeContext.UseTableSessionPool = true; } /// @@ -102,6 +111,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading return; } + var currentTime = DateTime.Now; + + //定时抄读 foreach (var item in taskInfos) { var tasksToBeIssueModel = await FreeRedisProvider.Instance.GetAsync(item); @@ -111,7 +123,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - //item 为 CacheTasksToBeIssuedKey 对应的缓存待下发的指令生产任务数据Redis Key tempArryay[0]=>CollectBus,tempArryay[1]=>SystemTypeConst,tempArryay[2]=>TaskInfo,tempArryay[3]=>表计类别,tempArryay[4]=>采集频率 + //item 为 CacheTasksToBeIssuedKey 对应的缓存待下发的指令生产任务数据Redis Key tempArryay[0]=>CollectBus,tempArryay[1]=>SystemTypeConst,tempArryay[2]=>ServerTagName,tempArryay[3]=>TaskInfo,tempArryay[4]=>表计类别,tempArryay[5]=>采集频率 var tempArryay = item.Split(":"); string meteryType = tempArryay[4];//表计类别 int timeDensity = Convert.ToInt32(tempArryay[5]);//采集频率 @@ -120,6 +132,50 @@ namespace JiShe.CollectBus.ScheduledMeterReading timeDensity = 15; } + + //电表定时广播校时,一天一次。 + string currentTimeStr = $"{currentTime:HH:mm:00}"; + if (string.Equals(currentTimeStr, _applicationOptions.AutomaticVerificationTime, StringComparison.CurrentCultureIgnoreCase))//自动校时 + { + //_logger.LogInformation($"{nameof(AmmeterScheduledAutomaticVerificationTime)} 电表自动校时,非自动校时时间"); + //return; + + _ = CreateMeterPublishTask( + timeDensity: timeDensity, + nextTaskTime: currentTime, + meterType: MeterTypeEnum.Ammeter, + taskCreateAction: async (timeDensity, data, groupIndex, timestamps) => + { + await AmmeterScheduledAutomaticVerificationTime(timeDensity, data, groupIndex, timestamps); + }); + } + else if (string.Equals(currentTimeStr, _applicationOptions.AutomaticGetTerminalVersionTime, StringComparison.CurrentCultureIgnoreCase))//集中器版本号读取 + { + _ = CreateMeterPublishTask( + timeDensity: timeDensity, + nextTaskTime: currentTime, + meterType: MeterTypeEnum.Ammeter, + taskCreateAction: async (timeDensity, data, groupIndex, timestamps) => + { + await ConcentratorScheduledAutomaticGetTerminalVersion(timeDensity, data, groupIndex, timestamps); + }); + } + else if (string.Equals(currentTimeStr, _applicationOptions.AutomaticGetTelematicsModuleTime, StringComparison.CurrentCultureIgnoreCase))//SIM卡读取 + { + _ = CreateMeterPublishTask( + timeDensity: timeDensity, + nextTaskTime: currentTime, + meterType: MeterTypeEnum.Ammeter, + taskCreateAction: async (timeDensity, data, groupIndex, timestamps) => + { + await ConcentratorScheduledAutomaticGetTelematicsModule(timeDensity, data, groupIndex, timestamps); + }); + } + else + { + _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} 不是自动校时、采集终端信息等时间,继续处理其他"); + } + //检查任务时间节点,由于定时任务10秒钟运行一次,需要判定当前时间是否在任务时间节点内,不在则跳过 if (!IsTaskTime(tasksToBeIssueModel.NextTaskTime, timeDensity)) { @@ -129,72 +185,44 @@ namespace JiShe.CollectBus.ScheduledMeterReading var meterTypes = EnumExtensions.ToEnumDictionary(); + var currentTaskTime = tasksToBeIssueModel.NextTaskTime;//程序启动缓存电表的时候,NextTaskTime已经格式化到下一个采集点时间。 + + var metadata = await _dbProvider.GetMetadata(); if (meteryType == MeterTypeEnum.Ammeter.ToString()) { - 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); - - - //处理数据 - //await DeviceGroupBalanceControl.ProcessGenericListAsync( - // items: meterInfos, - // deviceIdSelector: data => data.FocusAddress, - // processor: (data, groupIndex) => - // { - // _ = AmmerterCreatePublishTask(timeDensity, data, groupIndex, tasksToBeIssueModel.NextTaskTime.ToString("yyyyMMddHHmmss")); - // } - //); - - - - await DeviceGroupBalanceControl.ProcessWithThrottleAsync( - items: meterInfos, - deviceIdSelector: data => data.FocusAddress, - processor: (data, groupIndex) => + _ = CreateMeterPublishTask( + timeDensity: timeDensity, + nextTaskTime: currentTaskTime, + meterType: MeterTypeEnum.Ammeter, + taskCreateAction: async (timeDensity, data, groupIndex, timestamps) => { - AmmerterCreatePublishTask(timeDensity, data, groupIndex, tasksToBeIssueModel.NextTaskTime.ToString("yyyyMMddHHmmss")); - } - ); - - timer.Stop(); - _logger.LogInformation($"{nameof(CreateToBeIssueTasks)} {timeDensity}分钟采集待下发任务创建完成,{timer.ElapsedMilliseconds},总共{meterInfos.Count}表计信息"); - + var tempTask = await AmmerterCreatePublishTaskAction(timeDensity, data, groupIndex, timestamps); + if (tempTask == null || tempTask.Count <= 0) + { + _logger.LogWarning($"电表 {data.Name} 任务数据构建失败:{data.Serialize()}"); + return; + } + _ = _dbProvider.BatchInsertAsync(metadata, tempTask); + }); } else if (meteryType == MeterTypeEnum.WaterMeter.ToString()) { - //todo 水表任务创建待处理 - //await WatermeterScheduledMeterReadingIssued(timeDensity, meterInfos); + + _ = CreateMeterPublishTask( + timeDensity: timeDensity, + nextTaskTime: currentTaskTime, + meterType: MeterTypeEnum.WaterMeter, + taskCreateAction: async (timeDensity, data, groupIndex, timestamps) => + { + var tempTask = await WatermeterCreatePublishTaskAction(timeDensity, data, groupIndex, timestamps); + + if (tempTask == null || tempTask.Count <= 0) + { + _logger.LogWarning($"水表 {data.Name} 任务数据构建失败:{data.Serialize()}"); + return; + } + _ = _dbProvider.BatchInsertAsync(metadata, tempTask); + }); } else { @@ -206,9 +234,14 @@ namespace JiShe.CollectBus.ScheduledMeterReading //根据当前的采集频率和类型,重新更新下一个任务点,把任务的创建源固定在当前逻辑,避免任务处理的逻辑异常导致任务创建失败。 - tasksToBeIssueModel.NextTaskTime = tasksToBeIssueModel.NextTaskTime.AddMinutes(timeDensity); - await FreeRedisProvider.Instance.SetAsync(item, tasksToBeIssueModel); + tasksToBeIssueModel.LastTaskTime = currentTaskTime; + tasksToBeIssueModel.NextTaskTime = currentTaskTime.CalculateNextCollectionTime(timeDensity); + await FreeRedisProvider.Instance.SetAsync(item, tasksToBeIssueModel); } + + //电表定时阀控任务处理。 + _= AmmeterScheduledAutoValveControl(); + } #region 电表采集处理 @@ -230,89 +263,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)}*"; + //此处代码不要删除 + //#if DEBUG + // 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 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); - //} + // List meterInfos = new List(); + // List focusAddressDataLista = new List(); + // var timer1 = Stopwatch.StartNew(); + + // var allIds = new HashSet(); + // decimal? score = null; + // string member = null; + + // while (true) + // { + // var page = await _redisDataCacheService.GetAllPagedData( + // redisCacheMeterInfoHashKeyTemp, + // redisCacheMeterInfoZSetScoresIndexKeyTemp, + // pageSize: 1000, + // lastScore: score, + // lastMember: member); + + // meterInfos.AddRange(page.Items); + // focusAddressDataLista.AddRange(page.Items.Select(d => d.FocusAddress)); + // foreach (var item in page.Items) + // { + // if (!allIds.Add(item.MemberId)) + // { + // _logger.LogError($"{item.MemberId}Duplicate data found!"); + // } + // } + // if (!page.HasNext) break; + // score = page.NextScore; + // member = page.NextMember; + // } - - 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)}"; - - List meterInfos = new List(); - 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( - redisCacheMeterInfoHashKeyTemp, - redisCacheMeterInfoZSetScoresIndexKeyTemp, - pageSize: 1000, - lastScore: score, - lastMember: member); - - meterInfos.AddRange(page.Items); - focusAddressDataLista.AddRange(page.Items.Select(d => d.FocusAddress)); - foreach (var item in page.Items) - { - if (!allIds.Add(item.MemberId)) - { - _logger.LogError($"{item.MemberId}Duplicate data found!"); - } - } - if (!page.HasNext) break; - score = page.NextScore; - member = page.NextMember; - } - - - timer1.Stop(); - _logger.LogError($"读取数据更花费时间{timer1.ElapsedMilliseconds}毫秒"); - DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista, _kafkaOptions.NumPartitions); - return; -#else + // timer1.Stop(); + // _logger.LogError($"读取数据总共花费时间{timer1.ElapsedMilliseconds}毫秒"); + // DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista, _kafkaOptions.NumPartitions); + // return; + //#else + // var meterInfos = await GetAmmeterInfoList(gatherCode); + //#endif var meterInfos = await GetAmmeterInfoList(gatherCode); -#endif - if (meterInfos == null || meterInfos.Count <= 0) { throw new NullReferenceException($"{nameof(InitAmmeterCacheData)} 初始化电表缓存数据时,电表数据为空"); @@ -326,10 +323,31 @@ namespace JiShe.CollectBus.ScheduledMeterReading } var timer = Stopwatch.StartNew(); - List focusAddressDataList = new List();//用于处理Kafka主题分区数据的分发和处理。 + List deviceIds = new List();//用于处理Kafka主题分区数据的分发和处理。 //根据采集频率分组,获得采集频率分组 var meterInfoGroupByTimeDensity = meterInfos.GroupBy(d => d.TimeDensity); + + if (_applicationOptions.FirstCollectionTime.HasValue == false) + { + _applicationOptions.FirstCollectionTime = DateTime.Now; + } + //先处理采集频率任务缓存 + foreach (var item in meterInfoGroupByTimeDensity) + { + TasksToBeIssueModel nextTask = new TasksToBeIssueModel() + { + LastTaskTime = null, + TimeDensity = item.Key, + NextTaskTime = _applicationOptions.FirstCollectionTime.Value.CalculateNextCollectionTime(item.Key),//使用首次采集时间作为下一次采集时间 + }; + + //todo 首次采集时间节点到目前运行时间中漏采的时间点,可以考虑使用IoTDB的存储,利用时间序列处理。 + + var taskRedisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, item.Key); + await FreeRedisProvider.Instance.SetAsync(taskRedisCacheKey, nextTask); + } + foreach (var itemTimeDensity in meterInfoGroupByTimeDensity) { var redisCacheMeterInfoHashKey = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key)}"; @@ -346,21 +364,10 @@ 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 - //每次缓存时,删除缓存,避免缓存数据有不准确的问题 - //await FreeRedisProvider.Instance.DelAsync(redisCacheKey); -#else - //每次缓存时,删除缓存,避免缓存数据有不准确的问题 - //await FreeRedisProvider.Instance.DelAsync(redisCacheKey); -#endif - - //Dictionary keyValuePairs = new Dictionary(); foreach (var ammeter in item) { + deviceIds.Add(ammeter.MeterId.ToString()); + //处理ItemCode if (string.IsNullOrWhiteSpace(ammeter.ItemCodes) && !string.IsNullOrWhiteSpace(ammeter.DataTypes)) { @@ -404,36 +411,24 @@ namespace JiShe.CollectBus.ScheduledMeterReading } ammeterInfos.Add(ammeter); - //keyValuePairs.TryAdd($"{ammeter.MeterId}", ammeter); } - //await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); } await _redisDataCacheService.BatchInsertDataAsync( redisCacheMeterInfoHashKey, redisCacheMeterInfoSetIndexKey, redisCacheMeterInfoZSetScoresIndexKey, ammeterInfos); - - //在缓存表信息数据的时候,新增下一个时间的自动处理任务,1分钟后执行所有的采集频率任务 - TasksToBeIssueModel nextTask = new TasksToBeIssueModel() - { - TimeDensity = itemTimeDensity.Key, - NextTaskTime = DateTime.Now.AddMinutes(1) - }; - - var taskRedisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, itemTimeDensity.Key); - await FreeRedisProvider.Instance.SetAsync(taskRedisCacheKey, nextTask); } //初始化设备组负载控制 - if (focusAddressDataList == null || focusAddressDataList.Count <= 0) + if (deviceIds == null || deviceIds.Count <= 0) { _logger.LogError($"{nameof(InitAmmeterCacheData)} 初始化设备组负载控制失败,没有找到对应的设备信息"); } else { - DeviceGroupBalanceControl.InitializeCache(focusAddressDataList, _kafkaOptions.NumPartitions); + DeviceGroupBalanceControl.InitializeCache(deviceIds, _kafkaOptions.NumPartitions); } timer.Stop(); @@ -447,63 +442,34 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// public virtual async Task AmmeterScheduledMeterOneMinuteReading() { - //获取缓存中的电表信息 - int timeDensity = 1; - var currentTime = DateTime.Now; + int timeDensity = 5; + var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity); + var taskInfo = await FreeRedisProvider.Instance.GetAsync(redisCacheKey); - var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.Ammeter); - var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + if (taskInfo == null || taskInfo.LastTaskTime.HasValue == false) { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-101"); + _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} {timeDensity}分钟获取任务下发信息失败,请检查Redis中是否有对应的任务下发信息"); return; } - //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheDictionaryData(oneMinutekeyList, SystemType, ServerTagName, timeDensity.ToString(), MeterTypeEnum.Ammeter); - if (meterTaskInfos == null || meterTaskInfos.Count <= 0) + var pendingCopyReadTime = taskInfo.LastTaskTime.Value.GetDateTimeOffset().ToUnixTimeNanoseconds(); + + var conditions = new List(); + conditions.Add(new QueryCondition() { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); - return; - } + Field = "PendingCopyReadTime", + Operator = "=", + IsNumber = true, + Value = pendingCopyReadTime + }); - List meterTaskInfosList = new List(); - - //将取出的缓存任务数据发送到Kafka消息队列中 - foreach (var focusItem in meterTaskInfos) + _ = CreateMeterKafkaTaskMessage(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, new IoTDBQueryOptions() { - 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.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); - - _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg); - //_= _producerBus.Publish(tempMsg); - - - meterTaskInfosList.Add(ammerterItem.Value); - } - } - if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) - { - //_dbProvider.SwitchSessionPool(true); - //await _dbProvider.InsertAsync(meterTaskInfosList); - - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentTime); - } - - ////删除任务数据 - //await FreeRedisProvider.Instance.DelAsync(oneMinutekeyList); - //await CacheNextTaskData(timeDensity, MeterTypeEnum.Ammeter); - - - _logger.LogInformation($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理完成"); + TableNameOrTreePath = DevicePathBuilder.GetTableName(), + PageIndex = 1, + PageSize = pageSize, + Conditions = conditions, + }); } @@ -513,61 +479,34 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// public virtual async Task AmmeterScheduledMeterFiveMinuteReading() { - //获取缓存中的电表信息 int timeDensity = 5; - var currentTime = DateTime.Now; + var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity); + var taskInfo = await FreeRedisProvider.Instance.GetAsync(redisCacheKey); - var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.Ammeter); - var fiveMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - if (fiveMinutekeyList == null || fiveMinutekeyList.Length <= 0) + if (taskInfo == null || taskInfo.LastTaskTime.HasValue == false) { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-101"); + _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} {timeDensity}分钟获取任务下发信息失败,请检查Redis中是否有对应的任务下发信息"); return; } - //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheDictionaryData(fiveMinutekeyList, SystemType, ServerTagName, timeDensity.ToString(), MeterTypeEnum.Ammeter); - if (meterTaskInfos == null || meterTaskInfos.Count <= 0) + var pendingCopyReadTime = taskInfo.LastTaskTime.Value.GetDateTimeOffset().ToUnixTimeNanoseconds(); + + var conditions = new List(); + conditions.Add(new QueryCondition() { - _logger.LogError($"{nameof(AmmeterScheduledMeterOneMinuteReading)} {timeDensity}分钟采集电表数据处理时没有获取到缓存信息,-102"); - return; - } + Field = "PendingCopyReadTime", + Operator = "=", + IsNumber = true, + Value = pendingCopyReadTime + }); - List meterTaskInfosList = new List(); - - //将取出的缓存任务数据发送到Kafka消息队列中 - foreach (var focusItem in meterTaskInfos) + _ = CreateMeterKafkaTaskMessage(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, new IoTDBQueryOptions() { - 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.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg); - - _ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg); - - //_ = _producerBus.Publish(tempMsg); - - meterTaskInfosList.Add(ammerterItem.Value); - } - } - if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) - { - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentTime); - } - - ////删除任务数据 - //await FreeRedisProvider.Instance.DelAsync(fiveMinutekeyList); - - ////缓存下一个时间的任务 - //await CacheNextTaskData(timeDensity, MeterTypeEnum.Ammeter); - - _logger.LogInformation($"{nameof(AmmeterScheduledMeterFiveMinuteReading)} {timeDensity}分钟采集电表数据处理完成"); + TableNameOrTreePath = DevicePathBuilder.GetTableName(), + PageIndex = 1, + PageSize = pageSize, + Conditions = conditions, + }); } /// @@ -576,150 +515,77 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// public virtual async Task AmmeterScheduledMeterFifteenMinuteReading() { - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); - - //获取缓存中的电表信息 int timeDensity = 15; - var currentDateTime = DateTime.Now; + var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity); + var taskInfo = await FreeRedisProvider.Instance.GetAsync(redisCacheKey); - // 自动计算最佳并发度 - int recommendedThreads = DeviceGroupBalanceControl.CalculateOptimalThreadCount(); - - var options = new ParallelOptions + if (taskInfo == null || taskInfo.LastTaskTime.HasValue == false) { - MaxDegreeOfParallelism = recommendedThreads, - }; - string taskBatch = "20250417155016"; - Parallel.For(0, _kafkaOptions.NumPartitions, options, async groupIndex => + _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} {timeDensity}分钟获取任务下发信息失败,请检查Redis中是否有对应的任务下发信息"); + return; + } + + var pendingCopyReadTime = taskInfo.LastTaskTime.Value.GetDateTimeOffset().ToUnixTimeNanoseconds(); + + var conditions = new List(); + conditions.Add(new QueryCondition() { - 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 page = await _redisDataCacheService.GetAllPagedData( - redisCacheTelemetryPacketInfoHashKey, - redisCacheTelemetryPacketInfoZSetScoresIndexKey, - 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; - - await DeviceGroupBalanceControl.ProcessWithThrottleAsync( - items: meterInfos, - deviceIdSelector: data => data.FocusAddress, - processor: (data, groupIndex) => - { - _= KafkaProducerIssuedMessage(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName,data, groupIndex); - } - ); - - } while (hasNext); + Field = "PendingCopyReadTime", + Operator = "=", + IsNumber = true, + Value = pendingCopyReadTime }); - - - //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(); - - //_logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} {timeDensity}分钟采集电表数据处理完成,共消耗{stopwatch.ElapsedMilliseconds}毫秒。"); + _ = CreateMeterKafkaTaskMessage(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, new IoTDBQueryOptions() + { + TableNameOrTreePath = DevicePathBuilder.GetTableName(), + PageIndex = 1, + PageSize = pageSize, + Conditions = conditions, + }); } - - /// - /// 电表创建发布任务 + /// 创建电表待发送的任务数据 /// /// 采集频率 - /// 集中器号hash分组的集中器集合数据 + /// 电表信息 /// 集中器所在分组 - /// 时间格式的任务批次名称 + /// 采集频率对应的时间戳 /// - private void AmmerterCreatePublishTask(int timeDensity - , AmmeterInfo ammeterInfo, int groupIndex, string taskBatch) + private async Task> AmmerterCreatePublishTaskAction(int timeDensity, AmmeterInfo ammeterInfo, int groupIndex, DateTime timestamps) { - var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; - //todo 检查需要待补抄的电表的时间点信息,保存到需要待补抄的缓存中。如果此线程异常,该如何补偿? - var currentTime = DateTime.Now; - var pendingCopyReadTime = currentTime.AddMinutes(timeDensity); - - 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)}"; + + //根据电表型号获取协议插件 + var protocolPlugin = await _protocolService.GetProtocolServiceAsync(ammeterInfo.BrandType); + if (protocolPlugin == null) + { + //_logger.LogError($"{nameof(AmmeterScheduledAutoValveControl)} 定时阀控运行时间{currentTime}没有找到对应的协议组件,-105"); + //return; + } + + + + //todo 检查需要待补抄的电表的时间点信息,保存到需要待补抄的缓存中。如果此线程异常,该如何补偿? if (string.IsNullOrWhiteSpace(ammeterInfo.ItemCodes)) { - // _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,采集项为空,-101"); - return; + _logger.LogError($"{nameof(AmmerterCreatePublishTaskAction)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,采集项为空,-101"); + return null; } //载波的不处理 if (ammeterInfo.MeteringPort == (int)MeterLinkProtocolEnum.Carrierwave) { - //_logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,载波不处理,-102"); - return; + _logger.LogError($"{nameof(AmmerterCreatePublishTaskAction)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,载波不处理,-102"); + return null; } if (ammeterInfo.State.Equals(2)) { - //_logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} {ammeterInfo.Name} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}状态为禁用,不处理"); - return; + _logger.LogWarning($"{nameof(AmmerterCreatePublishTaskAction)} {ammeterInfo.Name} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}状态为禁用,不处理"); + return null; } ////排除1天未在线的集中器生成指令 或 排除集中器配置为自动上报的集中器 @@ -731,23 +597,23 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (string.IsNullOrWhiteSpace(ammeterInfo.AreaCode)) { - // _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信区号为空"); - return; + _logger.LogError($"{nameof(AmmerterCreatePublishTaskAction)} 表ID:{ammeterInfo.MeterId},集中器通信区号为空"); + return null; } if (string.IsNullOrWhiteSpace(ammeterInfo.Address)) { - //_logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址为空"); - return; + _logger.LogError($"{nameof(AmmerterCreatePublishTaskAction)} 表ID:{ammeterInfo.MeterId},集中器通信地址为空"); + return null; } if (Convert.ToInt32(ammeterInfo.Address) > 65535) { - //_logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址无效,确保大于65535"); - return; + _logger.LogError($"{nameof(AmmerterCreatePublishTaskAction)} 表ID:{ammeterInfo.MeterId},集中器通信地址无效,确保大于65535"); + return null; } if (ammeterInfo.MeteringCode <= 0 || ammeterInfo.MeteringCode > 33) { - //_logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},非有效测量点号({ammeterInfo.MeteringCode})"); - return; + _logger.LogError($"{nameof(AmmerterCreatePublishTaskAction)} 表ID:{ammeterInfo.MeterId},非有效测量点号({ammeterInfo.MeteringCode})"); + return null; } List tempCodes = ammeterInfo.ItemCodes.Deserialize>()!; @@ -774,7 +640,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (tempSubCodes == null || tempSubCodes.Count <= 0) { //_logger.LogInformation($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}自动上报数据主动采集1类数据时数据类型为空"); - return; + return null; } else { @@ -782,7 +648,6 @@ namespace JiShe.CollectBus.ScheduledMeterReading } } - //Dictionary keyValuePairs = new Dictionary(); List taskList = new List(); foreach (var tempItem in tempCodes) @@ -798,182 +663,180 @@ namespace JiShe.CollectBus.ScheduledMeterReading 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:特殊表 + //var itemCodeArr = tempItem.Split('_'); + //var aFNStr = itemCodeArr[0]; + //var aFN = (AFN)aFNStr.HexToDec(); + //var fn = int.Parse(itemCodeArr[1]); - if (dataInfos == null || dataInfos.Length <= 0) + //TODO:特殊表 + ProtocolBuildResponse builderResponse = await protocolPlugin.BuildAsync(new ProtocolBuildRequest() + { + FocusAddress = ammeterInfo.FocusAddress, + Pn = ammeterInfo.MeteringCode, + ItemCode = tempItem, + }); + if (builderResponse == null || builderResponse.Data.Length <= 0) { //_logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}采集项{tempItem}未能正确获取报文。"); continue; } - + string taskMark = CommonHelper.GetTaskMark(builderResponse.AFn, builderResponse.Fn, ammeterInfo.MeteringCode, builderResponse.MSA, builderResponse.Seq); var meterReadingRecords = new MeterReadingTelemetryPacketInfo() { - ProjectID = ammeterInfo.ProjectID, + SystemName = SystemType, + ProjectId = $"{ammeterInfo.ProjectID}", + DeviceType = $"{MeterTypeEnum.Ammeter}", + DeviceId = $"{ammeterInfo.MeterId}", + Timestamps = DateTimeOffset.Now.ToUnixTimeNanoseconds(), DatabaseBusiID = ammeterInfo.DatabaseBusiID, - PendingCopyReadTime = pendingCopyReadTime, + PendingCopyReadTime = timestamps, CreationTime = currentTime, MeterAddress = ammeterInfo.AmmerterAddress, - MeterId = ammeterInfo.MeterId, - MeterType = MeterTypeEnum.Ammeter, - FocusAddress = ammeterInfo.FocusAddress, - FocusId = ammeterInfo.FocusId, - AFN = aFN, - Fn = fn, + AFN = builderResponse.AFn, + Fn = builderResponse.Fn, + Seq = builderResponse.Seq, + MSA = builderResponse.MSA, ItemCode = tempItem, - TaskMark = CommonHelper.GetTaskMark((int)aFN, fn, ammeterInfo.MeteringCode), + TaskMark = taskMark, + IsSend = false, ManualOrNot = false, Pn = ammeterInfo.MeteringCode, IssuedMessageId = GuidGenerator.Create().ToString(), - IssuedMessageHexString = Convert.ToHexString(dataInfos), + IssuedMessageHexString = Convert.ToHexString(builderResponse.Data), + IsReceived = false, + ScoreValue = $"{ammeterInfo.FocusAddress}.{taskMark}".Md5Fun(), }; - //meterReadingRecords.CreateDataId(GuidGenerator.Create()); - taskList.Add(meterReadingRecords); } - //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(); - //} - if (taskList == null - || taskList.Count() <= 0 - || string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoHashKey) - || string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoSetIndexKey) - || string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoZSetScoresIndexKey)) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTask)} {ammeterInfo.Name}的写入参数异常,{redisCacheTelemetryPacketInfoHashKey}:{redisCacheTelemetryPacketInfoSetIndexKey}:{redisCacheTelemetryPacketInfoZSetScoresIndexKey},-101"); - return; - } - - 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); + return taskList; } /// - /// Kafka 推送消息 + /// 获取电表阀控配置 /// - /// 主题名称 - /// 任务记录 - /// 对应分区,也就是集中器号所在的分组序号 + /// 阀控的时间 /// - private async Task KafkaProducerIssuedMessage(string topicName, - MeterReadingTelemetryPacketInfo taskRecord,int partition) + public virtual Task> GetAmmeterAutoValveControlSetting(string currentTime) { - if (string.IsNullOrWhiteSpace(topicName) || taskRecord == null) - { - throw new Exception($"{nameof(KafkaProducerIssuedMessage)} 推送消息失败,参数异常,-101"); - } - - await _producerService.ProduceAsync(topicName, partition, taskRecord); + throw new NotImplementedException($"{nameof(GetAmmeterInfoList)}请根据不同系统类型进行实现"); } - private async Task AmmerterCreatePublishTask(int timeDensity, MeterTypeEnum meterType) + /// + /// 电表自动阀控 + /// + /// + public virtual Task AmmeterScheduledAutoValveControl() { - var currentDateTime = DateTime.Now; + throw new NotImplementedException($"{nameof(AmmeterScheduledAutoValveControl)}请根据不同系统类型进行实现"); + } - var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, meterType); + /// + /// 电表自动校时 + /// + /// 采集频率 + /// 电表信息 + /// 集中器所在分组 + /// 采集频率对应的时间戳 + /// + public virtual async Task AmmeterScheduledAutomaticVerificationTime(int timeDensity, AmmeterInfo ammeterInfo, int groupIndex, DateTime timestamps) + { + var currentTime = DateTime.Now; + string currentTimeStr = $"{currentTime:HH:mm:00}"; - //FreeRedisProvider.Instance.key() - - var fifteenMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - if (fifteenMinutekeyList == null || fifteenMinutekeyList.Length <= 0) + try { - _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) + //判断是否是自动校时时间 + if (!string.Equals(currentTimeStr , _applicationOptions.AutomaticVerificationTime,StringComparison.CurrentCultureIgnoreCase)) { - 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); + _logger.LogInformation($"{nameof(AmmeterScheduledAutomaticVerificationTime)} 电表自动校时,非自动校时时间"); + return; } + + List taskList = new List(); + var metadata = await _dbProvider.GetMetadata(); + + var itemCode = T37612012PacketItemCodeConst.AFN10HFN01H; + var subItemCode = T6452007PacketItemCodeConst.C08; + + //根据电表型号获取协议插件 + var protocolPlugin = await _protocolService.GetProtocolServiceAsync(ammeterInfo.BrandType); + if (protocolPlugin == null) + { + _logger.LogError($"{nameof(AmmeterScheduledAutoValveControl)} 定时阀控运行时间{currentTime}没有找到对应的协议组件,-105"); + return; + } + + ProtocolBuildResponse builderResponse = await protocolPlugin.BuildAsync(new ProtocolBuildRequest() + { + FocusAddress = ammeterInfo.FocusAddress, + Pn = ammeterInfo.MeteringCode, + ItemCode = itemCode, + SubProtocolRequest = new SubProtocolBuildRequest() + { + MeterAddress = ammeterInfo.AmmerterAddress, + Password = ammeterInfo.Password, + ItemCode = subItemCode, + } + }); + + string taskMark = CommonHelper.GetTaskMark(builderResponse.AFn, builderResponse.Fn, ammeterInfo.MeteringCode, builderResponse.MSA, builderResponse.Seq); + var meterReadingRecords = new MeterReadingTelemetryPacketInfo() + { + SystemName = SystemType, + ProjectId = $"{ammeterInfo.ProjectID}", + DeviceType = $"{MeterTypeEnum.Ammeter}", + DeviceId = $"{ammeterInfo.MeterId}", + Timestamps = currentTime.GetDateTimeOffset().ToUnixTimeNanoseconds(), + DatabaseBusiID = ammeterInfo.DatabaseBusiID, + PendingCopyReadTime = currentTime, + CreationTime = currentTime, + MeterAddress = ammeterInfo.AmmerterAddress, + AFN = builderResponse.AFn, + Fn = builderResponse.Fn, + Seq = builderResponse.Seq, + MSA = builderResponse.MSA, + ItemCode = itemCode, + SubItemCode = subItemCode, + TaskMark = taskMark, + IsSend = false, + ManualOrNot = false, + Pn = ammeterInfo.MeteringCode, + IssuedMessageId = GuidGenerator.Create().ToString(), + IssuedMessageHexString = Convert.ToHexString(builderResponse.Data), + IsReceived = false, + ScoreValue = $"{ammeterInfo.FocusAddress}.{taskMark}".Md5Fun(), + }; + taskList.Add(meterReadingRecords); + + if (taskList == null || taskList.Count <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledAutoValveControl)} 定时阀控运行时间{currentTime}没有自动阀控任务生成,-106"); + return; + } + + //任务记录入库 + await _dbProvider.BatchInsertAsync(metadata, taskList); + + //任务信息推送Kafka + _ = DeviceGroupBalanceControl.ProcessWithThrottleAsync( + items: taskList, + deviceIdSelector: data => data.DeviceId, + processor: (data, groupIndex) => + { + _ = KafkaProducerIssuedMessageAction(ProtocolConst.AmmeterSubscriberWorkerOtherIssuedEventName, data, groupIndex); + } + ); + + //todo 阀控记录入库,推送到新的服务 } - if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) + catch (Exception) { - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList, currentDateTime); + + throw; } } @@ -1012,10 +875,35 @@ namespace JiShe.CollectBus.ScheduledMeterReading throw new NullReferenceException($"{nameof(InitAmmeterCacheData)} 初始化水表缓存数据时,采集项类型数据为空"); } + List deviceIds = new List();//用于处理Kafka主题分区数据的分发和处理。 + //根据采集频率分组,获得采集频率分组 var meterInfoGroupByTimeDensity = meterInfos.GroupBy(d => d.TimeDensity); + if (_applicationOptions.FirstCollectionTime.HasValue == false) + { + _applicationOptions.FirstCollectionTime = DateTime.Now; + } + + //先处理采集频率任务缓存 + foreach (var item in meterInfoGroupByTimeDensity) + { + TasksToBeIssueModel nextTask = new TasksToBeIssueModel() + { + LastTaskTime = null, + TimeDensity = item.Key, + NextTaskTime = _applicationOptions.FirstCollectionTime.Value.CalculateNextCollectionTime(item.Key),//使用首次采集时间作为下一次采集时间 + }; + + //todo 首次采集时间节点到目前运行时间中漏采的时间点,可以考虑使用IoTDB的存储,利用时间序列处理。 + + var taskRedisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, MeterTypeEnum.WaterMeter, item.Key); + await FreeRedisProvider.Instance.SetAsync(taskRedisCacheKey, nextTask); + } + foreach (var itemTimeDensity in meterInfoGroupByTimeDensity) { + List watermeterInfo = new List(); + //将表计信息根据集中器分组,获得集中器号 var meterInfoGroup = itemTimeDensity.GroupBy(x => x.FocusAddress).ToList(); foreach (var item in meterInfoGroup) @@ -1025,26 +913,37 @@ namespace JiShe.CollectBus.ScheduledMeterReading continue; } - var redisCacheKey = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.WaterMeter, itemTimeDensity.Key)}{item.Key}"; - Dictionary keyValuePairs = new Dictionary(); + + 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)}"; + foreach (var subItem in item) { + deviceIds.Add(subItem.MeterId.ToString()); - keyValuePairs.TryAdd($"{subItem.MeterId}", subItem); + watermeterInfo.Add(subItem); } - await FreeRedisProvider.Instance.HSetAsync(redisCacheKey, keyValuePairs); + + await _redisDataCacheService.BatchInsertDataAsync( + redisCacheMeterInfoHashKey, + redisCacheMeterInfoSetIndexKey, + redisCacheMeterInfoZSetScoresIndexKey, watermeterInfo); } - - //在缓存表信息数据的时候,新增下一个时间的自动处理任务,1分钟后执行 - TasksToBeIssueModel nextTask = new TasksToBeIssueModel() - { - TimeDensity = itemTimeDensity.Key, - NextTaskTime = DateTime.Now.AddMinutes(1) - }; - - var taskRedisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, MeterTypeEnum.WaterMeter, itemTimeDensity.Key); - await FreeRedisProvider.Instance.SetAsync(taskRedisCacheKey, nextTask); } + + //初始化设备组负载控制 + if (deviceIds == null || deviceIds.Count <= 0) + { + _logger.LogError($"{nameof(InitAmmeterCacheData)} 初始化设备组负载控制失败,没有找到对应的设备信息"); + + } + else + { + DeviceGroupBalanceControl.InitializeCache(deviceIds, _kafkaOptions.NumPartitions); + } + + _logger.LogInformation($"{nameof(InitAmmeterCacheData)} 初始化水表缓存数据完成"); } @@ -1055,74 +954,360 @@ namespace JiShe.CollectBus.ScheduledMeterReading public virtual async Task WatermeterScheduledMeterAutoReading() { //获取缓存中的水表信息 - int timeDensity = 1; - var redisKeyList = GetTelemetryPacketCacheKeyPrefix(timeDensity, MeterTypeEnum.WaterMeter); - var oneMinutekeyList = await FreeRedisProvider.Instance.KeysAsync(redisKeyList); - if (oneMinutekeyList == null || oneMinutekeyList.Length <= 0) + int timeDensity = 60;//水表目前只有一个采集频率 60分钟 + var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, MeterTypeEnum.WaterMeter, timeDensity); + var taskInfo = await FreeRedisProvider.Instance.GetAsync(redisCacheKey); + + if (taskInfo == null || taskInfo.LastTaskTime.HasValue == false) { - _logger.LogError($"{nameof(WatermeterScheduledMeterAutoReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-101"); + _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} {timeDensity}分钟获取任务下发信息失败,请检查Redis中是否有对应的任务下发信息"); return; } - //获取下发任务缓存数据 - Dictionary> meterTaskInfos = await GetMeterRedisCacheDictionaryData(oneMinutekeyList, SystemType, ServerTagName, timeDensity.ToString(), MeterTypeEnum.WaterMeter); - if (meterTaskInfos == null || meterTaskInfos.Count <= 0) + var pendingCopyReadTime = taskInfo.LastTaskTime.Value.GetDateTimeOffset().ToUnixTimeNanoseconds(); + + var conditions = new List(); + conditions.Add(new QueryCondition() { - _logger.LogError($"{nameof(WatermeterScheduledMeterAutoReading)} {timeDensity}分钟采集水表数据处理时没有获取到缓存信息,-102"); - return; - } + Field = "PendingCopyReadTime", + Operator = "=", + IsNumber = true, + Value = pendingCopyReadTime + }); - List meterTaskInfosList = new List(); - - //将取出的缓存任务数据发送到Kafka消息队列中 - foreach (var focusItem in meterTaskInfos) + _ = CreateMeterKafkaTaskMessage(ProtocolConst.WatermeterSubscriberWorkerAutoReadingIssuedEventName, new IoTDBQueryOptions() { - 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.WatermeterSubscriberWorkerAutoReadingIssuedEventName, tempMsg); - - //_ = _producerBus.Publish(tempMsg); - - - meterTaskInfosList.Add(ammerterItem.Value); - } - } - if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) - { - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); - } - - ////删除任务数据 - //await FreeRedisProvider.Instance.DelAsync(oneMinutekeyList); - - ////缓存下一个时间的任务 - //await CacheNextTaskData(timeDensity, MeterTypeEnum.WaterMeter); - + TableNameOrTreePath = DevicePathBuilder.GetTableName(), + PageIndex = 1, + PageSize = pageSize, + Conditions = conditions, + }); _logger.LogInformation($"{nameof(WatermeterScheduledMeterAutoReading)} {timeDensity}分钟采集水表数据处理完成"); } + /// + /// 创建水表待发送的任务数据 + /// + /// 采集频率 + /// 水表信息 + /// 集中器所在分组 + /// 时间格式的任务批次名称 + /// + private async Task> WatermeterCreatePublishTaskAction(int timeDensity + , WatermeterInfo watermeter, int groupIndex, DateTime timestamps) + { + var currentTime = DateTime.Now; + + string typeName; + if (watermeter.MeterType == MeterTypeEnum.WaterMeter) + { + timeDensity = watermeter.TimeDensity;//水表默认为60分钟 + typeName = watermeter.LinkType; + if (watermeter.MeterBrand.Contains("泉高阀门") || watermeter.MeterBrand.Equals("LXSY-山水翔")) + { + typeName = watermeter.MeterBrand; + } + } + else if (watermeter.MeterType == MeterTypeEnum.WaterMeterFlowmeter) + { + typeName = watermeter.MeterBrand; + } + else + { + _logger.LogError($"{nameof(WatermeterCreatePublishTaskAction)} 水表类型错误:{watermeter.Serialize()}"); + return null; + } + + List taskList = new List(); + + + + //根据表型号获取协议插件 + var protocolPlugin = await _protocolService.GetProtocolServiceAsync(watermeter.Code); + if (protocolPlugin == null) + { + //_logger.LogError($"{nameof(AmmeterScheduledAutoValveControl)} 定时阀控运行时间{currentTime}没有找到对应的协议组件,-105"); + //return; + } + + string itemCode = T37612012PacketItemCodeConst.AFN10HFN01H; + string subItemCode = T1882018PacketItemCodeConst.CTR0190; + + ProtocolBuildResponse builderResponse = await protocolPlugin.BuildAsync(new ProtocolBuildRequest() + { + FocusAddress = watermeter.FocusAddress, + Pn = watermeter.MeteringCode, + ItemCode = itemCode, + SubProtocolRequest = new SubProtocolBuildRequest() + { + MeterAddress = watermeter.MeterAddress, + Password = watermeter.Password, + ItemCode = subItemCode, + } + }); + if (builderResponse == null || builderResponse.Data.Length <= 0) + { + //_logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}采集项{tempItem}未能正确获取报文。"); + return null; + } + + if (builderResponse == null || builderResponse.Data.Length <= 0) + { + _logger.LogWarning($"{nameof(WatermeterCreatePublishTaskAction)} 集中器{watermeter.FocusAddress}的水表{watermeter.Name}采集项{itemCode}未能正确获取报文。"); + return null; + } + + + string taskMark = CommonHelper.GetTaskMark(builderResponse.AFn, builderResponse.Fn, watermeter.MeteringCode, builderResponse.MSA, builderResponse.Seq); + var meterReadingRecords = new MeterReadingTelemetryPacketInfo() + { + SystemName = SystemType, + ProjectId = $"{watermeter.ProjectID}", + DeviceType = $"{MeterTypeEnum.Ammeter}", + DeviceId = $"{watermeter.MeterId}", + Timestamps = DateTimeOffset.Now.ToUnixTimeNanoseconds(), + DatabaseBusiID = watermeter.DatabaseBusiID, + PendingCopyReadTime = timestamps, + CreationTime = currentTime, + MeterAddress = watermeter.MeterAddress, + AFN = builderResponse.AFn, + Fn = builderResponse.Fn, + Seq = builderResponse.Seq, + MSA = builderResponse.MSA, + ItemCode = itemCode, + SubItemCode = subItemCode, + TaskMark = taskMark, + IsSend = false, + ManualOrNot = false, + Pn = watermeter.MeteringCode, + IssuedMessageId = GuidGenerator.Create().ToString(), + IssuedMessageHexString = Convert.ToHexString(builderResponse.Data), + IsReceived = false, + ScoreValue = $"{watermeter.FocusAddress}.{taskMark}".Md5Fun(), + }; + + taskList.Add(meterReadingRecords); + + return taskList; + + } + + #endregion + + #region 集中器处理 + /// + /// 自动获取终端版 + /// + /// 采集频率 + /// 电表信息 + /// 集中器所在分组 + /// 采集频率对应的时间戳 + /// + public virtual async Task ConcentratorScheduledAutomaticGetTerminalVersion(int timeDensity, AmmeterInfo ammeterInfo, int groupIndex, DateTime timestamps) + { + var currentTime = DateTime.Now; + string currentTimeStr = $"{currentTime:HH:mm:00}"; + + try + { + //判断是否是自动获取版本号时间 + if (!string.Equals(currentTimeStr, _applicationOptions.AutomaticGetTerminalVersionTime, StringComparison.CurrentCultureIgnoreCase)) + { + _logger.LogInformation($"{nameof(ConcentratorScheduledAutomaticGetTerminalVersion)} 集中器自动获取版本号,非自动处理时间"); + return; + } + + List taskList = new List(); + var metadata = await _dbProvider.GetMetadata(); + + var itemCode = T37612012PacketItemCodeConst.AFN09HFN01H; + //var subItemCode = T6452007PacketItemCodeConst.C08; + + //根据电表型号获取协议插件 + var protocolPlugin = await _protocolService.GetProtocolServiceAsync(ammeterInfo.BrandType); + if (protocolPlugin == null) + { + _logger.LogError($"{nameof(AmmeterScheduledAutoValveControl)} 集中器自动获取版本号{currentTime}没有找到对应的协议组件,-105"); + return; + } + + ProtocolBuildResponse builderResponse = await protocolPlugin.BuildAsync(new ProtocolBuildRequest() + { + FocusAddress = ammeterInfo.FocusAddress, + Pn = ammeterInfo.MeteringCode, + ItemCode = itemCode, + //SubProtocolRequest = new SubProtocolBuildRequest() + //{ + // MeterAddress = ammeterInfo.AmmerterAddress, + // Password = ammeterInfo.Password, + // ItemCode = subItemCode, + //} + }); + + string taskMark = CommonHelper.GetTaskMark(builderResponse.AFn, builderResponse.Fn, ammeterInfo.MeteringCode, builderResponse.MSA, builderResponse.Seq); + var meterReadingRecords = new MeterReadingTelemetryPacketInfo() + { + SystemName = SystemType, + ProjectId = $"{ammeterInfo.ProjectID}", + DeviceType = $"{MeterTypeEnum.Ammeter}", + DeviceId = $"{ammeterInfo.MeterId}", + Timestamps = currentTime.GetDateTimeOffset().ToUnixTimeNanoseconds(), + DatabaseBusiID = ammeterInfo.DatabaseBusiID, + PendingCopyReadTime = currentTime, + CreationTime = currentTime, + MeterAddress = ammeterInfo.AmmerterAddress, + AFN = builderResponse.AFn, + Fn = builderResponse.Fn, + Seq = builderResponse.Seq, + MSA = builderResponse.MSA, + ItemCode = itemCode, + //SubItemCode = subItemCode, + TaskMark = taskMark, + IsSend = false, + ManualOrNot = false, + Pn = ammeterInfo.MeteringCode, + IssuedMessageId = GuidGenerator.Create().ToString(), + IssuedMessageHexString = Convert.ToHexString(builderResponse.Data), + IsReceived = false, + ScoreValue = $"{ammeterInfo.FocusAddress}.{taskMark}".Md5Fun(), + }; + taskList.Add(meterReadingRecords); + + if (taskList == null || taskList.Count <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledAutoValveControl)} 定时阀控运行时间{currentTime}没有自动阀控任务生成,-106"); + return; + } + + //任务记录入库 + await _dbProvider.BatchInsertAsync(metadata, taskList); + + //任务信息推送Kafka + _ = DeviceGroupBalanceControl.ProcessWithThrottleAsync( + items: taskList, + deviceIdSelector: data => data.DeviceId, + processor: (data, groupIndex) => + { + _ = KafkaProducerIssuedMessageAction(ProtocolConst.AmmeterSubscriberWorkerOtherIssuedEventName, data, groupIndex); + } + ); + } + catch (Exception) + { + + throw; + } + } + + /// + /// 自动获取远程通信模块(SIM)版本信息 + /// + /// 采集频率 + /// 电表信息 + /// 集中器所在分组 + /// 采集频率对应的时间戳 + /// + public virtual async Task ConcentratorScheduledAutomaticGetTelematicsModule(int timeDensity, AmmeterInfo ammeterInfo, int groupIndex, DateTime timestamps) + { + var currentTime = DateTime.Now; + string currentTimeStr = $"{currentTime:HH:mm:00}"; + + try + { + //判断是否是自动获取版本号时间 + if (!string.Equals(currentTimeStr, _applicationOptions.AutomaticGetTerminalVersionTime, StringComparison.CurrentCultureIgnoreCase)) + { + _logger.LogInformation($"{nameof(ConcentratorScheduledAutomaticGetTelematicsModule)} 自动获取远程通信模块(SIM)版本信息,非自动处理时间"); + return; + } + + List taskList = new List(); + var metadata = await _dbProvider.GetMetadata(); + + var itemCode = T37612012PacketItemCodeConst.AFN09HFN09H; + + //根据电表型号获取协议插件 + var protocolPlugin = await _protocolService.GetProtocolServiceAsync(ammeterInfo.BrandType); + if (protocolPlugin == null) + { + _logger.LogError($"{nameof(ConcentratorScheduledAutomaticGetTelematicsModule)} 自动获取远程通信模块(SIM)版本信息{currentTime}没有找到对应的协议组件,-105"); + return; + } + + ProtocolBuildResponse builderResponse = await protocolPlugin.BuildAsync(new ProtocolBuildRequest() + { + FocusAddress = ammeterInfo.FocusAddress, + Pn = ammeterInfo.MeteringCode, + ItemCode = itemCode, + }); + + string taskMark = CommonHelper.GetTaskMark(builderResponse.AFn, builderResponse.Fn, ammeterInfo.MeteringCode, builderResponse.MSA, builderResponse.Seq); + var meterReadingRecords = new MeterReadingTelemetryPacketInfo() + { + SystemName = SystemType, + ProjectId = $"{ammeterInfo.ProjectID}", + DeviceType = $"{MeterTypeEnum.Ammeter}", + DeviceId = $"{ammeterInfo.MeterId}", + Timestamps = currentTime.GetDateTimeOffset().ToUnixTimeNanoseconds(), + DatabaseBusiID = ammeterInfo.DatabaseBusiID, + PendingCopyReadTime = currentTime, + CreationTime = currentTime, + MeterAddress = ammeterInfo.AmmerterAddress, + AFN = builderResponse.AFn, + Fn = builderResponse.Fn, + Seq = builderResponse.Seq, + MSA = builderResponse.MSA, + ItemCode = itemCode, + //SubItemCode = subItemCode, + TaskMark = taskMark, + IsSend = false, + ManualOrNot = false, + Pn = ammeterInfo.MeteringCode, + IssuedMessageId = GuidGenerator.Create().ToString(), + IssuedMessageHexString = Convert.ToHexString(builderResponse.Data), + IsReceived = false, + ScoreValue = $"{ammeterInfo.FocusAddress}.{taskMark}".Md5Fun(), + }; + taskList.Add(meterReadingRecords); + + if (taskList == null || taskList.Count <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledAutoValveControl)} 定时阀控运行时间{currentTime}没有自动阀控任务生成,-106"); + return; + } + + //任务记录入库 + await _dbProvider.BatchInsertAsync(metadata, taskList); + + //任务信息推送Kafka + _ = DeviceGroupBalanceControl.ProcessWithThrottleAsync( + items: taskList, + deviceIdSelector: data => data.DeviceId, + processor: (data, groupIndex) => + { + _ = KafkaProducerIssuedMessageAction(ProtocolConst.AmmeterSubscriberWorkerOtherIssuedEventName, data, groupIndex); + } + ); + } + catch (Exception) + { + + throw; + } + } #endregion #region 公共处理方法 - /// /// 判断是否需要生成采集指令 /// /// /// /// - private bool IsTaskTime(DateTime nextTaskTime, int timeDensity = 0) + protected bool IsTaskTime(DateTime nextTaskTime, int timeDensity = 0) { if (DateTime.Now.AddMinutes(timeDensity) >= nextTaskTime) { @@ -1132,48 +1317,129 @@ namespace JiShe.CollectBus.ScheduledMeterReading return false; } - ///// - ///// 指定时间对比当前时间 - ///// - ///// - ///// - ///// - //private bool IsGennerateCmd(DateTime lastTime, int subtrahend = 0) - //{ - // if (DateTime.Now.AddDays(subtrahend) >= lastTime)//当前时间减去一天,大于等于最后在线时间,不再生成该集中器下表生成采集指令 - // return false; - // return true; - //} + /// + /// 创建表的待发送的任务数据 + /// + /// 采集频率 + /// 采集频率对应的任务时间戳 + /// 表类型 + /// 具体的创建任务的委托 + /// + protected async Task CreateMeterPublishTask(int timeDensity, DateTime nextTaskTime, MeterTypeEnum meterType, Action taskCreateAction) where T : DeviceCacheBasicModel + { + var timer = Stopwatch.StartNew(); - ///// - ///// 缓存下一个时间的任务 - ///// - ///// 采集频率 - ///// 表类型 - ///// - //private async Task CacheNextTaskData(int timeDensity, MeterTypeEnum meterType) - //{ - // //缓存下一个时间的任务 - // TasksToBeIssueModel nextTask = new TasksToBeIssueModel() - // { - // TimeDensity = timeDensity, - // NextTask = DateTime.Now.AddMinutes(timeDensity) - // }; + //获取对应频率中的所有电表信息 + var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, ServerTagName, meterType, timeDensity)}"; + var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, ServerTagName, meterType, timeDensity)}"; + var redisCacheMeterInfoZSetScoresIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, ServerTagName, meterType, timeDensity)}"; - // var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName, meterType, timeDensity); - // await FreeRedisProvider.Instance.SetAsync(redisCacheKey, nextTask); - //} + 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); + + + //var page = await _redisDataCacheService.GetAllPagedData( + // redisCacheMeterInfoHashKeyTemp, + // redisCacheMeterInfoZSetScoresIndexKeyTemp, + // pageSize: 10, + // lastScore: cursor, + // lastMember: member); + //meterInfos.AddRange(page.Items); + + if (meterInfos == null || meterInfos.Count <= 0) + { + timer.Stop(); + _logger.LogError($"{nameof(CreateMeterPublishTask)} {meterType}的{timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-105"); + return; + } + + await DeviceGroupBalanceControl.ProcessWithThrottleAsync( + items: meterInfos, + deviceIdSelector: data => data.FocusAddress, + processor: (data, groupIndex) => + { + taskCreateAction(timeDensity, data, groupIndex, nextTaskTime); + } + ); + + timer.Stop(); + _logger.LogInformation($"{nameof(CreateMeterPublishTask)} {meterType} {timeDensity}分钟采集待下发任务创建完成,耗时{timer.ElapsedMilliseconds}毫秒,总共{meterInfos.Count}表计信息"); + } /// - /// 获取缓存表计下发指令缓存key前缀 + /// 创建Kafka消息 /// - /// - /// + /// + /// kafka主题名称 + /// 任务查询条件 /// - private string GetTelemetryPacketCacheKeyPrefix(int timeDensity, MeterTypeEnum meterType) + protected async Task CreateMeterKafkaTaskMessage(string kafkaTopicName, IoTDBQueryOptions options) where T : IoTEntity, new() { - return $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, meterType, timeDensity)}*"; + if (string.IsNullOrWhiteSpace(kafkaTopicName)) + { + _logger.LogInformation($"{nameof(CreateMeterKafkaTaskMessage)} Kafka消息推送主题不能为空,-101"); + return; + } + int pageNumber = 0; + bool hasNext; + var stopwatch = Stopwatch.StartNew(); + + do + { + options.PageIndex = pageNumber++; + + var pageResult = await _dbProvider.QueryAsync(options); + + hasNext = pageResult.HasNext; + + await DeviceGroupBalanceControl.ProcessWithThrottleAsync( + items: pageResult.Items.ToList(), + deviceIdSelector: data => data.DeviceId, + processor: (data, groupIndex) => + { + _ = KafkaProducerIssuedMessageAction(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, data, groupIndex); + } + ); + + } while (hasNext); + + stopwatch.Stop(); + _logger.LogInformation($"{nameof(CreateMeterKafkaTaskMessage)} {kafkaTopicName}主题的任务 {options.TableNameOrTreePath} 路径任务推送完成,共消耗{stopwatch.ElapsedMilliseconds}毫秒。"); + } + + /// + /// Kafka 推送消息 + /// + /// 主题名称 + /// 任务记录 + /// 对应分区,也就是集中器号所在的分组序号 + /// + protected async Task KafkaProducerIssuedMessageAction(string topicName, + T taskRecord, int partition) where T : class + { + if (string.IsNullOrWhiteSpace(topicName) || taskRecord == null) + { + throw new Exception($"{nameof(KafkaProducerIssuedMessageAction)} 推送消息失败,参数异常,-101"); + } + + await _producerService.ProduceAsync(topicName, taskRecord, partition); } #endregion diff --git a/services/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/services/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index 733f14a..9dc2fc3 100644 --- a/services/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/services/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -1,32 +1,30 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Confluent.Kafka; -using DotNetCore.CAP; -using JiShe.CollectBus.Ammeters; -using JiShe.CollectBus.Application.Contracts; +using JiShe.CollectBus.Application.Contracts; +using JiShe.CollectBus.Common; using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.DeviceBalanceControl; +using JiShe.CollectBus.Common.Encrypt; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.GatherItem; +using JiShe.CollectBus.IoTDB.Context; using JiShe.CollectBus.IoTDB.Interface; -using JiShe.CollectBus.IotSystems.Devices; -using JiShe.CollectBus.IotSystems.MessageIssueds; +using JiShe.CollectBus.IotSystems.Ammeters; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; -using JiShe.CollectBus.Kafka; +using JiShe.CollectBus.Kafka.Internal; using JiShe.CollectBus.Kafka.Producer; -using JiShe.CollectBus.Repository; -using JiShe.CollectBus.Repository.MeterReadingRecord; -using MassTransit; +using JiShe.CollectBus.Protocol.Interfaces; 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; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using JiShe.CollectBus.Protocol.Models; + namespace JiShe.CollectBus.ScheduledMeterReading { @@ -38,21 +36,32 @@ namespace JiShe.CollectBus.ScheduledMeterReading public class EnergySystemScheduledMeterReadingService : BasicScheduledMeterReadingService { string serverTagName = string.Empty; + private readonly ILogger _logger; + private readonly IIoTDbProvider _dbProvider; + private readonly IProtocolService _protocolService; + public EnergySystemScheduledMeterReadingService( ILogger logger, - IIoTDBProvider dbProvider, - IMeterReadingRecordRepository meterReadingRecordRepository, - IOptions kafkaOptions, - IProducerService producerService, - IRedisDataCacheService redisDataCacheService) + IIoTDbProvider dbProvider, + IOptions kafkaOptions, + IOptions applicationOptions, + IoTDBRuntimeContext runtimeContext, + IProducerService producerService, + IProtocolService protocolService, + IRedisDataCacheService redisDataCacheService) : base(logger, - meterReadingRecordRepository, producerService, - redisDataCacheService, + redisDataCacheService, dbProvider, - kafkaOptions) + runtimeContext, + protocolService, + kafkaOptions, + applicationOptions) { - serverTagName = kafkaOptions.Value.ServerTagName; + serverTagName = applicationOptions.Value.ServerTagName; + _dbProvider = dbProvider; + _logger = logger; + _protocolService = protocolService; } public sealed override string SystemType => SystemTypeConst.Energy; @@ -102,6 +111,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading // 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, + // BrandType = "", //}); //ammeterInfos.Add(new AmmeterInfo() //{ @@ -116,6 +126,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading // TypeName = 1, // DataTypes = "581,589,592,597,601", // TimeDensity = 15, + // BrandType = "", //}); //return ammeterInfos; @@ -128,15 +139,197 @@ 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); } + + /// + /// 获取电表阀控配置 + /// + /// 阀控的时间 + /// + public override async Task> GetAmmeterAutoValveControlSetting(string currentTime) + { + string sql = $@"SELECT A.*,B.AmmeterID as MeterId,E.GatherCode,D.AreaCode,D.[Address],CONCAT(D.AreaCode, D.[Address]) AS FocusAddress,D.ID as FocusId + FROM TB_AutoTripTask(nolock) AS A + INNER JOIN TB_AutoTripAmmeter(nolock) AS B ON A.ID=B.TripTaskID + INNER JOIN TB_AmmeterInfo(nolock) AS C ON B.AmmeterID=C.ID + INNER JOIN TB_FocusInfo(nolock) AS D ON D.ID=C.FocusID + INNER JOIN TB_GatherInfo(NOLOCK) AS E ON E.ID=D.GatherInfoID + WHERE A.IsForbid=0 and A.State<>-1 and E.GatherCode LIKE '%V4%' and A.TripTime"; + + if (!string.IsNullOrWhiteSpace(currentTime)) + { + sql = $@"{sql} AND A.TripTime = '{currentTime}'"; + } + + return await SqlProvider.Instance.Change(DbEnum.EnergyDB) + .Ado + .QueryAsync(sql); + } + + + /// + /// 电表自动阀控 + /// + /// + public override async Task AmmeterScheduledAutoValveControl() + { + var currentTime = DateTime.Now; + string currentTimeStr = $"{currentTime:HH:mm}"; + + try + { + //获取电表阀控配置 + var settingInfos = await GetAmmeterAutoValveControlSetting(currentTimeStr); + + if (settingInfos == null || settingInfos.Count <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledAutoValveControl)} 电表自动阀控时,阀控数据为空, -101"); + return; + } + + //批量获取对应的缓存电表信息 + var ammeterInfos = new List(); + List taskList = new List(); + var metadata = await _dbProvider.GetMetadata(); + + foreach (var settingInfo in settingInfos) + { + bool isGenerate = false; + switch (settingInfo.LoopType) + { + case "Once": + isGenerate = CommonHelper.JudgeIsGenerate_Once(settingInfo.OnceWithDate.Value, currentTime); + break; + case "EachDay": + isGenerate = CommonHelper.JudgeIsGenerate_Day(settingInfo.EachDayWithout, currentTime); + break; + case "EachWeek": + isGenerate = CommonHelper.JudgeIsGenerate_Week(settingInfo.EachWeekWith, currentTime); + break; + case "EachMonth": + isGenerate = CommonHelper.JudgeIsGenerate_Month(settingInfo.EachMonthWith, currentTime); + break; + } + + if (!isGenerate)//不生成,跳入下一循环 + { + continue; + } + + //获取对应的缓存电表信息 + var ammeterInfo = ammeterInfos.First(); + bool tripStateResult = false; + string itemCode = T37612012PacketItemCodeConst.AFN10HFN01H; + string subItemCode = string.Empty; + if (settingInfo.TripType.Equals("on")) + { + ammeterInfo.TripState = 0; + tripStateResult = true; + subItemCode = T6452007PacketItemCodeConst.C1C01C; + if (ammeterInfo.TypeName != 1) + { + subItemCode = T6452007PacketItemCodeConst.C1C01B; + } + } + else if (settingInfo.TripType.Equals("off")) + { + ammeterInfo.TripState = 1; + tripStateResult = false; + subItemCode = T6452007PacketItemCodeConst.C1C01A; + } + else + { + _logger.LogError($"集中器[{settingInfo.FocusAddress}],[{settingInfo.MeterId}]阀控命令错误:{settingInfo.TripType},-102"); + continue; + } + + + //根据电表型号获取协议插件 + var protocolPlugin = await _protocolService.GetProtocolServiceAsync(ammeterInfo.BrandType); + if (protocolPlugin == null) + { + _logger.LogError($"{nameof(AmmeterScheduledAutoValveControl)} 定时阀控运行时间{currentTime}没有找到对应的协议组件,-105"); + return; + } + + ProtocolBuildResponse builderResponse = await protocolPlugin.BuildAsync(new ProtocolBuildRequest() + { + FocusAddress = ammeterInfo.FocusAddress, + Pn = ammeterInfo.MeteringCode, + ItemCode = itemCode, + SubProtocolRequest = new SubProtocolBuildRequest() + { + MeterAddress = ammeterInfo.AmmerterAddress, + Password = ammeterInfo.Password, + ItemCode = subItemCode, + } + }); + + string taskMark = CommonHelper.GetTaskMark(builderResponse.AFn, builderResponse.Fn, ammeterInfo.MeteringCode, builderResponse.MSA, builderResponse.Seq); + var meterReadingRecords = new MeterReadingTelemetryPacketInfo() + { + SystemName = SystemType, + ProjectId = $"{ammeterInfo.ProjectID}", + DeviceType = $"{MeterTypeEnum.Ammeter}", + DeviceId = $"{ammeterInfo.MeterId}", + Timestamps = currentTime.GetDateTimeOffset().ToUnixTimeNanoseconds(), + DatabaseBusiID = ammeterInfo.DatabaseBusiID, + PendingCopyReadTime = currentTime, + CreationTime = currentTime, + MeterAddress = ammeterInfo.AmmerterAddress, + AFN = builderResponse.AFn, + Fn = builderResponse.Fn, + Seq = builderResponse.Seq, + MSA = builderResponse.MSA, + ItemCode = itemCode, + SubItemCode = subItemCode, + TaskMark = taskMark, + IsSend = false, + ManualOrNot = false, + Pn = ammeterInfo.MeteringCode, + IssuedMessageId = GuidGenerator.Create().ToString(), + IssuedMessageHexString = Convert.ToHexString(builderResponse.Data), + IsReceived = false, + ScoreValue = $"{ammeterInfo.FocusAddress}.{taskMark}".Md5Fun(), + }; + taskList.Add(meterReadingRecords); + } + if (taskList == null || taskList.Count <= 0) + { + _logger.LogError($"{nameof(AmmeterScheduledAutoValveControl)} 定时阀控运行时间{currentTime}没有自动阀控任务生成,-106"); + return; + } + + //任务记录入库 + await _dbProvider.BatchInsertAsync(metadata, taskList); + + //任务信息推送Kafka + _ = DeviceGroupBalanceControl.ProcessWithThrottleAsync( + items: taskList, + deviceIdSelector: data => data.DeviceId, + processor: (data, groupIndex) => + { + _ = KafkaProducerIssuedMessageAction(ProtocolConst.AmmeterSubscriberWorkerAutoValveControlIssuedEventName, data, groupIndex); + } + ); + + //todo 阀控记录入库,推送到新的服务 + } + catch (Exception) + { + + throw; + } + } + /// /// 获取水表信息 /// @@ -188,29 +381,5 @@ 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.InitializeCache(deviceList); - - // 打印分布统计 - DeviceGroupBalanceControl.PrintDistributionStats(); - - await Task.CompletedTask; - } } } \ No newline at end of file diff --git a/services/JiShe.CollectBus.Application/Subscribers/SubscriberAnalysisAppService.cs b/services/JiShe.CollectBus.Application/Subscribers/SubscriberAnalysisAppService.cs new file mode 100644 index 0000000..3fd32fb --- /dev/null +++ b/services/JiShe.CollectBus.Application/Subscribers/SubscriberAnalysisAppService.cs @@ -0,0 +1,80 @@ +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.Kafka.Attributes; +using JiShe.CollectBus.Kafka.Internal; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; +using JiShe.CollectBus.Repository.MeterReadingRecord; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; +using JiShe.CollectBus.Protocol; +using TouchSocket.Sockets; + +namespace JiShe.CollectBus.Subscribers +{ + public class SubscriberAnalysisAppService : CollectBusAppService, ISubscriberAnalysisAppService, IKafkaSubscribe + { + private readonly ILogger _logger; + private readonly ITcpService _tcpService; + private readonly IServiceProvider _serviceProvider; + private readonly IMeterReadingRecordRepository _meterReadingRecordsRepository; + private readonly IIoTDbProvider _dbProvider; + private readonly IProtocolService _protocolService; + + public SubscriberAnalysisAppService(ILogger logger, + ITcpService tcpService, + IServiceProvider serviceProvider, + IIoTDbProvider dbProvider, + IMeterReadingRecordRepository meterReadingRecordsRepository, IProtocolService protocolService) + { + _logger = logger; + _tcpService = tcpService; + _serviceProvider = serviceProvider; + _meterReadingRecordsRepository = meterReadingRecordsRepository; + _dbProvider = dbProvider; + _protocolService = protocolService; + } + + /// + /// 解析AFN00H + /// + /// + /// + [KafkaSubscribe(ProtocolConst.SubscriberAFN00HReceivedEventNameTemp)] + public async Task ReceivedAFN00Event(MessageProtocolAnalysis receivedMessage) + { + var protocolPlugin = await _protocolService.GetProtocolServiceAsync(receivedMessage.DeviceNo); + if (protocolPlugin == null) + { + _logger.LogError("协议不存在!"); + } + else + { + if (receivedMessage.Data==null) + { + Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); + return SubscribeAck.Success(); + } + if (receivedMessage.Data.DT == null || receivedMessage.Data.AFN_FC == null) + { + Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); + return SubscribeAck.Success(); + } + string serverName = $"AFN{receivedMessage.Data.AFN_FC.AFN}_F{receivedMessage.Data.DT.Fn}_Analysis"; + //var analysisStrategy = _serviceProvider.GetKeyedService($"AFN0_F1_Analysis"); + + //var data = await analysisStrategy.ExecuteAsync>(tB3761); + var executor = _serviceProvider.GetRequiredService(); + await executor.ExecuteAsync(serverName, receivedMessage.Data); + } + + return SubscribeAck.Fail(); + } + + } +} diff --git a/services/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/services/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index eb3abd9..a082551 100644 --- a/services/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/services/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -1,39 +1,35 @@ -using DotNetCore.CAP; -using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Consts; using JiShe.CollectBus.Common.Enums; -using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; -using JiShe.CollectBus.IotSystems.Devices; +using JiShe.CollectBus.Interceptors; +using JiShe.CollectBus.IoTDB.Interface; 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; +using JiShe.CollectBus.Kafka.Internal; +using JiShe.CollectBus.Protocol.Interfaces; +using JiShe.CollectBus.Protocol3761; using JiShe.CollectBus.Repository.MeterReadingRecord; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; -using System.Linq; +using System.Collections.Generic; using System.Threading.Tasks; -using JiShe.CollectBus.IoTDB.Interface; using TouchSocket.Sockets; using Volo.Abp.Domain.Repositories; namespace JiShe.CollectBus.Subscribers { - public class SubscriberAppService : CollectBusAppService, ISubscriberAppService, ICapSubscribe, IKafkaSubscribe + public class SubscriberAppService : CollectBusAppService, ISubscriberAppService, IKafkaSubscribe { 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 _deviceRepository; private readonly IMeterReadingRecordRepository _meterReadingRecordsRepository; - private readonly IIoTDBProvider _dbProvider; + private readonly IIoTDbProvider _dbProvider; + private readonly IProtocolService _protocolService; /// /// Initializes a new instance of the class. @@ -43,96 +39,71 @@ namespace JiShe.CollectBus.Subscribers /// The service provider. /// The message received login event repository. /// 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, + ITcpService tcpService, + IServiceProvider serviceProvider, IRepository messageReceivedLoginEventRepository, IRepository messageReceivedHeartbeatEventRepository, - IRepository messageReceivedEventRepository, - IRepository deviceRepository, - IIoTDBProvider dbProvider, - IMeterReadingRecordRepository meterReadingRecordsRepository) + IIoTDbProvider dbProvider, + IMeterReadingRecordRepository meterReadingRecordsRepository, IProtocolService protocolService) { _logger = logger; _tcpService = tcpService; _serviceProvider = serviceProvider; _messageReceivedLoginEventRepository = messageReceivedLoginEventRepository; _messageReceivedHeartbeatEventRepository = messageReceivedHeartbeatEventRepository; - _messageReceivedEventRepository = messageReceivedEventRepository; - _deviceRepository = deviceRepository; _meterReadingRecordsRepository = meterReadingRecordsRepository; _dbProvider = dbProvider; + _protocolService = protocolService; } - [KafkaSubscribe(ProtocolConst.SubscriberLoginIssuedEventName)] - //[CapSubscribe(ProtocolConst.SubscriberLoginIssuedEventName)] - public async Task LoginIssuedEvent(IssuedEventMessage issuedEventMessage) - { + [LogIntercept] + [KafkaSubscribe(ProtocolConst.SubscriberLoginIssuedEventName, EnableBatch = true)] + public async Task LoginIssuedEvent(List issuedEventMessages) + { bool isAck = false; - switch (issuedEventMessage.Type) + foreach (var issuedEventMessage in issuedEventMessages) { - case IssuedEventType.Heartbeat: + var loginEntity = await _messageReceivedLoginEventRepository.FirstOrDefaultAsync(a => a.MessageId == issuedEventMessage.MessageId); + if (loginEntity == null) + { + isAck=false; 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); - isAck = true; - break; - case IssuedEventType.Data: - break; - default: - throw new ArgumentOutOfRangeException(); + } + + loginEntity.AckTime = Clock.Now; + loginEntity.IsAck = true; + await _messageReceivedLoginEventRepository.UpdateAsync(loginEntity); + isAck = true; + } - - //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); + // TODO:暂时ACK,等后续处理是否放到私信队列中 return isAck? SubscribeAck.Success(): SubscribeAck.Fail(); } - [KafkaSubscribe(ProtocolConst.SubscriberHeartbeatIssuedEventName)] - //[CapSubscribe(ProtocolConst.SubscriberHeartbeatIssuedEventName)] - public async Task HeartbeatIssuedEvent(IssuedEventMessage issuedEventMessage) + [KafkaSubscribe(ProtocolConst.SubscriberHeartbeatIssuedEventName, EnableBatch = true)] + public async Task HeartbeatIssuedEvent(List issuedEventMessages) { bool isAck = false; - switch (issuedEventMessage.Type) + foreach (var issuedEventMessage in issuedEventMessages) { - case IssuedEventType.Heartbeat: - _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); - isAck = true; + var heartbeatEntity = await _messageReceivedHeartbeatEventRepository.FirstOrDefaultAsync(a => a.MessageId == issuedEventMessage.MessageId); + if (heartbeatEntity == null) + { + isAck = false; 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); + } + heartbeatEntity.AckTime = Clock.Now; + heartbeatEntity.IsAck = true; + await _messageReceivedHeartbeatEventRepository.UpdateAsync(heartbeatEntity); + } + // TODO:暂时ACK,等后续处理是否放到私信队列中 return isAck ? SubscribeAck.Success() : SubscribeAck.Fail(); } [KafkaSubscribe(ProtocolConst.SubscriberReceivedEventName)] - //[CapSubscribe(ProtocolConst.SubscriberReceivedEventName)] - public async Task ReceivedEvent(MessageReceived receivedMessage) + public async Task ReceivedEvent(MessageProtocolAnalysis receivedMessage) { var currentTime = Clock.Now; @@ -143,27 +114,25 @@ namespace JiShe.CollectBus.Subscribers } else { - //todo 会根据不同的协议进行解析,然后做业务处理 - TB3761 fN = await protocolPlugin.AnalyzeAsync(receivedMessage); - if(fN == null) - { - Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); - return SubscribeAck.Success(); - } - var tb3761FN = fN.FnList.FirstOrDefault(); - if (tb3761FN == null) - { - Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); - return SubscribeAck.Success(); - } + //TB3761? tB3761 = protocolPlugin.Analysis3761(receivedMessage.MessageHexString); + //if (tB3761 == null) + //{ + // Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); + // return SubscribeAck.Success(); + //} + //if (tB3761.DT == null || tB3761.AFN_FC == null) + //{ + // Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); + // return SubscribeAck.Success(); + //} //报文入库 var entity = new MeterReadingRecords() { ReceivedMessageHexString = receivedMessage.MessageHexString, - AFN = fN.Afn, - Fn = tb3761FN.Fn, + AFN = (AFN)receivedMessage.Data?.AFN_FC.AFN!, + Fn = receivedMessage.Data.DT.Fn, Pn = 0, FocusAddress = "", MeterAddress = "", @@ -187,38 +156,19 @@ namespace JiShe.CollectBus.Subscribers return SubscribeAck.Success(); } - [KafkaSubscribe(ProtocolConst.SubscriberHeartbeatReceivedEventName)] - //[CapSubscribe(ProtocolConst.SubscriberHeartbeatReceivedEventName)] - public async Task ReceivedHeartbeatEvent(MessageReceivedHeartbeat receivedHeartbeatMessage) + [KafkaSubscribe(ProtocolConst.SubscriberHeartbeatReceivedEventName, EnableBatch = true)] + public async Task ReceivedHeartbeatEvent(List receivedHeartbeatMessages) { - var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); - if (protocolPlugin == null) - { - _logger.LogError("协议不存在!"); - } - else - { - await protocolPlugin.HeartbeatAsync(receivedHeartbeatMessage); - await _messageReceivedHeartbeatEventRepository.InsertAsync(receivedHeartbeatMessage); - } + await _messageReceivedHeartbeatEventRepository.InsertManyAsync(receivedHeartbeatMessages); return SubscribeAck.Success(); } - [KafkaSubscribe(ProtocolConst.SubscriberLoginReceivedEventName)] - //[CapSubscribe(ProtocolConst.SubscriberLoginReceivedEventName)] - public async Task ReceivedLoginEvent(MessageReceivedLogin receivedLoginMessage) + [KafkaSubscribe(ProtocolConst.SubscriberLoginReceivedEventName,EnableBatch =true)] + public async Task ReceivedLoginEvent(List receivedLoginMessages) { - var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); - if (protocolPlugin == null) - { - _logger.LogError("协议不存在!"); - } - else - { - await protocolPlugin.LoginAsync(receivedLoginMessage); - await _messageReceivedLoginEventRepository.InsertAsync(receivedLoginMessage); - } + await _messageReceivedLoginEventRepository.InsertManyAsync(receivedLoginMessages); return SubscribeAck.Success(); } + } } diff --git a/services/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs b/services/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs index 65ecc01..e34c087 100644 --- a/services/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs +++ b/services/JiShe.CollectBus.Application/Subscribers/WorkerSubscriberAppService.cs @@ -1,59 +1,37 @@ -using System; -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; -using JiShe.CollectBus.IotSystems.MessageReceiveds; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.IoTDB.Interface; 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; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; +using JiShe.CollectBus.Kafka.Internal; using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; using TouchSocket.Sockets; -using Volo.Abp.Caching; -using Volo.Abp.Domain.Repositories; namespace JiShe.CollectBus.Subscribers { /// /// 定时抄读任务消息消费订阅 /// - [Route($"/worker/app/subscriber")] - public class WorkerSubscriberAppService : CollectBusAppService, IWorkerSubscriberAppService, ICapSubscribe, IKafkaSubscribe + public class WorkerSubscriberAppService : CollectBusAppService, IWorkerSubscriberAppService, IKafkaSubscribe { private readonly ILogger _logger; private readonly ITcpService _tcpService; - private readonly IServiceProvider _serviceProvider; - private readonly IRepository _deviceRepository; - private readonly IMeterReadingRecordRepository _meterReadingRecordsRepository; - + private readonly IIoTDbProvider _dbProvider; /// /// Initializes a new instance of the class. /// /// The logger. /// The TCP service. - /// The Device pepository. - /// The service provider. - public WorkerSubscriberAppService(ILogger logger, + /// IoTDB数据驱动 + public WorkerSubscriberAppService(ILogger logger, ITcpService tcpService, - IRepository deviceRepository, - IMeterReadingRecordRepository meterReadingRecordsRepository, - IServiceProvider serviceProvider) + IIoTDbProvider dbProvider) { _logger = logger; _tcpService = tcpService; - _serviceProvider = serviceProvider; - _deviceRepository = deviceRepository; - _meterReadingRecordsRepository = meterReadingRecordsRepository; + _dbProvider = dbProvider; } @@ -64,28 +42,12 @@ namespace JiShe.CollectBus.Subscribers /// /// /// - [HttpPost] - [Route("ammeter/oneminute/issued-event")] [KafkaSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] - //[CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName)] - public async Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) + public async Task AmmeterScheduledMeterOneMinuteReadingIssuedEvent(MeterReadingTelemetryPacketInfo receivedMessage) { _logger.LogInformation("1分钟采集电表数据下行消息消费队列开始处理"); - var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); - if (protocolPlugin == null) - { - _logger.LogError("【1分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); - } - else - { - var device = await _deviceRepository.FirstOrDefaultAsync(a => a.Number == receivedMessage.FocusAddress); - if (device != null) - { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); + return await SendMessagesAsync(receivedMessage); - } - } - return SubscribeAck.Success(); } /// @@ -93,28 +55,11 @@ namespace JiShe.CollectBus.Subscribers /// /// /// - [HttpPost] - [Route("ammeter/fiveminute/issued-event")] [KafkaSubscribe(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName)] - //[CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName)] - public async Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) + public async Task AmmeterScheduledMeterFiveMinuteReadingIssuedEvent(MeterReadingTelemetryPacketInfo receivedMessage) { _logger.LogInformation("5分钟采集电表数据下行消息消费队列开始处理"); - var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); - if (protocolPlugin == null) - { - _logger.LogError("【5分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); - } - else - { - var device = await _deviceRepository.FirstOrDefaultAsync(a => a.Number == receivedMessage.FocusAddress); - if (device != null) - { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); - - } - } - return SubscribeAck.Success(); + return await SendMessagesAsync(receivedMessage); } /// @@ -122,68 +67,69 @@ namespace JiShe.CollectBus.Subscribers /// /// /// - [HttpPost] - [Route("ammeter/fifteenminute/issued-event")] [KafkaSubscribe(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName)] - //[CapSubscribe(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName)] - public async Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) + public async Task AmmeterScheduledMeterFifteenMinuteReadingIssuedEvent(MeterReadingTelemetryPacketInfo receivedMessage) { _logger.LogInformation("15分钟采集电表数据下行消息消费队列开始处理"); - try - { - var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); - if (protocolPlugin == null) - { - _logger.LogError("【15分钟采集电表数据下行消息消费队列开始处理】协议不存在!"); - } - else - { - var device = await _deviceRepository.FirstOrDefaultAsync(a => a.Number == receivedMessage.FocusAddress); - if (device != null) - { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); + return await SendMessagesAsync(receivedMessage); + } - } - } - return SubscribeAck.Success(); - } - catch (Exception ex) - { - - throw ex; - } + /// + /// 电表自动阀控下行消息消费订阅 + /// + /// + /// + [KafkaSubscribe(ProtocolConst.AmmeterSubscriberWorkerAutoValveControlIssuedEventName)] + public async Task AmmeterScheduledAutoValveControl(MeterReadingTelemetryPacketInfo receivedMessage) + { + //todo 如果是时段自动阀控,需要检查当前的时间,如果时间在自动阀控时间段内,则发送自动阀控报文,否则不发送,尤其是消息队列阻塞或者延时过长的时候。以免造成生产事故。 + _logger.LogInformation("电表自动阀控下行消息消费队列开始处理"); + return await SendMessagesAsync(receivedMessage); } #endregion #region 水表消息采集 - + /// /// 水表数据下行消息消费订阅 /// /// /// - [HttpPost] - [Route("watermeter/fifteenminute/issued-event")] [KafkaSubscribe(ProtocolConst.WatermeterSubscriberWorkerAutoReadingIssuedEventName)] - //[CapSubscribe(ProtocolConst.WatermeterSubscriberWorkerAutoReadingIssuedEventName)] - public async Task WatermeterSubscriberWorkerAutoReadingIssuedEvent(ScheduledMeterReadingIssuedEventMessage receivedMessage) + public async Task WatermeterSubscriberWorkerAutoReadingIssuedEvent(MeterReadingTelemetryPacketInfo 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.FocusAddress); - if (device != null) - { - await _tcpService.SendAsync(device.ClientId, Convert.FromHexString(receivedMessage.MessageHexString)); - } - } - return SubscribeAck.Success(); + return await SendMessagesAsync(receivedMessage); } #endregion + + /// + /// 设备报文发送 + /// + /// 消息记录 + /// + private async Task SendMessagesAsync(MeterReadingTelemetryPacketInfo receivedMessage) + { + try + { + var checkResult = _tcpService.ClientExists(receivedMessage.FocusAddress); + if (checkResult) + { + await _tcpService.SendAsync(receivedMessage.FocusAddress, Convert.FromHexString(receivedMessage.IssuedMessageHexString)); + + receivedMessage.IsSend = true; + await _dbProvider.InsertAsync(receivedMessage); + + return SubscribeAck.Success(); + } + else + { + return SubscribeAck.Fail(); + } + } + catch (Exception) + { + throw; + } + } } } diff --git a/services/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs b/services/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs index b03193a..654abc4 100644 --- a/services/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs +++ b/services/JiShe.CollectBus.Application/Workers/CreateToBeIssueTaskWorker.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using Hangfire; using JiShe.CollectBus.Common.Consts; @@ -27,13 +28,15 @@ namespace JiShe.CollectBus.Workers { _logger = logger; RecurringJobId = nameof(CreateToBeIssueTaskWorker); - CronExpression = "* 10 * * * *"; + CronExpression = "0 0/1 * * * *"; + TimeZone = TimeZoneInfo.Local; this._scheduledMeterReadingService = scheduledMeterReadingService; } public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { + _logger.LogError($"{DateTime.Now}"); // await _scheduledMeterReadingService.CreateToBeIssueTasks(); } } diff --git a/services/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs b/services/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs index 12f3e37..925692e 100644 --- a/services/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs +++ b/services/JiShe.CollectBus.Application/Workers/EpiCollectWorker.cs @@ -10,7 +10,6 @@ using Volo.Abp.Uow; namespace JiShe.CollectBus.Workers { - [IgnoreJob] public class EpiCollectWorker : HangfireBackgroundWorkerBase, ITransientDependency,ICollectWorker { private readonly ILogger _logger; diff --git a/services/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs b/services/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs index 441b22a..005d46b 100644 --- a/services/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs +++ b/services/JiShe.CollectBus.Application/Workers/SubscriberFifteenMinuteWorker.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using Hangfire; using JiShe.CollectBus.ScheduledMeterReading; @@ -26,7 +27,8 @@ namespace JiShe.CollectBus.Workers { _logger = logger; RecurringJobId = nameof(SubscriberFifteenMinuteWorker); - CronExpression = "* 15 * * * *"; + CronExpression = "0 0/15 * * * *"; + TimeZone = TimeZoneInfo.Local; this._scheduledMeterReadingService = scheduledMeterReadingService; } diff --git a/services/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs b/services/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs index 0a61c63..fbd3668 100644 --- a/services/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs +++ b/services/JiShe.CollectBus.Application/Workers/SubscriberFiveMinuteWorker.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using Hangfire; using JiShe.CollectBus.ScheduledMeterReading; @@ -26,7 +27,8 @@ namespace JiShe.CollectBus.Workers { _logger = logger; RecurringJobId = nameof(SubscriberFiveMinuteWorker); - CronExpression = "* 5 * * * *"; + CronExpression = "0 0/5 * * * *"; + TimeZone = TimeZoneInfo.Local; this._scheduledMeterReadingService = scheduledMeterReadingService; } diff --git a/services/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs b/services/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs index 8b7cbfd..e9e0240 100644 --- a/services/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs +++ b/services/JiShe.CollectBus.Application/Workers/SubscriberOneMinuteWorker.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using Hangfire; using JiShe.CollectBus.ScheduledMeterReading; @@ -26,7 +27,8 @@ namespace JiShe.CollectBus.Workers { _logger = logger; RecurringJobId = nameof(SubscriberOneMinuteWorker); - CronExpression = "* 1 * * * *"; + CronExpression = "0 0/1 * * * *"; + TimeZone = TimeZoneInfo.Local; this._scheduledMeterReadingService = scheduledMeterReadingService; } diff --git a/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs b/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs index 2d9cc67..ee132d3 100644 --- a/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs +++ b/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs @@ -4,10 +4,12 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using JiShe.CollectBus.IoTDB.Attribute; -using JiShe.CollectBus.IoTDB.Provider; +using JiShe.CollectBus.IoTDB.Enums; +using JiShe.CollectBus.IoTDB.Model; namespace JiShe.CollectBus.Ammeters { + [EntityType(EntityTypeEnum.TableModel)] public class ElectricityMeter : IoTEntity { [ATTRIBUTEColumn] @@ -33,5 +35,8 @@ namespace JiShe.CollectBus.Ammeters [FIELDColumn] public double Power => Voltage * Current; + + [FIELDColumn] + public double? Currentd { get; set; } } } diff --git a/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeterTreeModel.cs b/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeterTreeModel.cs new file mode 100644 index 0000000..7fe7ebc --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeterTreeModel.cs @@ -0,0 +1,37 @@ +using JiShe.CollectBus.IoTDB.Attribute; +using JiShe.CollectBus.IoTDB.Enums; +using JiShe.CollectBus.IoTDB.Model; + +namespace JiShe.CollectBus.Ammeters +{ + [EntityType(EntityTypeEnum.TreeModel)] + public class ElectricityMeterTreeModel : IoTEntity + { + [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; } + + [FIELDColumn] + public double Current { get; set; } + + [FIELDColumn] + public double Power => Voltage * Current; + + [FIELDColumn] + public double? Currentd { get; set; } + } +} diff --git a/services/JiShe.CollectBus.Domain/IotSystems/Ammeters/AmmeterAutoValveControlSetting.cs b/services/JiShe.CollectBus.Domain/IotSystems/Ammeters/AmmeterAutoValveControlSetting.cs new file mode 100644 index 0000000..0116def --- /dev/null +++ b/services/JiShe.CollectBus.Domain/IotSystems/Ammeters/AmmeterAutoValveControlSetting.cs @@ -0,0 +1,107 @@ +using JiShe.CollectBus.Common.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IotSystems.Ammeters +{ + /// + /// 电表自动阀控制设置实体 + /// + public class AmmeterAutoValveControlSetting: DeviceCacheBasicModel + { + public int ID { get; set; } + + /// + /// 定时阀控名称 + /// + public string Name { get; set; } + + /// + /// 是否禁止: 1 禁止, 0 启用, + /// + public int IsForbid { get; set; } + + /// + /// 阀控命令:on 合闸(通电), off 断闸(断电) + /// + public string TripType { get; set; } + + /// + /// 执行周期:Once 一次、EachDay 每日、EachWeek 每周、EachMonth 每月、 + /// + public string LoopType { get; set; } + + /// + /// (专属字段)执行一次时,指定yyyy-MM-dd 00:00:00 (只用年月日) + /// + public DateTime? OnceWithDate { get; set; } + + /// + /// (专属字段)每日执行时排除(多个用英文逗号分隔),如:"周六,周日" + /// + public string EachDayWithout { get; set; } + + /// + /// (专属字段)每周执行时运行(多个用英文逗号分隔),如:"周一,周二,周日" + /// + public string EachWeekWith { get; set; } + + /// + /// (专属字段)每月执行时运行(多个用英文逗号分隔),如:"1,2,3,5" + /// + public string EachMonthWith { get; set; } + + /// + /// 阀控执行时分:HH:mm:00 + /// + public string TripTime { get; set; } + + /// + /// 项目ID + /// + public int ProjectID { get; set; } + + /// + /// 添加时间 + /// + public DateTime AddDate { get; set; } + + /// + /// 删除改成-1, + /// + public int State { get; set; } + + /// + /// 实际执行时间 + /// + public DateTime? WorkTime { get; set; } + + /// + /// 创建人ID + /// + public int CreateUserID { get; set; } + + /// + /// 采集编号 + /// + public string GatherCode { get; set; } + + /// + /// 集中器地址 + /// + public string Address { get; set; } + + /// + /// 集中器区域代码 + /// + public string AreaCode { get; set; } + + /// + /// 电表通信地址 + /// + public string AmmerterAddress { get; set; } + } +} diff --git a/services/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs b/services/JiShe.CollectBus.Domain/IotSystems/Ammeters/AmmeterInfo.cs similarity index 95% rename from services/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs rename to services/JiShe.CollectBus.Domain/IotSystems/Ammeters/AmmeterInfo.cs index 8b082bb..60d6c91 100644 --- a/services/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs +++ b/services/JiShe.CollectBus.Domain/IotSystems/Ammeters/AmmeterInfo.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.Ammeters +namespace JiShe.CollectBus.IotSystems.Ammeters { public class AmmeterInfo: DeviceCacheBasicModel { @@ -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 => $"{FocusAddress}:{MeteringCode}"; /// /// ZSet排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳 @@ -26,12 +26,7 @@ namespace JiShe.CollectBus.Ammeters /// 电表名称 /// public string Name { get; set; } - - /// - /// 集中器地址 - /// - public string FocusAddress { get; set; } - + /// /// 集中器地址 /// diff --git a/services/JiShe.CollectBus.Domain/IotSystems/CommunicationLogs/PacketLog.cs b/services/JiShe.CollectBus.Domain/IotSystems/CommunicationLogs/PacketLog.cs new file mode 100644 index 0000000..bd57e22 --- /dev/null +++ b/services/JiShe.CollectBus.Domain/IotSystems/CommunicationLogs/PacketLog.cs @@ -0,0 +1,43 @@ +using System; + +namespace JiShe.CollectBus.IotSystems.CommunicationLogs +{ + public class PacketLog : ICassandraEntity + { + /// + /// 下行报文 + /// + public string IssuedMessage { get; set; } = string.Empty; + + /// + /// 上行报文 + /// + public string ReportMessage { get; set; } = string.Empty; + + public DateTime? IssuedTime { get; set; } + public DateTime? ReportTime { get; set; } + + /// + /// 报文类型(是否需要回复) + /// + public PacketType PacketType { get; set; } + + public Guid Id { get; set; } + } + + public enum PacketType + { + /// + /// 只有下发,不需要回复 + /// + OnlyIssued, + /// + /// 只有上报,不需要下发 + /// + OnlyReport, + /// + /// 下发并且需要回复 + /// + IssuedAndReport + } +} diff --git a/services/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs b/services/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs index 91a5ce7..8861c70 100644 --- a/services/JiShe.CollectBus.Domain/IotSystems/Devices/Device.cs +++ b/services/JiShe.CollectBus.Domain/IotSystems/Devices/Device.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.Enums; +using Volo.Abp.Auditing; using Volo.Abp.Domain.Entities; +using Volo.Abp.Logging; namespace JiShe.CollectBus.IotSystems.Devices { - public class Device : AggregateRoot + public class Device : BasicAggregateRoot { /// /// Device @@ -20,6 +24,7 @@ namespace JiShe.CollectBus.IotSystems.Devices /// public Device(string number, string clientId, DateTime firstOnlineTime, DateTime lastOnlineTime, DeviceStatus status) { + Id = Guid.NewGuid(); Number = number; FirstOnlineTime = firstOnlineTime; LastOnlineTime = lastOnlineTime; @@ -30,6 +35,7 @@ namespace JiShe.CollectBus.IotSystems.Devices /// /// 集中器编号,在集中器登录时解析获取,并会更新为当前TCP连接的最新ClientId /// + [PartitionKey] public string Number { get; set; } /// @@ -55,6 +61,7 @@ namespace JiShe.CollectBus.IotSystems.Devices /// /// 设备状态 /// + [PartitionKey] public DeviceStatus Status { get; set; } /// diff --git a/services/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs b/services/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs index 072abcb..09294c1 100644 --- a/services/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs +++ b/services/JiShe.CollectBus.Domain/IotSystems/MessageIssueds/MessageIssued.cs @@ -11,19 +11,14 @@ using Volo.Abp.Domain.Entities; namespace JiShe.CollectBus.IotSystems.MessageIssueds { [CassandraTable] - public class MessageIssued:IEntity + public class MessageIssued: ICassandraEntity { 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] + [PartitionKey] public string Id { get; set; } - - public object?[] GetKeys() - { - return new object[] { Id }; - } } } diff --git a/services/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/MessageReceived.cs b/services/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/MessageReceived.cs index 4fad589..f194d00 100644 --- a/services/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/MessageReceived.cs +++ b/services/JiShe.CollectBus.Domain/IotSystems/MessageReceiveds/MessageReceived.cs @@ -50,4 +50,9 @@ namespace JiShe.CollectBus.IotSystems.MessageReceiveds public DateTime? AckTime { get; set; } } + + public class MessageProtocolAnalysis : MessageReceived + { + public T Data { get; set; } = default!; + } } diff --git a/services/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs b/services/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs index c3f75d3..e35d245 100644 --- a/services/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs +++ b/services/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs @@ -1,5 +1,9 @@ -using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Encrypt; +using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.IoTDB.Attribute; +using JiShe.CollectBus.IoTDB.Enums; +using JiShe.CollectBus.IoTDB.Model; using System; using System.Collections.Generic; using System.Linq; @@ -11,80 +15,82 @@ using Volo.Abp.Domain.Entities.Auditing; namespace JiShe.CollectBus.IotSystems.MeterReadingRecords { /// - /// 抄读任务Redis缓存数据记录 + /// 抄读任务数据 /// - public class MeterReadingTelemetryPacketInfo : DeviceCacheBasicModel + [EntityType(EntityTypeEnum.TableModel)] + public class MeterReadingTelemetryPacketInfo : IoTEntity { /// - /// 关系映射标识,用于ZSet的Member字段和Set的Value字段,具体值可以根据不同业务场景进行定义 + /// 排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳、或者某一个固定的标识1 /// - public override string MemberId => $"{FocusId}:{MeterId}:{ItemCode}"; - - /// - /// ZSet排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳 - /// - public override long ScoreValue => ((long)FocusId << 32) | (uint)DateTime.Now.Ticks; - - + [FIELDColumn] + public string ScoreValue { get; set; } + /// /// 是否手动操作 /// + [FIELDColumn] public bool ManualOrNot { get; set; } /// /// 任务数据唯一标记 /// + [FIELDColumn] public string TaskMark { get; set; } - /// - /// 时间戳标记,IoTDB时间列处理,上报通过构建标记获取唯一标记匹配时间戳。 - /// - public long Timestamps { get; set; } - /// /// 是否超时 /// + [FIELDColumn] public bool IsTimeout { get; set; } = false; /// /// 待抄读时间 /// + [FIELDColumn] public DateTime PendingCopyReadTime { get; set; } - - + + /// + /// 集中器Id + /// + [FIELDColumn] + public int FocusId { get; set; } + + /// /// 集中器地址 /// + [FIELDColumn] public string FocusAddress { get; set; } - + + /// + /// 表Id + /// + [FIELDColumn] + public int MeterId { get; set; } + /// /// 表地址 /// + [FIELDColumn] public string MeterAddress { get; set; } - /// - /// 表类型 - /// - public MeterTypeEnum MeterType { get; set; } - - /// - /// 项目ID - /// - public int ProjectID { get; set; } - /// /// 数据库业务ID /// + [FIELDColumn] public int DatabaseBusiID { get; set; } /// /// AFN功能码 /// - public AFN AFN { get; set; } + [FIELDColumn] + public int AFN { get; set; } /// /// 抄读功能码 /// + [FIELDColumn] public int Fn { get; set; } /// @@ -95,47 +101,80 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// /// 采集项编码 /// - public string ItemCode { get; set;} - + [FIELDColumn] + public string ItemCode { get; set; } + + + /// + /// 子项编码,一般用于透明转发的编码 + /// + [FIELDColumn] + public string SubItemCode { get; set; } + + /// + /// 帧序列域 SEQ + /// + public int Seq { get; set; } + + /// + /// 地址域A3的主站地址MSA + /// + [FIELDColumn] + public int MSA { get; set; } + + /// + /// 是否发送 + /// + [FIELDColumn] + public bool IsSend { get; set; } /// /// 创建时间 /// + [FIELDColumn] public DateTime CreationTime { get; set; } /// /// 下发消息内容 /// + [FIELDColumn] public string IssuedMessageHexString { get; set; } /// /// 下发消息Id /// + [FIELDColumn] public string IssuedMessageId { get; set; } /// /// 消息上报内容 /// + [FIELDColumn] public string? ReceivedMessageHexString { get; set; } /// /// 消息上报时间 /// + [FIELDColumn] public DateTime? ReceivedTime { get; set; } /// /// 上报消息Id /// - public string ReceivedMessageId { get; set; } + [FIELDColumn] + public string ReceivedMessageId { get; set; } /// /// 上报报文解析备注,异常情况下才有 /// + [FIELDColumn] public string ReceivedRemark { get; set; } - - //public void CreateDataId(Guid Id) - //{ - // this.Id = Id; - //} + + /// + /// 是否已上报 + /// + [FIELDColumn] + public bool IsReceived { get; set; } + } } diff --git a/services/JiShe.CollectBus.Domain/IotSystems/Protocols/ProtocolInfo.cs b/services/JiShe.CollectBus.Domain/IotSystems/Protocols/ProtocolInfo.cs index c193535..630b717 100644 --- a/services/JiShe.CollectBus.Domain/IotSystems/Protocols/ProtocolInfo.cs +++ b/services/JiShe.CollectBus.Domain/IotSystems/Protocols/ProtocolInfo.cs @@ -16,6 +16,7 @@ namespace JiShe.CollectBus.IotSystems.Protocols /// public ProtocolInfo(string name, string baseProtocol, string type, string description, string regularExpression) { + Code = $"PL-{DateTime.Now:yyyyMMddHHmmss}"; Name = name; Type = type; Description = description; @@ -23,6 +24,11 @@ namespace JiShe.CollectBus.IotSystems.Protocols BaseProtocol = baseProtocol; } + /// + /// 协议编码,唯一识别 + /// + public string Code { get; set; } + /// /// 协议名称 /// diff --git a/services/JiShe.CollectBus.Domain/IotSystems/Watermeter/WatermeterInfo.cs b/services/JiShe.CollectBus.Domain/IotSystems/Watermeter/WatermeterInfo.cs index 966192b..eac70a1 100644 --- a/services/JiShe.CollectBus.Domain/IotSystems/Watermeter/WatermeterInfo.cs +++ b/services/JiShe.CollectBus.Domain/IotSystems/Watermeter/WatermeterInfo.cs @@ -1,4 +1,5 @@ -using JiShe.CollectBus.Common.Enums; +using FreeSql.DataAnnotations; +using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Models; using System; using System.Collections.Generic; @@ -12,7 +13,19 @@ namespace JiShe.CollectBus.IotSystems.Watermeter /// 水表信息 /// public class WatermeterInfo: DeviceCacheBasicModel - { + { + /// + /// 关系映射标识,用于ZSet的Member字段和Set的Value字段,具体值可以根据不同业务场景进行定义 + /// + [Column(IsIgnore = true)] + public override string MemberId => $"{FocusAddress}:{MeteringCode}"; + + /// + /// ZSet排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳 + /// + [Column(IsIgnore = true)] + public override long ScoreValue => ((long)FocusId << 32) | (uint)DateTime.Now.Ticks; + /// /// 水表名称 /// @@ -21,11 +34,6 @@ namespace JiShe.CollectBus.IotSystems.Watermeter /// 表密码 /// public string Password { get; set; } - - /// - /// 集中器地址 - /// - public string FocusAddress { get; set; } /// /// 一个集中器下的[MeteringCode]必须唯一。 PN @@ -82,6 +90,7 @@ namespace JiShe.CollectBus.IotSystems.Watermeter //// 电表= 1,水表= 2,燃气表= 3,热能表= 4,水表流量计=5,燃气表流量计=6,特殊电表=7 /// public MeterTypeEnum MeterType { get; set; } + /// /// 设备品牌; /// (当 MeterType = 水表, 如 威铭、捷先 等) @@ -130,12 +139,17 @@ namespace JiShe.CollectBus.IotSystems.Watermeter /// 采集器编号 /// public string GatherCode { get; set; } - + /// /// 项目ID /// public int ProjectID { get; set; } + /// + /// 数据库业务ID + /// + public int DatabaseBusiID { get; set; } + /// /// 是否异常集中器 0:正常,1异常 /// diff --git a/services/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj b/services/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj index a4dbc5d..a60ffe1 100644 --- a/services/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj +++ b/services/JiShe.CollectBus.Domain/JiShe.CollectBus.Domain.csproj @@ -9,9 +9,9 @@ - - - + + + @@ -34,9 +34,4 @@ - - - - - diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN10_F10_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN10_F10_AnalysisDto.cs new file mode 100644 index 0000000..46b7581 --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN10_F10_AnalysisDto.cs @@ -0,0 +1,95 @@ +using JiShe.CollectBus.Protocol.Dto; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN10_F10_AnalysisDto + { + /// + /// 本次电能表/交流采样装置配置数量 n + /// + public int ConfigNum { get; set; } + + /// + /// 电能表/交流采样装置配置信息 + /// + public List AFN10F10Entitys { get; set; } = new List(); + } + + + public class AFN10F10Entity + { + /// + /// 电能表/交流采样装置序号 + /// + public int SerialNum { get; set; } + + /// + /// 所属测量点号 + /// + public int Point { get; set; } + + /// + /// 通信速率 + /// + public int BaudRate { get; set; } + + /// + /// 端口号 + /// + public int Port { get; set; } + + + /// + /// 通信协议类型 + /// + public string RuleType { get; set; } + + /// + /// 通信地址 + /// + public string ComAddress { get; set; } + + /// + /// 通信密码 + /// + public string ComPwd { get; set; } + + + /// + /// 电能费率个数 + /// + public int ElectricityRatesNum { get; set; } + + + /// + /// 有功电能示值整数位及小数位个数 + /// + public int IntegerBitsNum { get; set; } + + /// + /// 有功电能示值小数位个数 + /// + public int DecimalPlacesNum { get; set; } + + /// + /// 所属采集器通信地址 + /// + public string CollectorAddress{ get; set; } + + /// + /// 用户大类号 + /// + public int UserCategoryNum { get; set;} + + /// + /// 用户小类号 + /// + public int UserSubclassNum { get; set; } + } + +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN10_F66_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN10_F66_AnalysisDto.cs new file mode 100644 index 0000000..c3c8306 --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN10_F66_AnalysisDto.cs @@ -0,0 +1,45 @@ +using JiShe.CollectBus.Protocol.Dto; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN10_F66_AnalysisDto + { + /// + /// 定时发送周期 + /// 1:用 D6~D7 编码表示,取值 0~3 依次表示分、时、日、月 + /// 2:用 D0~D5 表示,为定时上报数据的时间周期 + /// + public int Cycle { get; set; } + /// + /// 定时发送周期(单位) + /// + public int Unit { get; set; } + /// + /// 发送基准时间 + /// + public DateTime BaseTime { get; set; } + + /// + /// 曲线数据提取倍率 + /// + public int CurveRatio { get; set; } + + /// + /// 任务号 + /// + public int Pn { get; set; } + + public List Details { get; set; } = new List(); + } + + public class SetAutoItemCodeDetails + { + public int Pn { get; set; } + public int Fn { get; set; } + } +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F129_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F129_AnalysisDto.cs new file mode 100644 index 0000000..30719e9 --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F129_AnalysisDto.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN12_F129_AnalysisDto : AnalysisBaseDto + { + public DateTime ReadTime { get; set; } + } +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F130_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F130_AnalysisDto.cs new file mode 100644 index 0000000..9a9259b --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F130_AnalysisDto.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN12_F130_AnalysisDto : AnalysisBaseDto + { + public DateTime ReadTime { get; set; } + } +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F131_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F131_AnalysisDto.cs new file mode 100644 index 0000000..edc5ca7 --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F131_AnalysisDto.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN12_F131_AnalysisDto: AnalysisBaseDto + { + public DateTime ReadTime { get; set; } + } +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F132_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F132_AnalysisDto.cs new file mode 100644 index 0000000..1625f15 --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F132_AnalysisDto.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN12_F132_AnalysisDto: AnalysisBaseDto + { + public DateTime ReadTime { get; set; } + } +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F145_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F145_AnalysisDto.cs new file mode 100644 index 0000000..96f0c37 --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F145_AnalysisDto.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN12_F145_AnalysisDto : AnalysisBaseDto + { + /// + /// 最大需量时标 + /// + public string TimeSpan { get; set; } + + public DateTime ReadingDate { get; set; } + } + +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F149_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F149_AnalysisDto.cs new file mode 100644 index 0000000..fb9dafe --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F149_AnalysisDto.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN12_F149_AnalysisDto : AnalysisBaseDto + { + /// + /// 最大需量时标 + /// + public string TimeSpan { get; set; } + + public DateTime ReadingDate { get; set; } + } +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F188_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F188_AnalysisDto.cs new file mode 100644 index 0000000..1afb8ff --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F188_AnalysisDto.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN12_F188_AnalysisDto : AnalysisBaseDto + { + } +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F25_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F25_AnalysisDto.cs new file mode 100644 index 0000000..ca8b8ff --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F25_AnalysisDto.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN12_F25_AnalysisDto: AnalysisBaseDto + { + /// + /// 读取时间 + /// + public DateTime ReadTime { get; set; } + } +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F2_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F2_AnalysisDto.cs new file mode 100644 index 0000000..e069382 --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F2_AnalysisDto.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN12_F2_AnalysisDto: AnalysisBaseDto + { + } +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F33_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F33_AnalysisDto.cs new file mode 100644 index 0000000..b917969 --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F33_AnalysisDto.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN12_F33_AnalysisDto + { + /// + /// 终端抄表时间 + /// + public string ReadTime { get; set; } + + /// + /// 费率数M(1≤M≤12) + /// + public int RatingCount { get; set; } + /// + /// 当前正向有功总电能示值 + /// + public ParentNodes F_A_Kwh { get; set; } + /// + /// 当前正向无功(组合无功1)总电能示值 + /// + public ParentNodes R_R_Kvarh { get; set; } + + /// + /// 当前一象限无功总电能示值 + /// + public ParentNodes Q1_R_Kvarh { get; set; } + /// + /// 当前四象限无功总电能示值 + /// + public ParentNodes Q4_R_Kvarh { get; set; } + } + + public class ParentNodes + { + /// + /// 总电能示值 + /// + public decimal Total_Value { get; set; } + public List childNodes { get; set; } + } + public class ChildNodes + { + /// + /// 费率总电能示值 + /// + public decimal Value { get; set; } + + } +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F49_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F49_AnalysisDto.cs new file mode 100644 index 0000000..ce0aeb7 --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN12_F49_AnalysisDto.cs @@ -0,0 +1,15 @@ +using JiShe.CollectBus.Protocol.Dto; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN12_F49_AnalysisDto: AnalysisBaseDto + { + } + + +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN9_F1_AnalysisDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN9_F1_AnalysisDto.cs new file mode 100644 index 0000000..7b7bc3a --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AFN9_F1_AnalysisDto.cs @@ -0,0 +1,38 @@ +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AFN9_F1_AnalysisDto + { + public int FocusID { get; set; } + public string? AreaCode { get; set; } + public string? Address { get; set; } + + /// + /// 厂商代号 + /// + public string? MakerNo { get; set; } + /// + /// 设备编号 + /// + public string? DeviceNo { get; set; } + /// + /// 终端软件版本号 + /// + public string? SoftwareVersion { get; set; } + /// + /// 终端软件发布日期:日月年 + /// + public string? SoftwareReleaseDate { get; set; } + + /// + /// 硬件软件版本号 + /// + public string? HardwareVersion { get; set; } + /// + /// 硬件软件发布日期:日月年 + /// + public string? HardwareReleaseDate { get; set; } + + public string? AddDate { get; set; } + } + +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AnalysisBaseDto.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AnalysisBaseDto.cs new file mode 100644 index 0000000..f637e57 --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/AnalysisBaseDto.cs @@ -0,0 +1,85 @@ +using JiShe.CollectBus.Common.Enums; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Protocol.Dto +{ + public class AnalysisBaseDto + { + + /// + /// 是否有效数据 + /// + public bool ValidData { get; set; } = true; + + /// + /// 数据类型 + /// + public string DataType { get; set; } = null!; + + /// + /// 错误码信息 + /// + public string? ErrorCodeMsg { get; set; } + + /// + /// 字段名 + /// + public string? FiledName { get; set; } + + /// + /// 字段描述 + /// + public string? FiledDesc { get; set; } + + /// + /// 数据时标 + /// + public DateTime TimeSpan { get; set; } + + /// + /// 数据时间 + /// + public DateTime DataTime { get; set; } + + /// + /// 项目Id + /// + public int ProjectId { get; set; } + + /// + /// 电表ID + /// + public int MeterId { get; set; } + + /// + /// 表类型 + /// + public MeterTypeEnum MeterType { get; set;} + + + /// + /// 数据业务库ID + /// + public int DatabaseBusiID { get; set; } + + /// + /// 表地址 + /// + public string MeterAddress { get; set; } = null!; + + } + + public class AnalysisBaseDto : AnalysisBaseDto + { + /// + /// 抄读值 + /// + public T? DataValue { get; set; } + } + +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/Dto/UnitDataAnalysis.cs b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/UnitDataAnalysis.cs new file mode 100644 index 0000000..b691b3a --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/Dto/UnitDataAnalysis.cs @@ -0,0 +1,67 @@ +using JiShe.CollectBus.Common.Enums; + +namespace JiShe.CollectBus.Protocol.Dto +{ + public class UnitDataAnalysis + { + /// + /// 集中器地址 + /// + public string Code { get; set; } = null!; + + /// + /// AFN功能码 + /// + public int AFN { get; set; } + + /// + /// 信息点 + /// + public int Pn { get; set; } + + /// + /// 信息类 + /// + public int Fn { get; set; } + + /// + /// 主站地址 MSA + /// + public int MSA { get; set; } + + /// + /// 响应帧序号 + /// + public int PSEQ { get; set; } + + /// + /// 响应帧 + /// + public string? HexMessage { get; set; } = null; + + /// + /// 消息ID + /// + public string? MessageId { get; set; } + + /// + /// 采集密度 + /// + public int TimeDensity { get; set; } + + /// + /// 采集密度单位 + /// + public DensityUnit DensityUnit { get; set; }= DensityUnit.Minute; + + } + + public class UnitDataAnalysis: UnitDataAnalysis + { + /// + /// 数据 + /// + public T? Data { get; set; } + } + +} diff --git a/services/JiShe.CollectBus.Domain/Protocol3761/TB3761.cs b/services/JiShe.CollectBus.Domain/Protocol3761/TB3761.cs new file mode 100644 index 0000000..8f9f776 --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Protocol3761/TB3761.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol3761 +{ + + /// + /// 解析3761报文 + /// + public class TB3761 + { + /// + /// 报文 + /// + public BaseHexMessage BaseHexMessage { get; set;}=new BaseHexMessage(); + + /// + /// 报文ID + /// + public string? MessageId { get; set; } + + /// + /// 控制域C + /// + public C C { get; set; } = new C(); + + /// + /// 地址域A + /// + public A A { get; set; } = new A(); + + /// + /// 帧序列域 SEQ + /// + public SEQ SEQ { get; set; } = new SEQ(); + + /// + /// 用户数据区 + /// 功能码 + /// + public AFN_FC AFN_FC { get; set; } = new AFN_FC(); + + /// + /// 用户数据区 + /// 信息点DA Pn + /// + public DA DA { get; set; } = new DA(); + + /// + /// 用户数据区 + /// 信息类DT Fn + /// + public DT DT { get; set; } = new DT(); + + /// + /// 数据单元标识和数据单元格式 + /// + public UnitData UnitData { get; set; } = new UnitData(); + } + + #region + + /// + /// 报文信息 + /// + public class BaseHexMessage + { + /// + /// 报文 + /// + public string? HexMessageString { get; set; } + + /// + /// 报文数组 + /// + public List? HexMessageList { get; set; } + } + + /// + /// 控制域C + /// + public class C + { + /// + /// 控制域C报文 + /// + public BaseHexMessage? BaseHexMessage { get; set; } + + /// + /// 传输方向位D7 DIR=0,表示此帧报文是由主站发出的下行报文;DIR=1,表示此帧报文是由终端发出的上行报文。 + /// + public int DIR { get; set; } + /// + /// D6启动标志位 0:表示此帧报文来自从动站(终端),1:表示此帧报文来自启动站(服务端) + /// + public int PRM { get; set; } + /// + /// D5下行:帧计数位(FCB)/上行(ACD):要求访问位(终端有重要事件等待访问), + /// + public int FCB { get; set; } + /// + /// 下行:帧计数有效位(决定FCB位有效/无效)/上行:保留 D4 + /// + public int FCV { get; set; } + /// + /// 功能码 D0-D3 + /// + public int FC { get; set; } + + } + + + /// + /// 地址域A + /// + public class A + { + /// + /// 地址域报文 + /// + public BaseHexMessage? BaseHexMessage { get; set; } + + /// + /// 集中器/终端编码 + /// + public string? Code { get; set; } + + /// + /// 行政区划码A1 + /// + public string? A1 { get; set; } + /// + /// 终端地址A2 + /// + public int A2 { get; set; } + /// + /// 站地址和组地址标志A3 + /// + public A3? A3 { get; set; } + } + + /// + /// 站地址和组地址标志A3 + /// + public class A3 + { + /// + /// 地址域A3报文 + /// + public BaseHexMessage? BaseHexMessage { get; set; } + + /// + /// 终端组地址标志,D0=0即False 表示终端地址A2 为单地址 + /// + public int D0 { get; set; } + /// + /// 主站地址 MSA D1~D7 组成 0~127 + /// + public int D1_D7 { get; set; } + } + + + /// + /// 帧序列域 SEQ + /// + public class SEQ + { + /// + /// 帧序列域报文 + /// + public BaseHexMessage? BaseHexMessage { get; set; } + /// + /// 响应帧序号 + /// + public int PSEQ { get; set; } + /// + /// CON为“1”,表示需要对该帧报文进行确认;置“0”,表示不需要对该帧报文进行确认。 + /// + public int CON { get; set; } + /// + /// 末帧标志 + /// + public int FIN { get; set; } + /// + /// 首帧标志 + /// + public int FIR { get; set; } + /// + /// 帧时间标签有效位,TpV=0,表示在附加信息域中无时间标签Tp;TpV=1,表示在附加信息域中带有时间标签Tp + /// + public int TpV { get; set; } + } + + + + /// + /// 用户数据区 + /// 功能码 + /// + public class AFN_FC + { + public BaseHexMessage? BaseHexMessage { get; set; } + + /// + /// 功能码 + /// + public int AFN { get; set; } + } + + + /// + /// 用户数据区 + /// 信息点DA Pn + /// + public class DA + { + public BaseHexMessage? BaseHexMessage { get; set; } + + /// + /// 信息点 DA Pn + /// + public int Pn { get; set; } + } + + + /// + /// 用户数据区 + /// 信息类DT Fn + /// + public class DT + { + public BaseHexMessage? BaseHexMessage { get; set; } + /// + /// 信息类 DT Fn + /// + public int Fn { get; set; } + } + + + /// + /// 数据单元标识和数据单元格式 + /// + public class UnitData: BaseHexMessage{ } + + #endregion + +} diff --git a/shared/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs b/shared/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs index 5ee6d8f..72db97c 100644 --- a/shared/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs +++ b/shared/JiShe.CollectBus.Common/Attributes/CassandraTableAttribute.cs @@ -21,4 +21,20 @@ namespace JiShe.CollectBus.Common.Attributes { } } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class PartitionKeyAttribute : Attribute + { + public PartitionKeyAttribute() + { + } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class ClusteringKeyAttribute : Attribute + { + public ClusteringKeyAttribute() + { + } + } } diff --git a/shared/JiShe.CollectBus.Common/Attributes/IgnoreJobAttribute.cs b/shared/JiShe.CollectBus.Common/Attributes/IgnoreJobAttribute.cs deleted file mode 100644 index 464d1ee..0000000 --- a/shared/JiShe.CollectBus.Common/Attributes/IgnoreJobAttribute.cs +++ /dev/null @@ -1,13 +0,0 @@ -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 - { - } -} diff --git a/shared/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs b/shared/JiShe.CollectBus.Common/Attributes/NumericalOrderAttribute.cs similarity index 92% rename from shared/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs rename to shared/JiShe.CollectBus.Common/Attributes/NumericalOrderAttribute.cs index a062f16..b337ae1 100644 --- a/shared/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs +++ b/shared/JiShe.CollectBus.Common/Attributes/NumericalOrderAttribute.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.Common.AttributeInfo +namespace JiShe.CollectBus.Common.Attributes { /// /// 排序序号 diff --git a/shared/JiShe.CollectBus.Common/Attributes/TopicNameAttribute.cs b/shared/JiShe.CollectBus.Common/Attributes/TopicNameAttribute.cs deleted file mode 100644 index 7e404a5..0000000 --- a/shared/JiShe.CollectBus.Common/Attributes/TopicNameAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -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/shared/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs b/shared/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs index f4dd11d..a96c16a 100644 --- a/shared/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs +++ b/shared/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs @@ -375,22 +375,30 @@ namespace JiShe.CollectBus.Common.BuildSendDatas #region 排序 var timeSets = timeSetDetails; - Dictionary dicTsDetails = new Dictionary(); + // 旧 + //Dictionary dicTsDetails = new Dictionary(); + //foreach (var timeSet in timeSets) + //{ + // int firstMonty = timeSet.Months[0]; + // if (!dicTsDetails.Keys.Contains(firstMonty)) + // dicTsDetails.Add(firstMonty, timeSet); + //} + + //var sortKeys = dicTsDetails.Keys.OrderBy(n => n).ToList(); + //List orderTsDetails = new List(); + //foreach (var key in sortKeys) + //{ + // orderTsDetails.Add(dicTsDetails[key]); + //} + + //timeSetDetails = orderTsDetails; + + // 新 foreach (var timeSet in timeSets) { - int firstMonty = timeSet.Months[0]; - if (!dicTsDetails.Keys.Contains(firstMonty)) - dicTsDetails.Add(firstMonty, timeSet); + timeSet.Months = timeSet.Months.OrderBy(m => m).ToArray(); } - - var sortKeys = dicTsDetails.Keys.OrderBy(n => n).ToList(); - List orderTsDetails = new List(); - foreach (var key in sortKeys) - { - orderTsDetails.Add(dicTsDetails[key]); - } - - timeSetDetails = orderTsDetails; + timeSetDetails = timeSets; #endregion diff --git a/shared/JiShe.CollectBus.Common/BuildSendDatas/Build645SendData.cs b/shared/JiShe.CollectBus.Common/BuildSendDatas/Build645SendData.cs index d51810c..023cb7f 100644 --- a/shared/JiShe.CollectBus.Common/BuildSendDatas/Build645SendData.cs +++ b/shared/JiShe.CollectBus.Common/BuildSendDatas/Build645SendData.cs @@ -25,7 +25,7 @@ namespace JiShe.CollectBus.Common.BuildSendDatas /// 电表地址 /// 特殊控制码 /// 密码 - /// 是否为开阀 + /// 是否为开阀,true合闸,false关闸 /// 型号码 /// public static List BuildAmmeterValveControlSendDataUnit(string ammeterAddress, string specialControlCode, string password, bool state, string modelCode = "") @@ -69,6 +69,7 @@ namespace JiShe.CollectBus.Common.BuildSendDatas var strP = password.StrAddSpan().StrReverseOrder(); var strSJY = " " + pwdLevel + " " + strP + " 01 00 00 00 " + code + " 00 " + strDate; var dataUnit = strSJY.Replace(" ", "").StringToPairs(); + var dataList = Build645SendCommand(ammeterAddress, "1C", dataUnit); return dataList; diff --git a/shared/JiShe.CollectBus.Common/Consts/CommonConst.cs b/shared/JiShe.CollectBus.Common/Consts/CommonConst.cs index 471c897..c3f1e9a 100644 --- a/shared/JiShe.CollectBus.Common/Consts/CommonConst.cs +++ b/shared/JiShe.CollectBus.Common/Consts/CommonConst.cs @@ -11,25 +11,31 @@ namespace JiShe.CollectBus.Common.Consts /// public class CommonConst { - /// - /// 服务器标识 - /// - public const string ServerTagName = "ServerTagName"; /// /// Kafka /// public const string Kafka = "Kafka"; + /// + /// 服务器标识 + /// + public const string ServerTagName = $"{Kafka}:ServerTagName"; + /// /// Kafka副本数量 /// - public const string KafkaReplicationFactor = "KafkaReplicationFactor"; + public const string KafkaReplicationFactor = $"{Kafka}:KafkaReplicationFactor"; /// /// Kafka主题分区数量 /// - public const string NumPartitions = "NumPartitions"; + public const string NumPartitions = $"{Kafka}:NumPartitions"; + + /// + /// 首次采集时间 + /// + public const string FirstCollectionTime = "FirstCollectionTime"; } } diff --git a/shared/JiShe.CollectBus.Common/Consts/DataFieldConst.cs b/shared/JiShe.CollectBus.Common/Consts/DataFieldConst.cs new file mode 100644 index 0000000..9cde24b --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Consts/DataFieldConst.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Consts +{ + /// + /// 数据保存字段常量 + /// + public class DataFieldConst + { + public const string ZYGDN = "ZYGDN"; + public const string ZWGDN = "ZWGDN"; + public const string FYGDN = "FYGDN"; + public const string FWGDN = "FWGDN"; + + public const string ZYGDNSZ = "ZYGDNSZ"; + public const string ZWGDNSZ = "ZWGDNSZ"; + public const string FYGDNSZ = "FYGDNSZ"; + public const string FWGDNSZ = "FWGDNSZ"; + + public const string WGDN1 = "WGDN1"; + public const string WGDN2 = "WGDN2"; + public const string WGDN3 = "WGDN3"; + public const string WGDN4 = "WGDN4"; + + public const string WGDNSZ1 = "WGDNSZ1"; + public const string WGDNSZ2 = "WGDNSZ2"; + public const string WGDNSZ3 = "WGDNSZ3"; + public const string WGDNSZ4 = "WGDNSZ4"; + + public const string YGGL = "YGGL"; + public const string AYGGL = "AYGGL"; + public const string BYGGL = "BYGGL"; + public const string CYGGL = "CYGGL"; + + public const string WGGL = "WGGL"; + public const string AWGGL = "AWGGL"; + public const string BWGGL = "BWGGL"; + public const string CWGGL = "CWGGL"; + + public const string GLYS = "GLYS"; + public const string AGLYS = "AGLYS"; + public const string BGLYS = "BGLYS"; + public const string CGLYS = "CGLYS"; + + public const string ADY = "ADY"; + public const string BDY = "BDY"; + public const string CDY = "CDY"; + + public const string ADL = "ADL"; + public const string BDL = "BDL"; + public const string CDL = "CDL"; + public const string LXDL = "LXDL"; + + public const string PowerGridFrequency = "PowerGridFrequency"; + + public const string Ua = "Ua"; + public const string Ub = "Ub"; + public const string Uc = "Uc"; + + public const string Ia = "Ia"; + public const string Ib = "Ib"; + public const string Ic = "Ic"; + + } + + public class ConstGatherDataType + { + public const string ZYGDN = "0D_97"; //正向有功总电能量 + public const string ZWGDN = "0D_98"; //正向无功总电能量曲线 + public const string FYGDN = "0D_99"; //反向有功总电能量曲线 + public const string FWGDN = "0D_100"; //反向无功总电能量曲线 + + public const string ZYGDNSZ = "0D_101"; //正向有功总电能示值曲线 + public const string ZWGDNSZ = "0D_102"; //正向无功总电能示值曲线 + public const string FYGDNSZ = "0D_103"; //反向有功总电能示值曲线 + public const string FWGDNSZ = "0D_104"; //反向无功总电能示值曲线 + + public const string WGDNSZ1 = "0D_145"; //一象限无功总电能示值曲线 + public const string WGDNSZ4 = "0D_146"; //四象限无功总电能示值曲线 + public const string WGDNSZ2 = "0D_147"; //二象限无功总电能示值曲线 + public const string WGDNSZ3 = "0D_148"; //三象限无功总电能示值曲线 + + public const string YGGL = "0D_81"; //有功功率曲线 + public const string AYGGL = "0D_82"; //A相有功功率曲线 + public const string BYGGL = "0D_83"; //B相有功功率曲线 + public const string CYGGL = "0D_84"; //C相有功功率曲线 + + public const string WGGL = "0D_85"; //无功功率曲线 + public const string AWGGL = "0D_86"; //A相无功功率曲线 + public const string BWGGL = "0D_87"; //B相无功功率曲线 + public const string CWGGL = "0D_88"; //C相无功功率曲线 + + public const string GLYS = "0D_105"; // 功率因数曲线 + public const string AGLYS = "0D_106"; // A相功率因数曲线 + public const string BGLYS = "0D_107"; // B相功率因数曲线 + public const string CGLYS = "0D_108"; // C相功率因数曲线 + + public const string ADY = "0D_89"; //A相电压曲线 + public const string BDY = "0D_90"; //B相电压曲线 + public const string CDY = "0D_91"; //C相电压曲线 + + public const string ADL = "0D_92"; //A相电流曲线 + public const string BDL = "0D_93"; //B相电流曲线 + public const string CDL = "0D_94"; //C相电流曲线 + public const string LXDL = "0D_95"; // 零序电流 + + public const string PowerGridFrequency = "10_97"; //电网频率 + + public const string Ua = "0C_49_Uab_Ua"; // 当前电压、电流相位角 + public const string Ub = "0C_49_Ub"; // 当前电压、电流相位角 + public const string Uc = "0C_49_Ucb_Uc"; // 当前电压、电流相位角 + public const string Ia = "0C_49_Ia"; // 当前电压、电流相位角 + public const string Ib = "0C_49_Ib"; // 当前电压、电流相位角 + public const string Ic = "0C_49_Ic"; // 当前电压、电流相位角 + + } + + + public static class DataFieldHelper + { + /// + /// 数据字段映射 + /// + public static Dictionary DataFieldDic = new Dictionary(); + + /// + /// 创建数据字段映射 + /// + /// + public static Dictionary CreateDataFieldMapping() + { + if (DataFieldDic.Count > 0) + return DataFieldDic; + var dataFieldMapping = new Dictionary(); + Type dataType = typeof(ConstGatherDataType); + Type fieldType = typeof(DataFieldConst); + + foreach (FieldInfo gatherField in dataType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)) + { + if (gatherField.IsLiteral && !gatherField.IsInitOnly) + { + // 获取ConstGatherDataType的字段值作为key + string key = (string)gatherField.GetValue(null)!; + + // 查找DataFieldConst中同名字段 + FieldInfo dataField = fieldType.GetField(gatherField.Name, + BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)!; + + if (dataField != null) + { + string value = (string)dataField.GetValue(null)!; + dataFieldMapping[key] = value; + } + } + } + + return dataFieldMapping; + } + + /// + /// 根据采集数据类型获取数据字段 + /// 返回null表示 无效字段 + /// + /// + /// + public static string? GetDataFieldByGatherDataType(this string gatherDataType) + { + if (CreateDataFieldMapping().TryGetValue(gatherDataType, out string? column)) + { + return column; + } + return null; + } + + + } +} diff --git a/shared/JiShe.CollectBus.Common/Consts/ExceptionCode.cs b/shared/JiShe.CollectBus.Common/Consts/ExceptionCode.cs new file mode 100644 index 0000000..8fd63ad --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Consts/ExceptionCode.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.Consts +{ + public class ExceptionCode + { + public const string NotFound = "500404"; + } +} diff --git a/shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs b/shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs index 6b65535..6af0fd0 100644 --- a/shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs +++ b/shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs @@ -47,7 +47,7 @@ namespace JiShe.CollectBus.Common.Consts public const string AmmeterSubscriberWorkerFifteenMinuteIssuedEventName = "issued.auto.fifteen.ammeter.event"; /// - /// 其他采集数据下行消息主题,日冻结,月冻结、集中器版本号控等 + /// 其他采集数据下行消息主题,日冻结,月冻结、集中器版本号、SIM卡号、定时校时等 /// public const string AmmeterSubscriberWorkerOtherIssuedEventName = "issued.auto.other.ammeter.event"; @@ -98,22 +98,22 @@ namespace JiShe.CollectBus.Common.Consts /// /// AFN00H上行主题格式 /// - public const string SubscriberAFN00ReceivedEventNameTemp = "received.afn00h.event"; + public const string SubscriberAFN00HReceivedEventNameTemp = "received.afn00h.event"; /// /// AFN01H上行主题格式 /// - public const string SubscriberAFN00HReceivedEventNameTemp = "received.afn01h.event"; + public const string SubscriberAFN01HReceivedEventNameTemp = "received.afn01h.event"; /// /// AFN02H上行主题格式 /// - public const string SubscriberAFN01HReceivedEventNameTemp = "received.afn02h.event"; + public const string SubscriberAFN02HReceivedEventNameTemp = "received.afn02h.event"; /// /// AFN03H上行主题格式 /// - public const string SubscriberAFN02HReceivedEventNameTemp = "received.afn03h.event"; + public const string SubscriberAFN03HReceivedEventNameTemp = "received.afn03h.event"; /// /// AFN04H上行主题格式 diff --git a/shared/JiShe.CollectBus.Common/Consts/RedisConst.cs b/shared/JiShe.CollectBus.Common/Consts/RedisConst.cs index 7ac170b..53ad4f6 100644 --- a/shared/JiShe.CollectBus.Common/Consts/RedisConst.cs +++ b/shared/JiShe.CollectBus.Common/Consts/RedisConst.cs @@ -72,6 +72,11 @@ namespace JiShe.CollectBus.Common.Consts ///// //public const string CacheDeviceBalanceRelationMapResultKey = $"{CacheBasicDirectoryKey}{"{0}:{1}"}:RelationMap"; - public const string CacheAmmeterFocusKey = "CacheAmmeterFocusKey"; + public const string CacheAmmeterFocusKey = $"{CacheBasicDirectoryKey}CacheAmmeterFocusKey"; + + /// + /// 协议池缓存标识 + /// + public const string ProtocolKey = $"{CacheBasicDirectoryKey}Protocols"; } } diff --git a/shared/JiShe.CollectBus.Common/Consts/T1882018PacketItemCodeConst.cs b/shared/JiShe.CollectBus.Common/Consts/T1882018PacketItemCodeConst.cs new file mode 100644 index 0000000..585d3df --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Consts/T1882018PacketItemCodeConst.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.Consts +{ + /// + /// T188-2018报文项编码 + /// + public class T1882018PacketItemCodeConst + { + #region 下行报文编码 + + /// + /// 基路径 + /// + public const string BasicT1882018 = "CTR"; + + #region 读数据 + + + /// + /// 读取计量数据1 + /// + public const string CTR0190 = $"01_90"; + + /// + /// 读取计量数据2 + /// + public const string CTR0191 = $"01_91"; + + #endregion + + #region 写数据 + + /// + /// 关阀 + /// + public const string CTR30455 = $"_04_55"; + + /// + /// 开阀 + /// + public const string CTR30499 = $"_04_99"; + + + #endregion + + #endregion + } +} diff --git a/shared/JiShe.CollectBus.Common/Consts/T37612012PacketItemCodeConst.cs b/shared/JiShe.CollectBus.Common/Consts/T37612012PacketItemCodeConst.cs new file mode 100644 index 0000000..6d15e19 --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Consts/T37612012PacketItemCodeConst.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Consts +{ + /// + /// T376.1-2012报文项编码 + /// + public class T37612012PacketItemCodeConst + { + #region 非可配置的,下行报文编码管理,主要是数据库没有配置,但是需要读取的采集项 + + #region 读数据 + /// + /// 透明转发 + /// + public const string AFN10HFN01H = $"10_01"; + + /// + /// 读取终端信息 + /// + public const string AFN09HFN01H = $"09_01"; + + /// + /// 远程通信模块版本信息 + /// + public const string AFN09HFN09H = $"09_09"; + + #endregion + + #region 写数据 + + #endregion + + #endregion + } +} diff --git a/shared/JiShe.CollectBus.Common/Consts/T6452007PacketItemCodeConst.cs b/shared/JiShe.CollectBus.Common/Consts/T6452007PacketItemCodeConst.cs new file mode 100644 index 0000000..92da5ba --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Consts/T6452007PacketItemCodeConst.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Consts +{ + /// + /// T645-2007报文项编码 + /// + public class T6452007PacketItemCodeConst + { + #region 下行报文编码 + #region 跳合闸、报警、保电 + /// + /// 跳闸 + /// + public const string C1C01A = "1C_1A"; + + /// + /// 单相合闸 + /// + public const string C1C01B = "1C_1B"; + + /// + /// 三相合闸 + /// + public const string C1C01C = "1C_1C"; + + /// + /// 触发报警 + /// + public const string C1C02A = "1C_2A"; + + /// + /// 报警解除 + /// + public const string C1C02B = "1C_2B"; + + /// + /// 保电开始 + /// + public const string C1C03A = "1C_3A"; + + /// + /// 保电结束 + /// + public const string C1C03B = "1C_3B"; + #endregion + + #region 广播校时 + + /// + /// 广播校时 + /// + public const string C08 = "08"; + #endregion + #endregion + } +} diff --git a/shared/JiShe.CollectBus.Common/Encrypt/EncryptUtil.cs b/shared/JiShe.CollectBus.Common/Encrypt/EncryptUtil.cs new file mode 100644 index 0000000..72cdf41 --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Encrypt/EncryptUtil.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Encrypt +{ + /// + /// 各种加密辅助类 + /// + public static class EncryptUtil + { + #region MD5加密 + + /// + /// MD5加密 + /// + public static string Md5Fun(this string value) + { + if (value == null) + { + throw new ArgumentNullException("未将对象引用设置到对象的实例。"); + } + + var encoding = Encoding.UTF8; + MD5 md5 = MD5.Create(); + return HashAlgorithmBase(md5, value, encoding); + } + + /// + /// 加权MD5加密 + /// + public static string Md5Fun(this string value, string salt) + { + return salt == null ? value.Md5Fun() : (value + "『" + salt + "』").Md5Fun(); + } + + #endregion + + /// + /// HashAlgorithm 加密统一方法 + /// + private static string HashAlgorithmBase(HashAlgorithm hashAlgorithmObj, string source, Encoding encoding) + { + byte[] btStr = encoding.GetBytes(source); + byte[] hashStr = hashAlgorithmObj.ComputeHash(btStr); + return hashStr.Bytes2Str(); + } + + /// + /// 转换成字符串 + /// + private static string Bytes2Str(this IEnumerable source, string formatStr = "{0:X2}") + { + StringBuilder pwd = new StringBuilder(); + foreach (byte btStr in source) + { + pwd.AppendFormat(formatStr, btStr); + } + return pwd.ToString(); + } + } +} diff --git a/shared/JiShe.CollectBus.Common/Enums/376Enums.cs b/shared/JiShe.CollectBus.Common/Enums/376Enums.cs index 68df1da..cff3340 100644 --- a/shared/JiShe.CollectBus.Common/Enums/376Enums.cs +++ b/shared/JiShe.CollectBus.Common/Enums/376Enums.cs @@ -368,4 +368,55 @@ namespace JiShe.CollectBus.Common.Enums HardwareReleaseDate=38 } + public enum FN + { + 登录 = 1, + 心跳 = 3 + } + + /// + /// 数据冻结密度 + /// + public enum DensityEnums + { + Sec_1 = 255, + Sec_5 = 254, + Sec_60 = 3, + Sec_30 = 2, + Sec_15 = 1, + Sec_0 = 0 + } + + /// + /// 数据冻结密度单位 + /// + public enum DensityUnit + { + /// + /// 秒 + /// + Second=0, + /// + /// 分钟 + /// + Minute=1, + /// + /// 小时 + /// + Hour=2, + /// + /// 天 + /// + Day=3, + /// + /// 月 + /// + Month=4, + /// + /// 年 + /// + Year=5 + + } + } diff --git a/shared/JiShe.CollectBus.Common/Enums/BrandTypeEnum.cs b/shared/JiShe.CollectBus.Common/Enums/BrandTypeEnum.cs new file mode 100644 index 0000000..83429ae --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Enums/BrandTypeEnum.cs @@ -0,0 +1,593 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Enums +{ + /// + /// 水表\流量计\特殊电表品牌 + /// + public enum BrandTypeEnum + { + /// + /// 默认(OC_129) + /// + //None = 0, + /// + /// 冻结数据(0D_101) + /// + //Freeze = 1, + /// + /// 默认(OC_129)or 冻结数据(0D_101) + /// + NoneOrFreeze = 0, + /// + /// 188-良禾 + /// + LiangHe188 = 1, + /// + /// 188-威铭 + /// + WeiMing188 = 2, + /// + /// 188-宁波 + /// + NingBo188 = 3, + /// + /// 西恩电磁流量计 + /// + XEDC = 4, + /// + /// 西恩超声波流量计 + /// + XECSB = 5, + /// + /// 电磁流量计(LDG-DN200) + /// + DCLDGDN200 = 6, + /// + /// 燃气表抄读 + /// + Gasmeter = 7, + /// + /// 涡街流量计 + /// + WJFlowmeter = 8, + /// + /// 流量计 + /// + Flowmeter = 9, + /// + /// 涡街流量计(LUGBDN100) + /// + WJFlowmeterLUGBDN100 = 10, + /// + /// 涡街流量计(ZC-LUGB-232ZDNNY) + /// + WJFlowmeterZCLUGB232ZDNNY = 11, + /// + /// SB2100蒸汽表 + /// + ZQBSB2100Flowmeter = 12, + /// + /// (HD)热式流量计 + /// + RSHDFlowmeter = 13, + /// + /// (HDWMNDN300)热式流量计 + /// + RSHDWMNDN300 = 14, + /// + /// 热式流量计(FLRS110-C100) + /// + RSFLRS110C100 = 15, + /// + /// 通用188协议 + /// + Universal188 = 16, + #region 特殊电表 + DTZ719 = 17, + AKKJMC800PY = 18, + HRKJ001 = 19, + THYB9D1 = 20, + DTSD342 = 21, + /// + /// 谐波 + /// + AFN16_F109 = 22, + /// + /// DL/T 645—2007 规约时采用该类 + /// + CustomItemCode_93 = 23, + /// + /// 电表组合有功示值透抄CustomItemCode_95 + /// + AFN16_F95 = 24, + #endregion + /// + /// 非特殊表 + /// + None = 25, + /// + /// SDM630MCT 导轨电表 + /// + SDM630MCT = 26, + /// + /// 水表通过0C_129采集 + /// + Watermeter0C_129 = 27, + /// + /// YYD电磁流量计 + /// + YYDFlowmeter = 28, + /// + /// 透明转发 跳合闸(水表) + /// + AFN16_F99 = 29, + /// + /// 透明转发 跳合闸(气表) + /// + AFN16_F100 = 30, + /// + /// 温度压力补偿涡街流量计(TPC1001)涡街流量计 + /// + WJTPC1001 = 31, + /// + /// (LDG-SP25)树普电磁流量计 + /// + ShupuLDGSP25Flowmeter = 32, + /// + /// 西恩涡街流量计(LUGBC-100) + /// + XEWJLUGBC100 = 33, + /// + /// 智能涡街流量计(UG-1132A) + /// + WJUG1132A = 34, + /// + /// 水表通过0D_101采集 + /// + Watermeter0D_101 = 35, + /// + /// 读取SIM卡号 + /// + AFN16_F101 = 36, + /// + /// 恒瑞科技三相导轨式电能表 or 恒瑞科技嵌入式电表测试 + /// + DTS600 = 37, + /// + /// 恒瑞科技单相导轨式电能表(DDS600) + /// + DDS600 = 38, + /// + /// 旋进漩涡流量计(LUXB) 天津凯隆仪表科技有限公司 + /// + XJXWLUXB = 39, + /// + /// DDSD720-L科陆单相导轨表 + /// + DDSD720L = 40, + /// + /// 东久电磁流量计DJLD + /// + DJLD = 41, + /// + /// DTSD720-L科陆三相导轨表 + /// + DTSD720L = 42, + /// + /// 世成(社为表计)涡街流量计 + /// + SCLUGB = 43, + /// + /// CL7339MN-ZY科陆三相表 + /// + CL7339MNZY = 44, + /// + /// 江森智能SNY723MC数显表 + /// + SNY723MC = 45, + /// + /// 珠海派诺科技PMAC770三相数显表 + /// + PMAC770 = 46, + /// + /// 北京中科涡街流量计(ZKTD-LUCBY) + /// + ZKTD_LUGBY = 47, + /// + /// 夏仪股份蒸汽流量计(LUGB-DN) + /// + LUGB_DN = 48, + /// + /// LWQ-D2型气体涡轮流量计 + /// + LWQ_D2 = 49, + /// + /// 西恩涡街流量计分体式(流量积算仪32FC系列) + /// + XEJSY32FC = 50, + /// + /// 寺崎科技PD652E-9S4电表 + /// + PD652E9S4 = 51, + /// + /// 液体涡轮流量计(LWGY) + /// + LWGY = 52, + /// + /// 多功能积算仪(RW-A) + /// + DGNRWA = 53, + /// + /// 杭梅电气DTS804导轨表 + /// + DTS804 = 54, + /// + /// 杭梅电气HG194-D93数显表 + /// + HG194D93 = 55, + /// + /// 连水超声波水表188 + /// + Lianshui188 = 56, + /// + /// 湖北回盛生物科技有限公司EZT96Y数显表 + /// + EZT96Y, + /// + /// 上海肖月智能流量积算仪 + /// + ZNLLJ, + /// + /// 西安诚通电磁流量计 + /// + CTLDE250SC31GM8FB, + /// + /// 雅达YD2040 + /// + YD2040, + /// + /// EVC智能体积修正仪 + /// + EVC, + /// + /// 气体超声流量计IGSM-TS + /// + IGSMTS, + /// + /// YX-9SYE三相多功能表 + /// + YX9SYE, + /// + /// 世成液体涡轮流量计(SCLWGY-DN50) + /// + SCLWGYDN50, + /// + /// 杭州盘古积算仪(FX6000F) + /// + FX6000F, + /// + /// "盘古电磁流量计(PMF-GM4.0A1-50M11K1F1T0C3) + /// + PFMGM40A150M11K1F1T0C3, + /// + /// 西恩液体涡轮流量计(SEAN LWGY-50) + /// + SeanLWGY50, + /// + /// 雷泰电磁流量计LD-LDE-DN50 + /// + LDLDEDN50, + /// + /// 雷泰涡街流量计(LT-LUGB-DN50) + /// + LTLUGBDN50, + /// + /// 珠海派诺科技股份有限公司SPM33电力仪表 + /// + SPM33, + /// + /// 株洲斯瑞克电气有限公司三相数显多功能电力仪表PD369E-AS4 + /// + PD369EAS4, + /// + /// 湖北回盛生物科技有限公司-涡街流量计(10VTEAD03A200C1A2HOAG) + /// + WJ10VTEAD03A200C1A2HOAG, + /// + /// 世成旋进旋涡流量计SCLUX-DN25 + /// + SCLUXDN25, + /// + /// 世成气体涡轮流量计(SCLWGQ-DN50) + /// + SCLWGQDN50, + /// + /// 智能电磁流量计(MDL210) + /// + MDL210, + /// + /// 江苏华海涡街流量计Focvor4202 + /// + Focvor4202, + /// + /// 华凯电力HK194E-9S4 + /// + HK194E9S4, + /// + /// 威胜测试-DTSD342_9N + /// + DTSD342Test, + /// + ///科迈捷涡街流量计VFM-60 + /// + VFM60, + /// + ///江苏华海涡街流量计积算仪 + /// + HHJSY, + /// + ///宏江4G水表 + /// + HJDN15, + /// + ///世成4G涡街流量计 + /// + LPV2, + /// + ///浙江正泰DTSU666 + /// + DTSU666, + /// + /// 浙江启唯电气-数码三相多功能电表QV194E-9S4 + /// + QV194E9S4, + /// + /// 施耐德PM2100 + /// + PM2100, + + /// + /// 天康电磁流量计 + /// + TK1100FT, + + /// + /// 西恩气体涡轮流量计(SEANLWQ) + /// + SEANLWQ, + + /// + /// V880BR涡街流量计 + /// + V880BR, + + /// + /// 大导SDD194E-9 + /// + SDD194E_9, + + /// + ///泉高阀门科技有限公司-超声波水表 + /// + QGFMCSB, + + #region 传感器型号 + SensorMeter, + #endregion + + /// + /// 分体式超声波明渠流量计SC-6000F + /// + SC6000F, + + /// + /// 江苏京仪JLWQ型气体流量计JLWQ型气体流量计 + /// + JLWQ, + + /// + /// 广州智光SMC200 + /// + SMC200, + + /// + /// 左拓ZFM2-621 + /// + ZFM2621, + + /// + /// 江苏华尔威涡街旋进流量计 + /// + HRW520, + + /// + /// 施耐德PM5350P + /// + PM5350P, + + /// + /// 施耐德PM810MG + /// + PM810MG, + + /// + /// 浙江之高ZL96-3E + /// + ZL96_3E, + /// + /// 拓普电子PD284Z-9S4 + /// + PD284Z_9S4, + /// + /// 上海普川DTSU5886 + /// + DTSU5886, + /// + /// 安德利SC194E-9S4 + /// + SC194E9S4, + /// + /// 浙江天电电气TD700E-AS3 + /// + TD700EAS3, + /// + /// 世成分体式涡街流量计SW-SCLUGB-DN + /// + SWSCLUGBDN, + /// + /// 东久电磁冷热量计SW-DJLD + /// + SWDJLD, + + /// + /// 北京中科拓达ZKTD-LUGB + /// + ZKTD_LUGB, + + /// + /// 江苏英美迪自动化有限公司三相液晶多功能仪表YMD96A-E4 + /// + YMD96A_E4, + + /// + /// 金湖盛元LWQ气体涡轮流量计 + /// + JHSYLWQ, + + /// + /// 天康涡街流量计TK2000 + /// + TK2000, + + /// + /// 浙江迈拓三相导轨电表DTSF1709 + /// + DTSF1709, + + /// + /// 杭州逸控科技超声波流量计ECUL30B-L2C1NSVC + /// + ECUL30BL2C1NSVC, + /// + /// 数字电测表HTD288-DM44/R + /// + HTD288, + /// + /// 杭州逸控科技有限公司ECLUGB2305W3C2N + /// + ECLUGB2305W3C2N, + + /// + /// 江苏华海测控科技有限公司温压补偿流量积算仪 + /// + XMJA9000, + + /// + /// 湖南佳一机电设备有限公司精致型蒸汽热能积算仪 + /// + F3200H, + + /// + /// 合兴加能电梯能量回馈装置 + /// + IPCPFE04MNDC, + + /// + /// 宁波创盛旋涡流量计 + /// + CX25, + /// + /// 群力电气QDCZY1N + /// + QDCZY1N, + + /// + ///深圳中电PMCS963C + /// + PMCS963C, + /// + /// 迅尔燃气表D2SD2ET3D4S + /// + D2SD2ET3D4S, + /// + /// INTELLIGENT积算仪F2000X + /// + F2000X, + + /// + ///多盟-DM194Z-9SY + /// + DM194Z9SY, + /// + /// 纳宇PD760 + /// + PD760, + /// + /// 纳宇DTS90031LPD + /// + DTS90031LPD, + + /// + /// 上海施易克SEIK680数显表 + /// + SEIK680, + + /// + /// 中灵电气ZL125SC + /// + ZL125SC, + /// + /// 江苏京仪气体涡轮流量计JLWQE + /// + JLWQE, + /// + /// HART智能转换器 + /// + SM100, + + /// + /// 拓思特涡街流量计H880BR + /// + H880BR, + /// + /// DDSD720-L-单相电子式导轨表 + /// + DDSD720L2, + + /// + /// 浙江智电三相三线大功率有功电能表 + /// + ZJZDSXSX, + + /// + /// 山水翔水表LXSY + /// + LXSY, + + /// + /// 衡水多元仪表有限公司气体涡轮流量计DYWQ + /// + DYWQ, + + /// + /// 安徽聚积电子 + /// + DDS2052, + + /// + /// 湖南中麦 + /// + ZMDTSD3429N, + + /// + ///DTS2377三相导轨式多功能智能电表 + /// + DTS2377 + + } +} diff --git a/shared/JiShe.CollectBus.Common/Enums/MeterLinkProtocol.cs b/shared/JiShe.CollectBus.Common/Enums/MeterLinkProtocol.cs new file mode 100644 index 0000000..6c06c5c --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Enums/MeterLinkProtocol.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 MeterLinkProtocol + { + /// + /// 无 + /// + 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/shared/JiShe.CollectBus.Common/Enums/TimestampUnit.cs b/shared/JiShe.CollectBus.Common/Enums/TimestampUnit.cs new file mode 100644 index 0000000..9bed83f --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Enums/TimestampUnit.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Enums +{ + public enum TimestampUnit + { + Seconds, // 秒级(Unix 时间戳) + Milliseconds, // 毫秒级(默认) + Microseconds, // 微秒级 + Nanoseconds // 纳秒级 + } +} diff --git a/shared/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs index 4e3fef9..6936ad3 100644 --- a/shared/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs +++ b/shared/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs @@ -181,24 +181,77 @@ namespace JiShe.CollectBus.Common.Extensions return $"{dateTime:yyyyMMddHH}"; #endif } + /// - /// 获取当前时间毫秒级时间戳 + /// 采集时间节点计算 /// + /// 待采集时间 + /// /// - public static long GetCurrentTimeMillis() + public static DateTime CalculateNextCollectionTime(this DateTime referenceTime, int interval) { - return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + // 计算精确到分钟的基准时间 + var baseTime = new DateTime( + referenceTime.Year, + referenceTime.Month, + referenceTime.Day, + referenceTime.Hour, + referenceTime.Minute, + 0); + + // 计算总分钟数和下一个间隔点 + int totalMinutes = baseTime.Hour * 60 + baseTime.Minute; + int nextTotalMinutes = ((totalMinutes / interval) + 1) * interval; + + // 处理跨天情况 + int daysToAdd = nextTotalMinutes / (24 * 60); + int remainingMinutes = nextTotalMinutes % (24 * 60); + int hours = remainingMinutes / 60; + int minutes = remainingMinutes % 60; + + return baseTime.Date + .AddDays(daysToAdd) + .AddHours(hours) + .AddMinutes(minutes); + } + + + /// + /// 格式化为微秒(μs) + /// + /// + /// + public static string ToMicrosecondString(this DateTime dt) + { + long microseconds = (dt.Ticks % TimeSpan.TicksPerSecond) / 10; // 1 Tick = 100ns → 0.1μs + return $"{dt:yyyy-MM-dd HH:mm:ss.fffffff}".Remove(23) + $"{microseconds:D6}"; } /// - /// 将Unix时间戳转换为日期时间 + /// 格式化为纳秒(ns) /// - /// + /// /// - public static DateTime FromUnixMillis(long millis) + public static string ToNanosecondString(this DateTime dt) { - return DateTimeOffset.FromUnixTimeMilliseconds(millis).DateTime; + long nanoseconds = (dt.Ticks % TimeSpan.TicksPerSecond) * 100; // 1 Tick = 100ns + return $"{dt:yyyy-MM-dd HH:mm:ss.fffffff}".Remove(23) + $"{nanoseconds:D9}"; + } + + /// + /// 毫米、微秒、纳秒时间戳转DateTime + /// + /// + /// + /// + public static DateTime ParseIntToDate(long dateLong) + { + if (dateLong < 10000101 || dateLong > 99991231) + { + throw new ArgumentException("Date must be between 10000101 and 99991231."); + } + return DateTime.TryParseExact(dateLong.ToString(), "yyyyMMdd HHmmssZZ", null, System.Globalization.DateTimeStyles.None, out DateTime date) ? date : throw new ArgumentException("Date must be between 10000101 and 99991231."); } } } diff --git a/shared/JiShe.CollectBus.Common/Extensions/DateTimeOffsetExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/DateTimeOffsetExtensions.cs new file mode 100644 index 0000000..0f07e9c --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Extensions/DateTimeOffsetExtensions.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace JiShe.CollectBus.Common.Extensions +{ + public static class DateTimeOffsetExtensions + { + + /// + /// 获取当前时间毫秒级时间戳 + /// + /// + public static long GetCurrentTimeMillis() + { + return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + } + + /// + /// 将Unix时间戳转换为日期时间 + /// + /// + /// + public static DateTime FromUnixMillis(long millis) + { + return DateTimeOffset.FromUnixTimeMilliseconds(millis).DateTime; + } + + /// + /// 将 DateTime 时间转换为 DateTimeOffset 时间 + /// + /// + /// + public static DateTimeOffset GetDateTimeOffset(this DateTime rawDateTime) + { + //确保 Kind 为 Local(如果是 Unspecified) + DateTime localDateTime = rawDateTime.Kind == DateTimeKind.Unspecified + ? DateTime.SpecifyKind(rawDateTime, DateTimeKind.Local) + : rawDateTime; + + // 转换为 DateTimeOffset(自动应用本地时区偏移) + return new DateTimeOffset(localDateTime); + } + } +} diff --git a/shared/JiShe.CollectBus.Common/Extensions/StringExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/StringExtensions.cs index 94e7421..d2cea60 100644 --- a/shared/JiShe.CollectBus.Common/Extensions/StringExtensions.cs +++ b/shared/JiShe.CollectBus.Common/Extensions/StringExtensions.cs @@ -49,7 +49,7 @@ namespace JiShe.CollectBus.Common.Extensions ///
/// [Description("正则匹配单个")] - public static string RegexMatch(this string str, string pattern) + public static string? RegexMatch(this string str, string pattern) { var reg = new Regex(pattern); var match = reg.Match(str); @@ -1128,6 +1128,21 @@ namespace JiShe.CollectBus.Common.Extensions return string.Join(" ", strArr.Reverse()); } + + /// + /// 高低位反转,并转换成字符串 + /// + /// 16进制字符集合 + /// 16进制字符集合开始索引 + /// 取多少个数据 + /// + public static string ListReverseToStr(this List list, int index, int count) + { + var addrList = list.GetRange(index, count); + addrList.Reverse();//高低位反转 + return string.Join("", addrList); + } + /// /// 二进制转十六进制 /// @@ -1163,6 +1178,17 @@ namespace JiShe.CollectBus.Common.Extensions return decimalNumber; } + /// + /// 转十进制字符串 + /// + /// + /// + public static string HexToDecStr(this int hexString) + { + var decimalString = Convert.ToString(hexString, 16); + return decimalString; + } + /// /// 十六进制转二进制 /// @@ -1174,6 +1200,25 @@ namespace JiShe.CollectBus.Common.Extensions return binaryValue; } + + /// + /// 十六进制转二进制 + /// 不足4位,前面补0 + /// + /// + /// + public static string HexTo4BinZero(this string hexString) + { + string result = string.Empty; + foreach (char c in hexString) + { + int v = Convert.ToInt32(c.ToString(), 16); + int v2 = int.Parse(Convert.ToString(v, 2)); + result += string.Format("{0:d4}", v2); + } + return result; + } + /// /// 数据值加33 /// @@ -1195,6 +1240,27 @@ namespace JiShe.CollectBus.Common.Extensions return string.Join(" ", strArr); } + + /// + /// 16进制字符串转换成byte数组 + /// + /// 16进制字符串 + /// byte数组 + public static byte[] HexToByte(this string hexString) + { + hexString = hexString.Replace(" ", ""); + string tempStr = ""; + byte by = 0; + List bytes = new List(); + for (int i = 0; i < hexString.Length; i += 2) + { + tempStr = hexString.Substring(i, 2); + by = byte.Parse(tempStr, System.Globalization.NumberStyles.AllowHexSpecifier); + bytes.Add(by); + } + return bytes.ToArray(); + } + /// /// 判断数据是否有误 /// diff --git a/shared/JiShe.CollectBus.Common/Helpers/CommonHelper.cs b/shared/JiShe.CollectBus.Common/Helpers/CommonHelper.cs index 22ebef5..59691ce 100644 --- a/shared/JiShe.CollectBus.Common/Helpers/CommonHelper.cs +++ b/shared/JiShe.CollectBus.Common/Helpers/CommonHelper.cs @@ -8,7 +8,7 @@ using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; -using JiShe.CollectBus.Common.AttributeInfo; +using JiShe.CollectBus.Common.Attributes; namespace JiShe.CollectBus.Common.Helpers { @@ -669,7 +669,7 @@ namespace JiShe.CollectBus.Common.Helpers return att == null ? field.Name : ((DescriptionAttribute)att).Description; } - + /// /// 将传入的字符串中间部分字符替换成特殊字符 @@ -759,7 +759,7 @@ namespace JiShe.CollectBus.Common.Helpers } return fontValue; - } + } /// /// 获取任务标识 @@ -767,10 +767,72 @@ namespace JiShe.CollectBus.Common.Helpers /// /// /// + /// + /// /// - public static string GetTaskMark(int afn,int fn,int pn) + public static string GetTaskMark(int afn, int fn, int pn, int msa,int seq) { - return $"{afn.ToString().PadLeft(2,'0')}{fn}{pn}"; + var makstr = $"{afn.ToString().PadLeft(2, '0')}{fn.ToString().PadLeft(2, '0')}{pn.ToString().PadLeft(2, '0')}{msa.ToString().PadLeft(2, '0')}{seq.ToString().PadLeft(2, '0')}"; + + return makstr;// Convert.ToInt32(makstr) << 32 | msa; } + + /// + /// 判断是生成月 + /// + /// + /// + /// + public static bool JudgeIsGenerate_Month(string eachMonthWith, DateTime curTime) + { + var arr = eachMonthWith.Split(','); + if (arr.Contains(curTime.Day.ToString())) + return true; + return false; + } + + /// + /// 判断是生成一次 + /// + /// + /// + /// + public static bool JudgeIsGenerate_Once(DateTime onceWithDate, DateTime curTime) => curTime.Date.Equals(onceWithDate);//为当天时发送 + + /// + /// 判断是生成日 + /// + /// + /// + /// + public static bool JudgeIsGenerate_Day(string eachDayWithout, DateTime curTime) + { + var weekName = strWeeks[(int)curTime.DayOfWeek]; + var arr = eachDayWithout.Split(','); + return !arr.Contains(weekName); + } + + public static readonly List strWeeks = new List() { "周日", "周一", "周二", "周三", "周四", "周五", "周六" }; + + /// + /// 判断是生成周 + /// + /// + /// + /// + public static bool JudgeIsGenerate_Week(string eachWeekWith, DateTime curTime) + { + if (string.IsNullOrWhiteSpace(eachWeekWith)) + { + return false; + } + + var weekName = strWeeks[(int)curTime.DayOfWeek]; + var arr = eachWeekWith.Split(','); + return arr.Contains(weekName); + + } + + } } diff --git a/shared/JiShe.CollectBus.Common/Helpers/DataFieldHelper.cs b/shared/JiShe.CollectBus.Common/Helpers/DataFieldHelper.cs new file mode 100644 index 0000000..a9173bb --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Helpers/DataFieldHelper.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Helpers +{ + public class DataFieldHelper + { + public static string GetDataField(string dataField) + { + if (string.IsNullOrWhiteSpace(dataField)) + { + return string.Empty; + } + + if (dataField.Contains(".")) + { + return dataField.Split('.')[0]; + } + + return dataField; + } + } +} diff --git a/shared/JiShe.CollectBus.Common/Helpers/TimestampHelper.cs b/shared/JiShe.CollectBus.Common/Helpers/TimestampHelper.cs new file mode 100644 index 0000000..9edc3cd --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Helpers/TimestampHelper.cs @@ -0,0 +1,68 @@ +using JiShe.CollectBus.Common.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Helpers +{ + /// + /// 时间戳帮助类 + /// + public static class TimestampHelper + { + private static readonly long UnixEpochTicks = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero).Ticks; + + /// + /// 获取当前 DateTimeOffset 距离 Unix 纪元(1970-01-01)的微秒数 + /// + public static long ToUnixTimeMicroseconds(this DateTimeOffset dateTimeOffset) + { + // Ticks 单位是 100 纳秒,转换为微秒需除以 10 + long elapsedTicks = dateTimeOffset.Ticks - UnixEpochTicks; + return elapsedTicks / 10; // 1 微秒 = 1000 纳秒 = 10 Ticks + } + + /// + /// 获取当前 DateTimeOffset 距离 Unix 纪元(1970-01-01)的纳秒数 + /// + public static long ToUnixTimeNanoseconds(this DateTimeOffset dateTimeOffset) + { + long nanoseconds = (dateTimeOffset.Ticks - UnixEpochTicks) * 100; + return nanoseconds; + } + + /// + /// 将 long 类型时间戳转换为 DateTime(UTC) + /// + /// 时间戳数值 + /// 时间戳单位 + public static DateTime ConvertToDateTime(long timestamp, TimestampUnit unit = TimestampUnit.Milliseconds) + { + long ticks = unit switch + { + TimestampUnit.Seconds => checked(timestamp * TimeSpan.TicksPerSecond), + TimestampUnit.Milliseconds => checked(timestamp * TimeSpan.TicksPerMillisecond), + TimestampUnit.Microseconds => checked(timestamp * 10), // 1微秒 = 10 Ticks(100纳秒) + TimestampUnit.Nanoseconds => checked(timestamp / 100),// 1 Tick = 100纳秒 + _ => throw new ArgumentException("无效的时间单位", nameof(unit)) + }; + + try + { + DateTime result = new DateTime(UnixEpochTicks + ticks, DateTimeKind.Utc); + // 校验结果是否在 DateTime 合法范围内(0001-01-01 至 9999-12-31) + if (result < DateTime.MinValue || result > DateTime.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(timestamp), "时间戳超出 DateTime 范围"); + } + return result; + } + catch (ArgumentOutOfRangeException ex) + { + throw new ArgumentOutOfRangeException("时间戳无效", ex); + } + } + } +} diff --git a/shared/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs b/shared/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs index d397151..1edc46a 100644 --- a/shared/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs +++ b/shared/JiShe.CollectBus.Common/Models/DeviceCacheBasicModel.cs @@ -30,5 +30,15 @@ namespace JiShe.CollectBus.Common.Models /// ZSet排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳 /// public virtual long ScoreValue=> ((long)FocusId << 32) | (uint)MeterId; + + /// + /// 是否已处理 + /// + public virtual bool IsHandle { get; set; } = false; + + /// + /// 集中器地址 + /// + public string FocusAddress { get; set;} } } diff --git a/shared/JiShe.CollectBus.Common/Models/ServerApplicationOptions.cs b/shared/JiShe.CollectBus.Common/Models/ServerApplicationOptions.cs new file mode 100644 index 0000000..8aed32b --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Models/ServerApplicationOptions.cs @@ -0,0 +1,38 @@ +namespace JiShe.CollectBus.Common +{ + /// + /// 服务器应用配置 + /// + public class ServerApplicationOptions + { + /// + /// 服务器标识 + /// + public required string ServerTagName { get; set; } + + /// + /// 系统类型 + /// + public required string SystemType { get; set; } + + /// + /// 首次采集时间 + /// + public DateTime? FirstCollectionTime { get; set; } + + /// + /// 自动验证时间 + /// + public required string AutomaticVerificationTime { get; set;} + + /// + /// 自动获取终端版时间 + /// + public required string AutomaticGetTerminalVersionTime { get; set; } + + /// + /// 自动获取远程通信模块(SIM)版本时间 + /// + public required string AutomaticGetTelematicsModuleTime { get; set; } + } +} diff --git a/shared/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs b/shared/JiShe.CollectBus.Common/Models/TasksToBeIssueModel.cs similarity index 74% rename from shared/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs rename to shared/JiShe.CollectBus.Common/Models/TasksToBeIssueModel.cs index 5184459..1484ab8 100644 --- a/shared/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs +++ b/shared/JiShe.CollectBus.Common/Models/TasksToBeIssueModel.cs @@ -4,13 +4,18 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace JiShe.CollectBus.Common.BuildSendDatas +namespace JiShe.CollectBus.Common.Models { /// /// 待下发的指令生产任务数据 /// public class TasksToBeIssueModel { + /// + /// 上次下发任务的时间 + /// + public DateTime? LastTaskTime { get; set; } + /// /// 下个任务时间 /// diff --git a/shared/JiShe.CollectBus.Domain.Shared/CassandraBaseEntity.cs b/shared/JiShe.CollectBus.Domain.Shared/CassandraBaseEntity.cs new file mode 100644 index 0000000..0e7fc58 --- /dev/null +++ b/shared/JiShe.CollectBus.Domain.Shared/CassandraBaseEntity.cs @@ -0,0 +1,14 @@ +using System; +using System.ComponentModel.DataAnnotations; +using JiShe.CollectBus.Common.Attributes; + +namespace JiShe.CollectBus +{ + public class CassandraBaseEntity: ICassandraEntity + { + /// + /// Id + /// + public TKey Id { get; set; } + } +} diff --git a/shared/JiShe.CollectBus.Domain.Shared/DynamicModule/DynamicModuleManager.cs b/shared/JiShe.CollectBus.Domain.Shared/DynamicModule/DynamicModuleManager.cs new file mode 100644 index 0000000..8f8e0eb --- /dev/null +++ b/shared/JiShe.CollectBus.Domain.Shared/DynamicModule/DynamicModuleManager.cs @@ -0,0 +1,156 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using System; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Modularity; + +namespace JiShe.CollectBus.DynamicModule +{ + /// + /// 动态模块管理器的实现 + /// + public class DynamicModuleManager : IDynamicModuleManager, ISingletonDependency + { + private readonly IModuleContainer _moduleContainer; + private readonly IServiceProvider _serviceProvider; + + public DynamicModuleManager( + IModuleContainer moduleContainer, + IServiceProvider serviceProvider) + { + _moduleContainer = moduleContainer; + _serviceProvider = serviceProvider; + } + + public Type[] GetRegisteredModuleTypes() + { + return _moduleContainer.Modules.Select(m => m.Type).ToArray(); + } + + public async Task ReinitializeModuleAsync(Type moduleType) + { + if (!typeof(IAbpModule).IsAssignableFrom(moduleType)) + { + throw new ArgumentException($"指定的类型 {moduleType.FullName} 不是有效的ABP模块类型", nameof(moduleType)); + } + + var moduleDescriptor = _moduleContainer.Modules.FirstOrDefault(m => m.Type.Name == moduleType.Name); + if (moduleDescriptor == null) + { + throw new InvalidOperationException($"找不到类型为 {moduleType.FullName} 的模块"); + } + + var module = moduleDescriptor.Instance; + + await CallModuleMethodAsync(module, "OnApplicationShutdown", [new ApplicationShutdownContext(_serviceProvider)]); + var configureServicesMethod = moduleType.GetMethod("ConfigureServices",BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); + if (configureServicesMethod != null) + { + var serviceConfigurationContext = CreateServiceConfigurationContext(); + configureServicesMethod.Invoke(module, [serviceConfigurationContext]); + } + await CallModuleMethodAsync(module, "OnApplicationInitializationAsync", [new ApplicationInitializationContext(_serviceProvider)]); + } + + public async Task UnloadModuleAsync(Type moduleType) + { + if (!typeof(IAbpModule).IsAssignableFrom(moduleType)) + { + throw new ArgumentException($"指定的类型 {moduleType.FullName} 不是有效的ABP模块类型", nameof(moduleType)); + } + + var moduleDescriptor = _moduleContainer.Modules.FirstOrDefault(m => m.Type.Name == moduleType.Name); + if (moduleDescriptor == null) + { + throw new InvalidOperationException($"找不到类型为 {moduleType.FullName} 的模块"); + } + + var module = moduleDescriptor.Instance; + + await CallModuleMethodAsync(module, "OnApplicationShutdown", new object[] { new ApplicationShutdownContext(_serviceProvider) }); + moduleDescriptor.GetType().GetProperty("IsUnloaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)? + .SetValue(moduleDescriptor, true); + } + + public bool IsModuleLoaded(Type moduleType) + { + var moduleDescriptor = _moduleContainer.Modules.FirstOrDefault(m => m.Type == moduleType); + if (moduleDescriptor == null) + { + return false; + } + + // 检查模块是否已被标记为卸载 + var isUnloaded = (bool?)moduleDescriptor.GetType().GetProperty("IsUnloaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)? + .GetValue(moduleDescriptor) ?? false; + + return !isUnloaded; + } + + public Type GetModuleTypeByName(string moduleName) + { + return _moduleContainer.Modules + .FirstOrDefault(m => m.Type.Name == moduleName || m.Type.FullName == moduleName)? + .Type; + } + + private async Task CallModuleMethodAsync(IAbpModule module, string methodName, object[] parameters) + { + var method = module.GetType().GetMethod(methodName); + if (method == null) + { + return; + } + + if (method.ReturnType == typeof(Task)) + { + await (Task)method.Invoke(module, parameters); + } + else + { + method.Invoke(module, parameters); + } + } + + private ServiceConfigurationContext CreateServiceConfigurationContext() + { + // 反射获取内部构造函数 + var contextType = typeof(ServiceConfigurationContext); + var constructor = contextType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(); + + if (constructor != null) + { + // 创建新的服务集合并添加现有服务 + var services = new ServiceCollection(); + foreach (var service in ((IServiceCollection)_serviceProvider.GetService()) ?? new ServiceCollection()) + { + services.Add(service); + } + + return (ServiceConfigurationContext)constructor.Invoke([services]); + } + + // 如果反射失败,使用备选方案 + try + { + var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance; + var services = new ServiceCollection(); + + var context = Activator.CreateInstance(contextType, bindingFlags, null, null, null); + + var servicesProperty = contextType.GetProperty("Services"); + servicesProperty?.SetValue(context, services); + + return (ServiceConfigurationContext)context; + } + catch + { + throw new InvalidOperationException("无法创建ServiceConfigurationContext。这可能是由于与ABP框架版本不兼容造成的。"); + } + } + } +} diff --git a/shared/JiShe.CollectBus.Domain.Shared/DynamicModule/IDynamicModuleManager.cs b/shared/JiShe.CollectBus.Domain.Shared/DynamicModule/IDynamicModuleManager.cs new file mode 100644 index 0000000..3f87820 --- /dev/null +++ b/shared/JiShe.CollectBus.Domain.Shared/DynamicModule/IDynamicModuleManager.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.DynamicModule +{ + /// + /// 提供动态管理ABP模块的功能 + /// + public interface IDynamicModuleManager + { + /// + /// 获取已注册的模块类型 + /// + /// 当前应用程序中已注册的所有模块类型 + Type[] GetRegisteredModuleTypes(); + + /// + /// 重新初始化指定的模块 + /// + /// 要重新初始化的模块类型 + /// 表示异步操作的任务 + Task ReinitializeModuleAsync(Type moduleType); + + /// + /// 卸载指定的模块 + /// + /// 要卸载的模块类型 + /// 表示异步操作的任务 + Task UnloadModuleAsync(Type moduleType); + + /// + /// 检查模块是否已加载 + /// + /// 要检查的模块类型 + /// 如果模块已加载,则为true;否则为false + bool IsModuleLoaded(Type moduleType); + + /// + /// 根据模块名称获取模块类型 + /// + /// 模块名称 + /// 模块类型,如果找不到则为null + Type GetModuleTypeByName(string moduleName); + } +} \ No newline at end of file diff --git a/shared/JiShe.CollectBus.Domain.Shared/ICassandraEntity.cs b/shared/JiShe.CollectBus.Domain.Shared/ICassandraEntity.cs new file mode 100644 index 0000000..760dd7a --- /dev/null +++ b/shared/JiShe.CollectBus.Domain.Shared/ICassandraEntity.cs @@ -0,0 +1,19 @@ +using System; + +namespace JiShe.CollectBus +{ + public interface ICassandraEntity + { + TKey Id { get; set; } + } + + public interface IHasCreationTime + { + DateTime CreationTime { get; set; } + } + + public interface IHasLastModificationTime + { + DateTime? LastModificationTime { get; set; } + } +} diff --git a/shared/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.csproj b/shared/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.csproj index a2747f4..874b688 100644 --- a/shared/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.csproj +++ b/shared/JiShe.CollectBus.Domain.Shared/JiShe.CollectBus.Domain.Shared.csproj @@ -31,4 +31,8 @@ + + + + diff --git a/web/JiShe.CollectBus.Host/CollectBusHostConst.cs b/web/JiShe.CollectBus.Host/CollectBusHostConst.cs index 319dfed..4ec4f70 100644 --- a/web/JiShe.CollectBus.Host/CollectBusHostConst.cs +++ b/web/JiShe.CollectBus.Host/CollectBusHostConst.cs @@ -1,5 +1,8 @@ namespace JiShe.CollectBus.Host { + /// + /// CollectBusHostConst + /// public static class CollectBusHostConst { /// @@ -23,9 +26,15 @@ public const string HangfireDashboardEndPoint = "/hangfire"; /// - /// CAP 端点 + /// 健康检查 端点 /// - public const string CapDashboardEndPoint = "/cap"; + public const string HealthEndPoint = "/health"; + + /// + /// 健康检查 端点 + /// + public const string HealthDashboardEndPoint = "/health-ui"; + } } diff --git a/web/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs b/web/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs index 82cc214..e6f1e7e 100644 --- a/web/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs +++ b/web/JiShe.CollectBus.Host/CollectBusHostModule.Configure.cs @@ -2,8 +2,8 @@ using Hangfire; using Hangfire.Redis.StackExchange; using JiShe.CollectBus.Host.Hangfire; +using JiShe.CollectBus.Host.HealthChecks; using JiShe.CollectBus.Host.Swaggers; -using MassTransit; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.DataProtection; using Microsoft.IdentityModel.Tokens; @@ -17,14 +17,9 @@ using Volo.Abp.Modularity; using TouchSocket.Core; using TouchSocket.Sockets; using JiShe.CollectBus.Plugins; -using JiShe.CollectBus.Consumers; -using JiShe.CollectBus.Protocol.Contracts; -using JiShe.CollectBus.IotSystems.MessageReceiveds; -using JiShe.CollectBus.IotSystems.MessageIssueds; -using Confluent.Kafka; -using MassTransit.SqlTransport.Topology; -using Confluent.Kafka.Admin; -using JiShe.CollectBus.Common.Consts; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using JiShe.CollectBus.Cassandra; namespace JiShe.CollectBus.Host @@ -215,7 +210,6 @@ namespace JiShe.CollectBus.Host private void ConfigureCustom(ServiceConfigurationContext context, IConfiguration configuration) { context.Services.AddSingleton(); - context.Services.AddHealthChecks(); } /// @@ -252,114 +246,35 @@ namespace JiShe.CollectBus.Host } /// - /// Configures the cap. + /// 健康检查 /// - /// The context. - /// The configuration. - public void ConfigureCap(ServiceConfigurationContext context, IConfiguration configuration) - { - context.Services.AddCap(x => - { - x.DefaultGroupName = ProtocolConst.SubscriberGroup; - var connectionStr = configuration.GetConnectionString(CollectBusDbProperties.MongoDbConnectionStringName); - x.UseMongoDB(connectionStr); //MongoDB 4.0+ cluster - var kafka = configuration.GetConnectionString("Kafka"); - x.UseKafka(option => + /// + /// + private void ConfigureHealthChecks(ServiceConfigurationContext context, IConfiguration configuration) + { + if (!configuration.GetValue("HealthChecks:IsEnable")) return; + var cassandraConfig = new CassandraConfig(); + configuration.GetSection("Cassandra").Bind(cassandraConfig); + context.Services.AddHealthChecks() + .AddMongoDb(configuration.GetConnectionString("Default"), "MongoDB", HealthStatus.Unhealthy) + .AddRedis(configuration.GetValue("Redis:Configuration") ?? string.Empty, "Redis", + HealthStatus.Unhealthy) + .AddKafka(new Confluent.Kafka.ProducerConfig { - 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"]); - }); + BootstrapServers = configuration.GetConnectionString("Kafka") + }, "Kafka", failureStatus: HealthStatus.Unhealthy) - x.UseDashboard(); - x.FailedRetryInterval = 10; - x.FailedRetryCount = 5; - }); + .AddCheck("Cassandra") + .AddCheck("IoTDB"); - } - - /// - /// Configures the mass transit. - /// - /// The context. - /// The configuration. - /// - /// Configures the mass transit. - /// - public void ConfigureMassTransit(ServiceConfigurationContext context, IConfiguration configuration) - { - var consumerConfig = new ConsumerConfig { GroupId = ProtocolConst.SubscriberGroup }; - var producerConfig = new ProducerConfig(); - - context.Services - .AddMassTransit(x => - { - x.UsingInMemory((context, cfg) => cfg.ConfigureEndpoints(context)); - - x.AddConfigureEndpointsCallback((c, name, cfg) => + context.Services + .AddHealthChecksUI(options => { - cfg.UseDelayedRedelivery(r => r.Intervals(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(30))); - cfg.UseMessageRetry(r => r.Immediate(5)); - cfg.UseInMemoryOutbox(c); - }); + options.AddHealthCheckEndpoint("JiSheCollectBus", "/health"); // 映射本地端点 + }) + .AddInMemoryStorage(); - x.AddRider(rider => - { - rider.AddConsumer(); - rider.AddConsumer(); - rider.AddConsumer(); - rider.AddConsumer(cfg => - { - cfg.Options(options => options - .SetMessageLimit(100) - .SetTimeLimit(s: 1) - .SetTimeLimitStart(BatchTimeLimitStart.FromLast) - .SetConcurrencyLimit(10)); - }); - rider.AddConsumer(); - rider.AddProducer(ProtocolConst.SubscriberLoginReceivedEventName); - rider.AddProducer(ProtocolConst.SubscriberHeartbeatReceivedEventName); - - rider.UsingKafka((c, cfg) => - { - cfg.Host(configuration.GetConnectionString("Kafka")); - - cfg.TopicEndpoint(ProtocolConst.SubscriberHeartbeatReceivedEventName, consumerConfig, configurator => - { - configurator.AutoOffsetReset = AutoOffsetReset.Earliest; - configurator.ConfigureConsumer(c); - }); - - cfg.TopicEndpoint(ProtocolConst.SubscriberLoginReceivedEventName, consumerConfig, configurator => - { - configurator.ConfigureConsumer(c); - configurator.AutoOffsetReset = AutoOffsetReset.Earliest; - }); - - cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedEventName, consumerConfig, configurator => - { - configurator.ConfigureConsumer(c); - configurator.AutoOffsetReset = AutoOffsetReset.Earliest; - }); - - cfg.TopicEndpoint(ProtocolConst.SubscriberReceivedEventName, consumerConfig, configurator => - { - configurator.ConfigureConsumer(c); - configurator.AutoOffsetReset = AutoOffsetReset.Earliest; - }); - - cfg.TopicEndpoint(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, consumerConfig, configurator => - { - configurator.ConfigureConsumer(c); - configurator.AutoOffsetReset = AutoOffsetReset.Earliest; - }); - }); - }); - }); } } } \ No newline at end of file diff --git a/web/JiShe.CollectBus.Host/CollectBusHostModule.cs b/web/JiShe.CollectBus.Host/CollectBusHostModule.cs index aabc2ba..e451a1d 100644 --- a/web/JiShe.CollectBus.Host/CollectBusHostModule.cs +++ b/web/JiShe.CollectBus.Host/CollectBusHostModule.cs @@ -1,7 +1,10 @@ using Hangfire; +using HealthChecks.UI.Client; +using JiShe.CollectBus.Common; using JiShe.CollectBus.Host.Extensions; using JiShe.CollectBus.Host.HealthChecks; using JiShe.CollectBus.Host.Swaggers; +using JiShe.CollectBus.IoTDB.Options; using JiShe.CollectBus.MongoDB; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Swashbuckle.AspNetCore.SwaggerUI; @@ -13,6 +16,7 @@ using Volo.Abp.BackgroundWorkers.Hangfire; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.Modularity; using Volo.Abp.Swashbuckle; +using Volo.Abp.Timing; namespace JiShe.CollectBus.Host { @@ -24,8 +28,9 @@ namespace JiShe.CollectBus.Host typeof(AbpAspNetCoreAuthenticationJwtBearerModule), typeof(AbpAspNetCoreSerilogModule), typeof(AbpSwashbuckleModule), + typeof(AbpTimingModule), typeof(CollectBusApplicationModule), - typeof(CollectBusMongoDbModule), + typeof(CollectBusMongoDbModule), typeof(AbpCachingStackExchangeRedisModule), typeof(AbpBackgroundWorkersHangfireModule) )] @@ -43,11 +48,16 @@ namespace JiShe.CollectBus.Host ConfigureNetwork(context, configuration); ConfigureJwtAuthentication(context, configuration); ConfigureHangfire(context); - ConfigureCap(context, configuration); - //ConfigureMassTransit(context, configuration); - //ConfigureKafkaTopic(context, configuration); ConfigureAuditLog(context); ConfigureCustom(context, configuration); + ConfigureHealthChecks(context, configuration); + Configure(options => { options.Kind = DateTimeKind.Local; }); + + Configure(options => + { + configuration.GetSection(nameof(ServerApplicationOptions)).Bind(options); + }); + } @@ -88,11 +98,16 @@ namespace JiShe.CollectBus.Host }); app.UseConfiguredEndpoints(endpoints => { + if (!configuration.GetValue("HealthChecks:IsEnable")) return; endpoints.MapHealthChecks("/health", new HealthCheckOptions { Predicate = _ => true, ResponseWriter = HealthCheckResponse.Writer }); + endpoints.MapHealthChecksUI(options => + { + options.UIPath = "/health-ui"; + }); }); } } diff --git a/web/JiShe.CollectBus.Host/Extensions/CustomApplicationBuilderExtensions.cs b/web/JiShe.CollectBus.Host/Extensions/CustomApplicationBuilderExtensions.cs index 6019cd2..f933497 100644 --- a/web/JiShe.CollectBus.Host/Extensions/CustomApplicationBuilderExtensions.cs +++ b/web/JiShe.CollectBus.Host/Extensions/CustomApplicationBuilderExtensions.cs @@ -1,7 +1,4 @@ -using JiShe.CollectBus.Protocol.Contracts.Interfaces; -using Volo.Abp.Caching; - -namespace JiShe.CollectBus.Host.Extensions +namespace JiShe.CollectBus.Host.Extensions { public static class CustomApplicationBuilderExtensions { diff --git a/web/JiShe.CollectBus.Host/HealthChecks/CassandraHealthCheck.cs b/web/JiShe.CollectBus.Host/HealthChecks/CassandraHealthCheck.cs new file mode 100644 index 0000000..1157239 --- /dev/null +++ b/web/JiShe.CollectBus.Host/HealthChecks/CassandraHealthCheck.cs @@ -0,0 +1,57 @@ +using Cassandra; +using JiShe.CollectBus.Cassandra; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace JiShe.CollectBus.Host.HealthChecks +{ + /// + /// CassandraHealthCheck + /// + /// + public class CassandraHealthCheck : IHealthCheck + { + private readonly IConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public CassandraHealthCheck(IConfiguration configuration) + { + _configuration = configuration; + } + + /// + /// Runs the health check, returning the status of the component being checked. + /// + /// A context object associated with the current execution. + /// A that can be used to cancel the health check. + /// + /// A that completes when the health check has finished, yielding the status of the component being checked. + /// + public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + var cassandraConfig = new CassandraConfig(); + _configuration.GetSection("Cassandra").Bind(cassandraConfig); + try + { + var clusterBuilder = Cluster.Builder(); + foreach (var node in cassandraConfig.Nodes) + { + clusterBuilder.AddContactPoint(node.Host) + .WithPort(node.Port); + } + clusterBuilder.WithCredentials(cassandraConfig.Username, cassandraConfig.Password); + var cluster = clusterBuilder.Build(); + using var session = await cluster.ConnectAsync(); + var result = await Task.FromResult(session.Execute("SELECT release_version FROM system.local")); + var version = result.First().GetValue("release_version"); + return HealthCheckResult.Healthy($"Cassandra is healthy. Version: {version}"); + } + catch (Exception ex) + { + return new HealthCheckResult(context.Registration.FailureStatus, $"Cassandra is unhealthy: {ex.Message}", ex); + } + } + } +} \ No newline at end of file diff --git a/web/JiShe.CollectBus.Host/HealthChecks/IoTDBHealthCheck.cs b/web/JiShe.CollectBus.Host/HealthChecks/IoTDBHealthCheck.cs new file mode 100644 index 0000000..d3cb45f --- /dev/null +++ b/web/JiShe.CollectBus.Host/HealthChecks/IoTDBHealthCheck.cs @@ -0,0 +1,51 @@ +using System.Net.Sockets; +using JiShe.CollectBus.Cassandra; +using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Options; +using JiShe.CollectBus.IoTDB.Provider; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace JiShe.CollectBus.Host.HealthChecks +{ + /// + /// IoTDBHealthCheck + /// + /// + public class IoTdbHealthCheck : IHealthCheck + { + private readonly IConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public IoTdbHealthCheck(IConfiguration configuration) + { + _configuration = configuration; + } + + /// + /// Runs the health check, returning the status of the component being checked. + /// + /// A context object associated with the current execution. + /// A that can be used to cancel the health check. + /// + /// A that completes when the health check has finished, yielding the status of the component being checked. + /// + public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + try + { + var ioTDbOptions = new IoTDbOptions(); + _configuration.GetSection("IoTDBOptions").Bind(ioTDbOptions); + var pool = new SessionPoolAdapter(ioTDbOptions); + await pool.OpenAsync(); + return HealthCheckResult.Healthy($"IoTDB is healthy."); + } + catch (Exception ex) + { + return new HealthCheckResult(context.Registration.FailureStatus, $"IoTDB不健康: {ex.Message}", ex); + } + } + } +} \ No newline at end of file diff --git a/web/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj b/web/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj index 76dfe60..4ebd67d 100644 --- a/web/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj +++ b/web/JiShe.CollectBus.Host/JiShe.CollectBus.Host.csproj @@ -20,13 +20,14 @@ - - - - + + + + + + + - - @@ -65,7 +66,16 @@ - + + Always + + + Always + + + Always + + Always diff --git a/web/JiShe.CollectBus.Host/JiShe.CollectBus.Protocol.Test.dll b/web/JiShe.CollectBus.Host/JiShe.CollectBus.Protocol.Test.dll new file mode 100644 index 0000000..01d6842 Binary files /dev/null and b/web/JiShe.CollectBus.Host/JiShe.CollectBus.Protocol.Test.dll differ diff --git a/web/JiShe.CollectBus.Host/JiShe.CollectBus.Protocol.dll b/web/JiShe.CollectBus.Host/JiShe.CollectBus.Protocol.dll new file mode 100644 index 0000000..96cf07b Binary files /dev/null and b/web/JiShe.CollectBus.Host/JiShe.CollectBus.Protocol.dll differ diff --git a/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml b/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml index aaadf3f..3a7fbc7 100644 --- a/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml +++ b/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml @@ -11,58 +11,59 @@ - - - - + + + + 后端服务 - - -
- -
-
-
- - - -
-

- SwaggerUI -

-
-
-
-
-
- - - -
-

- Hangfire面板 -

+ +
+ +
+
+
+ + + +
+

+ SwaggerUI +

+
-
-
-
- - - -
-

- CAP -

+ +
+
+ + + +
+

+ Hangfire面板 +

+
-
- @* *@ +
-
\ No newline at end of file diff --git a/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml.cs b/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml.cs index 6019db9..1e18bdb 100644 --- a/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml.cs +++ b/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml.cs @@ -4,6 +4,7 @@ namespace JiShe.CollectBus.Host.Pages { public class Monitor : PageModel { + public void OnGet() { diff --git a/web/JiShe.CollectBus.Host/Program.cs b/web/JiShe.CollectBus.Host/Program.cs index d75a227..3e6bdfd 100644 --- a/web/JiShe.CollectBus.Host/Program.cs +++ b/web/JiShe.CollectBus.Host/Program.cs @@ -1,12 +1,16 @@ -using JiShe.CollectBus.Host; -using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; using Serilog; using Volo.Abp.Modularity.PlugIns; +namespace JiShe.CollectBus.Host; + +/// +/// Program +/// public class Program { /// - /// + /// Main /// /// /// @@ -19,47 +23,13 @@ public class Program loggerConfiguration.ReadFrom.Configuration(context.Configuration); }) .UseAutofac(); + var configuration = builder.Configuration; await builder.AddApplicationAsync(options => { - // ز̶ģʽȲ - options.PlugInSources.AddFolder(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins")); + options.PlugInSources.AddFolder((configuration["PlugInFolder"].IsNullOrWhiteSpace()? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins"): configuration["PlugInFolder"]) ?? string.Empty); }); var app = builder.Build(); await app.InitializeApplicationAsync(); await app.RunAsync(); - - - //await CreateHostBuilder(args).Build().RunAsync(); - } - - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseSerilog((context, loggerConfiguration) => - { - loggerConfiguration.ReadFrom.Configuration(context.Configuration); - }) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }) - .UseAutofac(); - - - - - private static IHostBuilder CreateConsoleHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureServices((hostContext, services) => { ConfigureServices(services, hostContext); }) - .UseAutofac() - .UseSerilog((context, loggerConfiguration) => - { - loggerConfiguration.ReadFrom.Configuration(context.Configuration); - }); - - - private static async Task ConfigureServices(IServiceCollection services, HostBuilderContext hostContext) - { - await services.AddApplicationAsync(); } } \ No newline at end of file diff --git a/web/JiShe.CollectBus.Host/Startup.cs b/web/JiShe.CollectBus.Host/Startup.cs deleted file mode 100644 index 32740ee..0000000 --- a/web/JiShe.CollectBus.Host/Startup.cs +++ /dev/null @@ -1,53 +0,0 @@ -using TouchSocket.Core; -using Volo.Abp.Modularity.PlugIns; - -namespace JiShe.CollectBus.Host -{ - /// - /// Startup - /// - public class Startup - { - private readonly IConfiguration _configuration; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - /// - /// Configures the services. - /// - /// The services. - public void ConfigureServices(IServiceCollection services) - { - services.AddApplication(options => - { - // 加载插件,固定模式,可热插拔 - options.PlugInSources.AddFolder(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins")); - }); - } - - /// - /// Configures the specified application. - /// - /// The application. - /// The lifetime. - public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime) - { - - app.Use(async (context, next) => - { - // 在请求处理之前调用 InitializeApplicationAsync - await app.InitializeApplicationAsync(); - - // 继续请求管道中的下一个中间件 - await next(); - }); - } - } -} diff --git a/web/JiShe.CollectBus.Host/appsettings.Production.json b/web/JiShe.CollectBus.Host/appsettings.Production.json new file mode 100644 index 0000000..bcdd3fc --- /dev/null +++ b/web/JiShe.CollectBus.Host/appsettings.Production.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/web/JiShe.CollectBus.Host/appsettings.json b/web/JiShe.CollectBus.Host/appsettings.json index 7cf18d7..5abdef7 100644 --- a/web/JiShe.CollectBus.Host/appsettings.json +++ b/web/JiShe.CollectBus.Host/appsettings.json @@ -13,7 +13,8 @@ "DotNetCore.CAP": "Warning", "Serilog.AspNetCore": "Information", "Microsoft.EntityFrameworkCore": "Warning", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.AspNetCore.Diagnostics.HealthChecks": "Warning" } }, "WriteTo": [ @@ -43,7 +44,7 @@ "Configuration": "192.168.1.9:6380,password=1q2w3e!@#,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true", "MaxPoolSize": "50", "DefaultDB": "14", - "HangfireDB": "15" + "HangfireDB": "13" }, "Jwt": { "Audience": "JiShe.CollectBus", @@ -51,16 +52,11 @@ "Issuer": "JiShe.CollectBus", "ExpirationTime": 2 }, - "HealthCheck": { + "HealthChecks": { "IsEnable": true, - "MySql": { - "IsEnable": true - }, - "Pings": { - "IsEnable": true, - "Host": "https://www.baidu.com/", - "TimeOut": 5000 - } + "HealthCheckDatabaseName": "HealthChecks", + "EvaluationTimeInSeconds": 10, + "MinimumSecondsBetweenFailureNotifications": 60 }, "SwaggerConfig": [ { @@ -74,14 +70,6 @@ "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, @@ -92,38 +80,9 @@ "SaslPassword": "lixiao1980", "KafkaReplicationFactor": 3, "NumPartitions": 30, - "ServerTagName": "JiSheCollectBus2" - //"Topic": { - // "ReplicationFactor": 3, - // "NumPartitions": 1000 - //} + "ServerTagName": "JiSheCollectBus100", + "FirstCollectionTime": "2025-04-22 16:07:00" }, - //"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", @@ -133,7 +92,6 @@ "OpenDebugMode": true, "UseTableSessionPoolByDefault": false }, - "ServerTagName": "JiSheCollectBus3", "Cassandra": { "ReplicationStrategy": { "Class": "NetworkTopologyStrategy", //策略为NetworkTopologyStrategy时才会有多个数据中心,SimpleStrategy用在只有一个数据中心的情况下 @@ -182,5 +140,15 @@ "SerialConsistencyLevel": "Serial", "DefaultIdempotence": true } + }, + "PlugInFolder": "C:\\Users\\Dai Zan\\Desktop\\Plugins", + "ServerApplicationOptions": { + "ServerTagName": "JiSheCollectBus100", + "SystemType": null, + "FirstCollectionTime": "2025-04-22 16:07:00", + "AutomaticVerificationTime": "16:07:00", + "AutomaticGetTerminalVersionTime": "17:07:00", + "AutomaticGetTelematicsModuleTime": "17:30:00" } + "PlugInFolder": "" } \ No newline at end of file