Compare commits
No commits in common. "master" and "feature_定时抄读_11_CY" have entirely different histories.
master
...
feature_定时
@ -1,30 +0,0 @@
|
|||||||
**/.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/**
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
@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
|
|
||||||
52
Dockerfile
52
Dockerfile
@ -1,52 +0,0 @@
|
|||||||
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.Main.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.Main.sln"
|
|
||||||
|
|
||||||
# 构建项目
|
|
||||||
WORKDIR "/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"]
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
|
||||||
VisualStudioVersion = 17.9.34728.123
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.PluginFileWatcher", "external\JiShe.CollectBus.PluginFileWatcher\JiShe.CollectBus.PluginFileWatcher.csproj", "{0F67A493-A4DF-550E-AB4D-95F55144C706}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{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
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@ -1,188 +0,0 @@
|
|||||||
|
|
||||||
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}") = "5.Protocols", "5.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.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
|
|
||||||
PackageAndPublish.bat = PackageAndPublish.bat
|
|
||||||
readme.md = readme.md
|
|
||||||
Temp.Dockerfile = Temp.Dockerfile
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Analyzers.Shared", "shared\JiShe.CollectBus.Analyzers.Shared\JiShe.CollectBus.Analyzers.Shared.csproj", "{DD68F314-BC66-5601-B094-B1A7BE93F4E0}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Analyzers", "modules\JiShe.CollectBus.Analyzers\JiShe.CollectBus.Analyzers.csproj", "{EB97C7BB-1E4A-CBA4-04C1-22DBF48A253A}"
|
|
||||||
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
|
|
||||||
{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
|
|
||||||
{DD68F314-BC66-5601-B094-B1A7BE93F4E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{DD68F314-BC66-5601-B094-B1A7BE93F4E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{DD68F314-BC66-5601-B094-B1A7BE93F4E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{DD68F314-BC66-5601-B094-B1A7BE93F4E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{EB97C7BB-1E4A-CBA4-04C1-22DBF48A253A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{EB97C7BB-1E4A-CBA4-04C1-22DBF48A253A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{EB97C7BB-1E4A-CBA4-04C1-22DBF48A253A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{EB97C7BB-1E4A-CBA4-04C1-22DBF48A253A}.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}
|
|
||||||
{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}
|
|
||||||
{DD68F314-BC66-5601-B094-B1A7BE93F4E0} = {EBF7C01F-9B4F-48E6-8418-2CBFDA51EB0B}
|
|
||||||
{EB97C7BB-1E4A-CBA4-04C1-22DBF48A253A} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59}
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@ -1,179 +0,0 @@
|
|||||||
|
|
||||||
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
|
|
||||||
@ -19,6 +19,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Host", "we
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Common", "shared\JiShe.CollectBus.Common\JiShe.CollectBus.Common.csproj", "{AD2F1928-4411-4511-B564-5FB996EC08B9}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Common", "shared\JiShe.CollectBus.Common\JiShe.CollectBus.Common.csproj", "{AD2F1928-4411-4511-B564-5FB996EC08B9}"
|
||||||
EndProject
|
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}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.DbMigrator", "services\JiShe.CollectBus.DbMigrator\JiShe.CollectBus.DbMigrator.csproj", "{8BA01C3D-297D-42DF-BD63-EF07202A0A67}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.FreeSql", "modules\JiShe.CollectBus.FreeSql\JiShe.CollectBus.FreeSql.csproj", "{FE0457D9-4038-4A17-8808-DCAD06CFC0A0}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.FreeSql", "modules\JiShe.CollectBus.FreeSql\JiShe.CollectBus.FreeSql.csproj", "{FE0457D9-4038-4A17-8808-DCAD06CFC0A0}"
|
||||||
@ -37,43 +41,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1.Web", "1.Web", "{A02F7D8A
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.Modules", "4.Modules", "{2E0FE301-34C3-4561-9CAE-C7A9E65AEE59}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.Modules", "4.Modules", "{2E0FE301-34C3-4561-9CAE-C7A9E65AEE59}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5.Protocols", "5.Protocols", "{3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3.Protocols", "3.Protocols", "{3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2.Services", "2.Services", "{BA4DA3E7-9AD0-47AD-A0E6-A0BB6700DA23}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2.Services", "2.Services", "{BA4DA3E7-9AD0-47AD-A0E6-A0BB6700DA23}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3.Shared", "3.Shared", "{EBF7C01F-9B4F-48E6-8418-2CBFDA51EB0B}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5.Shared", "5.Shared", "{EBF7C01F-9B4F-48E6-8418-2CBFDA51EB0B}"
|
||||||
EndProject
|
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}"
|
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
|
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
|
|
||||||
PackageAndPublish.bat = PackageAndPublish.bat
|
|
||||||
readme.md = readme.md
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Analyzers.Shared", "shared\JiShe.CollectBus.Analyzers.Shared\JiShe.CollectBus.Analyzers.Shared.csproj", "{DD68F314-BC66-5601-B094-B1A7BE93F4E0}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Analyzers", "modules\JiShe.CollectBus.Analyzers\JiShe.CollectBus.Analyzers.csproj", "{EB97C7BB-1E4A-CBA4-04C1-22DBF48A253A}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Migration.Application.Contracts", "services\JiShe.CollectBus.Migration.Application.Contracts\JiShe.CollectBus.Migration.Application.Contracts.csproj", "{E01625B5-B5B7-7A4B-468F-EC12C1BDBA2A}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Migration.Application", "services\JiShe.CollectBus.Migration.Application\JiShe.CollectBus.Migration.Application.csproj", "{B955C5DA-3C20-35D2-0770-8FE473C41C44}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Migration.Host", "web\JiShe.CollectBus.Migration.Host\JiShe.CollectBus.Migration.Host.csproj", "{995D3D91-7221-D4A3-A7B2-FEC202328A18}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiShe.CollectBus.Migration.HttpApi", "web\JiShe.CollectBus.Migration.HttpApi\JiShe.CollectBus.Migration.HttpApi.csproj", "{8A113DE5-7D50-6E6B-739F-B6EEAD5E13B4}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -112,6 +87,14 @@ Global
|
|||||||
{AD2F1928-4411-4511-B564-5FB996EC08B9}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{AD2F1928-4411-4511-B564-5FB996EC08B9}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{8BA01C3D-297D-42DF-BD63-EF07202A0A67}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
@ -144,46 +127,6 @@ Global
|
|||||||
{6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B}.Release|Any CPU.Build.0 = 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
|
|
||||||
{DD68F314-BC66-5601-B094-B1A7BE93F4E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{DD68F314-BC66-5601-B094-B1A7BE93F4E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{DD68F314-BC66-5601-B094-B1A7BE93F4E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{DD68F314-BC66-5601-B094-B1A7BE93F4E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{EB97C7BB-1E4A-CBA4-04C1-22DBF48A253A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{EB97C7BB-1E4A-CBA4-04C1-22DBF48A253A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{EB97C7BB-1E4A-CBA4-04C1-22DBF48A253A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{EB97C7BB-1E4A-CBA4-04C1-22DBF48A253A}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{E01625B5-B5B7-7A4B-468F-EC12C1BDBA2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{E01625B5-B5B7-7A4B-468F-EC12C1BDBA2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{E01625B5-B5B7-7A4B-468F-EC12C1BDBA2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{E01625B5-B5B7-7A4B-468F-EC12C1BDBA2A}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{B955C5DA-3C20-35D2-0770-8FE473C41C44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{B955C5DA-3C20-35D2-0770-8FE473C41C44}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{B955C5DA-3C20-35D2-0770-8FE473C41C44}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{B955C5DA-3C20-35D2-0770-8FE473C41C44}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{995D3D91-7221-D4A3-A7B2-FEC202328A18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{995D3D91-7221-D4A3-A7B2-FEC202328A18}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{995D3D91-7221-D4A3-A7B2-FEC202328A18}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{995D3D91-7221-D4A3-A7B2-FEC202328A18}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{8A113DE5-7D50-6E6B-739F-B6EEAD5E13B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A113DE5-7D50-6E6B-739F-B6EEAD5E13B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{8A113DE5-7D50-6E6B-739F-B6EEAD5E13B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{8A113DE5-7D50-6E6B-739F-B6EEAD5E13B4}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -197,6 +140,8 @@ Global
|
|||||||
{077AA5F8-8B61-420C-A6B5-0150A66FDB34} = {A02F7D8A-04DC-44D6-94D4-3F65712D6B94}
|
{077AA5F8-8B61-420C-A6B5-0150A66FDB34} = {A02F7D8A-04DC-44D6-94D4-3F65712D6B94}
|
||||||
{35829A15-4127-4F69-8BDE-9405DEAACA9A} = {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}
|
{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}
|
{8BA01C3D-297D-42DF-BD63-EF07202A0A67} = {BA4DA3E7-9AD0-47AD-A0E6-A0BB6700DA23}
|
||||||
{FE0457D9-4038-4A17-8808-DCAD06CFC0A0} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59}
|
{FE0457D9-4038-4A17-8808-DCAD06CFC0A0} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59}
|
||||||
{C06C4082-638F-2996-5FED-7784475766C1} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59}
|
{C06C4082-638F-2996-5FED-7784475766C1} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59}
|
||||||
@ -205,16 +150,6 @@ Global
|
|||||||
{A377955E-7EA1-6F29-8CF7-774569E93925} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC}
|
{A377955E-7EA1-6F29-8CF7-774569E93925} = {3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC}
|
||||||
{443B4549-0AC0-4493-8F3E-49C83225DD76} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59}
|
{443B4549-0AC0-4493-8F3E-49C83225DD76} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59}
|
||||||
{6D6A2A58-7406-9C8C-7B23-3E442CCE3E6B} = {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}
|
|
||||||
{DD68F314-BC66-5601-B094-B1A7BE93F4E0} = {EBF7C01F-9B4F-48E6-8418-2CBFDA51EB0B}
|
|
||||||
{EB97C7BB-1E4A-CBA4-04C1-22DBF48A253A} = {2E0FE301-34C3-4561-9CAE-C7A9E65AEE59}
|
|
||||||
{E01625B5-B5B7-7A4B-468F-EC12C1BDBA2A} = {BA4DA3E7-9AD0-47AD-A0E6-A0BB6700DA23}
|
|
||||||
{B955C5DA-3C20-35D2-0770-8FE473C41C44} = {BA4DA3E7-9AD0-47AD-A0E6-A0BB6700DA23}
|
|
||||||
{995D3D91-7221-D4A3-A7B2-FEC202328A18} = {A02F7D8A-04DC-44D6-94D4-3F65712D6B94}
|
|
||||||
{8A113DE5-7D50-6E6B-739F-B6EEAD5E13B4} = {A02F7D8A-04DC-44D6-94D4-3F65712D6B94}
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD}
|
SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD}
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<configuration>
|
|
||||||
<packageSources>
|
|
||||||
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
|
|
||||||
</packageSources>
|
|
||||||
</configuration>
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
@echo off
|
|
||||||
setlocal enabledelayedexpansion
|
|
||||||
|
|
||||||
set VERSION=1.0.0
|
|
||||||
set CONFIGURATION=Release
|
|
||||||
set OUTPUT_DIR=%~dp0\nupkgs
|
|
||||||
set API_KEY=your-nuget-api-key
|
|
||||||
set SOURCE=https://api.nuget.org/v3/index.json
|
|
||||||
|
|
||||||
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 :
|
|
||||||
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.T37612012\JiShe.CollectBus.Protocol.T37612012.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
|
|
||||||
|
|
||||||
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 项目
|
|
||||||
@REM echo 打包 Services 项目...
|
|
||||||
@REM call :PackProject services\JiShe.CollectBus.Domain\JiShe.CollectBus.Domain.csproj
|
|
||||||
@REM call :PackProject services\JiShe.CollectBus.Application.Contracts\JiShe.CollectBus.Application.Contracts.csproj
|
|
||||||
@REM call :PackProject services\JiShe.CollectBus.Application\JiShe.CollectBus.Application.csproj
|
|
||||||
@REM 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 %SOURCE% --skip-duplicate
|
|
||||||
)
|
|
||||||
echo 所有包已发布完成!
|
|
||||||
) else (
|
|
||||||
echo 跳过发布操作。所有包都在 %OUTPUT_DIR% 目录中。
|
|
||||||
)
|
|
||||||
|
|
||||||
goto :eof
|
|
||||||
|
|
||||||
:PackProject
|
|
||||||
if exist "%~1" (
|
|
||||||
echo 打包: %~1
|
|
||||||
dotnet pack "%~1" -c %CONFIGURATION% --include-symbols -p:SymbolPackageFormat=snupkg -p:PackageVersion=%VERSION% -o "%OUTPUT_DIR%"
|
|
||||||
) else (
|
|
||||||
echo 警告: 项目不存在 - %~1
|
|
||||||
)
|
|
||||||
goto :eof
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
COPY ["publish/", "/app/publish"]
|
|
||||||
|
|
||||||
# # WORKDIR /src
|
|
||||||
# COPY ["JiShe.CollectBus.Main.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.Main.sln"
|
|
||||||
|
|
||||||
# # 构建项目
|
|
||||||
# WORKDIR "/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 .
|
|
||||||
|
|
||||||
# 设置入口点
|
|
||||||
ENTRYPOINT ["dotnet", "JiShe.CollectBus.Host.dll"]
|
|
||||||
|
|
||||||
# 启动命令
|
|
||||||
# 可选:添加命令行参数
|
|
||||||
# CMD ["--urls", "http://+:80"]
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,287 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.PluginFileWatcher
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 文件监控程序的配置类
|
|
||||||
/// </summary>
|
|
||||||
public class FileMonitorConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 基本配置
|
|
||||||
/// </summary>
|
|
||||||
public GeneralConfig General { get; set; } = new GeneralConfig();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件过滤配置
|
|
||||||
/// </summary>
|
|
||||||
public FileFiltersConfig FileFilters { get; set; } = new FileFiltersConfig();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 性能相关配置
|
|
||||||
/// </summary>
|
|
||||||
public PerformanceConfig Performance { get; set; } = new PerformanceConfig();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 健壮性相关配置
|
|
||||||
/// </summary>
|
|
||||||
public RobustnessConfig Robustness { get; set; } = new RobustnessConfig();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件存储和回放配置
|
|
||||||
/// </summary>
|
|
||||||
public EventStorageConfig EventStorage { get; set; } = new EventStorageConfig();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件系统通知过滤器配置
|
|
||||||
/// </summary>
|
|
||||||
public List<string> NotifyFilters { get; set; } = new List<string>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 日志配置
|
|
||||||
/// </summary>
|
|
||||||
public LoggingConfig Logging { get; set; } = new LoggingConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 常规配置
|
|
||||||
/// </summary>
|
|
||||||
public class GeneralConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 是否启用文件过滤
|
|
||||||
/// </summary>
|
|
||||||
public bool EnableFileFiltering { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 内存监控间隔(分钟)
|
|
||||||
/// </summary>
|
|
||||||
public int MemoryMonitorIntervalMinutes { get; set; } = 1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 默认监控路径
|
|
||||||
/// </summary>
|
|
||||||
public string DefaultMonitorPath { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件过滤配置
|
|
||||||
/// </summary>
|
|
||||||
public class FileFiltersConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 允许监控的文件扩展名
|
|
||||||
/// </summary>
|
|
||||||
public string[] AllowedExtensions { get; set; } = new[] { ".dll" };
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 排除的目录
|
|
||||||
/// </summary>
|
|
||||||
public string[] ExcludedDirectories { get; set; } = new[] { "bin", "obj", "node_modules" };
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否包含子目录
|
|
||||||
/// </summary>
|
|
||||||
public bool IncludeSubdirectories { get; set; } = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 性能相关配置
|
|
||||||
/// </summary>
|
|
||||||
public class PerformanceConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 内存清理阈值(事件数)
|
|
||||||
/// </summary>
|
|
||||||
public int MemoryCleanupThreshold { get; set; } = 5000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 通道容量
|
|
||||||
/// </summary>
|
|
||||||
public int ChannelCapacity { get; set; } = 1000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件去抖时间(秒)
|
|
||||||
/// </summary>
|
|
||||||
public int EventDebounceTimeSeconds { get; set; } = 3;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 最大字典大小
|
|
||||||
/// </summary>
|
|
||||||
public int MaxDictionarySize { get; set; } = 10000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清理间隔(秒)
|
|
||||||
/// </summary>
|
|
||||||
public int CleanupIntervalSeconds { get; set; } = 5;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 处理延迟(毫秒)
|
|
||||||
/// </summary>
|
|
||||||
public int ProcessingDelayMs { get; set; } = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 健壮性相关配置
|
|
||||||
/// </summary>
|
|
||||||
public class RobustnessConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 是否启用自动恢复机制
|
|
||||||
/// </summary>
|
|
||||||
public bool EnableAutoRecovery { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 监控器健康检查间隔(秒)
|
|
||||||
/// </summary>
|
|
||||||
public int WatcherHealthCheckIntervalSeconds { get; set; } = 30;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 监控器无响应超时时间(秒)
|
|
||||||
/// </summary>
|
|
||||||
public int WatcherTimeoutSeconds { get; set; } = 60;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 监控器重启尝试最大次数
|
|
||||||
/// </summary>
|
|
||||||
public int MaxRestartAttempts { get; set; } = 3;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重启尝试之间的延迟(秒)
|
|
||||||
/// </summary>
|
|
||||||
public int RestartDelaySeconds { get; set; } = 5;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否启用文件锁检测
|
|
||||||
/// </summary>
|
|
||||||
public bool EnableFileLockDetection { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 对锁定文件的处理策略: Skip(跳过), Retry(重试), Log(仅记录)
|
|
||||||
/// </summary>
|
|
||||||
public string LockedFileStrategy { get; set; } = "Retry";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件锁定重试次数
|
|
||||||
/// </summary>
|
|
||||||
public int FileLockRetryCount { get; set; } = 3;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件锁定重试间隔(毫秒)
|
|
||||||
/// </summary>
|
|
||||||
public int FileLockRetryDelayMs { get; set; } = 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件存储和回放配置
|
|
||||||
/// </summary>
|
|
||||||
public class EventStorageConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 是否启用事件存储
|
|
||||||
/// </summary>
|
|
||||||
public bool EnableEventStorage { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 存储类型:SQLite 或 File
|
|
||||||
/// </summary>
|
|
||||||
public string StorageType { get; set; } = "SQLite";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件存储目录
|
|
||||||
/// </summary>
|
|
||||||
public string StorageDirectory { get; set; } = "D:/EventLogs";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SQLite数据库文件路径
|
|
||||||
/// </summary>
|
|
||||||
public string DatabasePath { get; set; } = "D:/EventLogs/events.db";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SQLite连接字符串
|
|
||||||
/// </summary>
|
|
||||||
public string ConnectionString { get; set; } = "Data Source=D:/EventLogs/events.db";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 数据库命令超时(秒)
|
|
||||||
/// </summary>
|
|
||||||
public int CommandTimeout { get; set; } = 30;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件日志文件名格式 (使用DateTime.ToString格式)
|
|
||||||
/// </summary>
|
|
||||||
public string LogFileNameFormat { get; set; } = "FileEvents_{0:yyyy-MM-dd}.json";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 存储间隔(秒),多久将缓存的事件写入一次存储
|
|
||||||
/// </summary>
|
|
||||||
public int StorageIntervalSeconds { get; set; } = 60;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件批量写入大小,达到此数量时立即写入存储
|
|
||||||
/// </summary>
|
|
||||||
public int BatchSize { get; set; } = 100;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 最大保留事件记录条数
|
|
||||||
/// </summary>
|
|
||||||
public int MaxEventRecords { get; set; } = 100000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 数据保留天数
|
|
||||||
/// </summary>
|
|
||||||
public int DataRetentionDays { get; set; } = 30;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 最大保留日志文件数
|
|
||||||
/// </summary>
|
|
||||||
public int MaxLogFiles { get; set; } = 30;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否压缩存储的事件日志
|
|
||||||
/// </summary>
|
|
||||||
public bool CompressLogFiles { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否可以回放事件
|
|
||||||
/// </summary>
|
|
||||||
public bool EnableEventReplay { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 回放时间间隔(毫秒)
|
|
||||||
/// </summary>
|
|
||||||
public int ReplayIntervalMs { get; set; } = 100;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 回放速度倍率,大于1加速,小于1减速
|
|
||||||
/// </summary>
|
|
||||||
public double ReplaySpeedFactor { get; set; } = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 日志相关配置
|
|
||||||
/// </summary>
|
|
||||||
public class LoggingConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 日志级别:Verbose、Debug、Information、Warning、Error、Fatal
|
|
||||||
/// </summary>
|
|
||||||
public string LogLevel { get; set; } = "Information";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否记录文件事件处理详情
|
|
||||||
/// </summary>
|
|
||||||
public bool LogFileEventDetails { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 日志文件保留天数
|
|
||||||
/// </summary>
|
|
||||||
public int RetainedLogDays { get; set; } = 30;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 日志文件目录
|
|
||||||
/// </summary>
|
|
||||||
public string LogDirectory { get; set; } = "Logs";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,277 +0,0 @@
|
|||||||
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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 数据库操作工具类,用于命令行测试数据库功能
|
|
||||||
/// </summary>
|
|
||||||
public class DbUtility
|
|
||||||
{
|
|
||||||
private readonly EventDatabaseManager _dbManager;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly FileMonitorConfig _config;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化数据库工具类
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="configPath">配置文件路径</param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 执行数据库维护操作
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="args">命令行参数</param>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示帮助信息
|
|
||||||
/// </summary>
|
|
||||||
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条)");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示数据库统计信息
|
|
||||||
/// </summary>
|
|
||||||
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, "获取数据库统计信息时发生错误");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询事件
|
|
||||||
/// </summary>
|
|
||||||
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<FileEventType>(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, "查询事件时发生错误");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 生成测试数据
|
|
||||||
/// </summary>
|
|
||||||
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<FileEvent>();
|
|
||||||
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, "生成测试数据时发生错误");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
# 请参阅 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"]
|
|
||||||
@ -1,481 +0,0 @@
|
|||||||
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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// SQLite数据库管理器,用于管理文件事件的存储和检索
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化数据库管理器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="config">配置对象</param>
|
|
||||||
/// <param name="logger">日志记录器</param>
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化数据库,确保必要的表已创建
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保存文件事件到数据库
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="events">要保存的事件列表</param>
|
|
||||||
public async Task SaveEventsAsync(List<FileEvent> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="queryParams">查询参数</param>
|
|
||||||
/// <returns>查询结果</returns>
|
|
||||||
public async Task<EventQueryResult> 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<string>();
|
|
||||||
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<int>(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<dynamic>(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<dynamic>(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清理旧数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="retentionDays">数据保留天数</param>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取数据库统计信息
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>数据库统计信息</returns>
|
|
||||||
public async Task<DatabaseStats> 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<int>("SELECT COUNT(*) FROM FileEvents", commandTimeout: _commandTimeout);
|
|
||||||
|
|
||||||
// 获取最早和最新事件时间
|
|
||||||
stats.OldestEventTime = await connection.ExecuteScalarAsync<DateTime?>("SELECT Timestamp FROM FileEvents ORDER BY Timestamp ASC LIMIT 1", commandTimeout: _commandTimeout);
|
|
||||||
stats.NewestEventTime = await connection.ExecuteScalarAsync<DateTime?>("SELECT Timestamp FROM FileEvents ORDER BY Timestamp DESC LIMIT 1", commandTimeout: _commandTimeout);
|
|
||||||
|
|
||||||
// 获取事件类型分布
|
|
||||||
var eventTypeCounts = await connection.QueryAsync<dynamic>("SELECT EventType, COUNT(*) AS Count FROM FileEvents GROUP BY EventType", commandTimeout: _commandTimeout);
|
|
||||||
stats.EventTypeCounts = new Dictionary<FileEventType, int>();
|
|
||||||
|
|
||||||
foreach (var item in eventTypeCounts)
|
|
||||||
{
|
|
||||||
stats.EventTypeCounts[(FileEventType)item.EventType] = item.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取扩展名分布(前10个)
|
|
||||||
var extensionCounts = await connection.QueryAsync<dynamic>(
|
|
||||||
"SELECT Extension, COUNT(*) AS Count FROM FileEvents GROUP BY Extension ORDER BY Count DESC LIMIT 10",
|
|
||||||
commandTimeout: _commandTimeout);
|
|
||||||
stats.TopExtensions = new Dictionary<string, int>();
|
|
||||||
|
|
||||||
foreach (var item in extensionCounts)
|
|
||||||
{
|
|
||||||
stats.TopExtensions[item.Extension] = item.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "获取数据库统计信息时发生错误");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 释放资源
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 数据库统计信息
|
|
||||||
/// </summary>
|
|
||||||
public class DatabaseStats
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 事件总数
|
|
||||||
/// </summary>
|
|
||||||
public int TotalEvents { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 最早事件时间
|
|
||||||
/// </summary>
|
|
||||||
public DateTime? OldestEventTime { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 最新事件时间
|
|
||||||
/// </summary>
|
|
||||||
public DateTime? NewestEventTime { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件类型计数
|
|
||||||
/// </summary>
|
|
||||||
public Dictionary<FileEventType, int> EventTypeCounts { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 排名前列的文件扩展名
|
|
||||||
/// </summary>
|
|
||||||
public Dictionary<string, int> TopExtensions { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,745 +0,0 @@
|
|||||||
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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 负责文件事件的存储、查询和回放
|
|
||||||
/// </summary>
|
|
||||||
public class EventStorage : IDisposable
|
|
||||||
{
|
|
||||||
private readonly FileMonitorConfig _config;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly ConcurrentQueue<FileEvent> _eventQueue;
|
|
||||||
private readonly Timer _storageTimer;
|
|
||||||
private readonly SemaphoreSlim _storageLock = new SemaphoreSlim(1, 1);
|
|
||||||
private readonly string _storageDirectory;
|
|
||||||
private readonly EventDatabaseManager _dbManager;
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 创建新的事件存储管理器实例
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="config">文件监控配置</param>
|
|
||||||
/// <param name="logger">日志记录器</param>
|
|
||||||
public EventStorage(FileMonitorConfig config, ILogger logger)
|
|
||||||
{
|
|
||||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
||||||
_eventQueue = new ConcurrentQueue<FileEvent>();
|
|
||||||
|
|
||||||
// 确保存储目录存在
|
|
||||||
_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}秒");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 记录一个文件事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fileEvent">文件事件</param>
|
|
||||||
public void RecordEvent(FileEvent fileEvent)
|
|
||||||
{
|
|
||||||
if (fileEvent == null || !_config.EventStorage.EnableEventStorage) return;
|
|
||||||
|
|
||||||
_eventQueue.Enqueue(fileEvent);
|
|
||||||
_logger.LogDebug($"文件事件已加入队列:{fileEvent.EventType} - {fileEvent.FullPath}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 从FileSystemEventArgs记录事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="e">文件系统事件参数</param>
|
|
||||||
public void RecordEvent(FileSystemEventArgs e)
|
|
||||||
{
|
|
||||||
if (e == null || !_config.EventStorage.EnableEventStorage) return;
|
|
||||||
|
|
||||||
var fileEvent = FileEvent.FromFileSystemEventArgs(e);
|
|
||||||
RecordEvent(fileEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 定时将事件保存到文件
|
|
||||||
/// </summary>
|
|
||||||
private async void SaveEventsTimerCallback(object state)
|
|
||||||
{
|
|
||||||
if (_disposed || _eventQueue.IsEmpty) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 防止多个定时器回调同时执行
|
|
||||||
if (!await _storageLock.WaitAsync(0))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 从队列中取出事件
|
|
||||||
List<FileEvent> eventsToSave = new List<FileEvent>();
|
|
||||||
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, "保存事件时发生错误");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 将事件保存到文件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="events">要保存的事件列表</param>
|
|
||||||
private async Task SaveEventsToFileAsync(List<FileEvent> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 压缩并保存字符串到文件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">要保存的内容</param>
|
|
||||||
/// <param name="filePath">文件路径</param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清理过多的日志文件
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询历史事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="queryParams">查询参数</param>
|
|
||||||
/// <returns>查询结果</returns>
|
|
||||||
public async Task<EventQueryResult> 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<FileEvent> allEvents = new List<FileEvent>();
|
|
||||||
|
|
||||||
// 加载所有日志文件中的事件
|
|
||||||
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<FileEvent> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 从文件加载事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">文件路径</param>
|
|
||||||
/// <returns>事件列表</returns>
|
|
||||||
private async Task<List<FileEvent>> LoadEventsFromFileAsync(string filePath)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
|
|
||||||
{
|
|
||||||
return new List<FileEvent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
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<EventLogFile>(jsonContent);
|
|
||||||
return logFile?.Events ?? new List<FileEvent>();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"从文件加载事件失败:{filePath}");
|
|
||||||
return new List<FileEvent>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启动事件回放会话
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="queryParams">查询参数,定义要回放的事件</param>
|
|
||||||
/// <param name="replayHandler">回放处理回调</param>
|
|
||||||
/// <param name="cancellationToken">取消标记</param>
|
|
||||||
/// <returns>回放会话控制器</returns>
|
|
||||||
public async Task<EventReplaySession> StartReplayAsync(
|
|
||||||
EventQueryParams queryParams,
|
|
||||||
Func<FileEvent, Task> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 释放资源
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_disposed) return;
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
_storageTimer?.Dispose();
|
|
||||||
_storageLock?.Dispose();
|
|
||||||
_dbManager?.Dispose();
|
|
||||||
|
|
||||||
// 尝试保存剩余事件
|
|
||||||
if (_config.EventStorage.EnableEventStorage && !_eventQueue.IsEmpty)
|
|
||||||
{
|
|
||||||
var remainingEvents = new List<FileEvent>();
|
|
||||||
while (!_eventQueue.IsEmpty && _eventQueue.TryDequeue(out var evt))
|
|
||||||
{
|
|
||||||
remainingEvents.Add(evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remainingEvents.Count > 0)
|
|
||||||
{
|
|
||||||
SaveEventsToFileAsync(remainingEvents).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件回放会话
|
|
||||||
/// </summary>
|
|
||||||
public class EventReplaySession : IDisposable
|
|
||||||
{
|
|
||||||
private readonly List<FileEvent> _events;
|
|
||||||
private readonly Func<FileEvent, Task> _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);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 回放进度(0-100)
|
|
||||||
/// </summary>
|
|
||||||
public int Progress { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前回放的事件索引
|
|
||||||
/// </summary>
|
|
||||||
public int CurrentIndex { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件总数
|
|
||||||
/// </summary>
|
|
||||||
public int TotalEvents => _events?.Count ?? 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 回放是否已完成
|
|
||||||
/// </summary>
|
|
||||||
public bool IsCompleted { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 回放是否已暂停
|
|
||||||
/// </summary>
|
|
||||||
public bool IsPaused => _isPaused;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 回放已处理的事件数
|
|
||||||
/// </summary>
|
|
||||||
public int ProcessedEvents { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 回放开始时间
|
|
||||||
/// </summary>
|
|
||||||
public DateTime StartTime { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 回放结束时间(如果已完成)
|
|
||||||
/// </summary>
|
|
||||||
public DateTime? EndTime { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 创建新的事件回放会话
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="events">要回放的事件</param>
|
|
||||||
/// <param name="replayHandler">回放处理回调</param>
|
|
||||||
/// <param name="replayIntervalMs">回放间隔(毫秒)</param>
|
|
||||||
/// <param name="speedFactor">速度因子</param>
|
|
||||||
/// <param name="logger">日志记录器</param>
|
|
||||||
/// <param name="cancellationToken">取消标记</param>
|
|
||||||
public EventReplaySession(
|
|
||||||
List<FileEvent> events,
|
|
||||||
Func<FileEvent, Task> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启动回放
|
|
||||||
/// </summary>
|
|
||||||
public async Task StartAsync()
|
|
||||||
{
|
|
||||||
if (_replayTask != null) return;
|
|
||||||
|
|
||||||
StartTime = DateTime.Now;
|
|
||||||
_replayTask = Task.Run(ReplayEventsAsync, _linkedCts.Token);
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 暂停回放
|
|
||||||
/// </summary>
|
|
||||||
public async Task PauseAsync()
|
|
||||||
{
|
|
||||||
if (_isPaused || IsCompleted) return;
|
|
||||||
|
|
||||||
await _pauseSemaphore.WaitAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_isPaused = true;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_pauseSemaphore.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("事件回放已暂停");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 恢复回放
|
|
||||||
/// </summary>
|
|
||||||
public async Task ResumeAsync()
|
|
||||||
{
|
|
||||||
if (!_isPaused || IsCompleted) return;
|
|
||||||
|
|
||||||
await _pauseSemaphore.WaitAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_isPaused = false;
|
|
||||||
// 释放信号量以允许回放任务继续
|
|
||||||
_pauseSemaphore.Release();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_pauseSemaphore.Release();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("事件回放已恢复");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 停止回放
|
|
||||||
/// </summary>
|
|
||||||
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, "停止事件回放时发生错误");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 回放事件处理
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 释放资源
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_disposed) return;
|
|
||||||
_disposed = true;
|
|
||||||
|
|
||||||
_linkedCts?.Cancel();
|
|
||||||
_linkedCts?.Dispose();
|
|
||||||
_pauseSemaphore?.Dispose();
|
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,254 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.PluginFileWatcher
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 表示一个文件系统事件的数据模型,用于序列化和存储
|
|
||||||
/// </summary>
|
|
||||||
public class FileEvent
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 事件唯一标识
|
|
||||||
/// </summary>
|
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件发生时间
|
|
||||||
/// </summary>
|
|
||||||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件类型
|
|
||||||
/// </summary>
|
|
||||||
public FileEventType EventType { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件完整路径
|
|
||||||
/// </summary>
|
|
||||||
public string FullPath { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件名
|
|
||||||
/// </summary>
|
|
||||||
public string FileName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件所在目录
|
|
||||||
/// </summary>
|
|
||||||
public string Directory { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件扩展名
|
|
||||||
/// </summary>
|
|
||||||
public string Extension { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重命名前的旧文件名(仅在重命名事件中有效)
|
|
||||||
/// </summary>
|
|
||||||
public string OldFileName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重命名前的旧路径(仅在重命名事件中有效)
|
|
||||||
/// </summary>
|
|
||||||
public string OldFullPath { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件大小(字节),如果可获取
|
|
||||||
/// </summary>
|
|
||||||
public long? FileSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 自定义属性,可用于存储其他元数据
|
|
||||||
/// </summary>
|
|
||||||
public Dictionary<string, string> Metadata { get; set; } = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 从FileSystemEventArgs创建FileEvent
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="e">FileSystemEventArgs参数</param>
|
|
||||||
/// <returns>FileEvent对象</returns>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 将WatcherChangeTypes转换为FileEventType
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="changeType">WatcherChangeTypes枚举值</param>
|
|
||||||
/// <returns>对应的FileEventType</returns>
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件事件类型
|
|
||||||
/// </summary>
|
|
||||||
public enum FileEventType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 文件被创建
|
|
||||||
/// </summary>
|
|
||||||
Created,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件被修改
|
|
||||||
/// </summary>
|
|
||||||
Modified,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件被删除
|
|
||||||
/// </summary>
|
|
||||||
Deleted,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件被重命名
|
|
||||||
/// </summary>
|
|
||||||
Renamed,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 其他类型事件
|
|
||||||
/// </summary>
|
|
||||||
Other
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 表示一个事件日志文件
|
|
||||||
/// </summary>
|
|
||||||
public class EventLogFile
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 日志文件创建时间
|
|
||||||
/// </summary>
|
|
||||||
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 日志文件包含的事件列表
|
|
||||||
/// </summary>
|
|
||||||
public List<FileEvent> Events { get; set; } = new List<FileEvent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件查询结果
|
|
||||||
/// </summary>
|
|
||||||
public class EventQueryResult
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 查询到的事件列表
|
|
||||||
/// </summary>
|
|
||||||
public List<FileEvent> Events { get; set; } = new List<FileEvent>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 匹配的事件总数
|
|
||||||
/// </summary>
|
|
||||||
public int TotalCount { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询是否有更多结果
|
|
||||||
/// </summary>
|
|
||||||
public bool HasMore { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询时间范围的开始时间
|
|
||||||
/// </summary>
|
|
||||||
public DateTime StartTime { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询时间范围的结束时间
|
|
||||||
/// </summary>
|
|
||||||
public DateTime EndTime { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件查询参数
|
|
||||||
/// </summary>
|
|
||||||
public class EventQueryParams
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 查询开始时间
|
|
||||||
/// </summary>
|
|
||||||
public DateTime? StartTime { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询结束时间
|
|
||||||
/// </summary>
|
|
||||||
public DateTime? EndTime { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 事件类型过滤
|
|
||||||
/// </summary>
|
|
||||||
public FileEventType? EventType { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件路径过滤(支持包含关系)
|
|
||||||
/// </summary>
|
|
||||||
public string PathFilter { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件扩展名过滤
|
|
||||||
/// </summary>
|
|
||||||
public string ExtensionFilter { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 分页大小
|
|
||||||
/// </summary>
|
|
||||||
public int PageSize { get; set; } = 100;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 分页索引(从0开始)
|
|
||||||
/// </summary>
|
|
||||||
public int PageIndex { get; set; } = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 排序方向,true为升序,false为降序
|
|
||||||
/// </summary>
|
|
||||||
public bool AscendingOrder { get; set; } = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,157 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.PluginFileWatcher
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 文件监控相关工具类
|
|
||||||
/// </summary>
|
|
||||||
public static class FileWatcherUtils
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 检测文件是否被锁定
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">要检查的文件路径</param>
|
|
||||||
/// <returns>如果文件被锁定则返回true,否则返回false</returns>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 尝试处理一个可能被锁定的文件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">文件路径</param>
|
|
||||||
/// <param name="action">成功解锁后要执行的操作</param>
|
|
||||||
/// <param name="config">健壮性配置</param>
|
|
||||||
/// <returns>处理结果:true表示成功处理,false表示处理失败</returns>
|
|
||||||
public static async Task<bool> TryHandleLockedFileAsync(string filePath, Func<Task> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 检查文件系统监控器是否健康
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="watcher">要检查的监控器</param>
|
|
||||||
/// <param name="lastEventTime">最后一次事件的时间</param>
|
|
||||||
/// <param name="config">健壮性配置</param>
|
|
||||||
/// <returns>如果监控器健康则返回true,否则返回false</returns>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
|
||||||
<DockerfileContext>..\..</DockerfileContext>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.4" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
|
|
||||||
<!--<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />-->
|
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
|
|
||||||
|
|
||||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
|
||||||
|
|
||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
|
||||||
|
|
||||||
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.4" />
|
|
||||||
|
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
|
||||||
|
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="appsettings.json">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
1110
external/JiShe.CollectBus.PluginFileWatcher/Program.cs
vendored
1110
external/JiShe.CollectBus.PluginFileWatcher/Program.cs
vendored
File diff suppressed because it is too large
Load Diff
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"profiles": {
|
|
||||||
"JiShe.CollectBus.PluginFileWatcher": {
|
|
||||||
"commandName": "Project"
|
|
||||||
},
|
|
||||||
"Container (Dockerfile)": {
|
|
||||||
"commandName": "Docker"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,720 +0,0 @@
|
|||||||
using JiShe.CollectBus.Analyzers.Shared;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.IncrementalGenerator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 复杂类型增量源生成器
|
|
||||||
/// </summary>
|
|
||||||
[Generator(LanguageNames.CSharp)]
|
|
||||||
public class ComplexTypeSourceAnalyzers : IIncrementalGenerator
|
|
||||||
{
|
|
||||||
private const string AttributeFullName = "JiShe.CollectBus.Analyzers.Shared.SourceAnalyzersAttribute";
|
|
||||||
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
||||||
{
|
|
||||||
//Debugger.Launch();
|
|
||||||
|
|
||||||
// 步骤1:筛选带有 [SourceAnalyzers] 的类
|
|
||||||
var classDeclarations = context.SyntaxProvider
|
|
||||||
.CreateSyntaxProvider(
|
|
||||||
predicate: static (s, _) => IsClassWithAttribute(s),
|
|
||||||
transform: static (ctx, _) => GetClassDeclaration(ctx))
|
|
||||||
.Where(static c => c is not null);
|
|
||||||
|
|
||||||
// 步骤2:合并编译信息
|
|
||||||
var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
|
|
||||||
|
|
||||||
context.RegisterSourceOutput(compilationAndClasses, (spc, source) =>
|
|
||||||
GenerateCode(source.Left, source.Right!, spc));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsClassWithAttribute(SyntaxNode node) => node is ClassDeclarationSyntax cds && cds.AttributeLists.Count > 0;
|
|
||||||
|
|
||||||
private static ClassDeclarationSyntax GetClassDeclaration(GeneratorSyntaxContext context)
|
|
||||||
{
|
|
||||||
var classDecl = (ClassDeclarationSyntax)context.Node;
|
|
||||||
var semanticModel = context.SemanticModel;
|
|
||||||
|
|
||||||
// 获取类符号
|
|
||||||
var classSymbol = semanticModel.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;
|
|
||||||
if (classSymbol == null) return null;
|
|
||||||
|
|
||||||
// 检查是否包含 SourceAnalyzers 特性
|
|
||||||
var sourceAnalyzerAttr = classSymbol.GetAttributes().FirstOrDefault(attr => attr.AttributeClass?.ToDisplayString() == AttributeFullName);
|
|
||||||
|
|
||||||
// 必须包含 EntityType 参数
|
|
||||||
if (sourceAnalyzerAttr == null ||
|
|
||||||
sourceAnalyzerAttr.ConstructorArguments.Length == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return classDecl;
|
|
||||||
|
|
||||||
//var classDecl = (ClassDeclarationSyntax)context.Node;
|
|
||||||
//var attributeType = context.SemanticModel.Compilation.GetTypeByMetadataName(AttributeFullName);
|
|
||||||
|
|
||||||
//foreach (var attribute in classDecl.AttributeLists.SelectMany(al => al.Attributes))
|
|
||||||
//{
|
|
||||||
// var symbol = context.SemanticModel.GetSymbolInfo(attribute).Symbol;
|
|
||||||
// if (symbol is IMethodSymbol ctor &&
|
|
||||||
// SymbolEqualityComparer.Default.Equals(ctor.ContainingType, attributeType))
|
|
||||||
// {
|
|
||||||
// return classDecl;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 递归获取所有层级的属性
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="classSymbol"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static IEnumerable<IPropertySymbol> GetAllPropertiesInHierarchy(INamedTypeSymbol classSymbol)
|
|
||||||
{
|
|
||||||
var currentSymbol = classSymbol;
|
|
||||||
while (currentSymbol != null)
|
|
||||||
{
|
|
||||||
foreach (var prop in currentSymbol.GetMembers().OfType<IPropertySymbol>())
|
|
||||||
{
|
|
||||||
yield return prop;
|
|
||||||
}
|
|
||||||
currentSymbol = currentSymbol.BaseType; // 向上遍历基类
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 生成代码
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="compilation"></param>
|
|
||||||
/// <param name="classes"></param>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
private static void GenerateCode(
|
|
||||||
Compilation compilation,
|
|
||||||
IEnumerable<ClassDeclarationSyntax> classes,
|
|
||||||
SourceProductionContext context)
|
|
||||||
{
|
|
||||||
var processedTypes = new HashSet<ITypeSymbol>(SymbolEqualityComparer.Default);
|
|
||||||
|
|
||||||
if (!classes.Any())
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(Diagnostic.Create(
|
|
||||||
new DiagnosticDescriptor("GEN002", "没有目标类",
|
|
||||||
"没有找到SourceAnalyzers标记的类", "Debug", DiagnosticSeverity.Warning, true),
|
|
||||||
Location.None));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var classDecl in classes.Distinct())
|
|
||||||
{
|
|
||||||
var model = compilation.GetSemanticModel(classDecl.SyntaxTree);
|
|
||||||
var classSymbol = model.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;
|
|
||||||
|
|
||||||
if (classSymbol == null || !processedTypes.Add(classSymbol))
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(Diagnostic.Create(
|
|
||||||
new DiagnosticDescriptor("GEN003", "无效符号",
|
|
||||||
$"类名称为{classDecl.Identifier.Text} 符号为空", "Error", DiagnosticSeverity.Error, true),
|
|
||||||
Location.None));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var code3 = BuildAccessorsForSourceEntity(classSymbol, compilation, processedTypes);
|
|
||||||
context.AddSource($"{classSymbol.Name}Accessor.g.cs", code3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成工厂注册代码
|
|
||||||
context.AddSource("SourceEntityAccessorFactory.g.cs", BuildFactoryCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取泛型参数
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="symbol"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static string GetGenericParams(INamedTypeSymbol symbol)
|
|
||||||
{
|
|
||||||
if (!symbol.IsGenericType) return "";
|
|
||||||
var parameters = symbol.TypeParameters.Select(t => t.Name);
|
|
||||||
return $"<{string.Join(", ", parameters)}>";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 生成标准属性的访问器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="prop"></param>
|
|
||||||
/// <param name="propType"></param>
|
|
||||||
/// <param name="code"></param>
|
|
||||||
private static void GenerateStandardAccessors(IPropertySymbol prop, INamedTypeSymbol propType, StringBuilder code)
|
|
||||||
{
|
|
||||||
var parentType = prop.ContainingType.ToDisplayString();
|
|
||||||
code.AppendLine($" public static {propType.ToDisplayString()} Get{prop.Name}({parentType} obj) => obj.{prop.Name};");
|
|
||||||
|
|
||||||
if (prop.SetMethod != null)
|
|
||||||
{
|
|
||||||
code.AppendLine($" public static void Set{prop.Name}({parentType} obj, {propType.ToDisplayString()} value) => obj.{prop.Name} = value;");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构建实体访问器代码(支持泛型)
|
|
||||||
/// </summary>
|
|
||||||
private static string BuildAccessorsForSourceEntity(
|
|
||||||
INamedTypeSymbol classSymbol,
|
|
||||||
Compilation compilation,
|
|
||||||
HashSet<ITypeSymbol> processedTypes)
|
|
||||||
{
|
|
||||||
// 获取 SourceAnalyzers 特性的 EntityType 参数
|
|
||||||
var sourceAnalyzerAttr = classSymbol.GetAttributes()
|
|
||||||
.FirstOrDefault(attr =>
|
|
||||||
attr.AttributeClass?.ToDisplayString() == AttributeFullName);
|
|
||||||
|
|
||||||
// 解析 EntityType 枚举值
|
|
||||||
string entityTypeValue = "EntityTypeEnum.Other"; // 默认值
|
|
||||||
if (sourceAnalyzerAttr != null &&
|
|
||||||
sourceAnalyzerAttr.ConstructorArguments.Length > 0)
|
|
||||||
{
|
|
||||||
var arg = sourceAnalyzerAttr.ConstructorArguments[0];
|
|
||||||
if (arg.Kind == TypedConstantKind.Enum &&
|
|
||||||
arg.Type is INamedTypeSymbol enumType)
|
|
||||||
{
|
|
||||||
int enumValue = (int)arg.Value!;
|
|
||||||
entityTypeValue = GetEnumMemberName(enumType, enumValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var code = new StringBuilder();
|
|
||||||
code.AppendLine("// <auto-generated/>");
|
|
||||||
code.AppendLine("#nullable enable");
|
|
||||||
code.AppendLine("using System;");
|
|
||||||
code.AppendLine("using System.Reflection;");
|
|
||||||
code.AppendLine("using System.Collections.Generic;");
|
|
||||||
code.AppendLine("using JiShe.CollectBus.Analyzers.Shared;");
|
|
||||||
code.AppendLine($"namespace {classSymbol.ContainingNamespace.ToDisplayString()};");
|
|
||||||
code.AppendLine();
|
|
||||||
|
|
||||||
// 处理泛型类型名称
|
|
||||||
var accessibility = classSymbol.DeclaredAccessibility switch
|
|
||||||
{
|
|
||||||
Accessibility.Public => "public",
|
|
||||||
_ => "internal"
|
|
||||||
};
|
|
||||||
|
|
||||||
var genericParams = classSymbol.IsGenericType
|
|
||||||
? $"<{string.Join(", ", classSymbol.TypeParameters.Select(t => t.Name))}>"
|
|
||||||
: "";
|
|
||||||
|
|
||||||
code.AppendLine(
|
|
||||||
$"{accessibility} sealed class {classSymbol.Name}Accessor{genericParams} " + // 保留泛型参数
|
|
||||||
$": ISourceEntityAccessor<{classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>");
|
|
||||||
|
|
||||||
code.AppendLine("{");
|
|
||||||
|
|
||||||
var propList = GetAllPropertiesInHierarchy(classSymbol);
|
|
||||||
|
|
||||||
//类名称
|
|
||||||
code.AppendLine($" public string EntityName {{get;}} = \"{classSymbol.Name}\";");
|
|
||||||
// 添加 EntityType 属性
|
|
||||||
code.AppendLine($" public EntityTypeEnum? EntityType {{ get; }} = {entityTypeValue};");
|
|
||||||
|
|
||||||
foreach (var prop in propList)
|
|
||||||
{
|
|
||||||
// 安全类型转换
|
|
||||||
if (prop.Type is not ITypeSymbol propType) continue;
|
|
||||||
|
|
||||||
if (propType is INamedTypeSymbol namedType)
|
|
||||||
{
|
|
||||||
GenerateStandardAccessors(prop, namedType, code);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propType is INamedTypeSymbol { IsTupleType: true } tupleType)
|
|
||||||
{
|
|
||||||
GenerateTupleAccessors(prop, tupleType, code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//生成当前类属性名称集合
|
|
||||||
GeneratePropertyListForSourceEntity(propList, code, compilation, classSymbol);
|
|
||||||
|
|
||||||
//生成当前类属性信息集合
|
|
||||||
GenerateEntityMemberInfoList(propList, code, compilation, classSymbol);
|
|
||||||
|
|
||||||
|
|
||||||
//生成当前类属性访问
|
|
||||||
GetGeneratePropertyValueForSourceEntity(propList, code, compilation, classSymbol);
|
|
||||||
|
|
||||||
//生成当前类属性设置
|
|
||||||
SetGeneratePropertyValueForSourceEntity(propList, code, compilation, classSymbol);
|
|
||||||
|
|
||||||
code.AppendLine("}");
|
|
||||||
return code.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 生成ValueTuple元组属性访问器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="prop"></param>
|
|
||||||
/// <param name="tupleType"></param>
|
|
||||||
/// <param name="code"></param>
|
|
||||||
private static void GenerateTupleAccessors(
|
|
||||||
IPropertySymbol prop,
|
|
||||||
INamedTypeSymbol tupleType,
|
|
||||||
StringBuilder code)
|
|
||||||
{
|
|
||||||
var parentType = prop.ContainingType.ToDisplayString();
|
|
||||||
var tupleElements = tupleType.TupleElements;
|
|
||||||
|
|
||||||
for (int i = 0; i < tupleElements.Length; i++)
|
|
||||||
{
|
|
||||||
var element = tupleElements[i];
|
|
||||||
var elementType = element.Type.ToDisplayString();
|
|
||||||
var elementName = element.Name;
|
|
||||||
|
|
||||||
// Getter
|
|
||||||
code.AppendLine($"public static {elementType} Get{prop.Name}_{elementName}({parentType} obj) => obj.{prop.Name}.{elementName};");
|
|
||||||
|
|
||||||
// Setter
|
|
||||||
if (prop.SetMethod != null)
|
|
||||||
{
|
|
||||||
code.AppendLine($"public static void Set{prop.Name}_{elementName}({parentType} obj, {elementType} value) => obj.{prop.Name} = ({string.Join(", ", GetTupleElements(prop.Name, tupleElements, i))});");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<string> GetTupleElements(
|
|
||||||
string propName,
|
|
||||||
ImmutableArray<IFieldSymbol> elements,
|
|
||||||
int targetIndex)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < elements.Length; i++)
|
|
||||||
{
|
|
||||||
yield return i == targetIndex
|
|
||||||
? "value"
|
|
||||||
: $"obj.{propName}.{elements[i].Name}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 处理System.Tuple类型的访问器生成
|
|
||||||
/// </summary>
|
|
||||||
private static void GenerateSystemTupleAccessors(
|
|
||||||
IPropertySymbol prop,
|
|
||||||
INamedTypeSymbol tupleType,
|
|
||||||
StringBuilder code,
|
|
||||||
INamedTypeSymbol classSymbol)
|
|
||||||
{
|
|
||||||
var parentType = classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
||||||
var elementTypes = tupleType.TypeArguments;
|
|
||||||
var tupleTypeName = tupleType.ToDisplayString();
|
|
||||||
|
|
||||||
for (int i = 0; i < elementTypes.Length; i++)
|
|
||||||
{
|
|
||||||
var elementType = elementTypes[i].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
||||||
var elementName = $"Item{i + 1}";
|
|
||||||
|
|
||||||
// Getter
|
|
||||||
code.AppendLine(
|
|
||||||
$" public static {elementType} Get{prop.Name}_{elementName}" +
|
|
||||||
$"({parentType} obj) => obj.{prop.Name}.{elementName};");
|
|
||||||
|
|
||||||
// Setter
|
|
||||||
if (prop.SetMethod != null)
|
|
||||||
{
|
|
||||||
var assignments = elementTypes.Select((_, idx) =>
|
|
||||||
idx == i ? "value" : $"obj.{prop.Name}.Item{idx + 1}"
|
|
||||||
).ToList();
|
|
||||||
|
|
||||||
code.AppendLine(
|
|
||||||
$" public static void Set{prop.Name}_{elementName}" +
|
|
||||||
$"({parentType} obj, {elementType} value) => " +
|
|
||||||
$"obj.{prop.Name} = new {tupleTypeName}({string.Join(", ", assignments)});");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 增强的工厂类实现
|
|
||||||
/// </summary>
|
|
||||||
private static string BuildFactoryCode()
|
|
||||||
{
|
|
||||||
return """
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Analyzers.Shared;
|
|
||||||
|
|
||||||
public static class SourceEntityAccessorFactory
|
|
||||||
{
|
|
||||||
private static readonly ConcurrentDictionary<Type, object> _accessors = new();
|
|
||||||
|
|
||||||
public static ISourceEntityAccessor<T> GetAccessor<T>()
|
|
||||||
{
|
|
||||||
return (ISourceEntityAccessor<T>)_accessors.GetOrAdd(typeof(T), t =>
|
|
||||||
{
|
|
||||||
// 获取泛型类型定义信息(如果是泛型类型)
|
|
||||||
var isGeneric = t.IsGenericType;
|
|
||||||
var genericTypeDef = isGeneric ? t.GetGenericTypeDefinition() : null;
|
|
||||||
var arity = isGeneric ? genericTypeDef!.GetGenericArguments().Length : 0;
|
|
||||||
|
|
||||||
// 构建访问器类名
|
|
||||||
var typeName = isGeneric
|
|
||||||
? $"{t.Namespace}.{genericTypeDef!.Name.Split('`')[0]}Accessor`{arity}"
|
|
||||||
: $"{t.Namespace}.{t.Name}Accessor";
|
|
||||||
|
|
||||||
// 尝试从当前程序集加载
|
|
||||||
var accessorType = Assembly.GetAssembly(t)!.GetType(typeName)
|
|
||||||
?? throw new InvalidOperationException($"Accessor type {typeName} not found");
|
|
||||||
|
|
||||||
// 处理泛型参数
|
|
||||||
if (isGeneric && accessorType.IsGenericTypeDefinition)
|
|
||||||
{
|
|
||||||
accessorType = accessorType.MakeGenericType(t.GetGenericArguments());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Activator.CreateInstance(accessorType)!;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 属性访问生成逻辑
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propList">属性集合</param>
|
|
||||||
/// <param name="code"></param>
|
|
||||||
/// <param name="compilation"></param>
|
|
||||||
/// <param name="classSymbol"></param>
|
|
||||||
private static void GetGeneratePropertyValueForSourceEntity(
|
|
||||||
IEnumerable<IPropertySymbol> propList,
|
|
||||||
StringBuilder code,
|
|
||||||
Compilation compilation,
|
|
||||||
INamedTypeSymbol classSymbol)
|
|
||||||
{
|
|
||||||
code.AppendLine($" public object GetPropertyValue({classSymbol} targetEntity, string propertyName)");
|
|
||||||
code.AppendLine(" {");
|
|
||||||
code.AppendLine(" return propertyName switch");
|
|
||||||
code.AppendLine(" {");
|
|
||||||
|
|
||||||
foreach (var prop in propList)
|
|
||||||
{
|
|
||||||
code.AppendLine(
|
|
||||||
$" \"{prop.Name}\" => " +
|
|
||||||
$"Get{prop.Name}(targetEntity),");
|
|
||||||
|
|
||||||
if (prop.Type is INamedTypeSymbol { IsTupleType: true } tupleType)
|
|
||||||
{
|
|
||||||
foreach (var element in tupleType.TupleElements)
|
|
||||||
{
|
|
||||||
code.AppendLine(
|
|
||||||
$" \"{prop.Name}.{element.Name}\" => " +
|
|
||||||
$"Get{prop.Name}_{element.Name}(targetEntity),");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
code.AppendLine(" _ => throw new ArgumentException($\"Unknown property: {propertyName}\")");
|
|
||||||
code.AppendLine(" };");
|
|
||||||
code.AppendLine(" }");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 属性设置生成逻辑
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propList">属性集合</param>
|
|
||||||
/// <param name="code"></param>
|
|
||||||
/// <param name="compilation"></param>
|
|
||||||
/// <param name="classSymbol"></param>
|
|
||||||
private static void SetGeneratePropertyValueForSourceEntity(
|
|
||||||
IEnumerable<IPropertySymbol> propList,
|
|
||||||
StringBuilder code,
|
|
||||||
Compilation compilation,
|
|
||||||
INamedTypeSymbol classSymbol)
|
|
||||||
{
|
|
||||||
code.AppendLine($" public void SetPropertyValue({classSymbol} targetEntity, string propertyName, object value)");
|
|
||||||
code.AppendLine(" {");
|
|
||||||
code.AppendLine(" switch (propertyName)");
|
|
||||||
code.AppendLine(" {");
|
|
||||||
|
|
||||||
foreach (var prop in propList)
|
|
||||||
{
|
|
||||||
var propType = prop.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
||||||
code.AppendLine($" case \"{prop.Name}\":");
|
|
||||||
code.AppendLine($" Set{prop.Name}(");
|
|
||||||
code.AppendLine($" targetEntity, ({propType})value);");
|
|
||||||
code.AppendLine(" break;");
|
|
||||||
|
|
||||||
if (prop.Type is INamedTypeSymbol { IsTupleType: true } tupleType)
|
|
||||||
{
|
|
||||||
foreach (var element in tupleType.TupleElements)
|
|
||||||
{
|
|
||||||
var elementType = element.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
||||||
code.AppendLine($" case \"{prop.Name}.{element.Name}\":");
|
|
||||||
code.AppendLine($" Set{prop.Name}_{element.Name}(");
|
|
||||||
code.AppendLine($" targetEntity, ({elementType})value);");
|
|
||||||
code.AppendLine(" break;");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
code.AppendLine(" default:");
|
|
||||||
code.AppendLine(" throw new ArgumentException($\"Unknown property: {propertyName}\");");
|
|
||||||
code.AppendLine(" }");
|
|
||||||
code.AppendLine(" }");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 属性名称集合
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propList">属性集合</param>
|
|
||||||
/// <param name="code"></param>
|
|
||||||
/// <param name="compilation"></param>
|
|
||||||
/// <param name="classSymbol"></param>
|
|
||||||
private static void GeneratePropertyListForSourceEntity(
|
|
||||||
IEnumerable<IPropertySymbol> propList,
|
|
||||||
StringBuilder code,
|
|
||||||
Compilation compilation,
|
|
||||||
INamedTypeSymbol classSymbol)
|
|
||||||
{
|
|
||||||
code.AppendLine(" public List<string> PropertyNameList {get;} = new List<string>()");
|
|
||||||
code.AppendLine(" {");
|
|
||||||
List<string> tempPropList = new List<string>();
|
|
||||||
foreach (var prop in propList)
|
|
||||||
{
|
|
||||||
if (prop.Type is INamedTypeSymbol { IsTupleType: true } tupleType)
|
|
||||||
{
|
|
||||||
foreach (var element in tupleType.TupleElements)
|
|
||||||
{
|
|
||||||
tempPropList.Add($"\"{prop.Name}.{element.Name}\"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tempPropList.Add($"\"{prop.Name}\"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
code.Append(string.Join(",", tempPropList));
|
|
||||||
|
|
||||||
code.AppendLine(" };");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 生成当前类属性信息集合
|
|
||||||
/// </summary>
|
|
||||||
private static void GenerateEntityMemberInfoList(
|
|
||||||
IEnumerable<IPropertySymbol> propList,
|
|
||||||
StringBuilder code,
|
|
||||||
Compilation compilation,
|
|
||||||
INamedTypeSymbol classSymbol)
|
|
||||||
{
|
|
||||||
code.AppendLine(" public List<EntityMemberInfo> MemberList { get; } = new()");
|
|
||||||
code.AppendLine(" {");
|
|
||||||
|
|
||||||
var initializerLines = new List<string>();
|
|
||||||
|
|
||||||
foreach (var prop in propList)
|
|
||||||
{
|
|
||||||
var entityType = prop.ContainingType.ToDisplayString();//entity 实体类型名称
|
|
||||||
var propType = prop.Type;//实体属性的类型
|
|
||||||
var propTypeName = propType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
||||||
// var declaredTypeName = propType.Name; // 直接获取类型名称(如 "Int32")
|
|
||||||
// 处理可空类型,获取底层具体类型名称
|
|
||||||
var declaredTypeName = propType switch
|
|
||||||
{
|
|
||||||
INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } nullableType =>
|
|
||||||
nullableType.TypeArguments[0].Name, // 提取如 "Int32"
|
|
||||||
_ => propType.Name
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理主属性
|
|
||||||
var propAttributes = prop.GetAttributes()
|
|
||||||
.Where(a => !IsCompilerGeneratedAttribute(a))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var attributeInitializers = propAttributes
|
|
||||||
.Select(GenerateAttributeInitializer)
|
|
||||||
.Where(s => !string.IsNullOrEmpty(s));
|
|
||||||
|
|
||||||
var mainMember = new StringBuilder();
|
|
||||||
mainMember.Append(
|
|
||||||
$"new EntityMemberInfo(" +
|
|
||||||
$"\"{prop.Name}\", " +
|
|
||||||
$"typeof({propTypeName}), " +
|
|
||||||
$"\"{declaredTypeName}\", " +
|
|
||||||
$"(e) => Get{prop.Name}(({entityType})e), " +
|
|
||||||
$"(e, v) => Set{prop.Name}(({entityType})e, ({propTypeName})v))");
|
|
||||||
|
|
||||||
if (attributeInitializers.Any())
|
|
||||||
{
|
|
||||||
mainMember.AppendLine();
|
|
||||||
mainMember.Append(" { CustomAttributes = new List<Attribute>");
|
|
||||||
mainMember.Append($" {{ {string.Join(", ", attributeInitializers)} }} }}");
|
|
||||||
}
|
|
||||||
|
|
||||||
initializerLines.Add(mainMember.ToString());
|
|
||||||
|
|
||||||
// 处理元组元素,(暂不需要处理元组元素的特性)
|
|
||||||
if (prop.Type is INamedTypeSymbol { IsTupleType: true } tupleType)
|
|
||||||
{
|
|
||||||
foreach (var element in tupleType.TupleElements)
|
|
||||||
{
|
|
||||||
var elementType = element.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);//元组元素的类型
|
|
||||||
var elementName = element.Name;//元组元素名称
|
|
||||||
var elementDeclaredName = element.Type.Name;//元组元素类型名称
|
|
||||||
|
|
||||||
initializerLines.Add(
|
|
||||||
$"new EntityMemberInfo(" +
|
|
||||||
$"\"{prop.Name}.{elementName}\", " +
|
|
||||||
$"typeof({elementType}), " +
|
|
||||||
$"GetValueTupleElementDeclaredTypeName(typeof({elementType})), " +//$"\"{elementDeclaredName}\", " +
|
|
||||||
$"(e) => Get{prop.Name}_{elementName}(({entityType})e), " +
|
|
||||||
$"(e, v) => Set{prop.Name}_{elementName}(({entityType})e, ({elementType})v))");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
code.AppendLine(string.Join(",\n", initializerLines));
|
|
||||||
code.AppendLine(" };");
|
|
||||||
|
|
||||||
code.AppendLine(GetValueTupleElementName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetValueTupleElementName()
|
|
||||||
{
|
|
||||||
return """
|
|
||||||
public static string GetValueTupleElementDeclaredTypeName(Type declaredType)
|
|
||||||
{
|
|
||||||
string typeName;
|
|
||||||
// 处理可空类型
|
|
||||||
if (declaredType.IsGenericType && declaredType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
|
||||||
{
|
|
||||||
Type underlyingType = Nullable.GetUnderlyingType(declaredType);
|
|
||||||
typeName = underlyingType.Name;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
typeName = declaredType.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeName;
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static string GenerateAttributeInitializer(AttributeData attribute)
|
|
||||||
{
|
|
||||||
if (attribute.AttributeClass == null)
|
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
var attributeClass = attribute.AttributeClass.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
||||||
var args = attribute.ConstructorArguments;
|
|
||||||
var namedArgs = attribute.NamedArguments;
|
|
||||||
|
|
||||||
var parameters = new List<string>();
|
|
||||||
foreach (var arg in args)
|
|
||||||
{
|
|
||||||
parameters.Add(ConvertTypedConstantToCode(arg));
|
|
||||||
}
|
|
||||||
|
|
||||||
var constructorArgs = string.Join(", ", parameters);
|
|
||||||
|
|
||||||
var initializer = new StringBuilder();
|
|
||||||
initializer.Append($"new {attributeClass}({constructorArgs})");
|
|
||||||
|
|
||||||
if (namedArgs.Any())
|
|
||||||
{
|
|
||||||
initializer.Append(" { ");
|
|
||||||
var namedArgsList = namedArgs.Select(n => $"{n.Key} = {ConvertTypedConstantToCode(n.Value)}");
|
|
||||||
initializer.Append(string.Join(", ", namedArgsList));
|
|
||||||
initializer.Append(" }");
|
|
||||||
}
|
|
||||||
|
|
||||||
return initializer.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ConvertTypedConstantToCode(TypedConstant constant)
|
|
||||||
{
|
|
||||||
if (constant.IsNull)
|
|
||||||
return "null";
|
|
||||||
|
|
||||||
switch (constant.Kind)
|
|
||||||
{
|
|
||||||
case TypedConstantKind.Array:
|
|
||||||
var elements = constant.Values.Select(ConvertTypedConstantToCode);
|
|
||||||
return $"new[] {{ {string.Join(", ", elements)} }}";
|
|
||||||
case TypedConstantKind.Type:
|
|
||||||
var typeSymbol = (ITypeSymbol)constant.Value!;
|
|
||||||
return $"typeof({typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)})";
|
|
||||||
case TypedConstantKind.Enum:
|
|
||||||
return ConvertEnumTypedConstant(constant);
|
|
||||||
default:
|
|
||||||
return ConvertPrimitiveConstant(constant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ConvertEnumTypedConstant(TypedConstant constant)
|
|
||||||
{
|
|
||||||
var enumType = constant.Type!;
|
|
||||||
var enumValue = constant.Value!;
|
|
||||||
var enumTypeName = enumType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
||||||
|
|
||||||
foreach (var member in enumType.GetMembers().OfType<IFieldSymbol>())
|
|
||||||
{
|
|
||||||
if (member.ConstantValue != null && member.ConstantValue.Equals(enumValue))
|
|
||||||
return $"{enumTypeName}.{member.Name}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"({enumTypeName})({enumValue})";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ConvertPrimitiveConstant(TypedConstant constant)
|
|
||||||
{
|
|
||||||
var value = constant.Value!;
|
|
||||||
return value switch
|
|
||||||
{
|
|
||||||
string s => $"\"{s}\"",
|
|
||||||
char c => $"'{c}'",
|
|
||||||
bool b => b ? "true" : "false",
|
|
||||||
_ => value.ToString()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsCompilerGeneratedAttribute(AttributeData attribute)
|
|
||||||
{
|
|
||||||
return attribute.AttributeClass?.ToDisplayString() == "System.Runtime.CompilerServices.CompilerGeneratedAttribute";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取枚举的参数
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="enumType"></param>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static string GetEnumMemberName(INamedTypeSymbol enumType, int value)
|
|
||||||
{
|
|
||||||
foreach (var member in enumType.GetMembers().OfType<IFieldSymbol>())
|
|
||||||
{
|
|
||||||
if (member.ConstantValue is int intValue && intValue == value)
|
|
||||||
{
|
|
||||||
return $"{enumType.ToDisplayString()}.{member.Name}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $"{enumType.ToDisplayString()}.Other";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
|
||||||
<IsRoslynComponent>true</IsRoslynComponent>
|
|
||||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
|
||||||
<ImportDirectoryBuildProps>false</ImportDirectoryBuildProps>
|
|
||||||
<BaseOutputPath>bin</BaseOutputPath>
|
|
||||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
|
||||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\shared\JiShe.CollectBus.Analyzers.Shared\JiShe.CollectBus.Analyzers.Shared.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@ -38,5 +38,465 @@ namespace JiShe.CollectBus.FreeRedis
|
|||||||
Instance.Notice += (s, e) => Trace.WriteLine(e.Log);
|
Instance.Notice += (s, e) => Trace.WriteLine(e.Log);
|
||||||
return Instance;
|
return Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// 单个添加数据
|
||||||
|
///// </summary>
|
||||||
|
///// <typeparam name="T"></typeparam>
|
||||||
|
///// <param name="redisCacheKey">主数据存储Hash缓存Key</param>
|
||||||
|
///// <param name="redisCacheFocusIndexKey">集中器索引Set缓存Key</param>
|
||||||
|
///// <param name="redisCacheScoresIndexKey">集中器排序索引ZSET缓存Key</param>
|
||||||
|
///// <param name="redisCacheGlobalIndexKey">集中器采集频率分组全局索引ZSet缓存Key</param>
|
||||||
|
///// <param name="data">表计信息</param>
|
||||||
|
///// <param name="timestamp">可选时间戳</param>
|
||||||
|
///// <returns></returns>
|
||||||
|
//public async Task AddMeterCacheData<T>(
|
||||||
|
//string redisCacheKey,
|
||||||
|
//string redisCacheFocusIndexKey,
|
||||||
|
//string redisCacheScoresIndexKey,
|
||||||
|
//string redisCacheGlobalIndexKey,
|
||||||
|
//T data,
|
||||||
|
//DateTimeOffset? timestamp = null) where T : DeviceCacheBasicModel
|
||||||
|
//{
|
||||||
|
// // 参数校验增强
|
||||||
|
// if (data == null || string.IsNullOrWhiteSpace(redisCacheKey)
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheFocusIndexKey)
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey)
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey))
|
||||||
|
// {
|
||||||
|
// throw new ArgumentException($"{nameof(AddMeterCacheData)} 参数异常,-101");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 计算组合score(分类ID + 时间戳)
|
||||||
|
// var actualTimestamp = timestamp ?? DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
// long scoreValue = ((long)data.FocusId << 32) | (uint)actualTimestamp.Ticks;
|
||||||
|
|
||||||
|
// //全局索引写入
|
||||||
|
// long globalScore = actualTimestamp.ToUnixTimeMilliseconds();
|
||||||
|
|
||||||
|
// // 使用事务保证原子性
|
||||||
|
// using (var trans = Instance.Multi())
|
||||||
|
// {
|
||||||
|
// // 主数据存储Hash
|
||||||
|
// trans.HSet(redisCacheKey, data.MemberID, data.Serialize());
|
||||||
|
|
||||||
|
// // 分类索引
|
||||||
|
// trans.SAdd(redisCacheFocusIndexKey, data.MemberID);
|
||||||
|
|
||||||
|
// // 排序索引使用ZSET
|
||||||
|
// trans.ZAdd(redisCacheScoresIndexKey, scoreValue, data.MemberID);
|
||||||
|
|
||||||
|
// //全局索引
|
||||||
|
// trans.ZAdd(redisCacheGlobalIndexKey, globalScore, data.MemberID);
|
||||||
|
|
||||||
|
// var results = trans.Exec();
|
||||||
|
|
||||||
|
// if (results == null || results.Length <= 0)
|
||||||
|
// throw new Exception($"{nameof(AddMeterCacheData)} 事务提交失败,-102");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// await Task.CompletedTask;
|
||||||
|
//}
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// 批量添加数据
|
||||||
|
///// </summary>
|
||||||
|
///// <typeparam name="T"></typeparam>
|
||||||
|
///// <param name="redisCacheKey">主数据存储Hash缓存Key</param>
|
||||||
|
///// <param name="redisCacheFocusIndexKey">集中器索引Set缓存Key</param>
|
||||||
|
///// <param name="redisCacheScoresIndexKey">集中器排序索引ZSET缓存Key</param>
|
||||||
|
///// <param name="redisCacheGlobalIndexKey">集中器采集频率分组全局索引ZSet缓存Key</param>
|
||||||
|
///// <param name="items">数据集合</param>
|
||||||
|
///// <param name="timestamp">可选时间戳</param>
|
||||||
|
///// <returns></returns>
|
||||||
|
//public async Task BatchAddMeterData<T>(
|
||||||
|
//string redisCacheKey,
|
||||||
|
//string redisCacheFocusIndexKey,
|
||||||
|
//string redisCacheScoresIndexKey,
|
||||||
|
//string redisCacheGlobalIndexKey,
|
||||||
|
//IEnumerable<T> items,
|
||||||
|
//DateTimeOffset? timestamp = null) where T : DeviceCacheBasicModel
|
||||||
|
//{
|
||||||
|
// if (items == null
|
||||||
|
// || items.Count() <=0
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheKey)
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheFocusIndexKey)
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey)
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey))
|
||||||
|
// {
|
||||||
|
// throw new ArgumentException($"{nameof(BatchAddMeterData)} 参数异常,-101");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const int BATCH_SIZE = 1000; // 每批1000条
|
||||||
|
// var semaphore = new SemaphoreSlim(Environment.ProcessorCount * 2);
|
||||||
|
|
||||||
|
// foreach (var batch in items.Batch(BATCH_SIZE))
|
||||||
|
// {
|
||||||
|
// await semaphore.WaitAsync();
|
||||||
|
|
||||||
|
// _ = Task.Run(() =>
|
||||||
|
// {
|
||||||
|
// using (var pipe = Instance.StartPipe())
|
||||||
|
// {
|
||||||
|
// foreach (var item in batch)
|
||||||
|
// {
|
||||||
|
// // 计算组合score(分类ID + 时间戳)
|
||||||
|
// var actualTimestamp = timestamp ?? DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
// long scoreValue = ((long)item.FocusId << 32) | (uint)actualTimestamp.Ticks;
|
||||||
|
|
||||||
|
// //全局索引写入
|
||||||
|
// long globalScore = actualTimestamp.ToUnixTimeMilliseconds();
|
||||||
|
|
||||||
|
// // 主数据存储Hash
|
||||||
|
// pipe.HSet(redisCacheKey, item.MemberID, item.Serialize());
|
||||||
|
|
||||||
|
// // 分类索引Set
|
||||||
|
// pipe.SAdd(redisCacheFocusIndexKey, item.MemberID);
|
||||||
|
|
||||||
|
// // 排序索引使用ZSET
|
||||||
|
// pipe.ZAdd(redisCacheScoresIndexKey, scoreValue, item.MemberID);
|
||||||
|
|
||||||
|
// //全局索引
|
||||||
|
// pipe.ZAdd(redisCacheGlobalIndexKey, globalScore, item.MemberID);
|
||||||
|
// }
|
||||||
|
// pipe.EndPipe();
|
||||||
|
// }
|
||||||
|
// semaphore.Release();
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// await Task.CompletedTask;
|
||||||
|
//}
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// 删除指定redis缓存key的缓存数据
|
||||||
|
///// </summary>
|
||||||
|
///// <typeparam name="T"></typeparam>
|
||||||
|
///// <param name="redisCacheKey">主数据存储Hash缓存Key</param>
|
||||||
|
///// <param name="redisCacheFocusIndexKey">集中器索引Set缓存Key</param>
|
||||||
|
///// <param name="redisCacheScoresIndexKey">集中器排序索引ZSET缓存Key</param>
|
||||||
|
///// <param name="redisCacheGlobalIndexKey">集中器采集频率分组全局索引ZSet缓存Key</param>
|
||||||
|
///// <param name="data">表计信息</param>
|
||||||
|
///// <returns></returns>
|
||||||
|
//public async Task RemoveMeterData<T>(
|
||||||
|
//string redisCacheKey,
|
||||||
|
//string redisCacheFocusIndexKey,
|
||||||
|
//string redisCacheScoresIndexKey,
|
||||||
|
//string redisCacheGlobalIndexKey,
|
||||||
|
//T data) where T : DeviceCacheBasicModel
|
||||||
|
//{
|
||||||
|
|
||||||
|
// if (data == null
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheKey)
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheFocusIndexKey)
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey)
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey))
|
||||||
|
// {
|
||||||
|
// throw new ArgumentException($"{nameof(RemoveMeterData)} 参数异常,-101");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const string luaScript = @"
|
||||||
|
// local mainKey = KEYS[1]
|
||||||
|
// local focusIndexKey = KEYS[2]
|
||||||
|
// local scoresIndexKey = KEYS[3]
|
||||||
|
// local globalIndexKey = KEYS[4]
|
||||||
|
// local member = ARGV[1]
|
||||||
|
|
||||||
|
// local deleted = 0
|
||||||
|
// if redis.call('HDEL', mainKey, member) > 0 then
|
||||||
|
// deleted = 1
|
||||||
|
// end
|
||||||
|
|
||||||
|
// redis.call('SREM', focusIndexKey, member)
|
||||||
|
// redis.call('ZREM', scoresIndexKey, member)
|
||||||
|
// redis.call('ZREM', globalIndexKey, member)
|
||||||
|
// return deleted
|
||||||
|
// ";
|
||||||
|
|
||||||
|
// var keys = new[]
|
||||||
|
// {
|
||||||
|
// redisCacheKey,
|
||||||
|
// redisCacheFocusIndexKey,
|
||||||
|
// redisCacheScoresIndexKey,
|
||||||
|
// redisCacheGlobalIndexKey
|
||||||
|
// };
|
||||||
|
|
||||||
|
// var result = await Instance.EvalAsync(luaScript, keys, new[] { data.MemberID });
|
||||||
|
|
||||||
|
// if ((int)result == 0)
|
||||||
|
// throw new KeyNotFoundException("指定数据不存在");
|
||||||
|
//}
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// 修改表计缓存信息
|
||||||
|
///// </summary>
|
||||||
|
///// <typeparam name="T"></typeparam>
|
||||||
|
///// <param name="redisCacheKey">主数据存储Hash缓存Key</param>
|
||||||
|
///// <param name="oldRedisCacheFocusIndexKey">旧集中器索引Set缓存Key</param>
|
||||||
|
///// <param name="newRedisCacheFocusIndexKey">新集中器索引Set缓存Key</param>
|
||||||
|
///// <param name="redisCacheScoresIndexKey">集中器排序索引ZSET缓存Key</param>
|
||||||
|
///// <param name="redisCacheGlobalIndexKey">集中器采集频率分组全局索引ZSet缓存Key</param>
|
||||||
|
///// <param name="newData">表计信息</param>
|
||||||
|
///// <param name="newTimestamp">可选时间戳</param>
|
||||||
|
///// <returns></returns>
|
||||||
|
//public async Task UpdateMeterData<T>(
|
||||||
|
//string redisCacheKey,
|
||||||
|
//string oldRedisCacheFocusIndexKey,
|
||||||
|
//string newRedisCacheFocusIndexKey,
|
||||||
|
//string redisCacheScoresIndexKey,
|
||||||
|
//string redisCacheGlobalIndexKey,
|
||||||
|
//T newData,
|
||||||
|
//DateTimeOffset? newTimestamp = null) where T : DeviceCacheBasicModel
|
||||||
|
//{
|
||||||
|
// if (newData == null
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheKey)
|
||||||
|
// || string.IsNullOrWhiteSpace(oldRedisCacheFocusIndexKey)
|
||||||
|
// || string.IsNullOrWhiteSpace(newRedisCacheFocusIndexKey)
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheScoresIndexKey)
|
||||||
|
// || string.IsNullOrWhiteSpace(redisCacheGlobalIndexKey))
|
||||||
|
// {
|
||||||
|
// throw new ArgumentException($"{nameof(UpdateMeterData)} 参数异常,-101");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var luaScript = @"
|
||||||
|
// local mainKey = KEYS[1]
|
||||||
|
// local oldFocusIndexKey = KEYS[2]
|
||||||
|
// local newFocusIndexKey = KEYS[3]
|
||||||
|
// local scoresIndexKey = KEYS[4]
|
||||||
|
// local globalIndexKey = KEYS[5]
|
||||||
|
// local member = ARGV[1]
|
||||||
|
// local newData = ARGV[2]
|
||||||
|
// local newScore = ARGV[3]
|
||||||
|
// local newGlobalScore = ARGV[4]
|
||||||
|
|
||||||
|
// -- 校验存在性
|
||||||
|
// if redis.call('HEXISTS', mainKey, member) == 0 then
|
||||||
|
// return 0
|
||||||
|
// end
|
||||||
|
|
||||||
|
// -- 更新主数据
|
||||||
|
// redis.call('HSET', mainKey, member, newData)
|
||||||
|
|
||||||
|
// -- 处理变更
|
||||||
|
// if newScore ~= '' then
|
||||||
|
// -- 删除旧索引
|
||||||
|
// redis.call('SREM', oldFocusIndexKey, member)
|
||||||
|
// redis.call('ZREM', scoresIndexKey, member)
|
||||||
|
|
||||||
|
// -- 添加新索引
|
||||||
|
// redis.call('SADD', newFocusIndexKey, member)
|
||||||
|
// redis.call('ZADD', scoresIndexKey, newScore, member)
|
||||||
|
// end
|
||||||
|
|
||||||
|
// -- 更新全局索引
|
||||||
|
// if newGlobalScore ~= '' then
|
||||||
|
// -- 删除旧索引
|
||||||
|
// redis.call('ZREM', globalIndexKey, member)
|
||||||
|
|
||||||
|
// -- 添加新索引
|
||||||
|
// redis.call('ZADD', globalIndexKey, newGlobalScore, member)
|
||||||
|
// end
|
||||||
|
|
||||||
|
// return 1
|
||||||
|
// ";
|
||||||
|
|
||||||
|
// var actualTimestamp = newTimestamp ?? DateTimeOffset.UtcNow;
|
||||||
|
// var newGlobalScore = actualTimestamp.ToUnixTimeMilliseconds();
|
||||||
|
// var newScoreValue = ((long)newData.FocusId << 32) | (uint)actualTimestamp.Ticks;
|
||||||
|
|
||||||
|
// var result = await Instance.EvalAsync(luaScript,
|
||||||
|
// new[]
|
||||||
|
// {
|
||||||
|
// redisCacheKey,
|
||||||
|
// oldRedisCacheFocusIndexKey,
|
||||||
|
// newRedisCacheFocusIndexKey,
|
||||||
|
// redisCacheScoresIndexKey,
|
||||||
|
// redisCacheGlobalIndexKey
|
||||||
|
// },
|
||||||
|
// new object[]
|
||||||
|
// {
|
||||||
|
// newData.MemberID,
|
||||||
|
// newData.Serialize(),
|
||||||
|
// newScoreValue.ToString() ?? "",
|
||||||
|
// newGlobalScore.ToString() ?? ""
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if ((int)result == 0)
|
||||||
|
// {
|
||||||
|
// throw new KeyNotFoundException($"{nameof(UpdateMeterData)}指定Key{redisCacheKey}的数据不存在");
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public async Task<BusPagedResult<T>> SingleGetMeterPagedData<T>(
|
||||||
|
//string redisCacheKey,
|
||||||
|
//string redisCacheScoresIndexKey,
|
||||||
|
//int focusId,
|
||||||
|
//int pageSize = 10,
|
||||||
|
//int pageIndex = 1,
|
||||||
|
//bool descending = true)
|
||||||
|
//{
|
||||||
|
// // 计算score范围
|
||||||
|
// long minScore = (long)focusId << 32;
|
||||||
|
// long maxScore = ((long)focusId + 1) << 32;
|
||||||
|
|
||||||
|
// // 分页参数计算
|
||||||
|
// int start = (pageIndex - 1) * pageSize;
|
||||||
|
|
||||||
|
// // 获取排序后的member列表
|
||||||
|
// var members = descending
|
||||||
|
// ? await Instance.ZRevRangeByScoreAsync(
|
||||||
|
// redisCacheScoresIndexKey,
|
||||||
|
// maxScore,
|
||||||
|
// minScore,
|
||||||
|
// start,
|
||||||
|
// pageSize)
|
||||||
|
// : await Instance.ZRangeByScoreAsync(
|
||||||
|
// redisCacheScoresIndexKey,
|
||||||
|
// minScore,
|
||||||
|
// maxScore,
|
||||||
|
// start,
|
||||||
|
// pageSize);
|
||||||
|
|
||||||
|
// // 批量获取实际数据
|
||||||
|
// var dataTasks = members.Select(m =>
|
||||||
|
// Instance.HGetAsync<T>(redisCacheKey, m)).ToArray();
|
||||||
|
// await Task.WhenAll(dataTasks);
|
||||||
|
|
||||||
|
// // 总数统计优化
|
||||||
|
// var total = await Instance.ZCountAsync(
|
||||||
|
// redisCacheScoresIndexKey,
|
||||||
|
// minScore,
|
||||||
|
// maxScore);
|
||||||
|
|
||||||
|
// return new BusPagedResult<T>
|
||||||
|
// {
|
||||||
|
// Items = dataTasks.Select(t => t.Result).ToList(),
|
||||||
|
// TotalCount = total,
|
||||||
|
// PageIndex = pageIndex,
|
||||||
|
// PageSize = pageSize
|
||||||
|
// };
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
//public async Task<BusPagedResult<T>> GetFocusPagedData<T>(
|
||||||
|
//string redisCacheKey,
|
||||||
|
//string redisCacheScoresIndexKey,
|
||||||
|
//int focusId,
|
||||||
|
//int pageSize = 10,
|
||||||
|
//long? lastScore = null,
|
||||||
|
//string lastMember = null,
|
||||||
|
//bool descending = true) where T : DeviceCacheBasicModel
|
||||||
|
//{
|
||||||
|
// // 计算分数范围
|
||||||
|
// long minScore = (long)focusId << 32;
|
||||||
|
// long maxScore = ((long)focusId + 1) << 32;
|
||||||
|
|
||||||
|
// // 获取成员列表
|
||||||
|
// var members = await GetSortedMembers(
|
||||||
|
// redisCacheScoresIndexKey,
|
||||||
|
// minScore,
|
||||||
|
// maxScore,
|
||||||
|
// pageSize,
|
||||||
|
// lastScore,
|
||||||
|
// lastMember,
|
||||||
|
// descending);
|
||||||
|
|
||||||
|
// // 批量获取数据
|
||||||
|
// var dataDict = await Instance.HMGetAsync<T>(redisCacheKey, members.CurrentItems);
|
||||||
|
|
||||||
|
// return new BusPagedResult<T>
|
||||||
|
// {
|
||||||
|
// Items = dataDict,
|
||||||
|
// TotalCount = await GetTotalCount(redisCacheScoresIndexKey, minScore, maxScore),
|
||||||
|
// HasNext = members.HasNext,
|
||||||
|
// NextScore = members.NextScore,
|
||||||
|
// NextMember = members.NextMember
|
||||||
|
// };
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private async Task<(string[] CurrentItems, bool HasNext, decimal? NextScore, string NextMember)>
|
||||||
|
// GetSortedMembers(
|
||||||
|
// string zsetKey,
|
||||||
|
// long minScore,
|
||||||
|
// long maxScore,
|
||||||
|
// int pageSize,
|
||||||
|
// long? lastScore,
|
||||||
|
// string lastMember,
|
||||||
|
// bool descending)
|
||||||
|
//{
|
||||||
|
// var querySize = pageSize + 1;
|
||||||
|
// var (startScore, exclude) = descending
|
||||||
|
// ? (lastScore ?? maxScore, lastMember)
|
||||||
|
// : (lastScore ?? minScore, lastMember);
|
||||||
|
|
||||||
|
// var members = descending
|
||||||
|
// ? await Instance.ZRevRangeByScoreAsync(
|
||||||
|
// zsetKey,
|
||||||
|
// max: startScore,
|
||||||
|
// min: minScore,
|
||||||
|
// offset: 0,
|
||||||
|
// count: querySize)
|
||||||
|
// : await Instance.ZRangeByScoreAsync(
|
||||||
|
// zsetKey,
|
||||||
|
// min: startScore,
|
||||||
|
// max: maxScore,
|
||||||
|
// offset: 0,
|
||||||
|
// count: querySize);
|
||||||
|
|
||||||
|
// var hasNext = members.Length > pageSize;
|
||||||
|
// var currentItems = members.Take(pageSize).ToArray();
|
||||||
|
|
||||||
|
// var nextCursor = currentItems.Any()
|
||||||
|
// ? await GetNextCursor(zsetKey, currentItems.Last(), descending)
|
||||||
|
// : (null, null);
|
||||||
|
|
||||||
|
// return (currentItems, hasNext, nextCursor.score, nextCursor.member);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private async Task<long> GetTotalCount(string zsetKey, long min, long max)
|
||||||
|
//{
|
||||||
|
// // 缓存计数优化
|
||||||
|
// var cacheKey = $"{zsetKey}_count_{min}_{max}";
|
||||||
|
// var cached = await Instance.GetAsync<long?>(cacheKey);
|
||||||
|
|
||||||
|
// if (cached.HasValue)
|
||||||
|
// return cached.Value;
|
||||||
|
|
||||||
|
// var count = await Instance.ZCountAsync(zsetKey, min, max);
|
||||||
|
// await Instance.SetExAsync(cacheKey, 60, count); // 缓存60秒
|
||||||
|
// return count;
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
//public async Task<Dictionary<int, BusPagedResult<T>>> BatchGetMeterPagedData<T>(
|
||||||
|
//string redisCacheKey,
|
||||||
|
//string redisCacheScoresIndexKey,
|
||||||
|
//IEnumerable<int> focusIds,
|
||||||
|
//int pageSizePerFocus = 10) where T : DeviceCacheBasicModel
|
||||||
|
//{
|
||||||
|
// var results = new ConcurrentDictionary<int, BusPagedResult<T>>();
|
||||||
|
// var parallelOptions = new ParallelOptions
|
||||||
|
// {
|
||||||
|
// MaxDegreeOfParallelism = Environment.ProcessorCount * 2
|
||||||
|
// };
|
||||||
|
|
||||||
|
// await Parallel.ForEachAsync(focusIds, parallelOptions, async (focusId, _) =>
|
||||||
|
// {
|
||||||
|
// var data = await SingleGetMeterPagedData<T>(
|
||||||
|
// redisCacheKey,
|
||||||
|
// redisCacheScoresIndexKey,
|
||||||
|
// focusId,
|
||||||
|
// pageSizePerFocus);
|
||||||
|
|
||||||
|
// results.TryAdd(focusId, data);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return new Dictionary<int, BusPagedResult<T>>(results);
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
namespace JiShe.CollectBus.IoTDB.Attributes
|
namespace JiShe.CollectBus.IoTDB.Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Column分类标记特性(ATTRIBUTE字段),也就是属性字段
|
/// Column分类标记特性(ATTRIBUTE字段),也就是属性字段
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
using JiShe.CollectBus.IoTDB.Enums;
|
||||||
|
|
||||||
|
namespace JiShe.CollectBus.IoTDB.Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IoTDB实体类型特性
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class EntityTypeAttribute : System.Attribute
|
||||||
|
{
|
||||||
|
public EntityTypeEnum EntityType { get; }
|
||||||
|
|
||||||
|
|
||||||
|
public EntityTypeAttribute(EntityTypeEnum entityType)
|
||||||
|
{
|
||||||
|
EntityType = entityType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
namespace JiShe.CollectBus.IoTDB.Attributes
|
namespace JiShe.CollectBus.IoTDB.Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Column分类标记特性(FIELD字段),数据列字段
|
/// Column分类标记特性(FIELD字段),数据列字段
|
||||||
@ -1,4 +1,4 @@
|
|||||||
namespace JiShe.CollectBus.IoTDB.Attributes
|
namespace JiShe.CollectBus.IoTDB.Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用于标识当前实体为单侧点模式,单侧点模式只有一个Filed标识字段,类型是Tuple<string,T>,Item1=>测点名称,Item2=>测点值,泛型
|
/// 用于标识当前实体为单侧点模式,单侧点模式只有一个Filed标识字段,类型是Tuple<string,T>,Item1=>测点名称,Item2=>测点值,泛型
|
||||||
@ -1,4 +1,4 @@
|
|||||||
namespace JiShe.CollectBus.IoTDB.Attributes
|
namespace JiShe.CollectBus.IoTDB.Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Column分类标记特性(TAG字段),标签字段
|
/// Column分类标记特性(TAG字段),标签字段
|
||||||
@ -1,5 +1,6 @@
|
|||||||
|
using JiShe.CollectBus.IoTDB.Enums;
|
||||||
namespace JiShe.CollectBus.IoTDB.Attributes
|
|
||||||
|
namespace JiShe.CollectBus.IoTDB.Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// IoTDB实体存储路径或表名称,一般用于已经明确的存储路径或表名称,例如日志存储
|
/// IoTDB实体存储路径或表名称,一般用于已经明确的存储路径或表名称,例如日志存储
|
||||||
@ -7,7 +7,7 @@ namespace JiShe.CollectBus.IoTDB.Context
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// IoTDB SessionPool 运行时上下文
|
/// IoTDB SessionPool 运行时上下文
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class IoTDBRuntimeContext: IScopedDependency//ITransientDependency
|
public class IoTDBRuntimeContext: IScopedDependency
|
||||||
{
|
{
|
||||||
private readonly bool _defaultValue;
|
private readonly bool _defaultValue;
|
||||||
|
|
||||||
|
|||||||
@ -1,27 +1,24 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Analyzers.Shared
|
namespace JiShe.CollectBus.IoTDB.Enums
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 实体类型枚举
|
/// IoTDB实体类型枚举
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum EntityTypeEnum
|
public enum EntityTypeEnum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// IoTDB树模型
|
/// 树模型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TreeModel = 1,
|
TreeModel = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// IoTDB表模型
|
/// 表模型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TableModel = 2,
|
TableModel = 2,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 其他情况
|
|
||||||
/// </summary>
|
|
||||||
Other = 3
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.IoTDB.Exceptions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// IoTDB异常
|
|
||||||
/// </summary>
|
|
||||||
public class IoTException : Exception
|
|
||||||
{
|
|
||||||
public int ErrorCode { get; }
|
|
||||||
|
|
||||||
public IoTException(string message, int errorCode)
|
|
||||||
: base($"{message} (Code: {errorCode})")
|
|
||||||
{
|
|
||||||
ErrorCode = errorCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -16,8 +16,6 @@ namespace JiShe.CollectBus.IoTDB.Interface
|
|||||||
///// <param name="useTableSession">是否使用表模型</param>
|
///// <param name="useTableSession">是否使用表模型</param>
|
||||||
//void SwitchSessionPool(bool useTableSession);
|
//void SwitchSessionPool(bool useTableSession);
|
||||||
|
|
||||||
IIoTDbProvider GetSessionPool(bool sessionpolType);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 插入数据
|
/// 插入数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1,19 +1,15 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Apache.IoTDB" Version="2.0.2" />
|
<PackageReference Include="Apache.IoTDB" Version="2.0.2" />
|
||||||
<PackageReference Include="Volo.Abp" Version="8.3.3" />
|
<PackageReference Include="Volo.Abp" Version="8.3.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\shared\JiShe.CollectBus.Common\JiShe.CollectBus.Common.csproj" />
|
||||||
<ProjectReference Include="..\..\shared\JiShe.CollectBus.Analyzers.Shared\JiShe.CollectBus.Analyzers.Shared.csproj" />
|
|
||||||
<ProjectReference Include="..\..\shared\JiShe.CollectBus.Common\JiShe.CollectBus.Common.csproj" />
|
|
||||||
|
|
||||||
<ProjectReference Include="..\..\modules\JiShe.CollectBus.Analyzers\JiShe.CollectBus.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
using JiShe.CollectBus.Common.Attributes;
|
using JiShe.CollectBus.IoTDB.Attribute;
|
||||||
using JiShe.CollectBus.Common.Consts;
|
|
||||||
using JiShe.CollectBus.IoTDB.Attributes;
|
|
||||||
using Volo.Abp.Domain.Entities;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.IoTDB.Model
|
namespace JiShe.CollectBus.IoTDB.Model
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// IoT实体基类,此类适用于多个数据测点记录场景,单个测点请使用子类 SingleMeasuring,新增字段只能现有字段末尾添加,否则会导致数据写入失败。
|
/// IoT实体基类,此类适用于多个数据测点记录场景,单个测点请使用子类 SingleMeasuring
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class IoTEntity
|
public abstract class IoTEntity
|
||||||
{
|
{
|
||||||
@ -14,59 +11,29 @@ namespace JiShe.CollectBus.IoTDB.Model
|
|||||||
/// 系统名称
|
/// 系统名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TAGColumn]
|
[TAGColumn]
|
||||||
public string SystemName { get; set; }
|
public string SystemName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 项目编码
|
/// 项目编码
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TAGColumn]
|
[ATTRIBUTEColumn]
|
||||||
public string ProjectId { get; set; }
|
public string ProjectId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 数据类型
|
|
||||||
/// </summary>
|
|
||||||
[TAGColumn]
|
|
||||||
public string DataType { get; set; } = IOTDBDataTypeConst.Data;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设备类型集中器、电表、水表、流量计、传感器等
|
/// 设备类型集中器、电表、水表、流量计、传感器等
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TAGColumn]
|
[ATTRIBUTEColumn]
|
||||||
public string DeviceType { get; set; }
|
public string DeviceType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设备ID,数据生成者,例如集中器ID,电表ID、水表ID、流量计ID、传感器ID等
|
/// 设备ID,也就是通信设备的唯一标识符,例如集中器地址,或者其他传感器设备地址
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TAGColumn]
|
[TAGColumn]
|
||||||
public string DeviceId { get; set; }
|
public string DeviceId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 时标,也就是业务时间戳,单位毫秒,必须通过DateTimeOffset获取
|
/// 时标,也就是业务时间戳,单位毫秒,必须通过DateTimeOffset获取
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long Timestamps { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
public long Timestamps { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设备路径
|
|
||||||
/// </summary>
|
|
||||||
private string _devicePath;
|
|
||||||
/// <summary>
|
|
||||||
/// 设备路径
|
|
||||||
/// </summary>
|
|
||||||
public virtual string DevicePath
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
// 如果未手动设置路径,则自动生成
|
|
||||||
if (string.IsNullOrWhiteSpace(_devicePath))
|
|
||||||
{
|
|
||||||
return $"root.{SystemName.ToLower()}.`{ProjectId}`.`{DeviceType}`.{DataType}.`{DeviceId}`";
|
|
||||||
}
|
|
||||||
return _devicePath;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_devicePath = value; // 直接赋值给支持字段,避免递归
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,24 @@
|
|||||||
using JiShe.CollectBus.Analyzers.Shared;
|
using System;
|
||||||
using JiShe.CollectBus.IoTDB.Attributes;
|
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
|
namespace JiShe.CollectBus.IoTDB.Model
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Table模型单项数据实体
|
/// Table模型单项数据实体
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SourceAnalyzers(EntityTypeEnum.TableModel)]
|
[EntityType(EntityTypeEnum.TableModel)]
|
||||||
public class TableModelSingleMeasuringEntity<T> : IoTEntity
|
public class TableModelSingleMeasuringEntity<T> : IoTEntity
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 单项数据键值对
|
/// 单项数据键值对
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SingleMeasuring(nameof(SingleColumn))]
|
[SingleMeasuring(nameof(SingleColumn))]
|
||||||
public required ValueTuple<string, T> SingleColumn { get; set; }
|
public required Tuple<string, T> SingleColumn { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,24 @@
|
|||||||
using JiShe.CollectBus.Analyzers.Shared;
|
using System;
|
||||||
using JiShe.CollectBus.IoTDB.Attributes;
|
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
|
namespace JiShe.CollectBus.IoTDB.Model
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tree模型单项数据实体
|
/// Tree模型单项数据实体
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SourceAnalyzers(EntityTypeEnum.TreeModel)]
|
[EntityType(EntityTypeEnum.TreeModel)]
|
||||||
public class TreeModelSingleMeasuringEntity<T> : IoTEntity
|
public class TreeModelSingleMeasuringEntity<T> : IoTEntity
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 单项数据键值对
|
/// 单项数据键值对
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SingleMeasuring(nameof(SingleMeasuring))]
|
[SingleMeasuring(nameof(SingleMeasuring))]
|
||||||
public required ValueTuple<string, T> SingleMeasuring { get; set; }
|
public required Tuple<string, T> SingleMeasuring { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 连接池大小
|
/// 连接池大小
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int PoolSize { get; set; } = 8;
|
public int PoolSize { get; set; } = 2;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询时,每次查询的数据量,默认1024
|
/// 查询时,每次查询的数据量,默认1024
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
using JiShe.CollectBus.Common.Extensions;
|
namespace JiShe.CollectBus.IoTDB.Options
|
||||||
using JiShe.CollectBus.Common.Helpers;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.IoTDB.Options
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询条件
|
/// 查询条件
|
||||||
@ -23,42 +20,9 @@ namespace JiShe.CollectBus.IoTDB.Options
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsNumber { get; set; } = false;
|
public bool IsNumber { get; set; } = false;
|
||||||
|
|
||||||
private object _rawValue;
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 值
|
/// 值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public object Value
|
public object Value { get; set; }
|
||||||
{
|
|
||||||
get => ApplyValueConversion(_rawValue);
|
|
||||||
set => _rawValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 值转换
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="rawValue"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private object ApplyValueConversion(object rawValue)
|
|
||||||
{
|
|
||||||
string declaredTypeName = rawValue.GetType().Name;
|
|
||||||
|
|
||||||
Func<object, object> converter = GetQueryConditionValue(declaredTypeName);
|
|
||||||
return converter(rawValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询条件值转换委托
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="declaredTypeName"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private Func<object, object> GetQueryConditionValue(string declaredTypeName)
|
|
||||||
{
|
|
||||||
return declaredTypeName?.ToUpper() switch
|
|
||||||
{
|
|
||||||
"DATETIME" => v => v != null ? ((DateTime)v).ToUniversalTime().Ticks : null,
|
|
||||||
"STRING" => v => v != null ? $"'{v}'" : "''",
|
|
||||||
_ => v => v
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +1,17 @@
|
|||||||
using Apache.IoTDB;
|
using Apache.IoTDB;
|
||||||
using JiShe.CollectBus.Analyzers.Shared;
|
using JiShe.CollectBus.IoTDB.Enums;
|
||||||
|
|
||||||
namespace JiShe.CollectBus.IoTDB.Provider
|
namespace JiShe.CollectBus.IoTDB.Provider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设备元数据
|
/// 设备元数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class DeviceMetadata
|
public class DeviceMetadata
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 实体类名称
|
/// IoTDB实体类型枚举
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string EntityName { get; set; }
|
public EntityTypeEnum EntityType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设备表名或树路径,如果实体没有添加TableNameOrTreePath,此处为空
|
|
||||||
/// </summary>
|
|
||||||
public string TableNameOrTreePath { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 实体类型枚举
|
|
||||||
/// </summary>
|
|
||||||
public EntityTypeEnum? EntityType { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否有单测量值
|
/// 是否有单测量值
|
||||||
@ -41,57 +31,6 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 值类型集合,用于构建Table的值类型,也就是dataTypes参数
|
/// 值类型集合,用于构建Table的值类型,也就是dataTypes参数
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<TSDataType> DataTypes { get; set; } = new();
|
public List<TSDataType> DataTypes { get; } = new();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 列处理信息集合
|
|
||||||
/// </summary>
|
|
||||||
public List<ColumnProcessor> Processors { get; } = new List<ColumnProcessor>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 列处理信息结构
|
|
||||||
/// </summary>
|
|
||||||
public struct ColumnProcessor
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 列名
|
|
||||||
/// </summary>
|
|
||||||
public string ColumnName;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 数据类型
|
|
||||||
/// </summary>
|
|
||||||
public TSDataType TSDataType { get; set;}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 值获取委托(参数:实体对象)
|
|
||||||
/// </summary>
|
|
||||||
public Func<object, object> ValueGetter;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 值设置委托(参数:实体对象,新值)
|
|
||||||
/// </summary>
|
|
||||||
public Action<object, object> ValueSetter;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 类型转换委托
|
|
||||||
/// </summary>
|
|
||||||
public Func<object, object> GetConverter;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 类型转换委托
|
|
||||||
/// </summary>
|
|
||||||
public Func<object, object> SetConverter;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否单测点
|
|
||||||
/// </summary>
|
|
||||||
public bool IsSingleMeasuring;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 单测点名称委托
|
|
||||||
/// </summary>
|
|
||||||
public Func<object, object> SingleMeasuringNameGetter;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string GetDevicePath<T>(T entity) where T : IoTEntity
|
public static string GetDevicePath<T>(T entity) where T : IoTEntity
|
||||||
{
|
{
|
||||||
return $"root.{entity.SystemName.ToLower()}.`{entity.ProjectId}`.`{entity.DeviceType}`.{entity.DataType}.`{entity.DeviceId}`";
|
return $"root.{entity.SystemName.ToLower()}.`{entity.DeviceId}`";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string GetDeviceTableName<T>(T entity) where T : IoTEntity
|
public static string GetDeviceTableName<T>(T entity) where T : IoTEntity
|
||||||
{
|
{
|
||||||
return $"{entity.SystemName.ToLower()}.`{entity.ProjectId}`.`{entity.DeviceType}`.`{entity.DeviceId}`";
|
return $"{entity.SystemName.ToLower()}.`{entity.DeviceId}`";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Reflection.Metadata.Ecma335;
|
using System.Reflection.Metadata.Ecma335;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -12,7 +11,7 @@ using JiShe.CollectBus.Common.Enums;
|
|||||||
using JiShe.CollectBus.Common.Extensions;
|
using JiShe.CollectBus.Common.Extensions;
|
||||||
using JiShe.CollectBus.Common.Helpers;
|
using JiShe.CollectBus.Common.Helpers;
|
||||||
using JiShe.CollectBus.Common.Models;
|
using JiShe.CollectBus.Common.Models;
|
||||||
using JiShe.CollectBus.IoTDB.Attributes;
|
using JiShe.CollectBus.IoTDB.Attribute;
|
||||||
using JiShe.CollectBus.IoTDB.Context;
|
using JiShe.CollectBus.IoTDB.Context;
|
||||||
using JiShe.CollectBus.IoTDB.Interface;
|
using JiShe.CollectBus.IoTDB.Interface;
|
||||||
using JiShe.CollectBus.IoTDB.Model;
|
using JiShe.CollectBus.IoTDB.Model;
|
||||||
@ -20,12 +19,6 @@ using JiShe.CollectBus.IoTDB.Options;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.Domain.Entities;
|
using Volo.Abp.Domain.Entities;
|
||||||
using JiShe.CollectBus.Analyzers.Shared;
|
|
||||||
using JiShe.CollectBus.IoTDB.Exceptions;
|
|
||||||
using System.Diagnostics.Metrics;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.IoTDB.Provider
|
namespace JiShe.CollectBus.IoTDB.Provider
|
||||||
{
|
{
|
||||||
@ -37,21 +30,10 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
private static readonly ConcurrentDictionary<Type, DeviceMetadata> MetadataCache = new();
|
private static readonly ConcurrentDictionary<Type, DeviceMetadata> MetadataCache = new();
|
||||||
private readonly ILogger<IoTDbProvider> _logger;
|
private readonly ILogger<IoTDbProvider> _logger;
|
||||||
private readonly IIoTDbSessionFactory _sessionFactory;
|
private readonly IIoTDbSessionFactory _sessionFactory;
|
||||||
|
private readonly IoTDBRuntimeContext _runtimeContext;
|
||||||
|
|
||||||
/// <summary>
|
private IIoTDbSessionPool CurrentSession =>
|
||||||
/// 存储模型切换标识,是否使用table模型存储, 默认为false,标识tree模型存储
|
_sessionFactory.GetSessionPool(_runtimeContext.UseTableSessionPool);
|
||||||
/// </summary>
|
|
||||||
public bool UseTableSessionPool { get; set; }
|
|
||||||
|
|
||||||
private IIoTDbSessionPool CurrentSession { get; set; }
|
|
||||||
|
|
||||||
public IIoTDbProvider GetSessionPool(bool useTableSessionPool)
|
|
||||||
{
|
|
||||||
CurrentSession = _sessionFactory.GetSessionPool(useTableSessionPool);
|
|
||||||
UseTableSessionPool = useTableSessionPool;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// IoTDbProvider
|
/// IoTDbProvider
|
||||||
@ -61,10 +43,12 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
/// <param name="runtimeContext"></param>
|
/// <param name="runtimeContext"></param>
|
||||||
public IoTDbProvider(
|
public IoTDbProvider(
|
||||||
ILogger<IoTDbProvider> logger,
|
ILogger<IoTDbProvider> logger,
|
||||||
IIoTDbSessionFactory sessionFactory)
|
IIoTDbSessionFactory sessionFactory,
|
||||||
|
IoTDBRuntimeContext runtimeContext)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_sessionFactory = sessionFactory;
|
_sessionFactory = sessionFactory;
|
||||||
|
_runtimeContext = runtimeContext;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,18 +65,12 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
var metadata = await GetMetadata<T>();
|
var metadata = await GetMetadata<T>();
|
||||||
|
|
||||||
var tablet = BuildTablet(new[] { entity }, metadata);
|
var tablet = BuildTablet(new[] { entity }, metadata);
|
||||||
if (tablet == null || tablet.Count <= 0)
|
|
||||||
{
|
|
||||||
_logger.LogError($"{nameof(InsertAsync)} IoTDB插入{typeof(T).Name}的数据时 tablet 为null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_logger.LogError($"{nameof(InsertAsync)} IoTDB插入{typeof(T).Name}的数据时 路径为 {tablet.First().InsertTargetName}");
|
|
||||||
|
|
||||||
await CurrentSession.InsertAsync(tablet.First());
|
await CurrentSession.InsertAsync(tablet);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, $"{nameof(InsertAsync)} IoTDB插入{typeof(T).Name}的数据时发生异常");
|
_logger.LogError(ex, $"{nameof(InsertAsync)} 插入数据时发生异常");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,11 +84,6 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (entities == null || entities.Count() <= 0)
|
|
||||||
{
|
|
||||||
_logger.LogError($"{nameof(BatchInsertAsync)} 参数异常,-101");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var metadata = await GetMetadata<T>();
|
var metadata = await GetMetadata<T>();
|
||||||
|
|
||||||
var batchSize = 1000;
|
var batchSize = 1000;
|
||||||
@ -119,23 +92,12 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
foreach (var batch in batches)
|
foreach (var batch in batches)
|
||||||
{
|
{
|
||||||
var tablet = BuildTablet(batch, metadata);
|
var tablet = BuildTablet(batch, metadata);
|
||||||
if (tablet == null || tablet.Count <= 0)
|
await CurrentSession.InsertAsync(tablet);
|
||||||
{
|
|
||||||
_logger.LogError($"{nameof(InsertAsync)} IoTDB插入{typeof(T).Name}的数据时 tablet 为null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in tablet)
|
|
||||||
{
|
|
||||||
_logger.LogError($"{nameof(InsertAsync)} IoTDB插入{typeof(T).Name}的数据时 路径为 {item.InsertTargetName}");
|
|
||||||
|
|
||||||
await CurrentSession.InsertAsync(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, $"{nameof(BatchInsertAsync)} IoTDB批量插入{typeof(T).Name}的数据时发生异常");
|
_logger.LogError(ex, $"{nameof(BatchInsertAsync)} 批量插入数据时发生异常");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,26 +113,19 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
var batchSize = 1000;
|
var batchSize = 1000;
|
||||||
var batches = entities.Chunk(batchSize);
|
var batches = entities.Chunk(batchSize);
|
||||||
|
|
||||||
foreach (var batch in batches)
|
foreach (var batch in batches)
|
||||||
{
|
{
|
||||||
var tablet = BuildTablet(batch, deviceMetadata);
|
var tablet = BuildTablet(batch, deviceMetadata);
|
||||||
if (tablet == null || tablet.Count <= 0)
|
await CurrentSession.InsertAsync(tablet);
|
||||||
{
|
|
||||||
_logger.LogError($"{nameof(InsertAsync)} IoTDB插入{typeof(T).Name}的数据时 tablet 为null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
foreach (var item in tablet)
|
|
||||||
{
|
|
||||||
await CurrentSession.InsertAsync(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, $"{nameof(BatchInsertAsync)} IoTDB批量插入{typeof(T).Name}的数据时发生异常");
|
_logger.LogError(ex, $"{nameof(BatchInsertAsync)} 批量插入数据时发生异常");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,28 +142,21 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var query = await BuildDeleteSQL<T>(options);
|
var query = await BuildDeleteSQL<T>(options);
|
||||||
var result = await CurrentSession.ExecuteQueryStatementAsync(query);
|
var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query);
|
||||||
|
|
||||||
if (result == null)
|
if (!sessionDataSet.HasNext())
|
||||||
{
|
{
|
||||||
return 0;
|
_logger.LogWarning($"{typeof(T).Name} 删除数据时,没有返回受影响记录数量。");
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.HasNext())
|
|
||||||
{
|
|
||||||
_logger.LogWarning($"{typeof(T).Name} IoTDB删除{typeof(T).Name}的数据时,没有返回受影响记录数量。");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
//获取唯一结果行
|
//获取唯一结果行
|
||||||
var row = result.Next();
|
var row = sessionDataSet.Next();
|
||||||
await result.Close();
|
return row.Values[0];
|
||||||
var dataResult = row.Values[0];
|
|
||||||
return dataResult;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, $"{nameof(DeleteAsync)} IoTDB删除{typeof(T).Name}的数据时发生异常");
|
_logger.LogError(ex, $"{nameof(DeleteAsync)} 删除数据时发生异常");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -220,58 +168,22 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<DeviceMetadata> GetMetadata<T>() where T : IoTEntity
|
public async Task<DeviceMetadata> GetMetadata<T>() where T : IoTEntity
|
||||||
{
|
{
|
||||||
var accessor = SourceEntityAccessorFactory.GetAccessor<T>();
|
var columns = CollectColumnMetadata(typeof(T));
|
||||||
|
var metadata = BuildDeviceMetadata<T>(columns);
|
||||||
var columns = CollectColumnMetadata<T>(accessor);
|
|
||||||
var tmpMetadata = BuildDeviceMetadata<T>(columns, accessor);
|
|
||||||
|
|
||||||
string tableNameOrTreePath = string.Empty;
|
|
||||||
var tableNameOrTreePathAttribute = typeof(T).GetCustomAttribute<TableNameOrTreePathAttribute>();
|
|
||||||
if (tableNameOrTreePathAttribute != null)
|
|
||||||
{
|
|
||||||
tableNameOrTreePath = tableNameOrTreePathAttribute.TableNameOrTreePath;
|
|
||||||
}
|
|
||||||
tmpMetadata.EntityName = accessor.EntityName;
|
|
||||||
tmpMetadata.EntityType = accessor.EntityType;
|
|
||||||
tmpMetadata.TableNameOrTreePath = tableNameOrTreePath;
|
|
||||||
|
|
||||||
var metaData = MetadataCache.AddOrUpdate(
|
var metaData = MetadataCache.AddOrUpdate(
|
||||||
typeof(T),
|
typeof(T),
|
||||||
addValueFactory: t => tmpMetadata, // 如果键不存在,用此值添加
|
addValueFactory: t => metadata, // 如果键不存在,用此值添加
|
||||||
updateValueFactory: (t, existingValue) =>
|
updateValueFactory: (t, existingValue) =>
|
||||||
{
|
{
|
||||||
var columns = CollectColumnMetadata(accessor);
|
var columns = CollectColumnMetadata(t);
|
||||||
var metadata = BuildDeviceMetadata(columns, accessor);
|
var metadata = BuildDeviceMetadata<T>(columns);
|
||||||
|
|
||||||
//对现有值 existingValue 进行修改,返回新值
|
//对现有值 existingValue 进行修改,返回新值
|
||||||
string tableNameOrTreePath = string.Empty;
|
|
||||||
var tableNameOrTreePathAttribute = typeof(T).GetCustomAttribute<TableNameOrTreePathAttribute>();
|
|
||||||
if (tableNameOrTreePathAttribute != null)
|
|
||||||
{
|
|
||||||
tableNameOrTreePath = tableNameOrTreePathAttribute.TableNameOrTreePath;
|
|
||||||
}
|
|
||||||
existingValue.ColumnNames = metadata.ColumnNames;
|
existingValue.ColumnNames = metadata.ColumnNames;
|
||||||
existingValue.DataTypes = metadata.DataTypes;
|
|
||||||
return existingValue;
|
return existingValue;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
//var metaData = MetadataCache.GetOrAdd(typeof(T), type =>
|
|
||||||
//{
|
|
||||||
// var columns = CollectColumnMetadata(accessor);
|
|
||||||
// var metadata = BuildDeviceMetadata(columns, accessor);
|
|
||||||
// string tableNameOrTreePath = string.Empty;
|
|
||||||
// var tableNameOrTreePathAttribute = typeof(T).GetCustomAttribute<TableNameOrTreePathAttribute>();
|
|
||||||
// if (tableNameOrTreePathAttribute != null)
|
|
||||||
// {
|
|
||||||
// tableNameOrTreePath = tableNameOrTreePathAttribute.TableNameOrTreePath;
|
|
||||||
// }
|
|
||||||
// metadata.EntityName = accessor.EntityName;
|
|
||||||
// metadata.EntityType = accessor.EntityType;
|
|
||||||
// metadata.TableNameOrTreePath = tableNameOrTreePath;
|
|
||||||
// return metadata;
|
|
||||||
//});
|
|
||||||
|
|
||||||
return await Task.FromResult(metaData);
|
return await Task.FromResult(metaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,8 +197,6 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var stopwatch2 = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
var query = await BuildQuerySQL<T>(options);
|
var query = await BuildQuerySQL<T>(options);
|
||||||
var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query);
|
var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query);
|
||||||
|
|
||||||
@ -299,27 +209,15 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
PageSize = options.PageSize,
|
PageSize = options.PageSize,
|
||||||
|
|
||||||
};
|
};
|
||||||
stopwatch2.Stop();
|
|
||||||
|
|
||||||
//int totalPageCount = (int)Math.Ceiling((double)result.TotalCount / options.PageSize);
|
result.HasNext = result.TotalCount > 0 ? result.TotalCount < result.PageSize : false;
|
||||||
|
|
||||||
if (result.Items.Count() < result.PageSize)
|
|
||||||
{
|
|
||||||
result.HasNext = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.HasNext = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//result.HasNext = result.Items.Count() > 0 ? result.Items.Count() < result.PageSize : false;
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
CurrentSession.Dispose();
|
CurrentSession.Dispose();
|
||||||
_logger.LogError(ex, $"{nameof(QueryAsync)} IoTDB查询{typeof(T).Name}的数据时发生异常");
|
_logger.LogError(ex, $"{nameof(QueryAsync)} IoTDB查询数据时发生异常");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -331,117 +229,157 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
/// <param name="entities">表实体</param>
|
/// <param name="entities">表实体</param>
|
||||||
/// <param name="metadata">设备元数据</param></param>
|
/// <param name="metadata">设备元数据</param></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private List<Tablet> BuildTablet<T>(IEnumerable<T> entities, DeviceMetadata metadata) where T : IoTEntity
|
private Tablet BuildTablet<T>(IEnumerable<T> entities, DeviceMetadata metadata) where T : IoTEntity
|
||||||
{
|
{
|
||||||
var entitiyList = entities.ToList();
|
var timestamps = new List<long>();
|
||||||
if (entitiyList == null || entitiyList.Count <= 0)
|
var values = new List<List<object>>();
|
||||||
{
|
var devicePaths = new HashSet<string>();
|
||||||
return null;
|
List<string> tempColumnNames = new List<string>();
|
||||||
}
|
tempColumnNames.AddRange(metadata.ColumnNames);
|
||||||
|
|
||||||
|
var entityTypeAttribute = typeof(T).GetCustomAttribute<EntityTypeAttribute>();
|
||||||
|
|
||||||
if (metadata.EntityType == null)
|
if (entityTypeAttribute == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 没有指定,属于异常情况,-101");
|
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 没有指定,属于异常情况,-101");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadata.EntityType == EntityTypeEnum.Other)
|
if (metadata.EntityType != entityTypeAttribute.EntityType)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 不属于IoTDB数据模型实体,属于异常情况,-102");
|
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 和{nameof(DeviceMetadata)}的 EntityType 不一致,属于异常情况,-102");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadata.EntityType == EntityTypeEnum.TreeModel && UseTableSessionPool == true)
|
if (metadata.EntityType == Enums.EntityTypeEnum.TreeModel && _runtimeContext.UseTableSessionPool == true)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 tree模型不能使用table模型Session连接,属于异常情况,-103");
|
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 tree模型不能使用table模型Session连接,属于异常情况,-103");
|
||||||
}
|
}
|
||||||
else if (metadata.EntityType == EntityTypeEnum.TableModel && UseTableSessionPool == false)
|
else if (metadata.EntityType == Enums.EntityTypeEnum.TableModel && _runtimeContext.UseTableSessionPool == false)
|
||||||
{
|
{
|
||||||
throw new Exception($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 table模型不能使用tree模型Session连接,属于异常情况,-104");
|
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 table模型不能使用tree模型Session连接,属于异常情况,-104");
|
||||||
}
|
}
|
||||||
|
|
||||||
string tableNameOrTreePath = string.Empty;
|
string tableNameOrTreePath = string.Empty;
|
||||||
if ( UseTableSessionPool)//表模型
|
var tableNameOrTreePathAttribute = typeof(T).GetCustomAttribute<TableNameOrTreePathAttribute>();
|
||||||
|
if (tableNameOrTreePathAttribute != null)
|
||||||
{
|
{
|
||||||
//如果指定了路径
|
tableNameOrTreePath = tableNameOrTreePathAttribute.TableNameOrTreePath;
|
||||||
if (!string.IsNullOrWhiteSpace(metadata.TableNameOrTreePath))
|
}
|
||||||
|
|
||||||
|
foreach (var entity in entities)
|
||||||
|
{
|
||||||
|
timestamps.Add(entity.Timestamps);
|
||||||
|
var rowValues = new List<object>();
|
||||||
|
|
||||||
|
foreach (var measurement in tempColumnNames)
|
||||||
{
|
{
|
||||||
tableNameOrTreePath = metadata.TableNameOrTreePath;
|
|
||||||
|
PropertyInfo propertyInfo = typeof(T).GetProperty(measurement);
|
||||||
|
if (propertyInfo == null)
|
||||||
|
{
|
||||||
|
throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,没有找到{measurement}属性,属于异常情况,-101。");
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = propertyInfo.GetValue(entity);
|
||||||
|
if (propertyInfo.IsDefined(typeof(SingleMeasuringAttribute), false) && metadata.IsSingleMeasuring == true)//表示当前对象是单测点模式
|
||||||
|
{
|
||||||
|
if (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
|
||||||
|
{
|
||||||
|
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 (!string.IsNullOrWhiteSpace(tableNameOrTreePath))
|
||||||
|
{
|
||||||
|
devicePaths.Add(tableNameOrTreePath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
tableNameOrTreePath = DevicePathBuilder.GetTableName<T>();
|
if (!_runtimeContext.UseTableSessionPool)//树模型
|
||||||
}
|
|
||||||
|
|
||||||
return new List<Tablet>() { BuildTablet(entitiyList, metadata, tableNameOrTreePath) };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//树模型的时候,实体的设备Id可能会不同,因此需要根据不同路径进行存储。
|
|
||||||
var tabletList = new List<Tablet>();
|
|
||||||
var groupEntities = entitiyList.GroupBy(d => d.DevicePath).ToList();
|
|
||||||
foreach (var group in groupEntities)
|
|
||||||
{
|
|
||||||
tabletList.Add(BuildTablet(group.ToList(), metadata, group.Key));
|
|
||||||
}
|
|
||||||
|
|
||||||
return tabletList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Tablet BuildTablet<T>(List<T> entities, DeviceMetadata metadata, string tableNameOrTreePath) where T : IoTEntity
|
|
||||||
{
|
|
||||||
// 预分配内存结构
|
|
||||||
var rowCount = entities.Count;
|
|
||||||
var timestamps = new long[rowCount];
|
|
||||||
var values = new object[rowCount][];
|
|
||||||
for (var i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
values[i] = new object[metadata.ColumnNames.Count];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<string> tempColumnNames = new List<string>();
|
|
||||||
tempColumnNames.AddRange(metadata.ColumnNames);
|
|
||||||
|
|
||||||
// 顺序处理数据(保证线程安全)
|
|
||||||
for (var row = 0; row < rowCount; row++)
|
|
||||||
{
|
|
||||||
var entity = entities[row];
|
|
||||||
timestamps[row] = entity.Timestamps;
|
|
||||||
|
|
||||||
for (int i = 0; i < metadata.ColumnNames.Count; i++)
|
|
||||||
{
|
|
||||||
var processor = metadata.Processors[i];
|
|
||||||
if (processor.IsSingleMeasuring)
|
|
||||||
{
|
{
|
||||||
tempColumnNames[i] = (string)processor.SingleMeasuringNameGetter(entity);
|
devicePaths.Add(DevicePathBuilder.GetDevicePath(entity));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
devicePaths.Add(DevicePathBuilder.GetTableName<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取并转换值
|
|
||||||
values[row][i] = processor.ValueGetter(entity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return UseTableSessionPool
|
if (devicePaths.Count > 1)
|
||||||
? BuildTableSessionTablet(metadata, tableNameOrTreePath, tempColumnNames, values.Select(d => d.ToList()).ToList(), timestamps.ToList())
|
{
|
||||||
: BuildSessionTablet(metadata, tableNameOrTreePath, tempColumnNames, values.Select(d => d.ToList()).ToList(), timestamps.ToList());
|
throw new Exception($"{nameof(BuildTablet)} 构建Tablet《{typeof(T).Name}》时,批量插入的设备路径不一致。");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return _runtimeContext.UseTableSessionPool
|
||||||
|
? BuildTableSessionTablet(metadata, devicePaths.First(), values, timestamps)
|
||||||
|
: BuildSessionTablet(metadata, devicePaths.First(), values, timestamps);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构建tree模型的Tablet
|
/// 构建tree模型的Tablet
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="metadata">已解析的设备数据元数据</param>
|
/// <param name="metadata">已解析的设备数据元数据</param>
|
||||||
/// <param name="devicePath">设备路径</param>
|
/// <param name="devicePath">设备路径</param>
|
||||||
/// <param name="columns">数据列集合</param>
|
|
||||||
/// <param name="values">数据集合</param>
|
/// <param name="values">数据集合</param>
|
||||||
/// <param name="timestamps">时间戳集合</param>
|
/// <param name="timestamps">时间戳集合</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private Tablet BuildSessionTablet(DeviceMetadata metadata, string devicePath, List<string> columns, List<List<object>> values, List<long> timestamps)
|
private Tablet BuildSessionTablet(DeviceMetadata metadata, string devicePath, List<List<object>> values, List<long> timestamps)
|
||||||
{
|
{
|
||||||
//todo 树模型需要去掉TAG类型和ATTRIBUTE类型的字段,只需要保留FIELD类型字段即可
|
//todo 树模型需要去掉TAG类型和ATTRIBUTE类型的字段,只需要保留FIELD类型字段即可
|
||||||
|
|
||||||
return new Tablet(
|
return new Tablet(
|
||||||
devicePath,
|
devicePath,
|
||||||
columns,
|
metadata.ColumnNames,
|
||||||
metadata.DataTypes,
|
metadata.DataTypes,
|
||||||
values,
|
values,
|
||||||
timestamps
|
timestamps
|
||||||
@ -453,15 +391,14 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="metadata">已解析的设备数据元数据</param>
|
/// <param name="metadata">已解析的设备数据元数据</param>
|
||||||
/// <param name="tableName">表名称</param>
|
/// <param name="tableName">表名称</param>
|
||||||
/// <param name="columns">数据列集合</param>
|
|
||||||
/// <param name="values">数据集合</param>
|
/// <param name="values">数据集合</param>
|
||||||
/// <param name="timestamps">时间戳集合</param>
|
/// <param name="timestamps">时间戳集合</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private Tablet BuildTableSessionTablet(DeviceMetadata metadata, string tableName, List<string> columns, List<List<object>> values, List<long> timestamps)
|
private Tablet BuildTableSessionTablet(DeviceMetadata metadata, string tableName, List<List<object>> values, List<long> timestamps)
|
||||||
{
|
{
|
||||||
var tablet = new Tablet(
|
var tablet = new Tablet(
|
||||||
tableName,
|
tableName,
|
||||||
columns,
|
metadata.ColumnNames,
|
||||||
metadata.ColumnCategories,
|
metadata.ColumnCategories,
|
||||||
metadata.DataTypes,
|
metadata.DataTypes,
|
||||||
values,
|
values,
|
||||||
@ -480,7 +417,7 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
private async Task<string> BuildQuerySQL<T>(IoTDBQueryOptions options) where T : IoTEntity
|
private async Task<string> BuildQuerySQL<T>(IoTDBQueryOptions options) where T : IoTEntity
|
||||||
{
|
{
|
||||||
var metadata = await GetMetadata<T>();
|
var metadata = await GetMetadata<T>();
|
||||||
var sb = new StringBuilder("SELECT ");
|
var sb = new StringBuilder("SELECT TIME as Timestamps,");
|
||||||
sb.AppendJoin(", ", metadata.ColumnNames);
|
sb.AppendJoin(", ", metadata.ColumnNames);
|
||||||
sb.Append($" FROM {options.TableNameOrTreePath}");
|
sb.Append($" FROM {options.TableNameOrTreePath}");
|
||||||
|
|
||||||
@ -505,7 +442,7 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
var metadata = await GetMetadata<T>();
|
var metadata = await GetMetadata<T>();
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
if (!UseTableSessionPool)
|
if (!_runtimeContext.UseTableSessionPool)
|
||||||
{
|
{
|
||||||
sb.Append("DELETE ");
|
sb.Append("DELETE ");
|
||||||
}
|
}
|
||||||
@ -537,9 +474,9 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
{
|
{
|
||||||
return condition.Operator switch
|
return condition.Operator switch
|
||||||
{
|
{
|
||||||
">" => $"{condition.Field} > {condition.Value}",
|
">" => condition.IsNumber ? $"{condition.Field} > {condition.Value}" : $"{condition.Field} > '{condition.Value}'",
|
||||||
"<" => $"{condition.Field} < {condition.Value}",
|
"<" => condition.IsNumber ? $"{condition.Field} < {condition.Value}" : $"{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} 属于异常情况")
|
_ => throw new NotSupportedException($"{nameof(TranslateCondition)} 将查询条件转换为SQL语句时操作符 {condition.Operator} 属于异常情况")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -559,17 +496,12 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
}
|
}
|
||||||
|
|
||||||
var result = await CurrentSession.ExecuteQueryStatementAsync(countQuery);
|
var result = await CurrentSession.ExecuteQueryStatementAsync(countQuery);
|
||||||
if (result == null)
|
if (result.HasNext())
|
||||||
{
|
{
|
||||||
|
await result.Close();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.HasNext())
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var count = Convert.ToInt32(result.Next().Values[0]);
|
var count = Convert.ToInt32(result.Next().Values[0]);
|
||||||
await result.Close();
|
await result.Close();
|
||||||
|
|
||||||
@ -588,9 +520,14 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
var results = new List<T>();
|
var results = new List<T>();
|
||||||
var metadata = await GetMetadata<T>();
|
var metadata = await GetMetadata<T>();
|
||||||
|
|
||||||
var accessor = SourceEntityAccessorFactory.GetAccessor<T>();
|
var properties = typeof(T).GetProperties();
|
||||||
var memberCache = BuildMemberCache(accessor);
|
|
||||||
|
|
||||||
|
var columns = new List<string>() { "Timestamps" };
|
||||||
|
var dataTypes = new List<TSDataType>() { 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)
|
while (dataSet.HasNext() && results.Count < pageSize)
|
||||||
{
|
{
|
||||||
@ -600,13 +537,28 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
Timestamps = record.Timestamps
|
Timestamps = record.Timestamps
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < metadata.Processors.Count; i++)
|
foreach (var measurement in columns)
|
||||||
{
|
{
|
||||||
var value = record.Values[i];
|
int indexOf = columns.IndexOf(measurement);
|
||||||
if (!(value is System.DBNull))
|
var value = record.Values[indexOf];
|
||||||
|
TSDataType tSDataType = dataTypes[indexOf];
|
||||||
|
|
||||||
|
var prop = properties.FirstOrDefault(p =>
|
||||||
|
p.Name.Equals(measurement, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (prop != null && !(value is System.DBNull))
|
||||||
{
|
{
|
||||||
metadata.Processors[i].ValueSetter(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);
|
results.Add(entity);
|
||||||
@ -619,55 +571,72 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取设备元数据的列
|
/// 获取设备元数据的列
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="accessor"></param>
|
/// <param name="type"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private List<ColumnInfo> CollectColumnMetadata<T>(ISourceEntityAccessor<T> accessor)
|
private List<ColumnInfo> CollectColumnMetadata(Type type)
|
||||||
{
|
{
|
||||||
var columns = new List<ColumnInfo>();
|
var columns = new List<ColumnInfo>();
|
||||||
var memberCache = BuildMemberCache(accessor);
|
|
||||||
|
|
||||||
foreach (var member in accessor.MemberList)
|
foreach (var prop in type.GetProperties())
|
||||||
{
|
{
|
||||||
// 过滤元组子项
|
|
||||||
if (member.NameOrPath.Contains(".Item")) continue;
|
|
||||||
|
|
||||||
// 类型名称处理
|
string typeName = string.Empty;
|
||||||
string declaredTypeName = member.DeclaredTypeName;
|
|
||||||
|
|
||||||
// 特性查询优化
|
Type declaredType = prop.PropertyType;
|
||||||
var attributes = member.CustomAttributes ?? Enumerable.Empty<Attribute>();
|
// 处理可空类型
|
||||||
var tagAttr = attributes.OfType<TAGColumnAttribute>().FirstOrDefault();
|
if (declaredType.IsGenericType && declaredType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||||
var attrColumn = attributes.OfType<ATTRIBUTEColumnAttribute>().FirstOrDefault();
|
|
||||||
var fieldColumn = attributes.OfType<FIELDColumnAttribute>().FirstOrDefault();
|
|
||||||
var singleMeasuringAttr = attributes.OfType<SingleMeasuringAttribute>().FirstOrDefault();
|
|
||||||
|
|
||||||
// 构建ColumnInfo
|
|
||||||
ColumnInfo? column = null;
|
|
||||||
if (tagAttr != null)
|
|
||||||
{
|
{
|
||||||
column = new ColumnInfo(member.NameOrPath, ColumnCategory.TAG, GetDataTypeFromTypeName(member.DeclaredTypeName), false, member.DeclaredTypeName);
|
Type underlyingType = Nullable.GetUnderlyingType(declaredType);
|
||||||
|
typeName = underlyingType.Name;
|
||||||
}
|
}
|
||||||
else if (attrColumn != null)
|
else
|
||||||
{
|
{
|
||||||
column = new ColumnInfo(member.NameOrPath, ColumnCategory.ATTRIBUTE, GetDataTypeFromTypeName(member.DeclaredTypeName), false, member.DeclaredTypeName);
|
typeName = declaredType.Name;
|
||||||
}
|
|
||||||
else if (fieldColumn != null)
|
|
||||||
{
|
|
||||||
column = new ColumnInfo(member.NameOrPath, ColumnCategory.FIELD, GetDataTypeFromTypeName(member.DeclaredTypeName), false, member.DeclaredTypeName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 单测模式处理
|
//先获取Tag标签和属性标签
|
||||||
if (singleMeasuringAttr != null && column == null)
|
ColumnInfo? column = prop.GetCustomAttribute<TAGColumnAttribute>() is not null ? new ColumnInfo(
|
||||||
|
name: prop.Name,
|
||||||
|
category: ColumnCategory.TAG,
|
||||||
|
dataType: GetDataTypeFromTypeName(typeName),
|
||||||
|
false
|
||||||
|
) : prop.GetCustomAttribute<ATTRIBUTEColumnAttribute>() is not null ? new ColumnInfo(
|
||||||
|
prop.Name,
|
||||||
|
ColumnCategory.ATTRIBUTE,
|
||||||
|
GetDataTypeFromTypeName(typeName),
|
||||||
|
false
|
||||||
|
) : prop.GetCustomAttribute<FIELDColumnAttribute>() is not null ? new ColumnInfo(
|
||||||
|
prop.Name,
|
||||||
|
ColumnCategory.FIELD,
|
||||||
|
GetDataTypeFromTypeName(typeName),
|
||||||
|
false)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
//最先检查是不是单侧点模式
|
||||||
|
SingleMeasuringAttribute singleMeasuringAttribute = prop.GetCustomAttribute<SingleMeasuringAttribute>();
|
||||||
|
|
||||||
|
if (singleMeasuringAttribute != null && column == null)
|
||||||
{
|
{
|
||||||
var tupleItemKey = $"{member.NameOrPath}.Item2";
|
//warning: 单侧点模式注意事项
|
||||||
if (!memberCache.TryGetValue(tupleItemKey, out var tupleMember))
|
//Entity实体 字段类型是 Tuple<string,T>,Item1=>测点名称,Item2=>测点值,泛型
|
||||||
{
|
//只有一个Filed字段。
|
||||||
throw new Exception($"{nameof(CollectColumnMetadata)} {accessor.EntityName} {member.NameOrPath} 单侧点属性解析异常");
|
//MeasuringName 默认为 SingleMeasuringAttribute.FieldName,以便于在获取对应的Value的时候重置为 Item1 的值。
|
||||||
}
|
|
||||||
column = new ColumnInfo(member.NameOrPath, ColumnCategory.FIELD, GetDataTypeFromTypeName(tupleMember.DeclaredTypeName), true, tupleMember.DeclaredTypeName);
|
Type tupleType = prop.PropertyType;
|
||||||
|
Type[] tupleArgs = tupleType.GetGenericArguments();
|
||||||
|
|
||||||
|
column = new ColumnInfo(
|
||||||
|
singleMeasuringAttribute.FieldName,
|
||||||
|
ColumnCategory.FIELD,
|
||||||
|
GetDataTypeFromTypeName(tupleArgs[1].Name),
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (column.HasValue) columns.Add(column.Value);
|
if (column.HasValue)
|
||||||
|
{
|
||||||
|
columns.Add(column.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
@ -678,7 +647,7 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
/// <param name="typeInfo">待解析的类</param>
|
/// <param name="typeInfo">待解析的类</param>
|
||||||
/// <param name="columns">已处理好的数据列</param>
|
/// <param name="columns">已处理好的数据列</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private DeviceMetadata BuildDeviceMetadata<T>(List<ColumnInfo> columns, ISourceEntityAccessor<T> accessor) where T : IoTEntity
|
private DeviceMetadata BuildDeviceMetadata<T>(List<ColumnInfo> columns) where T : IoTEntity
|
||||||
{
|
{
|
||||||
var metadata = new DeviceMetadata();
|
var metadata = new DeviceMetadata();
|
||||||
|
|
||||||
@ -697,135 +666,18 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
ProcessCategory(groupedColumns, ColumnCategory.ATTRIBUTE, metadata);
|
ProcessCategory(groupedColumns, ColumnCategory.ATTRIBUTE, metadata);
|
||||||
ProcessCategory(groupedColumns, ColumnCategory.FIELD, metadata);
|
ProcessCategory(groupedColumns, ColumnCategory.FIELD, metadata);
|
||||||
|
|
||||||
// 新增处理器初始化
|
var entityTypeAttribute = typeof(T).GetCustomAttribute<EntityTypeAttribute>();
|
||||||
foreach (var item in metadata.ColumnNames)
|
|
||||||
|
if (entityTypeAttribute == null)
|
||||||
{
|
{
|
||||||
ColumnInfo column = columns.FirstOrDefault(d => d.Name == item);
|
throw new ArgumentException($"{nameof(BuildDeviceMetadata)} 构建设备元数据时 {nameof(IoTEntity)} 的EntityType 没有指定,属于异常情况,-101");
|
||||||
|
|
||||||
var processor = new ColumnProcessor
|
|
||||||
{
|
|
||||||
ColumnName = column.Name,
|
|
||||||
IsSingleMeasuring = column.IsSingleMeasuring,
|
|
||||||
GetConverter = GetterConverter(column.DeclaredTypeName.ToUpper()),
|
|
||||||
SetConverter = SetterConverter(column.Name.ToUpper()),
|
|
||||||
TSDataType = column.DataType,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理单测点
|
|
||||||
if (column.IsSingleMeasuring)
|
|
||||||
{
|
|
||||||
var item1Member = accessor.MemberList
|
|
||||||
.First(m => m.NameOrPath == $"{column.Name}.Item1");
|
|
||||||
|
|
||||||
processor.SingleMeasuringNameGetter = (obj) =>
|
|
||||||
{
|
|
||||||
// 获取原始值并转为字符串
|
|
||||||
object rawValue = item1Member.Getter(obj);
|
|
||||||
string value = rawValue?.ToString();
|
|
||||||
|
|
||||||
ValidateSingleMeasuringName(value);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
var item2Member = accessor.MemberList
|
|
||||||
.First(m => m.NameOrPath == $"{column.Name}.Item2");
|
|
||||||
processor.ValueGetter = (obj) =>
|
|
||||||
{
|
|
||||||
object rawValue = item2Member.Getter(obj);
|
|
||||||
return processor.GetConverter(rawValue);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 获取对应的成员访问器
|
|
||||||
var member = accessor.MemberList.First(m => m.NameOrPath == column.Name);
|
|
||||||
processor.ValueGetter = (obj) =>
|
|
||||||
{
|
|
||||||
object rawValue = member.Getter(obj);
|
|
||||||
return processor.GetConverter(rawValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
//对应的属性成员进行赋值
|
|
||||||
processor.ValueSetter = (obj, value) =>
|
|
||||||
{
|
|
||||||
dynamic tempValue = GetTSDataValue(processor.TSDataType, value);
|
|
||||||
var rawValue = processor.SetConverter(value);
|
|
||||||
member.Setter(obj, rawValue);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata.Processors.Add(processor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metadata.EntityType = entityTypeAttribute.EntityType;
|
||||||
|
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证单测点名称格式
|
|
||||||
/// </summary>
|
|
||||||
private void ValidateSingleMeasuringName(string value)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 规则1: 严格检查ASCII字母和数字(0-9, A-Z, a-z)
|
|
||||||
bool hasInvalidChars = value.Any(c =>
|
|
||||||
!((c >= 'A' && c <= 'Z') ||
|
|
||||||
(c >= 'a' && c <= 'z') ||
|
|
||||||
(c >= '0' && c <= '9')));
|
|
||||||
|
|
||||||
// 规则2: 首字符不能是数字
|
|
||||||
bool startsWithDigit = value[0] >= '0' && value[0] <= '9';
|
|
||||||
|
|
||||||
// 规则3: 全字符串不能都是数字
|
|
||||||
bool allDigits = value.All(c => c >= '0' && c <= '9');
|
|
||||||
|
|
||||||
// 按优先级抛出具体异常
|
|
||||||
if (hasInvalidChars)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"SingleMeasuring name '{value}' 包含非法字符,只允许字母和数字");
|
|
||||||
}
|
|
||||||
else if (startsWithDigit)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"SingleMeasuring name '{value}' 不能以数字开头");
|
|
||||||
}
|
|
||||||
else if (allDigits)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"SingleMeasuring name '{value}' 不能全为数字");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 取值的处理器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="declaredTypeName"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private Func<object, object> GetterConverter(string declaredTypeName)
|
|
||||||
{
|
|
||||||
return declaredTypeName switch
|
|
||||||
{
|
|
||||||
"DATETIME" => value => value != null ? Convert.ToDateTime(value).GetDateTimeOffset().ToUnixTimeNanoseconds() : null,
|
|
||||||
"DECIMAL" => value => value != null ? Convert.ToDouble( value) : null,
|
|
||||||
_ => value => value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置值的处理
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="columnName"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private Func<object, object> SetterConverter(string columnName) =>
|
|
||||||
columnName.ToLower().EndsWith("time")
|
|
||||||
? value => value != null ? TimestampHelper.ConvertToDateTime(Convert.ToInt64(value), TimestampUnit.Nanoseconds) : null
|
|
||||||
: value => value;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 处理不同列类型的逻辑
|
/// 处理不同列类型的逻辑
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -852,11 +704,6 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 声明的类型的名称
|
|
||||||
/// </summary>
|
|
||||||
public string DeclaredTypeName { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否是单测点
|
/// 是否是单测点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -872,13 +719,12 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public TSDataType DataType { get; }
|
public TSDataType DataType { get; }
|
||||||
|
|
||||||
public ColumnInfo(string name, ColumnCategory category, TSDataType dataType, bool isSingleMeasuring, string declaredTypeName)
|
public ColumnInfo(string name, ColumnCategory category, TSDataType dataType, bool isSingleMeasuring)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Category = category;
|
Category = category;
|
||||||
DataType = dataType;
|
DataType = dataType;
|
||||||
IsSingleMeasuring = isSingleMeasuring;
|
IsSingleMeasuring = isSingleMeasuring;
|
||||||
DeclaredTypeName = declaredTypeName;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -913,7 +759,7 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
["DATETIME"] = TSDataType.TIMESTAMP,
|
["DATETIME"] = TSDataType.TIMESTAMP,
|
||||||
["DATE"] = TSDataType.DATE,
|
["DATE"] = TSDataType.DATE,
|
||||||
["BLOB"] = TSDataType.BLOB,
|
["BLOB"] = TSDataType.BLOB,
|
||||||
["DECIMAL"] = TSDataType.DOUBLE,
|
["DECIMAL"] = TSDataType.STRING,
|
||||||
["STRING"] = TSDataType.STRING
|
["STRING"] = TSDataType.STRING
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -949,7 +795,7 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
TSDataType.BOOLEAN => Convert.ToBoolean(value),
|
TSDataType.BOOLEAN => Convert.ToBoolean(value),
|
||||||
TSDataType.INT32 => Convert.ToInt32(value),
|
TSDataType.INT32 => Convert.ToInt32(value),
|
||||||
TSDataType.INT64 => Convert.ToInt64(value),
|
TSDataType.INT64 => Convert.ToInt64(value),
|
||||||
TSDataType.FLOAT => Convert.ToSingle(value),
|
TSDataType.FLOAT => Convert.ToDouble(value),
|
||||||
TSDataType.DOUBLE => Convert.ToDouble(value),
|
TSDataType.DOUBLE => Convert.ToDouble(value),
|
||||||
TSDataType.TEXT => Convert.ToString(value),
|
TSDataType.TEXT => Convert.ToString(value),
|
||||||
TSDataType.NONE => null,
|
TSDataType.NONE => null,
|
||||||
@ -959,23 +805,5 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
TSDataType.STRING => Convert.ToString(value),
|
TSDataType.STRING => Convert.ToString(value),
|
||||||
_ => Convert.ToString(value)
|
_ => Convert.ToString(value)
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 缓存实体属性信息
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="accessor"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private Dictionary<string, EntityMemberInfo> BuildMemberCache<T>(ISourceEntityAccessor<T> accessor)
|
|
||||||
{
|
|
||||||
var cache = new Dictionary<string, EntityMemberInfo>(StringComparer.Ordinal);
|
|
||||||
foreach (var member in accessor.MemberList)
|
|
||||||
{
|
|
||||||
cache[member.NameOrPath] = member;
|
|
||||||
}
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly Regex _asciiAlphanumericRegex = new Regex(@"^[a-zA-Z0-9]*$", RegexOptions.Compiled);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,7 +70,7 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
var result = await _sessionPool.InsertAlignedTabletAsync(tablet);
|
var result = await _sessionPool.InsertAlignedTabletAsync(tablet);
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
{
|
{
|
||||||
throw new Exception($"{nameof(SessionPoolAdapter)} Tree模型数据入库没有成功,返回结果为:{result},请检查IoTEntity继承子类属性索引是否有变动。");
|
throw new Exception($"{nameof(SessionPoolAdapter)} Tree模型数据入库没有成功,返回结果为:{result}");
|
||||||
}
|
}
|
||||||
//await CloseAsync();
|
//await CloseAsync();
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@ -68,7 +68,7 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
|||||||
var result = await _sessionPool.InsertAsync(tablet);
|
var result = await _sessionPool.InsertAsync(tablet);
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
{
|
{
|
||||||
throw new Exception($"{nameof(TableSessionPoolAdapter)} table模型数据入库没有成功,返回结果为:{result},请检查IoTEntity继承子类属性索引是否有变动。");
|
throw new Exception($"{nameof(TableSessionPoolAdapter)} table模型数据入库没有成功,返回结果为:{result}");
|
||||||
}
|
}
|
||||||
|
|
||||||
//await CloseAsync();
|
//await CloseAsync();
|
||||||
|
|||||||
@ -31,14 +31,10 @@
|
|||||||
<ProjectReference Include="..\JiShe.CollectBus.Kafka\JiShe.CollectBus.Kafka.csproj" />
|
<ProjectReference Include="..\JiShe.CollectBus.Kafka\JiShe.CollectBus.Kafka.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!--注意:基准测试需要引用dll测试-->
|
<!--<ItemGroup>
|
||||||
<!--<ItemGroup>
|
<Reference Include="JiShe.CollectBus.Kafka">
|
||||||
<Reference Include="JiShe.CollectBus.Common">
|
<HintPath>Lib\JiShe.CollectBus.Kafka.dll</HintPath>
|
||||||
<HintPath>Lib\JiShe.CollectBus.Common.dll</HintPath>
|
</Reference>
|
||||||
</Reference>
|
</ItemGroup>-->
|
||||||
<Reference Include="JiShe.CollectBus.Kafka">
|
|
||||||
<HintPath>Lib\JiShe.CollectBus.Kafka.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>-->
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using Confluent.Kafka;
|
using Confluent.Kafka;
|
||||||
using JiShe.CollectBus.Common;
|
|
||||||
using JiShe.CollectBus.Kafka.AdminClient;
|
using JiShe.CollectBus.Kafka.AdminClient;
|
||||||
using JiShe.CollectBus.Kafka.Consumer;
|
using JiShe.CollectBus.Kafka.Consumer;
|
||||||
using JiShe.CollectBus.Kafka.Internal;
|
|
||||||
using JiShe.CollectBus.Kafka.Producer;
|
using JiShe.CollectBus.Kafka.Producer;
|
||||||
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
@ -26,11 +24,11 @@ namespace JiShe.CollectBus.Kafka.Test
|
|||||||
{
|
{
|
||||||
|
|
||||||
// 每批消息数量
|
// 每批消息数量
|
||||||
[Params(1000, 10000, 100000, 1000000)]
|
[Params(1000, 10000, 100000)]
|
||||||
public int N;
|
public int N;
|
||||||
public ServiceProvider _serviceProvider;
|
public ServiceProvider _serviceProvider;
|
||||||
public IConsumerService _consumerService;
|
public IConsumerService _consumerService;
|
||||||
public IProducerService _producerService;
|
public IProducerService _producerService;
|
||||||
public string topic = "test-topic1";
|
public string topic = "test-topic1";
|
||||||
|
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
@ -42,22 +40,13 @@ namespace JiShe.CollectBus.Kafka.Test
|
|||||||
.AddJsonFile("appsettings.json")
|
.AddJsonFile("appsettings.json")
|
||||||
.Build();
|
.Build();
|
||||||
// 直接读取配置项
|
// 直接读取配置项
|
||||||
var greeting = config["Kafka:ServerTagName"];
|
var greeting = config["ServerTagName"];
|
||||||
Console.WriteLine(greeting); // 输出: Hello, World!
|
Console.WriteLine(greeting); // 输出: Hello, World!
|
||||||
// 创建服务容器
|
// 创建服务容器
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
// 注册 IConfiguration 实例
|
// 注册 IConfiguration 实例
|
||||||
services.AddSingleton<IConfiguration>(config);
|
services.AddSingleton<IConfiguration>(config);
|
||||||
|
|
||||||
services.Configure<KafkaOptionConfig>(options =>
|
|
||||||
{
|
|
||||||
config.GetSection("Kafka").Bind(options);
|
|
||||||
});
|
|
||||||
services.Configure<ServerApplicationOptions>(options =>
|
|
||||||
{
|
|
||||||
config.GetSection(nameof(ServerApplicationOptions)).Bind(options);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化日志
|
// 初始化日志
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.ReadFrom.Configuration(config) // 从 appsettings.json 读取配置
|
.ReadFrom.Configuration(config) // 从 appsettings.json 读取配置
|
||||||
@ -72,8 +61,6 @@ namespace JiShe.CollectBus.Kafka.Test
|
|||||||
services.AddSingleton<IAdminClientService, AdminClientService>();
|
services.AddSingleton<IAdminClientService, AdminClientService>();
|
||||||
services.AddSingleton<IProducerService, ProducerService>();
|
services.AddSingleton<IProducerService, ProducerService>();
|
||||||
services.AddSingleton<IConsumerService, ConsumerService>();
|
services.AddSingleton<IConsumerService, ConsumerService>();
|
||||||
services.AddSingleton<KafkaPollyPipeline>();
|
|
||||||
services.AddTransient<KafkaSubscribeTest>();
|
|
||||||
|
|
||||||
// 构建ServiceProvider
|
// 构建ServiceProvider
|
||||||
_serviceProvider = services.BuildServiceProvider();
|
_serviceProvider = services.BuildServiceProvider();
|
||||||
@ -88,7 +75,7 @@ namespace JiShe.CollectBus.Kafka.Test
|
|||||||
|
|
||||||
//await adminClientService.DeleteTopicAsync(topic);
|
//await adminClientService.DeleteTopicAsync(topic);
|
||||||
// 创建 topic
|
// 创建 topic
|
||||||
//adminClientService.CreateTopicAsync(topic, 3, 3).ConfigureAwait(false).GetAwaiter();
|
adminClientService.CreateTopicAsync(topic, 3, 3).ConfigureAwait(false).GetAwaiter();
|
||||||
|
|
||||||
_consumerService = _serviceProvider.GetRequiredService<IConsumerService>();
|
_consumerService = _serviceProvider.GetRequiredService<IConsumerService>();
|
||||||
|
|
||||||
@ -113,7 +100,7 @@ namespace JiShe.CollectBus.Kafka.Test
|
|||||||
List<Task> tasks = new();
|
List<Task> tasks = new();
|
||||||
for (int i = 0; i < N; ++i)
|
for (int i = 0; i < N; ++i)
|
||||||
{
|
{
|
||||||
var task = _producerService.ProduceAsync<string>(topic, i.ToString(), null);
|
var task = _producerService.ProduceAsync<string>(topic, i.ToString(),null);
|
||||||
}
|
}
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@ -1,5 +1,8 @@
|
|||||||
// See https://aka.ms/new-console-template for more information
|
// See https://aka.ms/new-console-template for more information
|
||||||
using JiShe.CollectBus.Common;
|
using BenchmarkDotNet.Configs;
|
||||||
|
using BenchmarkDotNet.Running;
|
||||||
|
using Confluent.Kafka;
|
||||||
|
using DeviceDetectorNET.Parser.Device;
|
||||||
using JiShe.CollectBus.Common.Consts;
|
using JiShe.CollectBus.Common.Consts;
|
||||||
using JiShe.CollectBus.Kafka;
|
using JiShe.CollectBus.Kafka;
|
||||||
using JiShe.CollectBus.Kafka.AdminClient;
|
using JiShe.CollectBus.Kafka.AdminClient;
|
||||||
@ -14,6 +17,10 @@ using Microsoft.Extensions.Hosting;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Reflection.PortableExecutable;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
#region 基准测试
|
#region 基准测试
|
||||||
//var summary = BenchmarkRunner.Run<KafkaProduceBenchmark>();
|
//var summary = BenchmarkRunner.Run<KafkaProduceBenchmark>();
|
||||||
@ -31,7 +38,7 @@ var host = Host.CreateDefaultBuilder(args)
|
|||||||
.AddJsonFile("appsettings.json")
|
.AddJsonFile("appsettings.json")
|
||||||
.Build();
|
.Build();
|
||||||
// 直接读取配置项
|
// 直接读取配置项
|
||||||
var greeting = config["ServerApplicationOptions:ServerTagName"];
|
var greeting = config["Kafka:ServerTagName"];
|
||||||
Console.WriteLine(greeting); // 输出: Hello, World!
|
Console.WriteLine(greeting); // 输出: Hello, World!
|
||||||
|
|
||||||
|
|
||||||
@ -51,23 +58,11 @@ var host = Host.CreateDefaultBuilder(args)
|
|||||||
logging.ClearProviders();
|
logging.ClearProviders();
|
||||||
logging.AddSerilog();
|
logging.AddSerilog();
|
||||||
});
|
});
|
||||||
//services.Configure<KafkaOptionConfig>(config.GetSection("Kafka"));
|
services.Configure<KafkaOptionConfig>(config.GetSection("Kafka"));
|
||||||
//services.Configure<ServerApplicationOptions>(config.GetSection("ServerApplicationOptions"));
|
|
||||||
var dss = config.GetSection("Kafka");
|
|
||||||
|
|
||||||
services.Configure<KafkaOptionConfig>(options =>
|
|
||||||
{
|
|
||||||
config.GetSection("Kafka").Bind(options);
|
|
||||||
});
|
|
||||||
services.Configure<ServerApplicationOptions>(options =>
|
|
||||||
{
|
|
||||||
config.GetSection(nameof(ServerApplicationOptions)).Bind(options);
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddSingleton<IAdminClientService, AdminClientService>();
|
services.AddSingleton<IAdminClientService, AdminClientService>();
|
||||||
services.AddSingleton<IProducerService, ProducerService>();
|
services.AddSingleton<IProducerService, ProducerService>();
|
||||||
services.AddSingleton<IConsumerService, ConsumerService>();
|
services.AddSingleton<IConsumerService, ConsumerService>();
|
||||||
services.AddSingleton<KafkaPollyPipeline>();
|
|
||||||
services.AddTransient<KafkaSubscribeTest>();
|
services.AddTransient<KafkaSubscribeTest>();
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -90,20 +85,8 @@ var host = Host.CreateDefaultBuilder(args)
|
|||||||
var loggerFactory = host.Services.GetRequiredService<ILoggerFactory>();
|
var loggerFactory = host.Services.GetRequiredService<ILoggerFactory>();
|
||||||
var logger = loggerFactory.CreateLogger<Program>();
|
var logger = loggerFactory.CreateLogger<Program>();
|
||||||
logger.LogInformation("程序启动");
|
logger.LogInformation("程序启动");
|
||||||
|
|
||||||
|
|
||||||
var _kafkaPollyPipeline = host.Services.GetRequiredService<KafkaPollyPipeline>();
|
|
||||||
if (_kafkaPollyPipeline == null)
|
|
||||||
{
|
|
||||||
logger.LogInformation("KafkaPollyPipeline未注册!");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var adminClientService = host.Services.GetRequiredService<IAdminClientService>();
|
var adminClientService = host.Services.GetRequiredService<IAdminClientService>();
|
||||||
var configuration = host.Services.GetRequiredService<IConfiguration>();
|
var configuration = host.Services.GetRequiredService<IConfiguration>();
|
||||||
|
|
||||||
var kafkaOptionConfig=host.Services.GetRequiredService<IOptions<ServerApplicationOptions>>();
|
|
||||||
|
|
||||||
string topic = ProtocolConst.TESTTOPIC;
|
string topic = ProtocolConst.TESTTOPIC;
|
||||||
//await adminClientService.DeleteTopicAsync(topic);
|
//await adminClientService.DeleteTopicAsync(topic);
|
||||||
// 创建 topic
|
// 创建 topic
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"profiles": {
|
|
||||||
"WSL": {
|
|
||||||
"commandName": "WSL2",
|
|
||||||
"distributionName": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -13,8 +13,7 @@
|
|||||||
"DotNetCore.CAP": "Warning",
|
"DotNetCore.CAP": "Warning",
|
||||||
"Serilog.AspNetCore": "Information",
|
"Serilog.AspNetCore": "Information",
|
||||||
"Microsoft.EntityFrameworkCore": "Warning",
|
"Microsoft.EntityFrameworkCore": "Warning",
|
||||||
"Microsoft.AspNetCore": "Warning",
|
"Microsoft.AspNetCore": "Warning"
|
||||||
"Microsoft.AspNetCore.Diagnostics.HealthChecks": "Warning"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"WriteTo": [
|
"WriteTo": [
|
||||||
@ -35,7 +34,7 @@
|
|||||||
"CorsOrigins": "http://localhost:4200,http://localhost:3100"
|
"CorsOrigins": "http://localhost:4200,http://localhost:3100"
|
||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"Default": "mongodb://mongo_PmEeF3:lixiao1980@192.168.1.9:27017/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000",
|
"Default": "mongodb://admin:admin02023@118.190.144.92:37117,118.190.144.92:37119,118.190.144.92:37120/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000",
|
||||||
"Kafka": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092",
|
"Kafka": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092",
|
||||||
"PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False",
|
"PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False",
|
||||||
"EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False"
|
"EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False"
|
||||||
@ -44,7 +43,7 @@
|
|||||||
"Configuration": "192.168.1.9:6380,password=1q2w3e!@#,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true",
|
"Configuration": "192.168.1.9:6380,password=1q2w3e!@#,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true",
|
||||||
"MaxPoolSize": "50",
|
"MaxPoolSize": "50",
|
||||||
"DefaultDB": "14",
|
"DefaultDB": "14",
|
||||||
"HangfireDB": "13"
|
"HangfireDB": "15"
|
||||||
},
|
},
|
||||||
"Jwt": {
|
"Jwt": {
|
||||||
"Audience": "JiShe.CollectBus",
|
"Audience": "JiShe.CollectBus",
|
||||||
@ -52,11 +51,16 @@
|
|||||||
"Issuer": "JiShe.CollectBus",
|
"Issuer": "JiShe.CollectBus",
|
||||||
"ExpirationTime": 2
|
"ExpirationTime": 2
|
||||||
},
|
},
|
||||||
"HealthChecks": {
|
"HealthCheck": {
|
||||||
"IsEnable": true,
|
"IsEnable": true,
|
||||||
"HealthCheckDatabaseName": "HealthChecks",
|
"MySql": {
|
||||||
"EvaluationTimeInSeconds": 10,
|
"IsEnable": true
|
||||||
"MinimumSecondsBetweenFailureNotifications": 60
|
},
|
||||||
|
"Pings": {
|
||||||
|
"IsEnable": true,
|
||||||
|
"Host": "https://www.baidu.com/",
|
||||||
|
"TimeOut": 5000
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"SwaggerConfig": [
|
"SwaggerConfig": [
|
||||||
{
|
{
|
||||||
@ -70,6 +74,14 @@
|
|||||||
"Version": "V1"
|
"Version": "V1"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"Cap": {
|
||||||
|
"RabbitMq": {
|
||||||
|
"HostName": "118.190.144.92",
|
||||||
|
"UserName": "collectbus",
|
||||||
|
"Password": "123456",
|
||||||
|
"Port": 5672
|
||||||
|
}
|
||||||
|
},
|
||||||
"Kafka": {
|
"Kafka": {
|
||||||
"BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092",
|
"BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092",
|
||||||
"EnableFilter": true,
|
"EnableFilter": true,
|
||||||
@ -80,17 +92,48 @@
|
|||||||
"SaslPassword": "lixiao1980",
|
"SaslPassword": "lixiao1980",
|
||||||
"KafkaReplicationFactor": 3,
|
"KafkaReplicationFactor": 3,
|
||||||
"NumPartitions": 30,
|
"NumPartitions": 30,
|
||||||
"FirstCollectionTime": "2025-04-22 16:07:00"
|
"ServerTagName": "JiSheCollectBus99"
|
||||||
|
//"Topic": {
|
||||||
|
// "ReplicationFactor": 3,
|
||||||
|
// "NumPartitions": 1000
|
||||||
|
//}
|
||||||
},
|
},
|
||||||
|
//"Kafka": {
|
||||||
|
// "Connections": {
|
||||||
|
// "Default": {
|
||||||
|
// "BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092"
|
||||||
|
// // "SecurityProtocol": "SASL_PLAINTEXT",
|
||||||
|
// // "SaslMechanism": "PLAIN",
|
||||||
|
// // "SaslUserName": "lixiao",
|
||||||
|
// // "SaslPassword": "lixiao1980",
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "Consumer": {
|
||||||
|
// "GroupId": "JiShe.CollectBus"
|
||||||
|
// },
|
||||||
|
// "Producer": {
|
||||||
|
// "MessageTimeoutMs": 6000,
|
||||||
|
// "Acks": -1
|
||||||
|
// },
|
||||||
|
// "Topic": {
|
||||||
|
// "ReplicationFactor": 3,
|
||||||
|
// "NumPartitions": 1000
|
||||||
|
// },
|
||||||
|
// "EventBus": {
|
||||||
|
// "GroupId": "JiShe.CollectBus",
|
||||||
|
// "TopicName": "DefaultTopicName"
|
||||||
|
// }
|
||||||
|
//},
|
||||||
"IoTDBOptions": {
|
"IoTDBOptions": {
|
||||||
"UserName": "root",
|
"UserName": "root",
|
||||||
"Password": "root",
|
"Password": "root",
|
||||||
"ClusterList": [ "192.168.1.9:6667" ],
|
"ClusterList": [ "192.168.1.9:6667" ],
|
||||||
"PoolSize": 32,
|
"PoolSize": 2,
|
||||||
"DataBaseName": "energy",
|
"DataBaseName": "energy",
|
||||||
"OpenDebugMode": true,
|
"OpenDebugMode": true,
|
||||||
"UseTableSessionPoolByDefault": false
|
"UseTableSessionPoolByDefault": false
|
||||||
},
|
},
|
||||||
|
"ServerTagName": "JiSheCollectBus3",
|
||||||
"Cassandra": {
|
"Cassandra": {
|
||||||
"ReplicationStrategy": {
|
"ReplicationStrategy": {
|
||||||
"Class": "NetworkTopologyStrategy", //策略为NetworkTopologyStrategy时才会有多个数据中心,SimpleStrategy用在只有一个数据中心的情况下
|
"Class": "NetworkTopologyStrategy", //策略为NetworkTopologyStrategy时才会有多个数据中心,SimpleStrategy用在只有一个数据中心的情况下
|
||||||
@ -113,12 +156,6 @@
|
|||||||
"Port": 9043,
|
"Port": 9043,
|
||||||
"DataCenter": "dc1",
|
"DataCenter": "dc1",
|
||||||
"Rack": "RAC2"
|
"Rack": "RAC2"
|
||||||
},
|
|
||||||
{
|
|
||||||
"Host": "192.168.1.9",
|
|
||||||
"Port": 9044,
|
|
||||||
"DataCenter": "dc1",
|
|
||||||
"Rack": "RAC2"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Username": "admin",
|
"Username": "admin",
|
||||||
@ -139,17 +176,5 @@
|
|||||||
"SerialConsistencyLevel": "Serial",
|
"SerialConsistencyLevel": "Serial",
|
||||||
"DefaultIdempotence": true
|
"DefaultIdempotence": true
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"ServerApplicationOptions": {
|
|
||||||
"ServerTagName": "JiSheCollectBus4",
|
|
||||||
"SystemType": "Energy",
|
|
||||||
"FirstCollectionTime": "2025-04-28 15:07:00",
|
|
||||||
"AutomaticVerificationTime": "16:07:00",
|
|
||||||
"AutomaticTerminalVersionTime": "17:07:00",
|
|
||||||
"AutomaticTelematicsModuleTime": "17:30:00",
|
|
||||||
"AutomaticDayFreezeTime": "02:30:00",
|
|
||||||
"AutomaticMonthFreezeTime": "03:30:00",
|
|
||||||
"DefaultProtocolPlugin": "T37612012ProtocolPlugin"
|
|
||||||
},
|
|
||||||
"PlugInFolder": ""
|
|
||||||
}
|
}
|
||||||
@ -1,9 +1,7 @@
|
|||||||
using Confluent.Kafka;
|
using Confluent.Kafka;
|
||||||
using Confluent.Kafka.Admin;
|
using Confluent.Kafka.Admin;
|
||||||
using JiShe.CollectBus.Kafka.Internal;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Kafka.AdminClient;
|
namespace JiShe.CollectBus.Kafka.AdminClient;
|
||||||
@ -11,17 +9,16 @@ namespace JiShe.CollectBus.Kafka.AdminClient;
|
|||||||
public class AdminClientService : IAdminClientService, IDisposable, ISingletonDependency
|
public class AdminClientService : IAdminClientService, IDisposable, ISingletonDependency
|
||||||
{
|
{
|
||||||
private readonly ILogger<AdminClientService> _logger;
|
private readonly ILogger<AdminClientService> _logger;
|
||||||
private readonly KafkaOptionConfig _kafkaOptionConfig;
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AdminClientService" /> class.
|
/// Initializes a new instance of the <see cref="AdminClientService" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="configuration"></param>
|
/// <param name="configuration"></param>
|
||||||
/// <param name="logger"></param>
|
/// <param name="logger"></param>
|
||||||
public AdminClientService(IConfiguration configuration, ILogger<AdminClientService> logger, IOptions<KafkaOptionConfig> kafkaOptionConfig)
|
public AdminClientService(IConfiguration configuration, ILogger<AdminClientService> logger)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_kafkaOptionConfig = kafkaOptionConfig.Value;
|
Instance = GetInstance(configuration);
|
||||||
Instance = GetInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -145,19 +142,22 @@ public class AdminClientService : IAdminClientService, IDisposable, ISingletonDe
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the instance.
|
/// Gets the instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="configuration">The configuration.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public IAdminClient GetInstance()
|
public IAdminClient GetInstance(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(configuration["Kafka:EnableAuthorization"]);
|
||||||
|
var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]!);
|
||||||
var adminClientConfig = new AdminClientConfig
|
var adminClientConfig = new AdminClientConfig
|
||||||
{
|
{
|
||||||
BootstrapServers = _kafkaOptionConfig.BootstrapServers
|
BootstrapServers = configuration["Kafka:BootstrapServers"]
|
||||||
};
|
};
|
||||||
if (_kafkaOptionConfig.EnableAuthorization)
|
if (enableAuthorization)
|
||||||
{
|
{
|
||||||
adminClientConfig.SecurityProtocol = _kafkaOptionConfig.SecurityProtocol;
|
adminClientConfig.SecurityProtocol = SecurityProtocol.SaslPlaintext;
|
||||||
adminClientConfig.SaslMechanism = _kafkaOptionConfig.SaslMechanism;
|
adminClientConfig.SaslMechanism = SaslMechanism.Plain;
|
||||||
adminClientConfig.SaslUsername = _kafkaOptionConfig.SaslUserName;
|
adminClientConfig.SaslUsername = configuration["Kafka:SaslUserName"];
|
||||||
adminClientConfig.SaslPassword = _kafkaOptionConfig.SaslPassword;
|
adminClientConfig.SaslPassword = configuration["Kafka:SaslPassword"];
|
||||||
}
|
}
|
||||||
return new AdminClientBuilder(adminClientConfig).Build();
|
return new AdminClientBuilder(adminClientConfig).Build();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,9 +41,6 @@ namespace JiShe.CollectBus.Kafka
|
|||||||
// 注册Consumer
|
// 注册Consumer
|
||||||
context.Services.AddSingleton<IConsumerService, ConsumerService>();
|
context.Services.AddSingleton<IConsumerService, ConsumerService>();
|
||||||
|
|
||||||
// 注册Polly
|
|
||||||
context.Services.AddSingleton<KafkaPollyPipeline>();
|
|
||||||
|
|
||||||
//context.Services.AddHostedService<HostedService>();
|
//context.Services.AddHostedService<HostedService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,52 +1,32 @@
|
|||||||
using Confluent.Kafka;
|
using Confluent.Kafka;
|
||||||
using JiShe.CollectBus.Common;
|
|
||||||
using JiShe.CollectBus.Common.Consts;
|
using JiShe.CollectBus.Common.Consts;
|
||||||
using JiShe.CollectBus.Common.Helpers;
|
|
||||||
using JiShe.CollectBus.Kafka.Internal;
|
using JiShe.CollectBus.Kafka.Internal;
|
||||||
using JiShe.CollectBus.Kafka.Serialization;
|
using JiShe.CollectBus.Kafka.Serialization;
|
||||||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Kafka.Consumer
|
namespace JiShe.CollectBus.Kafka.Consumer
|
||||||
{
|
{
|
||||||
public class ConsumerService : IConsumerService, IDisposable
|
public class ConsumerService : IConsumerService, IDisposable
|
||||||
{
|
{
|
||||||
private readonly ILogger<ConsumerService> _logger;
|
private readonly ILogger<ConsumerService> _logger;
|
||||||
/// <summary>
|
private readonly ConcurrentDictionary<Type, (object Consumer, CancellationTokenSource CTS)>
|
||||||
/// 消费者存储
|
|
||||||
/// Key 格式:{groupId}_{topic}_{TKey}_{TValue}
|
|
||||||
/// </summary>
|
|
||||||
private readonly ConcurrentDictionary<string, (object Consumer, CancellationTokenSource CTS)>
|
|
||||||
_consumerStore = new();
|
_consumerStore = new();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 消费完或者无数据时的延迟时间
|
|
||||||
/// </summary>
|
|
||||||
private static TimeSpan DelayTime => TimeSpan.FromMilliseconds(100);
|
|
||||||
|
|
||||||
private readonly KafkaOptionConfig _kafkaOptionConfig;
|
private readonly KafkaOptionConfig _kafkaOptionConfig;
|
||||||
|
private class KafkaConsumer<TKey, TValue> where TKey : notnull where TValue : class { }
|
||||||
private readonly ServerApplicationOptions _applicationOptions;
|
|
||||||
|
|
||||||
private readonly KafkaPollyPipeline _kafkaPollyPipeline;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ConsumerService
|
/// ConsumerService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger"></param>
|
/// <param name="logger"></param>
|
||||||
/// <param name="kafkaOptionConfig"></param>
|
/// <param name="kafkaOptionConfig"></param>
|
||||||
public ConsumerService(ILogger<ConsumerService> logger, IOptions<KafkaOptionConfig> kafkaOptionConfig, KafkaPollyPipeline kafkaPollyPipeline, IOptions<ServerApplicationOptions> applicationOptions)
|
public ConsumerService(ILogger<ConsumerService> logger, IOptions<KafkaOptionConfig> kafkaOptionConfig)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_kafkaOptionConfig = kafkaOptionConfig.Value;
|
_kafkaOptionConfig = kafkaOptionConfig.Value;
|
||||||
_applicationOptions = applicationOptions.Value;
|
|
||||||
_kafkaPollyPipeline = kafkaPollyPipeline;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region private 私有方法
|
#region private 私有方法
|
||||||
@ -72,7 +52,7 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
var config = new ConsumerConfig
|
var config = new ConsumerConfig
|
||||||
{
|
{
|
||||||
BootstrapServers = _kafkaOptionConfig.BootstrapServers,
|
BootstrapServers = _kafkaOptionConfig.BootstrapServers,
|
||||||
GroupId = groupId ?? _applicationOptions.ServerTagName,
|
GroupId = groupId ?? _kafkaOptionConfig.ServerTagName,
|
||||||
AutoOffsetReset = AutoOffsetReset.Earliest,
|
AutoOffsetReset = AutoOffsetReset.Earliest,
|
||||||
EnableAutoCommit = false, // 禁止AutoCommit
|
EnableAutoCommit = false, // 禁止AutoCommit
|
||||||
EnablePartitionEof = true, // 启用分区末尾标记
|
EnablePartitionEof = true, // 启用分区末尾标记
|
||||||
@ -115,7 +95,7 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task SubscribeAsync<TValue>(string topic, Func<TValue, Task<bool>> messageHandler, string? groupId = null) where TValue : class
|
public async Task SubscribeAsync<TValue>(string topic, Func<TValue, Task<bool>> messageHandler, string? groupId = null) where TValue : class
|
||||||
{
|
{
|
||||||
await SubscribeAsync<TValue>(new[] { topic }, messageHandler, groupId);
|
await SubscribeAsync<TValue>(new[] { topic }, messageHandler,groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -128,88 +108,59 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task SubscribeAsync<TKey, TValue>(string[] topics, Func<TKey, TValue, Task<bool>> messageHandler, string? groupId = null) where TKey : notnull where TValue : class
|
public async Task SubscribeAsync<TKey, TValue>(string[] topics, Func<TKey, TValue, Task<bool>> messageHandler, string? groupId = null) where TKey : notnull where TValue : class
|
||||||
{
|
{
|
||||||
try
|
var consumerKey = typeof(KafkaConsumer<TKey, TValue>);
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
//var consumer = _consumerStore.GetOrAdd(consumerKey, _ =>
|
||||||
|
//(
|
||||||
|
// CreateConsumer<TKey, TValue>(groupId),
|
||||||
|
// cts
|
||||||
|
//)).Consumer as IConsumer<TKey, TValue>;
|
||||||
|
var consumer = CreateConsumer<TKey, TValue>(groupId);
|
||||||
|
consumer!.Subscribe(topics);
|
||||||
|
|
||||||
|
await Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await _kafkaPollyPipeline.KafkaPipeline.ExecuteAsync(async token =>
|
while (!cts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
var consumerKey = $"{groupId}_{string.Join("_", topics)}_{typeof(TKey).Name}_{typeof(TValue).Name}";
|
|
||||||
var cts = new CancellationTokenSource();
|
|
||||||
|
|
||||||
var consumer = _consumerStore.GetOrAdd(consumerKey, _ =>
|
|
||||||
(
|
|
||||||
CreateConsumer<TKey, TValue>(groupId),
|
|
||||||
cts
|
|
||||||
)).Consumer as IConsumer<TKey, TValue>;
|
|
||||||
|
|
||||||
consumer!.Subscribe(topics);
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
while (!cts.IsCancellationRequested)
|
//_logger.LogInformation($"Kafka消费: {string.Join("", topics)} 开始拉取消息....");
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//_logger.LogInformation($"Kafka消费: {string.Join("", topics)} 开始拉取消息....");
|
|
||||||
|
|
||||||
var result = consumer.Consume(cts.Token);
|
var result = consumer.Consume(cts.Token);
|
||||||
if (result == null || result.Message == null || result.Message.Value == null)
|
if (result == null || result.Message==null || result.Message.Value == null)
|
||||||
{
|
continue;
|
||||||
await Task.Delay(DelayTime, cts.Token);
|
|
||||||
continue;
|
if (result.IsPartitionEOF)
|
||||||
}
|
{
|
||||||
if (result.IsPartitionEOF)
|
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
||||||
{
|
await Task.Delay(TimeSpan.FromSeconds(1),cts.Token);
|
||||||
#if DEBUG
|
continue;
|
||||||
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
}
|
||||||
#endif
|
if (_kafkaOptionConfig.EnableFilter)
|
||||||
await Task.Delay(DelayTime, cts.Token);
|
{
|
||||||
continue;
|
var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } };
|
||||||
}
|
// 检查 Header 是否符合条件
|
||||||
if (_kafkaOptionConfig.EnableFilter)
|
if (!headersFilter.Match(result.Message.Headers))
|
||||||
{
|
|
||||||
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); // 手动提交
|
|
||||||
}
|
|
||||||
catch (ConsumeException ex) when (KafkaPollyPipeline.IsRecoverableError(ex))
|
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, $"{string.Join("、", topics)}消息消费失败: {ex.Error.Reason}");
|
//consumer.Commit(result); // 提交偏移量
|
||||||
throw; // 抛出异常,以便重试
|
// 跳过消息
|
||||||
}
|
continue;
|
||||||
catch (KafkaException ex) when (KafkaPollyPipeline.IsRecoverableError(ex))
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"{string.Join("、", topics)} 消息消费失败: {ex.Error.Reason}");
|
|
||||||
throw; // 抛出异常,以便重试
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
//ignore
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "处理消息时发生未知错误");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, cts.Token);
|
bool sucess= await messageHandler(result.Message.Key, result.Message.Value);
|
||||||
await Task.CompletedTask;
|
if (sucess)
|
||||||
});
|
{
|
||||||
|
consumer.Commit(result); // 手动提交
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
}
|
||||||
{
|
catch (ConsumeException ex)
|
||||||
|
{
|
||||||
throw;
|
_logger.LogError(ex, $"{string.Join("、", topics)}消息消费失败: {ex.Error.Reason}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -224,84 +175,75 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task SubscribeAsync<TValue>(string[] topics, Func<TValue, Task<bool>> messageHandler, string? groupId) where TValue : class
|
public async Task SubscribeAsync<TValue>(string[] topics, Func<TValue, Task<bool>> messageHandler, string? groupId) where TValue : class
|
||||||
{
|
{
|
||||||
try
|
try {
|
||||||
{
|
var consumerKey = typeof(KafkaConsumer<Ignore, TValue>);
|
||||||
await _kafkaPollyPipeline.KafkaPipeline.ExecuteAsync(async token =>
|
var cts = new CancellationTokenSource();
|
||||||
|
//if (topics.Contains(ProtocolConst.SubscriberLoginReceivedEventName))
|
||||||
|
//{
|
||||||
|
// string ssss = "";
|
||||||
|
//}
|
||||||
|
//var consumer = _consumerStore.GetOrAdd(consumerKey, _ =>
|
||||||
|
//(
|
||||||
|
// CreateConsumer<string, TValue>(groupId),
|
||||||
|
// cts
|
||||||
|
//)).Consumer as IConsumer<string, TValue>;
|
||||||
|
|
||||||
|
var consumer = CreateConsumer<Ignore, TValue>(groupId);
|
||||||
|
consumer!.Subscribe(topics);
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var consumerKey = $"{groupId}_{string.Join("_", topics)}_{typeof(Ignore).Name}_{typeof(TValue).Name}";
|
int count = 0;
|
||||||
var cts = new CancellationTokenSource();
|
while (!cts.IsCancellationRequested)
|
||||||
var consumer = _consumerStore.GetOrAdd(consumerKey, _ =>
|
|
||||||
(
|
|
||||||
CreateConsumer<Ignore, TValue>(groupId),
|
|
||||||
cts
|
|
||||||
)).Consumer as IConsumer<Ignore, TValue>;
|
|
||||||
|
|
||||||
consumer!.Subscribe(topics);
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
int count = 0;
|
try
|
||||||
while (!cts.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
try
|
//_logger.LogInformation($"Kafka消费: {string.Join("", topics)}_{count} 开始拉取消息....");
|
||||||
|
count++;
|
||||||
|
var result = consumer.Consume(cts.Token);
|
||||||
|
if (result == null || result.Message == null || result.Message.Value == null)
|
||||||
{
|
{
|
||||||
//_logger.LogInformation($"Kafka消费: {string.Join("", topics)}_{count} 开始拉取消息....");
|
await Task.Delay(500, cts.Token);
|
||||||
count++;
|
continue;
|
||||||
var result = consumer.Consume(cts.Token);
|
}
|
||||||
if (result == null || result.Message == null || result.Message.Value == null)
|
|
||||||
{
|
|
||||||
await Task.Delay(DelayTime, cts.Token);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.IsPartitionEOF)
|
if (result.IsPartitionEOF)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
||||||
|
await Task.Delay(100, cts.Token);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (_kafkaOptionConfig.EnableFilter)
|
||||||
|
{
|
||||||
|
var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } };
|
||||||
|
// 检查 Header 是否符合条件
|
||||||
|
if (!headersFilter.Match(result.Message.Headers))
|
||||||
{
|
{
|
||||||
#if DEBUG
|
await Task.Delay(500, cts.Token);
|
||||||
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
//consumer.Commit(result); // 提交偏移量
|
||||||
#endif
|
// 跳过消息
|
||||||
await Task.Delay(DelayTime, cts.Token);
|
|
||||||
continue;
|
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.Value);
|
|
||||||
if (sucess)
|
|
||||||
consumer.Commit(result); // 手动提交
|
|
||||||
//else
|
|
||||||
// consumer.StoreOffset(result);
|
|
||||||
}
|
|
||||||
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, "处理消息时发生未知错误");
|
|
||||||
}
|
}
|
||||||
|
bool sucess = await messageHandler(result.Message.Value);
|
||||||
|
if (sucess)
|
||||||
|
consumer.Commit(result); // 手动提交
|
||||||
|
else
|
||||||
|
consumer.StoreOffset(result);
|
||||||
}
|
}
|
||||||
}, cts.Token);
|
catch (ConsumeException ex)
|
||||||
await Task.CompletedTask;
|
{
|
||||||
|
_logger.LogError(ex, $"{string.Join("、", topics)}消息消费失败: {ex.Error.Reason}");
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
} catch (Exception ex)
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
|
_logger.LogWarning($"Kafka消费异常: {ex.Message}");
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -317,15 +259,7 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
/// <param name="batchTimeout">批次超时时间</param>
|
/// <param name="batchTimeout">批次超时时间</param>
|
||||||
public async Task SubscribeBatchAsync<TKey, TValue>(string topic, Func<List<TValue>, Task<bool>> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null) where TKey : notnull where TValue : class
|
public async Task SubscribeBatchAsync<TKey, TValue>(string topic, Func<List<TValue>, Task<bool>> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null) where TKey : notnull where TValue : class
|
||||||
{
|
{
|
||||||
try
|
await SubscribeBatchAsync<TKey, TValue>(new[] { topic }, messageBatchHandler, groupId, batchSize, batchTimeout);
|
||||||
{
|
|
||||||
await SubscribeBatchAsync<TKey, TValue>(new[] { topic }, messageBatchHandler, groupId, batchSize, batchTimeout);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -338,127 +272,109 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
/// <param name="groupId">消费组ID</param>
|
/// <param name="groupId">消费组ID</param>
|
||||||
/// <param name="batchSize">批次大小</param>
|
/// <param name="batchSize">批次大小</param>
|
||||||
/// <param name="batchTimeout">批次超时时间</param>
|
/// <param name="batchTimeout">批次超时时间</param>
|
||||||
public async Task SubscribeBatchAsync<TKey, TValue>(string[] topics, Func<List<TValue>, Task<bool>> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null) where TKey : notnull where TValue : class
|
public async Task SubscribeBatchAsync<TKey, TValue>(string[] topics,Func<List<TValue>, Task<bool>> messageBatchHandler, string? groupId = null,int batchSize = 100, TimeSpan? batchTimeout = null) where TKey : notnull where TValue : class
|
||||||
{
|
{
|
||||||
try
|
var consumerKey = typeof(KafkaConsumer<TKey, TValue>);
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
//var consumer = _consumerStore.GetOrAdd(consumerKey, _ =>
|
||||||
|
// (
|
||||||
|
// CreateConsumer<TKey, TValue>(groupId),
|
||||||
|
// cts
|
||||||
|
// )).Consumer as IConsumer<TKey, TValue>;
|
||||||
|
var consumer = CreateConsumer<string, TValue>(groupId);
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
var consumerKey = $"{groupId}_{string.Join("_", topics)}_{typeof(TKey).Name}_{typeof(TValue).Name}";
|
|
||||||
var cts = new CancellationTokenSource();
|
|
||||||
|
|
||||||
var consumer = _consumerStore.GetOrAdd(consumerKey, _ =>
|
|
||||||
(
|
|
||||||
CreateConsumer<TKey, TValue>(groupId),
|
|
||||||
cts
|
|
||||||
)).Consumer as IConsumer<TKey, TValue>;
|
|
||||||
consumer!.Subscribe(topics);
|
|
||||||
|
|
||||||
var timeout = batchTimeout ?? TimeSpan.FromSeconds(5); // 默认超时时间调整为5秒
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
var messages = new List<(TValue Value, TopicPartitionOffset Offset)>();
|
// 非阻塞快速累积消息
|
||||||
var startTime = DateTime.UtcNow;
|
while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout)
|
||||||
|
|
||||||
while (!cts.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
try
|
var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
{
|
{
|
||||||
// 非阻塞快速累积消息
|
if (result.IsPartitionEOF)
|
||||||
while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout)
|
|
||||||
{
|
{
|
||||||
var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用
|
//_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
||||||
|
await Task.Delay(10, cts.Token);
|
||||||
if (result != null)
|
|
||||||
{
|
|
||||||
if (result.IsPartitionEOF)
|
|
||||||
{
|
|
||||||
#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)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (result.Message.Value != null)
|
||||||
// 处理批次
|
|
||||||
if (messages.Count > 0)
|
|
||||||
{
|
{
|
||||||
bool success = await messageBatchHandler(messages.Select(m => m.Value).ToList());
|
if (_kafkaOptionConfig.EnableFilter)
|
||||||
if (success)
|
|
||||||
{
|
{
|
||||||
var offsetsByPartition = new Dictionary<TopicPartition, long>();
|
var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } };
|
||||||
foreach (var msg in messages)
|
// 检查 Header 是否符合条件
|
||||||
|
if (!headersFilter.Match(result.Message.Headers))
|
||||||
{
|
{
|
||||||
var tp = msg.Offset.TopicPartition;
|
//consumer.Commit(result); // 提交偏移量
|
||||||
var offset = msg.Offset.Offset;
|
// 跳过消息
|
||||||
if (!offsetsByPartition.TryGetValue(tp, out var currentMax) || offset > currentMax)
|
continue;
|
||||||
{
|
|
||||||
offsetsByPartition[tp] = offset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var offsetsToCommit = offsetsByPartition
|
|
||||||
.Select(kv => new TopicPartitionOffset(kv.Key, new Offset(kv.Value + 1)))
|
|
||||||
.ToList();
|
|
||||||
consumer.Commit(offsetsToCommit);
|
|
||||||
}
|
}
|
||||||
messages.Clear();
|
messages.Add((result.Message.Value, result.TopicPartitionOffset));
|
||||||
|
//messages.Add(result.Message.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
startTime = DateTime.UtcNow;
|
|
||||||
}
|
}
|
||||||
catch (ConsumeException ex) when (KafkaPollyPipeline.IsRecoverableError(ex))
|
else
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, $"{string.Join("、", topics)} 消息消费失败: {ex.Error.Reason}");
|
// 无消息时短暂等待
|
||||||
throw; // 抛出异常,以便重试
|
await Task.Delay(10, cts.Token);
|
||||||
}
|
|
||||||
catch (KafkaException ex) when (KafkaPollyPipeline.IsRecoverableError(ex))
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"{string.Join("、", topics)} 消息消费失败: {ex.Error.Reason}");
|
|
||||||
throw; // 抛出异常,以便重试
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
//ignore
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "处理批量消息时发生未知错误");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, cts.Token);
|
|
||||||
|
|
||||||
await Task.CompletedTask;
|
// 处理批次
|
||||||
});
|
if (messages.Count > 0)
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
bool success = await messageBatchHandler(messages.Select(m => m.Value).ToList());
|
||||||
{
|
if (success)
|
||||||
|
{
|
||||||
|
var offsetsByPartition = new Dictionary<TopicPartition, long>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw;
|
var offsetsToCommit = offsetsByPartition
|
||||||
}
|
.Select(kv => new TopicPartitionOffset(kv.Key, new Offset(kv.Value + 1)))
|
||||||
|
.ToList();
|
||||||
|
consumer.Commit(offsetsToCommit);
|
||||||
|
}
|
||||||
|
messages.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
catch (ConsumeException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"{string.Join("、", topics)} 消息消费失败: {ex.Error.Reason}");
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// 任务取消,正常退出
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "处理批量消息时发生未知错误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, cts.Token);
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -474,15 +390,7 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
/// <param name="consumeTimeout">消费等待时间</param>
|
/// <param name="consumeTimeout">消费等待时间</param>
|
||||||
public async Task SubscribeBatchAsync<TValue>(string topic, Func<List<TValue>, Task<bool>> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null, TimeSpan? consumeTimeout = null) where TValue : class
|
public async Task SubscribeBatchAsync<TValue>(string topic, Func<List<TValue>, Task<bool>> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null, TimeSpan? consumeTimeout = null) where TValue : class
|
||||||
{
|
{
|
||||||
try
|
await SubscribeBatchAsync(new[] { topic }, messageBatchHandler, groupId, batchSize, batchTimeout, consumeTimeout);
|
||||||
{
|
|
||||||
await SubscribeBatchAsync(new[] { topic }, messageBatchHandler, groupId, batchSize, batchTimeout, consumeTimeout);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,126 +405,111 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
/// <param name="batchSize">批次大小</param>
|
/// <param name="batchSize">批次大小</param>
|
||||||
/// <param name="batchTimeout">批次超时时间</param>
|
/// <param name="batchTimeout">批次超时时间</param>
|
||||||
/// <param name="consumeTimeout">消费等待时间</param>
|
/// <param name="consumeTimeout">消费等待时间</param>
|
||||||
public async Task SubscribeBatchAsync<TValue>(string[] topics, Func<List<TValue>, Task<bool>> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null, TimeSpan? consumeTimeout = null) where TValue : class
|
public async Task SubscribeBatchAsync<TValue>(string[] topics,Func<List<TValue>, Task<bool>> messageBatchHandler, string? groupId = null, int batchSize = 100,TimeSpan? batchTimeout = null,TimeSpan? consumeTimeout = null)where TValue : class
|
||||||
{
|
{
|
||||||
try
|
var consumerKey = typeof(KafkaConsumer<string, TValue>);
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
//var consumer = _consumerStore.GetOrAdd(consumerKey, _ =>
|
||||||
|
// (
|
||||||
|
// CreateConsumer<string, TValue>(groupId),
|
||||||
|
// cts
|
||||||
|
// )).Consumer as IConsumer<string, TValue>;
|
||||||
|
|
||||||
|
var consumer= CreateConsumer<string, TValue> (groupId);
|
||||||
|
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<ConsumeResult<TKey, TValue>>();
|
||||||
|
var startTime = DateTime.UtcNow;
|
||||||
|
|
||||||
|
while (!cts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
var consumerKey = $"{groupId}_{string.Join("_", topics)}_{typeof(Ignore).Name}_{typeof(TValue).Name}";
|
|
||||||
var cts = new CancellationTokenSource();
|
|
||||||
|
|
||||||
var consumer = _consumerStore.GetOrAdd(consumerKey, _ =>
|
|
||||||
(
|
|
||||||
CreateConsumer<Ignore, TValue>(groupId),
|
|
||||||
cts
|
|
||||||
)).Consumer as IConsumer<Ignore, TValue>;
|
|
||||||
|
|
||||||
consumer!.Subscribe(topics);
|
|
||||||
|
|
||||||
var timeout = batchTimeout ?? TimeSpan.FromSeconds(5); // 默认超时时间调整为5秒
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
var messages = new List<(TValue Value, TopicPartitionOffset Offset)>();
|
// 非阻塞快速累积消息
|
||||||
var startTime = DateTime.UtcNow;
|
while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout)
|
||||||
|
|
||||||
while (!cts.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
try
|
var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
{
|
{
|
||||||
// 非阻塞快速累积消息
|
if (result.IsPartitionEOF)
|
||||||
while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout)
|
|
||||||
{
|
{
|
||||||
var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用
|
//_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
||||||
|
await Task.Delay(10, cts.Token);
|
||||||
if (result != null)
|
|
||||||
{
|
|
||||||
if (result.IsPartitionEOF)
|
|
||||||
{
|
|
||||||
//_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
|
||||||
await Task.Delay(DelayTime, cts.Token);
|
|
||||||
}
|
|
||||||
else if (result.Message.Value != null)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
messages.Add((result.Message.Value, result.TopicPartitionOffset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 无消息时短暂等待
|
|
||||||
await Task.Delay(DelayTime, cts.Token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (result.Message.Value != null)
|
||||||
// 处理批次
|
|
||||||
if (messages.Count > 0)
|
|
||||||
{
|
{
|
||||||
bool success = await messageBatchHandler(messages.Select(m => m.Value).ToList());
|
if (_kafkaOptionConfig.EnableFilter)
|
||||||
if (success)
|
|
||||||
{
|
{
|
||||||
var offsetsByPartition = new Dictionary<TopicPartition, long>();
|
var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) } };
|
||||||
foreach (var msg in messages)
|
// 检查 Header 是否符合条件
|
||||||
|
if (!headersFilter.Match(result.Message.Headers))
|
||||||
{
|
{
|
||||||
var tp = msg.Offset.TopicPartition;
|
//consumer.Commit(result); // 提交偏移量
|
||||||
var offset = msg.Offset.Offset;
|
// 跳过消息
|
||||||
if (!offsetsByPartition.TryGetValue(tp, out var currentMax) || offset > currentMax)
|
continue;
|
||||||
{
|
|
||||||
offsetsByPartition[tp] = offset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var offsetsToCommit = offsetsByPartition
|
|
||||||
.Select(kv => new TopicPartitionOffset(kv.Key, new Offset(kv.Value + 1)))
|
|
||||||
.ToList();
|
|
||||||
consumer.Commit(offsetsToCommit);
|
|
||||||
}
|
}
|
||||||
messages.Clear();
|
messages.Add((result.Message.Value, result.TopicPartitionOffset));
|
||||||
|
//messages.Add(result.Message.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
startTime = DateTime.UtcNow;
|
|
||||||
}
|
}
|
||||||
catch (ConsumeException ex) when (KafkaPollyPipeline.IsRecoverableError(ex))
|
else
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, $"消息消费失败: {ex.Error.Reason}");
|
// 无消息时短暂等待
|
||||||
throw; // 抛出异常,以便重试
|
await Task.Delay(10, cts.Token);
|
||||||
}
|
|
||||||
catch (KafkaException ex) when (KafkaPollyPipeline.IsRecoverableError(ex))
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"{string.Join("、", topics)} 消息消费失败: {ex.Error.Reason}");
|
|
||||||
throw; // 抛出异常,以便重试
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
//ignore
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "处理批量消息时发生未知错误");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, cts.Token);
|
|
||||||
|
|
||||||
await Task.CompletedTask;
|
// 处理批次
|
||||||
});
|
if (messages.Count > 0)
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
bool success = await messageBatchHandler(messages.Select(m => m.Value).ToList());
|
||||||
{
|
if (success)
|
||||||
|
{
|
||||||
|
var offsetsByPartition = new Dictionary<TopicPartition, long>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw;
|
var offsetsToCommit = offsetsByPartition
|
||||||
}
|
.Select(kv => new TopicPartitionOffset(kv.Key, new Offset(kv.Value + 1)))
|
||||||
|
.ToList();
|
||||||
|
consumer.Commit(offsetsToCommit);
|
||||||
|
}
|
||||||
|
messages.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
catch (ConsumeException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"消息消费失败: {ex.Error.Reason}");
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// 任务取消,正常退出
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "处理批量消息时发生未知错误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, cts.Token);
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -625,22 +518,14 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TKey"></typeparam>
|
/// <typeparam name="TKey"></typeparam>
|
||||||
/// <typeparam name="TValue"></typeparam>
|
/// <typeparam name="TValue"></typeparam>
|
||||||
public void Unsubscribe<TKey, TValue>(string[] topics, string? groupId) where TKey : notnull where TValue : class
|
public void Unsubscribe<TKey, TValue>() where TKey : notnull where TValue : class
|
||||||
{
|
{
|
||||||
try
|
var consumerKey = typeof((TKey, TValue));
|
||||||
|
if (_consumerStore.TryRemove(consumerKey, out var entry))
|
||||||
{
|
{
|
||||||
var consumerKey = $"{groupId}_{string.Join("_", topics)}_{typeof(TKey).Name}_{typeof(TValue).Name}";
|
entry.CTS.Cancel();
|
||||||
if (_consumerStore.TryRemove(consumerKey, out var entry))
|
(entry.Consumer as IDisposable)?.Dispose();
|
||||||
{
|
entry.CTS.Dispose();
|
||||||
entry.CTS.Cancel();
|
|
||||||
(entry.Consumer as IDisposable)?.Dispose();
|
|
||||||
entry.CTS.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,5 +46,5 @@ public interface IConsumerService
|
|||||||
string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null, TimeSpan? consumeTimeout = null)
|
string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null, TimeSpan? consumeTimeout = null)
|
||||||
where TValue : class;
|
where TValue : class;
|
||||||
|
|
||||||
void Unsubscribe<TKey, TValue>(string[] topics, string groupId) where TKey : notnull where TValue : class;
|
void Unsubscribe<TKey, TValue>() where TKey : notnull where TValue : class;
|
||||||
}
|
}
|
||||||
@ -9,6 +9,11 @@ public class KafkaOptionConfig
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string BootstrapServers { get; set; } = null!;
|
public string BootstrapServers { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 服务器标识
|
||||||
|
/// </summary>
|
||||||
|
public string ServerTagName { get; set; } = "KafkaFilterKey";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// kafka主题副本数量
|
/// kafka主题副本数量
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -49,4 +54,8 @@ public class KafkaOptionConfig
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? SaslPassword { get; set; }
|
public string? SaslPassword { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 首次采集时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? FirstCollectionTime { get; set; }
|
||||||
}
|
}
|
||||||
@ -1,105 +0,0 @@
|
|||||||
using Confluent.Kafka;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Polly;
|
|
||||||
using Polly.CircuitBreaker;
|
|
||||||
using Polly.Retry;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Kafka.Internal
|
|
||||||
{
|
|
||||||
public class KafkaPollyPipeline
|
|
||||||
{
|
|
||||||
|
|
||||||
private readonly ILogger<KafkaPollyPipeline> _logger;
|
|
||||||
public KafkaPollyPipeline(ILogger<KafkaPollyPipeline> logger)
|
|
||||||
{
|
|
||||||
_logger= logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 判断是否可恢复的异常
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ex"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static bool IsRecoverableError(Exception ex)
|
|
||||||
{
|
|
||||||
var errorList= new List<ErrorCode>
|
|
||||||
{
|
|
||||||
ErrorCode.GroupLoadInProgress,
|
|
||||||
ErrorCode.Local_Retry,
|
|
||||||
ErrorCode.Local_MaxPollExceeded,
|
|
||||||
ErrorCode.RequestTimedOut,
|
|
||||||
ErrorCode.LeaderNotAvailable,
|
|
||||||
ErrorCode.NotLeaderForPartition,
|
|
||||||
ErrorCode.RebalanceInProgress,
|
|
||||||
ErrorCode.NotCoordinatorForGroup,
|
|
||||||
ErrorCode.NetworkException,
|
|
||||||
ErrorCode.GroupCoordinatorNotAvailable,
|
|
||||||
ErrorCode.InvalidGroupId,
|
|
||||||
ErrorCode.IllegalGeneration
|
|
||||||
};
|
|
||||||
return ex switch
|
|
||||||
{
|
|
||||||
ConsumeException kafkaEx => errorList.Contains(kafkaEx.Error.Code),
|
|
||||||
KafkaException kafkaEx =>kafkaEx.Error.IsFatal && errorList.Contains(kafkaEx.Error.Code),
|
|
||||||
_ => false
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 创建重试 + 断路器
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Confluent.Kafka" Version="2.9.0" />
|
<PackageReference Include="Confluent.Kafka" Version="2.9.0" />
|
||||||
<PackageReference Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
|
|
||||||
<PackageReference Include="Polly.Core" Version="8.5.2" />
|
|
||||||
<PackageReference Include="Volo.Abp.AspNetCore" Version="8.3.3" />
|
<PackageReference Include="Volo.Abp.AspNetCore" Version="8.3.3" />
|
||||||
<PackageReference Include="Volo.Abp.Core" Version="8.3.3" />
|
<PackageReference Include="Volo.Abp.Core" Version="8.3.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@ -21,10 +21,6 @@ namespace JiShe.CollectBus.Kafka
|
|||||||
public static class KafkaSubscribeExtensions
|
public static class KafkaSubscribeExtensions
|
||||||
{
|
{
|
||||||
|
|
||||||
private static long _threadCount = 0;
|
|
||||||
private static long _topicSubscribeCount = 0;
|
|
||||||
private static long _threadStartCount = 0;
|
|
||||||
|
|
||||||
public static void UseInitKafkaTopic(this IServiceProvider provider)
|
public static void UseInitKafkaTopic(this IServiceProvider provider)
|
||||||
{
|
{
|
||||||
//初始化主题信息
|
//初始化主题信息
|
||||||
@ -50,12 +46,12 @@ namespace JiShe.CollectBus.Kafka
|
|||||||
lifetime.ApplicationStarted.Register(() =>
|
lifetime.ApplicationStarted.Register(() =>
|
||||||
{
|
{
|
||||||
var logger = provider.GetRequiredService<ILogger<CollectBusKafkaModule>>();
|
var logger = provider.GetRequiredService<ILogger<CollectBusKafkaModule>>();
|
||||||
//var threadCount = 0;
|
var threadCount = 0;
|
||||||
//var topicCount = 0;
|
var topicCount = 0;
|
||||||
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
|
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
|
||||||
if (string.IsNullOrWhiteSpace(assemblyPath))
|
if (string.IsNullOrWhiteSpace(assemblyPath))
|
||||||
{
|
{
|
||||||
logger.LogWarning($"kafka订阅未能找到程序路径");
|
logger.LogInformation($"kafka订阅未能找到程序路径");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var dllFiles = Directory.GetFiles(assemblyPath, "*.dll");
|
var dllFiles = Directory.GetFiles(assemblyPath, "*.dll");
|
||||||
@ -73,35 +69,21 @@ namespace JiShe.CollectBus.Kafka
|
|||||||
if (subscribeTypes.Count == 0)
|
if (subscribeTypes.Count == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// 并行处理
|
foreach (var subscribeType in subscribeTypes)
|
||||||
Parallel.ForEach(subscribeTypes, subscribeType =>
|
|
||||||
{
|
{
|
||||||
var subscribes = provider.GetServices(subscribeType).ToList();
|
var subscribes = provider.GetServices(subscribeType).ToList();
|
||||||
Parallel.ForEach(subscribes,subscribe =>
|
subscribes.ForEach(subscribe =>
|
||||||
{
|
{
|
||||||
if (subscribe != null)
|
if (subscribe != null)
|
||||||
{
|
{
|
||||||
Tuple<int, int> tuple = BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value);
|
Tuple<int, int> tuple = BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value);
|
||||||
//threadCount += tuple.Item1;
|
threadCount += tuple.Item1;
|
||||||
//topicCount += tuple.Item2;
|
topicCount += tuple.Item2;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
//foreach (var subscribeType in subscribeTypes)
|
|
||||||
//{
|
|
||||||
// var subscribes = provider.GetServices(subscribeType).ToList();
|
|
||||||
// subscribes.ForEach(subscribe =>
|
|
||||||
// {
|
|
||||||
// if (subscribe != null)
|
|
||||||
// {
|
|
||||||
// Tuple<int, int> tuple = BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value);
|
|
||||||
// threadCount += tuple.Item1;
|
|
||||||
// topicCount += tuple.Item2;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
logger.LogWarning($"kafka订阅主题:{_topicSubscribeCount}数,共启动:{_threadCount}线程");
|
logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,50 +135,24 @@ namespace JiShe.CollectBus.Kafka
|
|||||||
//var configuration = provider.GetRequiredService<IConfiguration>();
|
//var configuration = provider.GetRequiredService<IConfiguration>();
|
||||||
int threadCount = 0;
|
int threadCount = 0;
|
||||||
|
|
||||||
Parallel.ForEach(subscribedMethods, sub =>
|
foreach (var sub in subscribedMethods)
|
||||||
{
|
{
|
||||||
Interlocked.Increment(ref _topicSubscribeCount);
|
int partitionCount = 3;// kafkaOptionConfig.NumPartitions;
|
||||||
int partitionCount = sub.Attribute!.TaskCount == -1 ? 3 : sub.Attribute!.TaskCount;// kafkaOptionConfig.NumPartitions;
|
#if DEBUG
|
||||||
var adminClientService = provider.GetRequiredService<IAdminClientService>();
|
var adminClientService = provider.GetRequiredService<IAdminClientService>();
|
||||||
|
|
||||||
int topicCount = adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic);
|
int topicCount = adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic);
|
||||||
|
|
||||||
//int partitionCount = sub.Attribute!.TaskCount == -1 ? topicCount : sub.Attribute!.TaskCount;// kafkaOptionConfig.NumPartitions;
|
|
||||||
|
|
||||||
partitionCount = partitionCount > topicCount ? topicCount : partitionCount;
|
partitionCount = partitionCount > topicCount ? topicCount : partitionCount;
|
||||||
//partitionCount = sub.Attribute!.TaskCount == -1 ? adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic) : sub.Attribute!.TaskCount;
|
#endif
|
||||||
|
//int partitionCount = sub.Attribute!.TaskCount==-1?adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic) : sub.Attribute!.TaskCount;
|
||||||
if (partitionCount <= 0)
|
if (partitionCount <= 0)
|
||||||
partitionCount = 1;
|
partitionCount = 1;
|
||||||
Parallel.For(0,partitionCount, async (partition) =>
|
for (int i = 0; i < partitionCount; i++)
|
||||||
{
|
{
|
||||||
Interlocked.Increment(ref _threadCount);
|
//if (sub.Attribute!.Topic == ProtocolConst.SubscriberLoginReceivedEventName)
|
||||||
//Task.Run(() => StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe, logger));
|
Task.Run(() => StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe, logger));
|
||||||
//threadCount++;
|
threadCount++;
|
||||||
await StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe, logger);
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
//foreach (var sub in subscribedMethods)
|
|
||||||
//{
|
|
||||||
// //int partitionCount = sub.Attribute!.TaskCount==-1?3: sub.Attribute!.TaskCount;// kafkaOptionConfig.NumPartitions;
|
|
||||||
// var adminClientService = provider.GetRequiredService<IAdminClientService>();
|
|
||||||
|
|
||||||
// int topicCount = adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic);
|
|
||||||
|
|
||||||
// int partitionCount = sub.Attribute!.TaskCount == -1 ? topicCount : sub.Attribute!.TaskCount;// kafkaOptionConfig.NumPartitions;
|
|
||||||
|
|
||||||
// partitionCount = partitionCount > topicCount ? topicCount : partitionCount;
|
|
||||||
// //partitionCount = sub.Attribute!.TaskCount == -1 ? adminClientService.GetTopicPartitionsNum(sub.Attribute!.Topic) : sub.Attribute!.TaskCount;
|
|
||||||
// if (partitionCount <= 0)
|
|
||||||
// partitionCount = 1;
|
|
||||||
// for (int i = 0; i < partitionCount; i++)
|
|
||||||
// {
|
|
||||||
// //if (sub.Attribute!.Topic == ProtocolConst.SubscriberLoginReceivedEventName)
|
|
||||||
// Task.Run(() => StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe, logger));
|
|
||||||
// threadCount++;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
return Tuple.Create(threadCount, subscribedMethods.Length);
|
return Tuple.Create(threadCount, subscribedMethods.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,8 +165,6 @@ namespace JiShe.CollectBus.Kafka
|
|||||||
|
|
||||||
if (attr.EnableBatch)
|
if (attr.EnableBatch)
|
||||||
{
|
{
|
||||||
Interlocked.Increment(ref _threadStartCount);
|
|
||||||
logger.LogInformation($"kafka开启线程消费:{_threadStartCount}");
|
|
||||||
await consumerService.SubscribeBatchAsync<dynamic>(attr.Topic, async (message) =>
|
await consumerService.SubscribeBatchAsync<dynamic>(attr.Topic, async (message) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -226,18 +180,11 @@ namespace JiShe.CollectBus.Kafka
|
|||||||
// 处理消费错误
|
// 处理消费错误
|
||||||
logger.LogError($"kafka批量消费异常:{ex.Message}");
|
logger.LogError($"kafka批量消费异常:{ex.Message}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// 处理消费错误
|
|
||||||
logger.LogError($"kafka批量消费异常:{ex.Message}");
|
|
||||||
}
|
|
||||||
return await Task.FromResult(false);
|
return await Task.FromResult(false);
|
||||||
}, attr.GroupId, attr.BatchSize, attr.BatchTimeout);
|
}, attr.GroupId, attr.BatchSize, attr.BatchTimeout);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Interlocked.Increment(ref _threadStartCount);
|
|
||||||
logger.LogInformation($"kafka开启线程消费:{_threadStartCount}");
|
|
||||||
await consumerService.SubscribeAsync<dynamic>(attr.Topic, async (message) =>
|
await consumerService.SubscribeAsync<dynamic>(attr.Topic, async (message) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -253,11 +200,6 @@ namespace JiShe.CollectBus.Kafka
|
|||||||
// 处理消费错误
|
// 处理消费错误
|
||||||
logger.LogError($"kafka消费异常:{ex.Message}");
|
logger.LogError($"kafka消费异常:{ex.Message}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// 处理消费错误
|
|
||||||
logger.LogError($"kafka批量消费异常:{ex.Message}");
|
|
||||||
}
|
|
||||||
return await Task.FromResult(false);
|
return await Task.FromResult(false);
|
||||||
}, attr.GroupId);
|
}, attr.GroupId);
|
||||||
}
|
}
|
||||||
@ -270,133 +212,125 @@ namespace JiShe.CollectBus.Kafka
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static async Task<bool> ProcessMessageAsync(List<dynamic> messages, MethodInfo method, object subscribe)
|
private static async Task<bool> ProcessMessageAsync(List<dynamic> messages, MethodInfo method, object subscribe)
|
||||||
{
|
{
|
||||||
try
|
var parameters = method.GetParameters();
|
||||||
|
bool isGenericTask = method.ReturnType.IsGenericType
|
||||||
|
&& method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>);
|
||||||
|
bool existParameters = parameters.Length > 0;
|
||||||
|
object[]? executeParameters = null;
|
||||||
|
|
||||||
|
if (existParameters)
|
||||||
{
|
{
|
||||||
var parameters = method.GetParameters();
|
IList? list = null;
|
||||||
bool isGenericTask = method.ReturnType.IsGenericType
|
Tuple<Type, Type?> tuple = method.GetParameterTypeInfo();
|
||||||
&& method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>);
|
bool isEnumerable = false;
|
||||||
bool existParameters = parameters.Length > 0;
|
if (tuple.Item2 != null)
|
||||||
object[]? executeParameters = null;
|
|
||||||
|
|
||||||
if (existParameters)
|
|
||||||
{
|
{
|
||||||
IList? list = null;
|
Type listType = typeof(List<>).MakeGenericType(tuple.Item2);
|
||||||
Tuple<Type, Type?> tuple = method.GetParameterTypeInfo();
|
list = (IList)Activator.CreateInstance(listType)!;
|
||||||
bool isEnumerable = false;
|
isEnumerable = tuple.Item2.IsConvertType();
|
||||||
if (tuple.Item2 != null)
|
}
|
||||||
{
|
else
|
||||||
Type listType = typeof(List<>).MakeGenericType(tuple.Item2);
|
{
|
||||||
list = (IList)Activator.CreateInstance(listType)!;
|
isEnumerable = tuple.Item1.IsConvertType();
|
||||||
isEnumerable = tuple.Item2.IsConvertType();
|
}
|
||||||
}
|
#region 暂时
|
||||||
else
|
//foreach (var msg in messages)
|
||||||
{
|
//{
|
||||||
isEnumerable = tuple.Item1.IsConvertType();
|
// if (tuple.Item2 != null)
|
||||||
}
|
// {
|
||||||
#region 暂时
|
// if (isEnumerable)
|
||||||
//foreach (var msg in messages)
|
// {
|
||||||
//{
|
// var parameterType = parameters[0].ParameterType;
|
||||||
// if (tuple.Item2 != null)
|
// var data=messages?.Serialize().Deserialize(parameterType);
|
||||||
// {
|
// messageObj = data!=null? new[] { data }:null;
|
||||||
// if (isEnumerable)
|
// break;
|
||||||
// {
|
// }
|
||||||
// var parameterType = parameters[0].ParameterType;
|
// else
|
||||||
// var data=messages?.Serialize().Deserialize(parameterType);
|
// {
|
||||||
// messageObj = data!=null? new[] { data }:null;
|
// // 集合类型
|
||||||
// break;
|
// var data = msg?.Serialize().Deserialize(tuple.Item2) /*isEnumerable ? Convert.ChangeType(msg, tuple.Item2) : msg?.Serialize().Deserialize(tuple.Item2)*/;
|
||||||
// }
|
// if (data != null)
|
||||||
// else
|
// list?.Add(data);
|
||||||
// {
|
// }
|
||||||
// // 集合类型
|
|
||||||
// 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
|
// else
|
||||||
// {
|
// {
|
||||||
// // (dynamic)Convert.ChangeType(msg, tuple.Item1)
|
// // (dynamic)Convert.ChangeType(msg, tuple.Item1)
|
||||||
// using (var stream = new MemoryStream(msg))
|
// using (var stream = new MemoryStream(msg))
|
||||||
// {
|
// {
|
||||||
// var data1= System.Text.Json.JsonSerializer.Deserialize(stream, tuple.Item1);
|
// 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);
|
// var data = isEnumerable ? System.Text.Json.JsonSerializer.Deserialize(msg, tuple.Item1): msg?.ToString()?.Deserialize(tuple.Item1);
|
||||||
// if (data != null)
|
// if (data != null)
|
||||||
// messageObj = new[] { data };
|
// messageObj = new[] { data };
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
//if (tuple.Item2 != null && list != null && list.Count > 0)
|
//if (tuple.Item2 != null && list != null && list.Count > 0)
|
||||||
//{
|
//{
|
||||||
// messageObj = new[] { list };
|
// messageObj = new[] { list };
|
||||||
//}
|
//}
|
||||||
#endregion
|
#endregion
|
||||||
var parameterDescriptors = method.GetParameters();
|
var parameterDescriptors = method.GetParameters();
|
||||||
executeParameters = new object?[parameterDescriptors.Length];
|
executeParameters = new object?[parameterDescriptors.Length];
|
||||||
for (var i = 0; i < parameterDescriptors.Length; i++)
|
for (var i = 0; i < parameterDescriptors.Length; i++)
|
||||||
|
{
|
||||||
|
foreach (var item in messages)
|
||||||
{
|
{
|
||||||
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
|
||||||
{
|
{
|
||||||
|
|
||||||
object? tempParameter = null;
|
var converter = TypeDescriptor.GetConverter(parameterDescriptor.ParameterType);
|
||||||
var parameterDescriptor = parameterDescriptors[i];
|
if (converter.CanConvertFrom(item.GetType()))
|
||||||
if (KafkaSerialization.IsJsonType(item))
|
|
||||||
{
|
{
|
||||||
tempParameter = KafkaSerialization.Deserialize(item, tuple.Item2 != null ? tuple.Item2 : parameterDescriptor.ParameterType);
|
tempParameter = converter.ConvertFrom(item);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (parameterDescriptor.ParameterType.IsInstanceOfType(item))
|
||||||
var converter = TypeDescriptor.GetConverter(parameterDescriptor.ParameterType);
|
tempParameter = item;
|
||||||
if (converter.CanConvertFrom(item.GetType()))
|
|
||||||
{
|
|
||||||
tempParameter = converter.ConvertFrom(item);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
tempParameter =Convert.ChangeType(item, parameterDescriptor.ParameterType);
|
||||||
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)
|
if (tuple.Item2 == null)
|
||||||
executeParameters[i] = list;
|
{
|
||||||
|
executeParameters[i] = tempParameter;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list.Add(tempParameter);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
if(list!=null && list.Count>0)
|
||||||
|
executeParameters[i] = list;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = method.Invoke(subscribe, executeParameters);
|
|
||||||
if (result is Task<ISubscribeAck> genericTask)
|
|
||||||
{
|
|
||||||
await genericTask.ConfigureAwait(false);
|
|
||||||
return genericTask.Result.Ack;
|
|
||||||
}
|
|
||||||
else if (result is Task nonGenericTask)
|
|
||||||
{
|
|
||||||
await nonGenericTask.ConfigureAwait(false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (result is ISubscribeAck ackResult)
|
|
||||||
{
|
|
||||||
return ackResult.Ack;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
var result = method.Invoke(subscribe, executeParameters);
|
||||||
|
if (result is Task<ISubscribeAck> genericTask)
|
||||||
{
|
{
|
||||||
|
await genericTask.ConfigureAwait(false);
|
||||||
throw;
|
return genericTask.Result.Ack;
|
||||||
}
|
}
|
||||||
|
else if (result is Task nonGenericTask)
|
||||||
|
{
|
||||||
|
await nonGenericTask.ConfigureAwait(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (result is ISubscribeAck ackResult)
|
||||||
|
{
|
||||||
|
return ackResult.Ack;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Confluent.Kafka;
|
using Confluent.Kafka;
|
||||||
using JiShe.CollectBus.Common;
|
|
||||||
using JiShe.CollectBus.Common.Helpers;
|
|
||||||
using JiShe.CollectBus.Kafka.Consumer;
|
using JiShe.CollectBus.Kafka.Consumer;
|
||||||
using JiShe.CollectBus.Kafka.Internal;
|
using JiShe.CollectBus.Kafka.Internal;
|
||||||
using JiShe.CollectBus.Kafka.Serialization;
|
using JiShe.CollectBus.Kafka.Serialization;
|
||||||
@ -26,19 +23,18 @@ namespace JiShe.CollectBus.Kafka.Producer
|
|||||||
private readonly ConcurrentDictionary<Type, object> _producerCache = new();
|
private readonly ConcurrentDictionary<Type, object> _producerCache = new();
|
||||||
private class KafkaProducer<TKey, TValue> where TKey : notnull where TValue : class { }
|
private class KafkaProducer<TKey, TValue> where TKey : notnull where TValue : class { }
|
||||||
private readonly KafkaOptionConfig _kafkaOptionConfig;
|
private readonly KafkaOptionConfig _kafkaOptionConfig;
|
||||||
private readonly ServerApplicationOptions _applicationOptions;
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ProducerService
|
/// ProducerService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="configuration"></param>
|
/// <param name="configuration"></param>
|
||||||
/// <param name="logger"></param>
|
/// <param name="logger"></param>
|
||||||
/// <param name="kafkaOptionConfig"></param>
|
/// <param name="kafkaOptionConfig"></param>
|
||||||
public ProducerService(IConfiguration configuration,ILogger<ProducerService> logger, IOptions<KafkaOptionConfig> kafkaOptionConfig, IOptions<ServerApplicationOptions> applicationOptions)
|
public ProducerService(IConfiguration configuration,ILogger<ProducerService> logger, IOptions<KafkaOptionConfig> kafkaOptionConfig)
|
||||||
{
|
{
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_kafkaOptionConfig = kafkaOptionConfig.Value;
|
_kafkaOptionConfig = kafkaOptionConfig.Value;
|
||||||
_applicationOptions = applicationOptions.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region private 私有方法
|
#region private 私有方法
|
||||||
@ -70,11 +66,10 @@ namespace JiShe.CollectBus.Kafka.Producer
|
|||||||
{
|
{
|
||||||
BootstrapServers = _kafkaOptionConfig.BootstrapServers,
|
BootstrapServers = _kafkaOptionConfig.BootstrapServers,
|
||||||
//AllowAutoCreateTopics = true,
|
//AllowAutoCreateTopics = true,
|
||||||
QueueBufferingMaxKbytes = 4_194_304, // 4_194_304 2_097_151 // 修改缓冲区最大为2GB,默认为1GB
|
QueueBufferingMaxKbytes = 2_097_151, // 修改缓冲区最大为2GB,默认为1GB
|
||||||
QueueBufferingMaxMessages = int.MaxValue, // 缓冲区消息条
|
|
||||||
CompressionType = CompressionType.Lz4, // 配置使用压缩算法LZ4,其他:gzip/snappy/zstd
|
CompressionType = CompressionType.Lz4, // 配置使用压缩算法LZ4,其他:gzip/snappy/zstd
|
||||||
BatchSize = 32_768, // 修改批次大小为32K
|
BatchSize = 32_768, // 修改批次大小为32K
|
||||||
LingerMs = 20, // 修改等待时间为20ms,默认为5ms
|
LingerMs = 20, // 修改等待时间为20ms
|
||||||
Acks = Acks.All, // 表明只有所有副本Broker都收到消息才算提交成功, 可以 Acks.Leader
|
Acks = Acks.All, // 表明只有所有副本Broker都收到消息才算提交成功, 可以 Acks.Leader
|
||||||
MessageSendMaxRetries = 50, // 消息发送失败最大重试50次
|
MessageSendMaxRetries = 50, // 消息发送失败最大重试50次
|
||||||
MessageTimeoutMs = 120000, // 消息发送超时时间为2分钟,设置值MessageTimeoutMs > LingerMs
|
MessageTimeoutMs = 120000, // 消息发送超时时间为2分钟,设置值MessageTimeoutMs > LingerMs
|
||||||
@ -117,25 +112,17 @@ namespace JiShe.CollectBus.Kafka.Producer
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task ProduceAsync<TKey, TValue>(string topic, TKey key, TValue value)where TKey : notnull where TValue : class
|
public async Task ProduceAsync<TKey, TValue>(string topic, TKey key, TValue value)where TKey : notnull where TValue : class
|
||||||
{
|
{
|
||||||
try
|
var typeKey = typeof(KafkaProducer<TKey, TValue>);
|
||||||
|
var producer = GetProducer<TKey, TValue>(typeKey);
|
||||||
|
var message = new Message<TKey, TValue>
|
||||||
{
|
{
|
||||||
var typeKey = typeof(KafkaProducer<TKey, TValue>);
|
Key = key,
|
||||||
var producer = GetProducer<TKey, TValue>(typeKey);
|
Value = value,
|
||||||
var message = new Message<TKey, TValue>
|
Headers = new Headers{
|
||||||
{
|
{ "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) }
|
||||||
Key = key,
|
|
||||||
Value = value,
|
|
||||||
Headers = new Headers{
|
|
||||||
{ "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
await producer.ProduceAsync(topic, message);
|
await producer.ProduceAsync(topic, message);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -147,24 +134,17 @@ namespace JiShe.CollectBus.Kafka.Producer
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task ProduceAsync<TValue>(string topic, TValue value) where TValue : class
|
public async Task ProduceAsync<TValue>(string topic, TValue value) where TValue : class
|
||||||
{
|
{
|
||||||
try
|
var typeKey = typeof(KafkaProducer<string, TValue>);
|
||||||
|
var producer = GetProducer<Null, TValue>(typeKey);
|
||||||
|
var message = new Message<Null, TValue>
|
||||||
{
|
{
|
||||||
var typeKey = typeof(KafkaProducer<string, TValue>);
|
//Key= _kafkaOptionConfig.ServerTagName,
|
||||||
var producer = GetProducer<Null, TValue>(typeKey);
|
Value = value,
|
||||||
var message = new Message<Null, TValue>
|
Headers = new Headers{
|
||||||
{
|
{ "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) }
|
||||||
Value = value,
|
|
||||||
Headers = new Headers{
|
|
||||||
{ "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
await producer.ProduceAsync(topic, message);
|
await producer.ProduceAsync(topic, message);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -180,34 +160,26 @@ namespace JiShe.CollectBus.Kafka.Producer
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task ProduceAsync<TKey, TValue>(string topic,TKey key,TValue value,int? partition=null, Action<DeliveryReport<TKey, TValue>>? deliveryHandler = null)where TKey : notnull where TValue : class
|
public async Task ProduceAsync<TKey, TValue>(string topic,TKey key,TValue value,int? partition=null, Action<DeliveryReport<TKey, TValue>>? deliveryHandler = null)where TKey : notnull where TValue : class
|
||||||
{
|
{
|
||||||
try
|
var message = new Message<TKey, TValue>
|
||||||
{
|
{
|
||||||
var message = new Message<TKey, TValue>
|
Key = key,
|
||||||
{
|
Value = value,
|
||||||
Key = key,
|
Headers = new Headers{
|
||||||
Value = value,
|
{ "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) }
|
||||||
Headers = new Headers{
|
|
||||||
{ "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var typeKey = typeof(KafkaProducer<TKey, TValue>);
|
var typeKey = typeof(KafkaProducer<TKey, TValue>);
|
||||||
var producer = GetProducer<TKey, TValue>(typeKey);
|
var producer = GetProducer<TKey, TValue>(typeKey);
|
||||||
if (partition.HasValue)
|
if (partition.HasValue)
|
||||||
{
|
|
||||||
var topicPartition = new TopicPartition(topic, new Partition(partition.Value));
|
|
||||||
producer.Produce(topicPartition, message, deliveryHandler);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
producer.Produce(topic, message, deliveryHandler);
|
|
||||||
}
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
|
var topicPartition = new TopicPartition(topic, partition.Value);
|
||||||
throw;
|
producer.Produce(topicPartition, message, deliveryHandler);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
producer.Produce(topic, message, deliveryHandler);
|
||||||
|
}
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,34 +195,26 @@ namespace JiShe.CollectBus.Kafka.Producer
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task ProduceAsync<TValue>(string topic, TValue value, int? partition=null, Action<DeliveryReport<Null, TValue>>? deliveryHandler = null) where TValue : class
|
public async Task ProduceAsync<TValue>(string topic, TValue value, int? partition=null, Action<DeliveryReport<Null, TValue>>? deliveryHandler = null) where TValue : class
|
||||||
{
|
{
|
||||||
try
|
var message = new Message<Null, TValue>
|
||||||
{
|
{
|
||||||
var message = new Message<Null, TValue>
|
//Key = _kafkaOptionConfig.ServerTagName,
|
||||||
{
|
Value = value,
|
||||||
Value = value,
|
Headers = new Headers{
|
||||||
Headers = new Headers{
|
{ "route-key", Encoding.UTF8.GetBytes(_kafkaOptionConfig.ServerTagName) }
|
||||||
{ "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var typeKey = typeof(KafkaProducer<Null, TValue>);
|
var typeKey = typeof(KafkaProducer<Null, TValue>);
|
||||||
var producer = GetProducer<Null, TValue>(typeKey);
|
var producer = GetProducer<Null, TValue>(typeKey);
|
||||||
if (partition.HasValue)
|
if (partition.HasValue)
|
||||||
{
|
|
||||||
var topicPartition = new TopicPartition(topic, new Partition(partition.Value));
|
|
||||||
//_logger.LogError($"push消息:{topic}-{partition.Value}");
|
|
||||||
producer.Produce(topicPartition, message, deliveryHandler);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
producer.Produce(topic, message, deliveryHandler);
|
|
||||||
}
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
|
var topicPartition = new TopicPartition(topic, partition.Value);
|
||||||
throw;
|
producer.Produce(topicPartition, message, deliveryHandler);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
producer.Produce(topic, message, deliveryHandler);
|
||||||
|
}
|
||||||
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@ -19,7 +19,6 @@ namespace JiShe.CollectBus.Kafka.Serialization
|
|||||||
{
|
{
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||||
WriteIndented = false,// 设置格式化输出
|
WriteIndented = false,// 设置格式化输出
|
||||||
IncludeFields = true,// 允许反序列化到非公共 setter 和字段
|
|
||||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,// 允许特殊字符
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,// 允许特殊字符
|
||||||
IgnoreReadOnlyFields = true,
|
IgnoreReadOnlyFields = true,
|
||||||
IgnoreReadOnlyProperties = true,
|
IgnoreReadOnlyProperties = true,
|
||||||
@ -54,7 +53,7 @@ namespace JiShe.CollectBus.Kafka.Serialization
|
|||||||
{
|
{
|
||||||
if (data.IsEmpty)
|
if (data.IsEmpty)
|
||||||
return default;
|
return default;
|
||||||
return JsonSerializer.Deserialize<T>(data, _options)!;
|
return JsonSerializer.Deserialize<T>(data, _options)!;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -103,37 +102,24 @@ namespace JiShe.CollectBus.Kafka.Serialization
|
|||||||
}
|
}
|
||||||
public static object? Deserialize(object value, Type valueType)
|
public static object? Deserialize(object value, Type valueType)
|
||||||
{
|
{
|
||||||
try
|
var _jsonSerializerOptions = new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
var _jsonSerializerOptions = new JsonSerializerOptions
|
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||||
{
|
WriteIndented = false,// 设置格式化输出
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,// 允许特殊字符
|
||||||
WriteIndented = false,// 设置格式化输出
|
IgnoreReadOnlyFields = true,
|
||||||
IncludeFields = true,// 允许反序列化到非公共 setter 和字段
|
IgnoreReadOnlyProperties = true,
|
||||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,// 允许特殊字符
|
NumberHandling = JsonNumberHandling.AllowReadingFromString, // 允许数字字符串
|
||||||
IgnoreReadOnlyFields = true,
|
AllowTrailingCommas = true, // 忽略尾随逗号
|
||||||
IgnoreReadOnlyProperties = true,
|
ReadCommentHandling = JsonCommentHandling.Skip, // 忽略注释
|
||||||
NumberHandling = JsonNumberHandling.AllowReadingFromString, // 允许数字字符串
|
PropertyNameCaseInsensitive = true, // 属性名称大小写不敏感
|
||||||
AllowTrailingCommas = true, // 忽略尾随逗号
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // 属性名称使用驼峰命名规则
|
||||||
ReadCommentHandling = JsonCommentHandling.Skip, // 忽略注释
|
Converters = { new DateTimeJsonConverter() } // 注册你的自定义转换器,
|
||||||
PropertyNameCaseInsensitive = true, // 属性名称大小写不敏感
|
};
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // 属性名称使用驼峰命名规则
|
|
||||||
Converters = { new DateTimeJsonConverter() } // 注册你的自定义转换器,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (value is JsonElement jsonElement)
|
if (value is JsonElement jsonElement) return jsonElement.Deserialize(valueType, _jsonSerializerOptions);
|
||||||
{
|
|
||||||
//return jsonElement.Deserialize(valueType, _jsonSerializerOptions);
|
|
||||||
return JsonSerializer.Deserialize(jsonElement, valueType, _jsonSerializerOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
throw new NotSupportedException("Type is not of type JsonElement");
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,6 @@ using JiShe.CollectBus.IotSystems.MessageIssueds;
|
|||||||
using Volo.Abp.Data;
|
using Volo.Abp.Data;
|
||||||
using Volo.Abp.MongoDB;
|
using Volo.Abp.MongoDB;
|
||||||
using Volo.Abp.MultiTenancy;
|
using Volo.Abp.MultiTenancy;
|
||||||
using JiShe.CollectBus.IotSystems.LogRecord;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.MongoDB;
|
namespace JiShe.CollectBus.MongoDB;
|
||||||
|
|
||||||
@ -34,6 +33,7 @@ public class CollectBusMongoDbContext : AbpMongoDbContext, ICollectBusMongoDbCon
|
|||||||
public IMongoCollection<MessageIssued> MessageIssueds => Collection<MessageIssued>();
|
public IMongoCollection<MessageIssued> MessageIssueds => Collection<MessageIssued>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected override void CreateModel(IMongoModelBuilder modelBuilder)
|
protected override void CreateModel(IMongoModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
//modelBuilder.Entity<MeterReadingRecords>(builder =>
|
//modelBuilder.Entity<MeterReadingRecords>(builder =>
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
using JiShe.CollectBus.IotSystems.LogRecord;
|
using JiShe.CollectBus.IotSystems.MeterReadingRecords;
|
||||||
using JiShe.CollectBus.IotSystems.MeterReadingRecords;
|
|
||||||
using JiShe.CollectBus.Repository;
|
using JiShe.CollectBus.Repository;
|
||||||
using JiShe.CollectBus.Repository.LogRecord;
|
|
||||||
using JiShe.CollectBus.Repository.MeterReadingRecord;
|
using JiShe.CollectBus.Repository.MeterReadingRecord;
|
||||||
using JiShe.CollectBus.ShardingStrategy;
|
using JiShe.CollectBus.ShardingStrategy;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@ -37,14 +35,10 @@ public class CollectBusMongoDbModule : AbpModule
|
|||||||
typeof(IShardingStrategy<>),
|
typeof(IShardingStrategy<>),
|
||||||
typeof(DayShardingStrategy<>));
|
typeof(DayShardingStrategy<>));
|
||||||
|
|
||||||
|
|
||||||
context.Services.AddTransient(typeof(HourShardingStrategy<>));
|
|
||||||
|
|
||||||
//// 分表策略仓储 替换默认仓储
|
//// 分表策略仓储 替换默认仓储
|
||||||
//options.AddRepository<MeterReadingRecords, MeterReadingRecordRepository>();
|
//options.AddRepository<MeterReadingRecords, MeterReadingRecordRepository>();
|
||||||
|
|
||||||
options.AddRepository<LogRecords, LogRecordRepository>();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
context.Services.AddAlwaysDisableUnitOfWorkTransaction();
|
context.Services.AddAlwaysDisableUnitOfWorkTransaction();
|
||||||
Configure<AbpUnitOfWorkDefaultOptions>(options =>
|
Configure<AbpUnitOfWorkDefaultOptions>(options =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,57 +0,0 @@
|
|||||||
using JiShe.CollectBus.IotSystems.LogRecord;
|
|
||||||
using JiShe.CollectBus.IotSystems.MeterReadingRecords;
|
|
||||||
using MongoDB.Driver;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Volo.Abp.Domain.Repositories;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Repository.LogRecord
|
|
||||||
{
|
|
||||||
public interface ILogRecordRepository : IRepository<LogRecords, Guid>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 批量插入
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entities"></param>
|
|
||||||
/// <param name="dateTime"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task InsertManyAsync(List<LogRecords> entities,
|
|
||||||
DateTime? dateTime);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 单个插入
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity"></param>
|
|
||||||
/// <param name="dateTime"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task<LogRecords> InsertAsync(LogRecords entity, DateTime? dateTime);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 单条更新
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filter">过滤条件,示例:Builders<LogRecords>.Filter.Eq(x => x.Id, filter.Id)</param>
|
|
||||||
/// <param name="update">包含待更新的内容,示例:Builders<LogRecords>.Update.Set(x => x.SendHexMessage, SendHexMessage).Set(x => x.MessageId, MessageId)</param>
|
|
||||||
/// <param name="entity">数据实体,用于获取对应的分片库</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task<LogRecords> UpdateOneAsync(FilterDefinition<LogRecords> filter, UpdateDefinition<LogRecords> update, LogRecords entity);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 单个获取
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity"></param>
|
|
||||||
/// <param name="dateTime"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task<LogRecords> FirOrDefaultAsync(LogRecords entity, DateTime dateTime);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 多集合数据查询
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startTime"></param>
|
|
||||||
/// <param name="endTime"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task<List<LogRecords>> ParallelQueryAsync(DateTime startTime, DateTime endTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,166 +0,0 @@
|
|||||||
using JiShe.CollectBus.IotSystems.LogRecord;
|
|
||||||
using JiShe.CollectBus.IotSystems.MeterReadingRecords;
|
|
||||||
using JiShe.CollectBus.MongoDB;
|
|
||||||
using JiShe.CollectBus.Repository.MeterReadingRecord;
|
|
||||||
using JiShe.CollectBus.ShardingStrategy;
|
|
||||||
using MongoDB.Driver;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Volo.Abp.DependencyInjection;
|
|
||||||
using Volo.Abp.Domain.Repositories.MongoDB;
|
|
||||||
using Volo.Abp.MongoDB;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Repository.LogRecord
|
|
||||||
{
|
|
||||||
public class LogRecordRepository : MongoDbRepository<CollectBusMongoDbContext, LogRecords, Guid>, ILogRecordRepository
|
|
||||||
{
|
|
||||||
|
|
||||||
private readonly HourShardingStrategy<LogRecords> _hourShardingStrategy;
|
|
||||||
private readonly IMongoDbContextProvider<CollectBusMongoDbContext> _dbContextProvider;
|
|
||||||
|
|
||||||
public LogRecordRepository(
|
|
||||||
IMongoDbContextProvider<CollectBusMongoDbContext> dbContextProvider,
|
|
||||||
HourShardingStrategy<LogRecords> hourShardingStrategy
|
|
||||||
)
|
|
||||||
: base(dbContextProvider)
|
|
||||||
{
|
|
||||||
_dbContextProvider = dbContextProvider;
|
|
||||||
_hourShardingStrategy = hourShardingStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 批量插入
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entities"></param>
|
|
||||||
/// <param name="cancellationToken"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override async Task<IEnumerable<LogRecords>> InsertManyAsync(IEnumerable<LogRecords> entities, bool autoSave = false, CancellationToken cancellationToken = default(CancellationToken))
|
|
||||||
{
|
|
||||||
var collection = await GetShardedCollection(DateTime.Now);
|
|
||||||
await collection.InsertManyAsync(entities);
|
|
||||||
|
|
||||||
return entities;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 批量插入
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entities"></param>
|
|
||||||
/// <param name="dateTime"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task InsertManyAsync(List<LogRecords> entities, DateTime? dateTime)
|
|
||||||
{
|
|
||||||
var collection = await GetShardedCollection(dateTime);
|
|
||||||
await collection.InsertManyAsync(entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 单条插入
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity"></param>
|
|
||||||
/// <param name="cancellationToken"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override async Task<LogRecords> InsertAsync(LogRecords entity, bool autoSave = false, CancellationToken cancellationToken = default(CancellationToken))
|
|
||||||
{
|
|
||||||
var collection = await GetShardedCollection(DateTime.Now);
|
|
||||||
await collection.InsertOneAsync(entity);
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 单条插入
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity"></param>
|
|
||||||
/// <param name="dateTime"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<LogRecords> InsertAsync(LogRecords entity, DateTime? dateTime)
|
|
||||||
{
|
|
||||||
var collection = await GetShardedCollection(dateTime);
|
|
||||||
await collection.InsertOneAsync(entity);
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 单条更新
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filter">过滤条件,示例:Builders<LogRecords>.Filter.Eq(x => x.Id, filter.Id)</param>
|
|
||||||
/// <param name="update">包含待更新的内容,示例:Builders<LogRecords>.Update.Set(x => x.SendHexMessage, SendHexMessage).Set(x => x.MessageId, MessageId)</param>
|
|
||||||
/// <param name="entity">数据实体,用于获取对应的分片库</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<LogRecords> UpdateOneAsync(FilterDefinition<LogRecords> filter, UpdateDefinition<LogRecords> update, LogRecords entity)
|
|
||||||
{
|
|
||||||
var collection = await GetShardedCollection(entity.CreationTime);
|
|
||||||
|
|
||||||
await collection.UpdateOneAsync(filter, update);
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 单个获取
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity"></param>
|
|
||||||
/// <param name="dateTime"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
/// <exception cref="NotImplementedException"></exception>
|
|
||||||
public async Task<LogRecords> FirOrDefaultAsync(LogRecords entity, DateTime dateTime)
|
|
||||||
{
|
|
||||||
var collection = await GetShardedCollection(dateTime);
|
|
||||||
var query = await collection.FindAsync(d => d.CreationTime == dateTime && d.AFN == entity.AFN && d.Fn == entity.Fn && d.Code == entity.Code);
|
|
||||||
return await query.FirstOrDefaultAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 多集合数据查询
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startTime"></param>
|
|
||||||
/// <param name="endTime"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<List<LogRecords>> ParallelQueryAsync(DateTime startTime, DateTime endTime)
|
|
||||||
{
|
|
||||||
var collectionNames = _hourShardingStrategy.GetQueryCollectionNames(startTime, endTime);
|
|
||||||
|
|
||||||
var dbContext = await DbContextProvider.GetDbContextAsync();
|
|
||||||
|
|
||||||
var tasks = collectionNames.Select(async name =>
|
|
||||||
{
|
|
||||||
var collection = dbContext.Database.GetCollection<LogRecords>(name);
|
|
||||||
var filter = Builders<LogRecords>.Filter.And(
|
|
||||||
Builders<LogRecords>.Filter.Gte(x => x.CreationTime, startTime),
|
|
||||||
Builders<LogRecords>.Filter.Lte(x => x.CreationTime, endTime)
|
|
||||||
);
|
|
||||||
return await collection.Find(filter).ToListAsync();
|
|
||||||
});
|
|
||||||
|
|
||||||
var results = await Task.WhenAll(tasks);
|
|
||||||
return results.SelectMany(r => r).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获得分片集合
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
private async Task<IMongoCollection<LogRecords>> GetShardedCollection(DateTime? dateTime)
|
|
||||||
{
|
|
||||||
var dbContext = await DbContextProvider.GetDbContextAsync();
|
|
||||||
string collectionName = string.Empty;
|
|
||||||
|
|
||||||
if (dateTime != null)
|
|
||||||
{
|
|
||||||
collectionName = _hourShardingStrategy.GetCollectionName(dateTime.Value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
collectionName = _hourShardingStrategy.GetCurrentCollectionName();
|
|
||||||
}
|
|
||||||
|
|
||||||
return dbContext.Database.GetCollection<LogRecords>(collectionName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
using JiShe.CollectBus.Common.Enums;
|
using JiShe.CollectBus.Common.Extensions;
|
||||||
using JiShe.CollectBus.Common.Extensions;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -23,7 +22,7 @@ namespace JiShe.CollectBus.ShardingStrategy
|
|||||||
public string GetCollectionName(DateTime dateTime)
|
public string GetCollectionName(DateTime dateTime)
|
||||||
{
|
{
|
||||||
var baseName = typeof(TEntity).Name;
|
var baseName = typeof(TEntity).Name;
|
||||||
return $"{baseName}_{dateTime.GetDataTableShardingStrategy(TableTimeStrategyEnum.DayShardingStrategy)}";
|
return $"{baseName}_{dateTime.GetDataTableShardingStrategy()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -33,7 +32,7 @@ namespace JiShe.CollectBus.ShardingStrategy
|
|||||||
public string GetCurrentCollectionName()
|
public string GetCurrentCollectionName()
|
||||||
{
|
{
|
||||||
var baseName = typeof(TEntity).Name;
|
var baseName = typeof(TEntity).Name;
|
||||||
return $"{baseName}_{DateTime.Now.GetDataTableShardingStrategy(TableTimeStrategyEnum.DayShardingStrategy)}";
|
return $"{baseName}_{DateTime.Now.GetDataTableShardingStrategy()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -51,7 +50,7 @@ namespace JiShe.CollectBus.ShardingStrategy
|
|||||||
|
|
||||||
while (current <= end)
|
while (current <= end)
|
||||||
{
|
{
|
||||||
months.Add($"{baseName}_{current.GetDataTableShardingStrategy(TableTimeStrategyEnum.DayShardingStrategy)}");
|
months.Add($"{baseName}_{current.GetDataTableShardingStrategy()}");
|
||||||
current = current.AddMonths(1);
|
current = current.AddMonths(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
using JiShe.CollectBus.Common.Enums;
|
|
||||||
using JiShe.CollectBus.Common.Extensions;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.ShardingStrategy
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 按小时分表
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TEntity"></typeparam>
|
|
||||||
public class HourShardingStrategy<TEntity>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 获取指定时间对应的集合名
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dateTime"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public string GetCollectionName(DateTime dateTime)
|
|
||||||
{
|
|
||||||
var baseName = typeof(TEntity).Name;
|
|
||||||
return $"{baseName}_{dateTime.GetDataTableShardingStrategy(TableTimeStrategyEnum.HourShardingStrategy)}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前时间对应的集合名
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public string GetCurrentCollectionName()
|
|
||||||
{
|
|
||||||
var baseName = typeof(TEntity).Name;
|
|
||||||
return $"{baseName}_{DateTime.Now.GetDataTableShardingStrategy(TableTimeStrategyEnum.HourShardingStrategy)}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用于查询时确定目标集合
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startTime"></param>
|
|
||||||
/// <param name="endTime"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public IEnumerable<string> GetQueryCollectionNames(DateTime? startTime, DateTime? endTime)
|
|
||||||
{
|
|
||||||
var list = new List<string>();
|
|
||||||
var current = startTime ?? DateTime.MinValue;
|
|
||||||
var end = endTime ?? DateTime.MaxValue;
|
|
||||||
var baseName = typeof(TEntity).Name;
|
|
||||||
|
|
||||||
while (current <= end)
|
|
||||||
{
|
|
||||||
list.Add($"{baseName}_{current.GetDataTableShardingStrategy(TableTimeStrategyEnum.HourShardingStrategy)}");
|
|
||||||
current = current.AddHours(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.Distinct();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.ShardingStrategy
|
|
||||||
{
|
|
||||||
public interface IHourShardingStrategy<TEntity> : IShardingStrategy<TEntity>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
using FreeRedis;
|
using JiShe.CollectBus.Common.Enums;
|
||||||
using JiShe.CollectBus.Common.Enums;
|
|
||||||
using JiShe.CollectBus.Common.Extensions;
|
using JiShe.CollectBus.Common.Extensions;
|
||||||
using JiShe.CollectBus.Common.Models;
|
using JiShe.CollectBus.Common.Models;
|
||||||
using JiShe.CollectBus.Protocol.Contracts.Interfaces;
|
using JiShe.CollectBus.Protocol.Contracts.Interfaces;
|
||||||
@ -13,7 +12,6 @@ using JiShe.CollectBus.IotSystems.MessageReceiveds;
|
|||||||
using JiShe.CollectBus.IotSystems.Protocols;
|
using JiShe.CollectBus.IotSystems.Protocols;
|
||||||
using JiShe.CollectBus.Kafka.Producer;
|
using JiShe.CollectBus.Kafka.Producer;
|
||||||
using JiShe.CollectBus.Common.Consts;
|
using JiShe.CollectBus.Common.Consts;
|
||||||
using JiShe.CollectBus.FreeRedis;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Protocol.Contracts.Abstracts
|
namespace JiShe.CollectBus.Protocol.Contracts.Abstracts
|
||||||
{
|
{
|
||||||
@ -22,7 +20,6 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts
|
|||||||
private readonly IProducerService _producerService;
|
private readonly IProducerService _producerService;
|
||||||
private readonly ILogger<BaseProtocolPlugin_bak> _logger;
|
private readonly ILogger<BaseProtocolPlugin_bak> _logger;
|
||||||
private readonly IRepository<ProtocolInfo, Guid> _protocolInfoRepository;
|
private readonly IRepository<ProtocolInfo, Guid> _protocolInfoRepository;
|
||||||
private readonly IFreeRedisProvider _redisProvider;
|
|
||||||
|
|
||||||
//头部字节长度
|
//头部字节长度
|
||||||
public const int hearderLen = 6;
|
public const int hearderLen = 6;
|
||||||
@ -41,14 +38,13 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts
|
|||||||
_logger = serviceProvider.GetRequiredService<ILogger<BaseProtocolPlugin_bak>>();
|
_logger = serviceProvider.GetRequiredService<ILogger<BaseProtocolPlugin_bak>>();
|
||||||
_protocolInfoRepository = serviceProvider.GetRequiredService<IRepository<ProtocolInfo, Guid>>();
|
_protocolInfoRepository = serviceProvider.GetRequiredService<IRepository<ProtocolInfo, Guid>>();
|
||||||
_producerService = serviceProvider.GetRequiredService<IProducerService>();
|
_producerService = serviceProvider.GetRequiredService<IProducerService>();
|
||||||
_redisProvider = serviceProvider.GetRequiredService<IFreeRedisProvider>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract ProtocolInfo Info { get; }
|
public abstract ProtocolInfo Info { get; }
|
||||||
|
|
||||||
public virtual async Task<ProtocolInfo> GetAsync() => await Task.FromResult(Info);
|
public virtual async Task<ProtocolInfo> GetAsync() => await Task.FromResult(Info);
|
||||||
|
|
||||||
public virtual async Task LoadAsync()
|
public virtual async Task AddAsync()
|
||||||
{
|
{
|
||||||
if (Info == null)
|
if (Info == null)
|
||||||
{
|
{
|
||||||
@ -57,8 +53,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts
|
|||||||
|
|
||||||
await _protocolInfoRepository.DeleteDirectAsync(a => a.Name == Info.Name);
|
await _protocolInfoRepository.DeleteDirectAsync(a => a.Name == Info.Name);
|
||||||
await _protocolInfoRepository.InsertAsync(Info);
|
await _protocolInfoRepository.InsertAsync(Info);
|
||||||
await _redisProvider.Instance.HDelAsync($"{RedisConst.ProtocolKey}", Info.Name);
|
//await _protocolInfoCache.Get()
|
||||||
await _redisProvider.Instance.HSetAsync($"{RedisConst.ProtocolKey}", Info.Name, Info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Task<T> AnalyzeAsync<T>(MessageReceived messageReceived, Action<byte[]>? sendAction = null) where T : TB3761;
|
public abstract Task<T> AnalyzeAsync<T>(MessageReceived messageReceived, Action<byte[]>? sendAction = null) where T : TB3761;
|
||||||
@ -1173,5 +1168,6 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,374 @@
|
|||||||
|
using JiShe.CollectBus.Common.Extensions;
|
||||||
|
using JiShe.CollectBus.IotSystems.Protocols;
|
||||||
|
using JiShe.CollectBus.Protocol.Contracts.Interfaces;
|
||||||
|
using JiShe.CollectBus.Protocol.Contracts.Models;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using TouchSocket.Sockets;
|
||||||
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
|
||||||
|
namespace JiShe.CollectBus.Protocol.Contracts.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<ProtocolInfo, Guid> _protocolInfoRepository;
|
||||||
|
public ProtocolPlugin(IServiceProvider serviceProvider, ILogger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_protocolInfoRepository = serviceProvider.GetRequiredService<IRepository<ProtocolInfo, Guid>>();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public abstract ProtocolInfo Info { get; }
|
||||||
|
|
||||||
|
public virtual async Task<ProtocolInfo> GetAsync() => await Task.FromResult(Info);
|
||||||
|
|
||||||
|
public virtual async Task AddAsync()
|
||||||
|
{
|
||||||
|
if (Info == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(Info));
|
||||||
|
}
|
||||||
|
|
||||||
|
await _protocolInfoRepository.DeleteDirectAsync(a => a.Name == Info.Name);
|
||||||
|
await _protocolInfoRepository.InsertAsync(Info);
|
||||||
|
//await _protocolInfoCache.Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Task<T> AnalyzeAsync<T>(ITcpSessionClient client, string messageReceived, Action<T>? receivedAction = null) where T :class;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析376.1帧
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageReceived"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 控制域C解析
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual C? Analysis_C(List<string> hexStringList)
|
||||||
|
{
|
||||||
|
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 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 null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 地址域A解析
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hexStringList"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual A? Analysis_A(List<string> hexStringList)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (hexStringList.Count > 7)
|
||||||
|
{
|
||||||
|
BaseHexMessage baseHexMessage = new BaseHexMessage
|
||||||
|
{
|
||||||
|
HexMessageList = hexStringList.GetRange(7, 5) // 地址域 5个字节
|
||||||
|
};
|
||||||
|
baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList);
|
||||||
|
if (baseHexMessage.HexMessageList.Count == 0)
|
||||||
|
return null;
|
||||||
|
A 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')}";
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"解析Analysis_A错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 站地址和组地址标志A3
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hexAList">地址域A集合</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual A3? Analysis_A3(List<string> hexAList)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BaseHexMessage baseHexMessage = new BaseHexMessage
|
||||||
|
{
|
||||||
|
HexMessageList = hexAList.GetRange(4, 1) // 站地址和组地址标志A3 1个字节
|
||||||
|
};
|
||||||
|
baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList);
|
||||||
|
if (baseHexMessage.HexMessageList.Count == 0)
|
||||||
|
return null;
|
||||||
|
var binStr = baseHexMessage.HexMessageString.HexTo4BinZero();
|
||||||
|
A3 a3 = new A3
|
||||||
|
{
|
||||||
|
BaseHexMessage = baseHexMessage,
|
||||||
|
D0 = binStr.Substring(binStr.Length - 1, 1).BinToDec(),
|
||||||
|
D1_D7 = binStr.Substring(0, binStr.Length - 1).BinToDec()
|
||||||
|
};
|
||||||
|
return a3;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"解析Analysis_A3错误,报文:{string.Join("", hexAList)},异常:{ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AFN_FC功能码
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual AFN_FC? Analysis_AFN_FC(List<string> hexStringList)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BaseHexMessage baseHexMessage = new BaseHexMessage
|
||||||
|
{
|
||||||
|
HexMessageList = hexStringList.GetRange(12, 1) //AFN功能码 1个字节
|
||||||
|
};
|
||||||
|
baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList);
|
||||||
|
if (baseHexMessage.HexMessageList.Count == 0)
|
||||||
|
return null;
|
||||||
|
AFN_FC aFN_FC = new AFN_FC
|
||||||
|
{
|
||||||
|
BaseHexMessage = baseHexMessage,
|
||||||
|
AFN = baseHexMessage.HexMessageString.HexToDec(),
|
||||||
|
};
|
||||||
|
return aFN_FC;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"解析Analysis_AFN_FC错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析帧序列域SEQ
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual SEQ? Analysis_SEQ(List<string> hexStringList)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BaseHexMessage baseHexMessage = new BaseHexMessage
|
||||||
|
{
|
||||||
|
HexMessageList = hexStringList.GetRange(13, 1) //帧序列域 SEQ 1个字节
|
||||||
|
};
|
||||||
|
baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList);
|
||||||
|
if (baseHexMessage.HexMessageList.Count == 0)
|
||||||
|
return null;
|
||||||
|
var binStr = baseHexMessage.HexMessageString.HexTo4BinZero();
|
||||||
|
SEQ 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()
|
||||||
|
};
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"解析Analysis_SEQ错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据单元标识及数据单元数据
|
||||||
|
/// </summary>
|
||||||
|
public virtual UnitData? Analysis_UnitData(List<string> hexStringList)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
UnitData unitData = new UnitData
|
||||||
|
{
|
||||||
|
HexMessageList = hexStringList.GetRange(14, hexStringList.Count - 14 - 2) //总数字节数-固定长度报文头-控制域C-地址域A-校验和CS-结束字符(16H)
|
||||||
|
};
|
||||||
|
unitData.HexMessageString = string.Join("", unitData.HexMessageList);
|
||||||
|
if (unitData.HexMessageList.Count == 0)
|
||||||
|
return null;
|
||||||
|
return unitData;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"解析Analysis_UnitData错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 信息点DA Pn
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual DA? Analysis_DA(List<string> hexStringList)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BaseHexMessage baseHexMessage = new BaseHexMessage
|
||||||
|
{
|
||||||
|
HexMessageList = hexStringList.GetRange(14, 2) //信息点DA Pn 2个字节
|
||||||
|
};
|
||||||
|
baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList);
|
||||||
|
if (baseHexMessage.HexMessageList.Count == 0)
|
||||||
|
return null;
|
||||||
|
var da1 = baseHexMessage.HexMessageList[0];
|
||||||
|
var da2 = baseHexMessage.HexMessageList[1];
|
||||||
|
DA da = new DA()
|
||||||
|
{
|
||||||
|
BaseHexMessage = baseHexMessage,
|
||||||
|
Pn = CalculatePn(da1, da2)
|
||||||
|
};
|
||||||
|
return da;
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"解析Analysis_DA错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 信息类DT Fn
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual DT? Analysis_DT(List<string> hexStringList)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BaseHexMessage baseHexMessage = new BaseHexMessage
|
||||||
|
{
|
||||||
|
HexMessageList = hexStringList.GetRange(16, 2) //信息类DT Fn 2个字节
|
||||||
|
};
|
||||||
|
baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList);
|
||||||
|
if (baseHexMessage.HexMessageList.Count == 0)
|
||||||
|
return null;
|
||||||
|
var dt1 = baseHexMessage.HexMessageList[0];
|
||||||
|
var dt2 = baseHexMessage.HexMessageList[1];
|
||||||
|
DT dt = new DT()
|
||||||
|
{
|
||||||
|
BaseHexMessage = baseHexMessage,
|
||||||
|
Fn = CalculateFn(dt1, dt2)
|
||||||
|
};
|
||||||
|
return dt;
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"解析Analysis_DT错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算Pn
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="da1"></param>
|
||||||
|
/// <param name="da2"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int CalculatePn(string da1, string da2) => (da2.HexToDec() - 1) * 8 + (8 - da1.HexTo4BinZero().IndexOf(da1.Equals("00") ? "0" : "1"));
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算Fn
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dt1"></param>
|
||||||
|
/// <param name="dt2"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int CalculateFn(string dt1, string dt2) => dt2.HexToDec() * 8 + (8 - dt1.HexTo4BinZero().IndexOf("1"));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,12 @@
|
|||||||
using JiShe.CollectBus.Protocol.Models;
|
using JiShe.CollectBus.Protocol.Contracts.Models;
|
||||||
|
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.Adapters
|
namespace JiShe.CollectBus.Protocol.Contracts.Adapters
|
||||||
{
|
{
|
||||||
public class StandardFixedHeaderDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter<CustomFixedHeaderRequestInfo>
|
public class StandardFixedHeaderDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter<CustomFixedHeaderRequestInfo>
|
||||||
{
|
{
|
||||||
@ -1,6 +1,11 @@
|
|||||||
using JiShe.CollectBus.Common.Extensions;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using JiShe.CollectBus.Common.Extensions;
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Protocol.AnalysisData
|
namespace JiShe.CollectBus.Protocol.Contracts.AnalysisData
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 附录
|
/// 附录
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
using JiShe.CollectBus.Protocol.Contracts.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace JiShe.CollectBus.Protocol.Contracts
|
||||||
|
{
|
||||||
|
|
||||||
|
public class AnalysisStrategyContext<TInput, TResult>
|
||||||
|
{
|
||||||
|
private readonly IAnalysisStrategy<TInput, TResult> _analysisStrategy;
|
||||||
|
|
||||||
|
public AnalysisStrategyContext(IAnalysisStrategy<TInput, TResult> analysisStrategy)
|
||||||
|
{
|
||||||
|
_analysisStrategy = analysisStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<TResult> ExecuteAnalysisStrategy(TInput input)
|
||||||
|
{
|
||||||
|
return _analysisStrategy.ExecuteAsync(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,6 @@
|
|||||||
namespace JiShe.CollectBus.Protocol.Attributes
|
using System;
|
||||||
|
|
||||||
|
namespace JiShe.CollectBus.Protocol.Contracts.Attributes
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
public class ProtocolNameAttribute(string name) : Attribute
|
public class ProtocolNameAttribute(string name) : Attribute
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
using JiShe.CollectBus.Protocol.Contracts.Models;
|
||||||
|
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.Interfaces
|
||||||
|
{
|
||||||
|
public interface IAnalysisStrategy<TInput, TResult>
|
||||||
|
{
|
||||||
|
Task<TResult> ExecuteAsync(TInput input);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
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<ProtocolInfo> GetAsync();
|
||||||
|
|
||||||
|
Task AddAsync();
|
||||||
|
|
||||||
|
Task<T> AnalyzeAsync<T>(ITcpSessionClient client, string messageReceived, Action<T>? sendAction = null) where T : class;
|
||||||
|
|
||||||
|
TB3761? Analysis3761(string messageReceived);
|
||||||
|
|
||||||
|
//Task LoginAsync(MessageReceivedLogin messageReceived);
|
||||||
|
|
||||||
|
//Task HeartbeatAsync(MessageReceivedHeartbeat messageReceived);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="Extensions\**" />
|
||||||
|
<EmbeddedResource Remove="Extensions\**" />
|
||||||
|
<None Remove="Extensions\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
||||||
|
<PackageReference Include="TouchSocket" Version="2.1.9" />
|
||||||
|
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\modules\JiShe.CollectBus.Kafka\JiShe.CollectBus.Kafka.csproj" />
|
||||||
|
<ProjectReference Include="..\..\services\JiShe.CollectBus.Domain\JiShe.CollectBus.Domain.csproj" />
|
||||||
|
<ProjectReference Include="..\..\shared\JiShe.CollectBus.Common\JiShe.CollectBus.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@ -1,6 +1,11 @@
|
|||||||
using TouchSocket.Core;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using TouchSocket.Core;
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Protocol.Models
|
namespace JiShe.CollectBus.Protocol.Contracts.Models
|
||||||
{
|
{
|
||||||
public class CustomFixedHeaderRequestInfo : IFixedHeaderRequestInfo
|
public class CustomFixedHeaderRequestInfo : IFixedHeaderRequestInfo
|
||||||
{
|
{
|
||||||
@ -4,7 +4,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Protocol3761
|
namespace JiShe.CollectBus.Protocol.Contracts.Models
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -15,55 +15,45 @@ namespace JiShe.CollectBus.Protocol3761
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 报文
|
/// 报文
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BaseHexMessage BaseHexMessage { get; set;}=new BaseHexMessage();
|
public BaseHexMessage? BaseHexMessage { get; set;}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 报文ID
|
|
||||||
/// </summary>
|
|
||||||
public string? MessageId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 消息时间
|
|
||||||
/// </summary>
|
|
||||||
public DateTime ReceivedTime { get; set; }=DateTime.Now;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 控制域C
|
/// 控制域C
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public C C { get; set; } = new C();
|
public C? C { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 地址域A
|
/// 地址域A
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public A A { get; set; } = new A();
|
public A? A { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 帧序列域 SEQ
|
/// 帧序列域 SEQ
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SEQ SEQ { get; set; } = new SEQ();
|
public SEQ? SEQ { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用户数据区
|
/// 用户数据区
|
||||||
/// 功能码
|
/// 功能码
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AFN_FC AFN_FC { get; set; } = new AFN_FC();
|
public AFN_FC? AFN_FC { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用户数据区
|
/// 用户数据区
|
||||||
/// 信息点DA Pn
|
/// 信息点DA Pn
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DA DA { get; set; } = new DA();
|
public DA? DA { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用户数据区
|
/// 用户数据区
|
||||||
/// 信息类DT Fn
|
/// 信息类DT Fn
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DT DT { get; set; } = new DT();
|
public DT? DT { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 数据单元标识和数据单元格式
|
/// 数据单元标识和数据单元格式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public UnitData UnitData { get; set; } = new UnitData();
|
public UnitData? UnitData { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
#region
|
#region
|
||||||
@ -248,7 +238,10 @@ namespace JiShe.CollectBus.Protocol3761
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 数据单元标识和数据单元格式
|
/// 数据单元标识和数据单元格式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UnitData: BaseHexMessage{ }
|
public class UnitData: BaseHexMessage
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -4,9 +4,9 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Subscribers
|
namespace JiShe.CollectBus.Protocol.Dto
|
||||||
{
|
{
|
||||||
public interface ISubscriberAnalysisAppService
|
public class AFN0_F1_AnalysisDto: UnitDataDto
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace JiShe.CollectBus.Protocol.Dto
|
||||||
|
{
|
||||||
|
public class UnitDataDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 集中器地址
|
||||||
|
/// </summary>
|
||||||
|
public string? Code { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AFN功能码
|
||||||
|
/// </summary>
|
||||||
|
public int AFN { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 信息点
|
||||||
|
/// </summary>
|
||||||
|
public int Pn { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 信息类
|
||||||
|
/// </summary>
|
||||||
|
public int Fn { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据时标(最近数据时间点的时间),如:8:00 08:15 记录08:15
|
||||||
|
/// </summary>
|
||||||
|
public string? DataTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 密度(分)
|
||||||
|
/// </summary>
|
||||||
|
public int TimeDensity { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,6 +0,0 @@
|
|||||||
namespace JiShe.CollectBus.Protocol.Contracts.ProtocolPools
|
|
||||||
{
|
|
||||||
public interface IPluginContainer
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
using JiShe.CollectBus.Interfaces;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Protocol.Contracts.ProtocolPools
|
|
||||||
{
|
|
||||||
public class PluginContainer: IPluginContainer
|
|
||||||
{
|
|
||||||
public Dictionary<string, object> ProtocolPools;
|
|
||||||
|
|
||||||
public PluginContainer()
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ServiceProviderKeyedServiceExtensions
|
|
||||||
{
|
|
||||||
//public static Task AddKeyedSingleton<TImp>(this IServiceProvider provider, string key)
|
|
||||||
//{
|
|
||||||
// //var aa = Activator.CreateInstance<TImp>();
|
|
||||||
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -1,24 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
|
|
||||||
<PackageReference Include="Volo.Abp.Core" Version="8.3.3" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\services\JiShe.CollectBus.Domain\JiShe.CollectBus.Domain.csproj" />
|
|
||||||
<ProjectReference Include="..\..\shared\JiShe.CollectBus.Common\JiShe.CollectBus.Common.csproj" />
|
|
||||||
<ProjectReference Include="..\JiShe.CollectBus.Protocol.T37612012\JiShe.CollectBus.Protocol.T37612012.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
|
||||||
<Exec Command="copy $(TargetDir)JiShe.CollectBus.Protocol.T1882018.dll $(ProjectDir)..\..\web\JiShe.CollectBus.Host\Plugins\" />
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
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<IProtocolPlugin, T1882018ProtocolPlugin>(nameof(T1882018ProtocolPlugin));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"{nameof(T1882018ProtocolPlugin)} OnApplicationInitializationAsync");
|
|
||||||
var standardProtocol = context.ServiceProvider.GetRequiredKeyedService<IProtocolPlugin>(nameof(T1882018ProtocolPlugin));
|
|
||||||
await standardProtocol.LoadAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnApplicationShutdown(ApplicationShutdownContext context)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"{nameof(T1882018ProtocolPlugin)} OnApplicationShutdown");
|
|
||||||
base.OnApplicationShutdown(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
using JiShe.CollectBus.Common.BuildSendDatas;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Protocol.T1882018.SendData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 构建188-2018下发报文
|
|
||||||
/// </summary>
|
|
||||||
public static class Telemetry1882018PacketBuilder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 构建报文的委托
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public delegate Telemetry1882018PacketResponse T1882018Delegate(Telemetry1882018PacketRequest request);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 编码与方法的映射表
|
|
||||||
/// </summary>
|
|
||||||
public static readonly Dictionary<string, T1882018Delegate> 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 读数据
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 读取计量数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static Telemetry1882018PacketResponse CTR_01_Send(Telemetry1882018PacketRequest request)
|
|
||||||
{
|
|
||||||
var itemCodeArr = request.ItemCode.Split('_');
|
|
||||||
var c_data = itemCodeArr[0];//01
|
|
||||||
var d_data = itemCodeArr[2];//91 或者 90
|
|
||||||
var dataUnit = new List<string>() { "1F", d_data, "00" };
|
|
||||||
var dataList = Build188SendData.Build188SendCommand(request.MeterAddress, c_data, dataUnit);
|
|
||||||
|
|
||||||
return new Telemetry1882018PacketResponse() { Data = dataList };
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 写数据
|
|
||||||
/// <summary>
|
|
||||||
/// 阀控
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static Telemetry1882018PacketResponse CTR_04_Send(Telemetry1882018PacketRequest request)
|
|
||||||
{
|
|
||||||
var itemCodeArr = request.ItemCode.Split('_');
|
|
||||||
var c_data = itemCodeArr[0];//01
|
|
||||||
var d_data = itemCodeArr[2];//55 或者 99
|
|
||||||
var dataUnit = new List<string>() { "A0", "17", "00", d_data };
|
|
||||||
var dataList = Build188SendData.Build188SendCommand(request.MeterAddress, c_data, dataUnit);
|
|
||||||
|
|
||||||
return new Telemetry1882018PacketResponse() { Data = dataList };
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
namespace JiShe.CollectBus.Protocol.T1882018.SendData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 构建645报文参数
|
|
||||||
/// </summary>
|
|
||||||
public class Telemetry1882018PacketRequest
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 表地址
|
|
||||||
/// </summary>
|
|
||||||
public required string MeterAddress { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 密码
|
|
||||||
/// </summary>
|
|
||||||
public required string Password { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 操作码
|
|
||||||
/// </summary>
|
|
||||||
public required string ItemCode { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
namespace JiShe.CollectBus.Protocol.T1882018.SendData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 返回645报文结果
|
|
||||||
/// </summary>
|
|
||||||
public class Telemetry1882018PacketResponse
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 报文体
|
|
||||||
/// </summary>
|
|
||||||
public List<string> Data { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,142 +0,0 @@
|
|||||||
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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// T1882018协议插件
|
|
||||||
/// </summary>
|
|
||||||
public class T1882018ProtocolPlugin : T37612012ProtocolPlugin
|
|
||||||
{
|
|
||||||
private readonly ILogger<T1882018ProtocolPlugin> _logger;
|
|
||||||
|
|
||||||
public readonly Dictionary<string, Telemetry1882018PacketBuilder.T1882018Delegate> T188ControlHandlers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="T1882018ProtocolPlugin"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceProvider">The service provider.</param>
|
|
||||||
public T1882018ProtocolPlugin(IServiceProvider serviceProvider, ILogger<T1882018ProtocolPlugin> 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<T> AnalyzeAsync<T>(ITcpSessionClient client, string messageReceived, Action<T>? 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 组装报文
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">报文构建参数</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override async Task<ProtocolBuildResponse> 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<string> dataUnit = new List<string>();
|
|
||||||
//数据转发场景 10H_F1
|
|
||||||
if (request.ItemCode == T37612012PacketItemCodeConst.AFN10HFN01H && request.SubProtocolRequest != null && string.IsNullOrWhiteSpace(request.SubProtocolRequest.ItemCode) == false)
|
|
||||||
{
|
|
||||||
//var subItemCodeArr = request.SubProtocolRequest.ItemCode.Split("_");
|
|
||||||
|
|
||||||
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<ProtocolBuildResponse>();
|
|
||||||
result.IsSuccess = true;
|
|
||||||
|
|
||||||
return await Task.FromResult(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
using Apache.IoTDB;
|
|
||||||
using JiShe.CollectBus.Common.Consts;
|
|
||||||
using JiShe.CollectBus.Common.Enums;
|
|
||||||
using JiShe.CollectBus.Common.Extensions;
|
|
||||||
using JiShe.CollectBus.IotSystems.Ammeters;
|
|
||||||
using JiShe.CollectBus.IotSystems.Devices;
|
|
||||||
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_00H
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 5.1.3.1 F1:全部确认:对收到报文中的全部数据单元标识进行确认
|
|
||||||
/// </summary>
|
|
||||||
public class AFN0_F1_Analysis: IAnalysisStrategy<TB3761>
|
|
||||||
{
|
|
||||||
private readonly ILogger<AFN0_F1_Analysis> _logger;
|
|
||||||
private readonly DataStorage _dataStorage;
|
|
||||||
|
|
||||||
public AFN0_F1_Analysis(ILogger<AFN0_F1_Analysis> logger, DataStorage dataStorage)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_dataStorage= dataStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> ExecuteAsync(TB3761 input, Action<dynamic>? result = null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(input);
|
|
||||||
ArgumentNullException.ThrowIfNull(input.A.Code);
|
|
||||||
var data = new AnalysisBaseDto<bool?>()
|
|
||||||
{
|
|
||||||
FiledDesc = "全部确认",
|
|
||||||
DataValue = true,
|
|
||||||
ItemType= $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"
|
|
||||||
};
|
|
||||||
// 查询电表信息
|
|
||||||
DeviceInfo? deviceInfo = await _dataStorage.GetDeviceInfoAsync(input.A.Code);
|
|
||||||
if (deviceInfo != null)
|
|
||||||
{
|
|
||||||
data.ProjectId = deviceInfo.ProjectID;
|
|
||||||
data.DeviceId = deviceInfo.FocusId;
|
|
||||||
data.DatabaseBusiID = deviceInfo.DatabaseBusiID;
|
|
||||||
data.DeviceAddress = deviceInfo.FocusAddress;
|
|
||||||
data.DeviceType = MeterTypeEnum.Focus;
|
|
||||||
data.FocusId= deviceInfo.FocusId;
|
|
||||||
}
|
|
||||||
UnitDataAnalysis<AnalysisBaseDto<bool?>> dto = new UnitDataAnalysis<AnalysisBaseDto<bool?>>
|
|
||||||
{
|
|
||||||
Code = input.A.Code,
|
|
||||||
AFN = input.AFN_FC.AFN,
|
|
||||||
Fn = input.DT.Fn,
|
|
||||||
Pn = input.DA.Pn,
|
|
||||||
Data = data,
|
|
||||||
ReceivedHexMessage = input.BaseHexMessage.HexMessageString,
|
|
||||||
MessageId = input.MessageId,
|
|
||||||
ReceivedTime = input.ReceivedTime,
|
|
||||||
DensityUnit = DensityUnit.None,
|
|
||||||
TimeDensity = -1,
|
|
||||||
DataType= IOTDBDataTypeConst.Log
|
|
||||||
};
|
|
||||||
result?.Invoke(dto);
|
|
||||||
await _dataStorage.SaveDataToIotDbAsync<bool?>(dto);
|
|
||||||
return await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"00_1解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}");
|
|
||||||
}
|
|
||||||
return await Task.FromResult(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
using JiShe.CollectBus.Common.Consts;
|
|
||||||
using JiShe.CollectBus.Common.Enums;
|
|
||||||
using JiShe.CollectBus.Common.Extensions;
|
|
||||||
using JiShe.CollectBus.IotSystems.Ammeters;
|
|
||||||
using JiShe.CollectBus.IotSystems.Devices;
|
|
||||||
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_00H
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 5.1.3.2 F2:全部否认
|
|
||||||
/// </summary>
|
|
||||||
public class AFN0_F2_Analysis : IAnalysisStrategy<TB3761>
|
|
||||||
{
|
|
||||||
private readonly ILogger<AFN0_F2_Analysis> _logger;
|
|
||||||
private readonly DataStorage _dataStorage;
|
|
||||||
public AFN0_F2_Analysis(ILogger<AFN0_F2_Analysis> logger, DataStorage dataStorage)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_dataStorage = dataStorage;
|
|
||||||
}
|
|
||||||
public async Task<bool> ExecuteAsync(TB3761 input, Action<dynamic>? result = null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(input);
|
|
||||||
ArgumentNullException.ThrowIfNull(input.A.Code);
|
|
||||||
var data = new AnalysisBaseDto<bool?>()
|
|
||||||
{
|
|
||||||
FiledDesc = "全部否认",
|
|
||||||
DataValue = false,
|
|
||||||
ItemType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"
|
|
||||||
};
|
|
||||||
// 查询电表信息
|
|
||||||
DeviceInfo? deviceInfo = await _dataStorage.GetDeviceInfoAsync(input.A.Code);
|
|
||||||
if (deviceInfo != null)
|
|
||||||
{
|
|
||||||
data.ProjectId = deviceInfo.ProjectID;
|
|
||||||
data.DeviceId = deviceInfo.FocusId;
|
|
||||||
data.DatabaseBusiID = deviceInfo.DatabaseBusiID;
|
|
||||||
data.DeviceAddress = deviceInfo.FocusAddress;
|
|
||||||
data.DeviceType = MeterTypeEnum.Focus;
|
|
||||||
data.FocusId = deviceInfo.FocusId;
|
|
||||||
}
|
|
||||||
UnitDataAnalysis<AnalysisBaseDto<bool?>> dto = new UnitDataAnalysis<AnalysisBaseDto<bool?>>
|
|
||||||
{
|
|
||||||
Code = input.A.Code,
|
|
||||||
AFN = input.AFN_FC.AFN,
|
|
||||||
Fn = input.DT.Fn,
|
|
||||||
Pn = input.DA.Pn,
|
|
||||||
Data = data,
|
|
||||||
ReceivedHexMessage = input.BaseHexMessage.HexMessageString,
|
|
||||||
MessageId = input.MessageId,
|
|
||||||
ReceivedTime =input.ReceivedTime,
|
|
||||||
DensityUnit = DensityUnit.None,
|
|
||||||
TimeDensity = -1,
|
|
||||||
DataType = IOTDBDataTypeConst.Log
|
|
||||||
};
|
|
||||||
result?.Invoke(dto);
|
|
||||||
#if DEBUG
|
|
||||||
_logger.LogWarning($"全部否认:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString}");
|
|
||||||
#endif
|
|
||||||
await _dataStorage.SaveDataToIotDbAsync<bool?>(dto);
|
|
||||||
return await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"00_2解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}");
|
|
||||||
}
|
|
||||||
return await Task.FromResult(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
using JiShe.CollectBus.Common.Consts;
|
|
||||||
using JiShe.CollectBus.Common.Enums;
|
|
||||||
using JiShe.CollectBus.Common.Extensions;
|
|
||||||
using JiShe.CollectBus.IotSystems.Ammeters;
|
|
||||||
using JiShe.CollectBus.IotSystems.Devices;
|
|
||||||
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_02H
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 5.3.3.1 F1:登录
|
|
||||||
/// </summary>
|
|
||||||
public class AFN2_F1_Analysis : IAnalysisStrategy<TB3761>
|
|
||||||
{
|
|
||||||
private readonly ILogger<AFN2_F1_Analysis> _logger;
|
|
||||||
private readonly DataStorage _dataStorage;
|
|
||||||
public AFN2_F1_Analysis(ILogger<AFN2_F1_Analysis> logger, DataStorage dataStorage)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_dataStorage= dataStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> ExecuteAsync(TB3761 input, Action<dynamic>? result = null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(input);
|
|
||||||
ArgumentNullException.ThrowIfNull(input.A.Code);
|
|
||||||
var data = new AnalysisBaseDto<string>()
|
|
||||||
{
|
|
||||||
FiledDesc = "登录",
|
|
||||||
FiledName = "Type",
|
|
||||||
DataValue = "Login",
|
|
||||||
ItemType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"
|
|
||||||
};
|
|
||||||
// 查询设备信息
|
|
||||||
DeviceInfo? deviceInfo = await _dataStorage.GetDeviceInfoAsync(input.A.Code);
|
|
||||||
if (deviceInfo != null)
|
|
||||||
{
|
|
||||||
data.ProjectId = deviceInfo.ProjectID;
|
|
||||||
data.DeviceId = deviceInfo.FocusId;
|
|
||||||
data.DatabaseBusiID = deviceInfo.DatabaseBusiID;
|
|
||||||
data.DeviceAddress = deviceInfo.FocusAddress;
|
|
||||||
data.DeviceType = MeterTypeEnum.Focus;
|
|
||||||
data.FocusId = deviceInfo.FocusId;
|
|
||||||
}
|
|
||||||
UnitDataAnalysis<AnalysisBaseDto<string>> dto = new UnitDataAnalysis<AnalysisBaseDto<string>>
|
|
||||||
{
|
|
||||||
Code = input.A.Code,
|
|
||||||
AFN = input.AFN_FC.AFN,
|
|
||||||
Fn = input.DT.Fn,
|
|
||||||
Pn = input.DA.Pn,
|
|
||||||
Data = data,
|
|
||||||
ReceivedHexMessage = input.BaseHexMessage.HexMessageString,
|
|
||||||
MessageId = input.MessageId,
|
|
||||||
ReceivedTime = input.ReceivedTime,
|
|
||||||
DensityUnit = DensityUnit.None,
|
|
||||||
TimeDensity = -1,
|
|
||||||
DataType = IOTDBDataTypeConst.Status
|
|
||||||
};
|
|
||||||
result?.Invoke(dto);
|
|
||||||
await _dataStorage.SaveStatusToIotDbAsync<string>(dto);
|
|
||||||
return await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"02_1解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}");
|
|
||||||
}
|
|
||||||
return await Task.FromResult(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
using JiShe.CollectBus.Common.Consts;
|
|
||||||
using JiShe.CollectBus.Common.Enums;
|
|
||||||
using JiShe.CollectBus.Common.Extensions;
|
|
||||||
using JiShe.CollectBus.IotSystems.Ammeters;
|
|
||||||
using JiShe.CollectBus.IotSystems.Devices;
|
|
||||||
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_02H
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 5.3.3.2 F2:退出登录
|
|
||||||
/// </summary>
|
|
||||||
public class AFN2_F2_Analysis : IAnalysisStrategy<TB3761>
|
|
||||||
{
|
|
||||||
private readonly ILogger<AFN2_F2_Analysis> _logger;
|
|
||||||
private readonly DataStorage _dataStorage;
|
|
||||||
public AFN2_F2_Analysis(ILogger<AFN2_F2_Analysis> logger, DataStorage dataStorage)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_dataStorage = dataStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> ExecuteAsync(TB3761 input, Action<dynamic>? result = null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(input);
|
|
||||||
ArgumentNullException.ThrowIfNull(input.A.Code);
|
|
||||||
var data = new AnalysisBaseDto<string>()
|
|
||||||
{
|
|
||||||
FiledDesc = "退出登录",
|
|
||||||
FiledName = "Type",
|
|
||||||
DataValue = "LogOut",
|
|
||||||
ItemType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"
|
|
||||||
};
|
|
||||||
// 查询设备信息
|
|
||||||
DeviceInfo? deviceInfo = await _dataStorage.GetDeviceInfoAsync(input.A.Code);
|
|
||||||
if (deviceInfo != null)
|
|
||||||
{
|
|
||||||
data.ProjectId = deviceInfo.ProjectID;
|
|
||||||
data.DeviceId = deviceInfo.FocusId;
|
|
||||||
data.DatabaseBusiID = deviceInfo.DatabaseBusiID;
|
|
||||||
data.DeviceAddress = deviceInfo.FocusAddress;
|
|
||||||
data.DeviceType = MeterTypeEnum.Focus;
|
|
||||||
data.FocusId = deviceInfo.FocusId;
|
|
||||||
}
|
|
||||||
UnitDataAnalysis<AnalysisBaseDto<string>> dto = new UnitDataAnalysis<AnalysisBaseDto<string>>
|
|
||||||
{
|
|
||||||
Code = input.A.Code,
|
|
||||||
AFN = input.AFN_FC.AFN,
|
|
||||||
Fn = input.DT.Fn,
|
|
||||||
Pn = input.DA.Pn,
|
|
||||||
Data = data,
|
|
||||||
ReceivedHexMessage = input.BaseHexMessage.HexMessageString,
|
|
||||||
MessageId = input.MessageId,
|
|
||||||
ReceivedTime = input.ReceivedTime,
|
|
||||||
DensityUnit = DensityUnit.None,
|
|
||||||
TimeDensity = -1,
|
|
||||||
DataType = IOTDBDataTypeConst.Status
|
|
||||||
};
|
|
||||||
result?.Invoke(dto);
|
|
||||||
await _dataStorage.SaveStatusToIotDbAsync<string>(dto);
|
|
||||||
return await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"02_2解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}");
|
|
||||||
}
|
|
||||||
return await Task.FromResult(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
using JiShe.CollectBus.Common.Consts;
|
|
||||||
using JiShe.CollectBus.Common.Enums;
|
|
||||||
using JiShe.CollectBus.Common.Extensions;
|
|
||||||
using JiShe.CollectBus.IotSystems.Ammeters;
|
|
||||||
using JiShe.CollectBus.IotSystems.Devices;
|
|
||||||
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_02H
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 5.3.3.3 F3:心跳
|
|
||||||
/// </summary>
|
|
||||||
public class AFN2_F3_Analysis : IAnalysisStrategy<TB3761>
|
|
||||||
{
|
|
||||||
private readonly ILogger<AFN2_F3_Analysis> _logger;
|
|
||||||
private readonly DataStorage _dataStorage;
|
|
||||||
public AFN2_F3_Analysis(ILogger<AFN2_F3_Analysis> logger, DataStorage dataStorage)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_dataStorage = dataStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> ExecuteAsync(TB3761 input, Action<dynamic>? result = null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(input);
|
|
||||||
ArgumentNullException.ThrowIfNull(input.A.Code);
|
|
||||||
var data = new AnalysisBaseDto<string>()
|
|
||||||
{
|
|
||||||
FiledDesc = "心跳",
|
|
||||||
FiledName = "Type",
|
|
||||||
DataValue = "Heartbeat",
|
|
||||||
ItemType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"
|
|
||||||
};
|
|
||||||
// 查询设备信息
|
|
||||||
DeviceInfo? deviceInfo = await _dataStorage.GetDeviceInfoAsync(input.A.Code);
|
|
||||||
if (deviceInfo != null)
|
|
||||||
{
|
|
||||||
data.ProjectId = deviceInfo.ProjectID;
|
|
||||||
data.DeviceId = deviceInfo.FocusId;
|
|
||||||
data.DatabaseBusiID = deviceInfo.DatabaseBusiID;
|
|
||||||
data.DeviceAddress = deviceInfo.FocusAddress;
|
|
||||||
data.DeviceType = MeterTypeEnum.Focus;
|
|
||||||
data.FocusId = deviceInfo.FocusId;
|
|
||||||
}
|
|
||||||
UnitDataAnalysis<AnalysisBaseDto<string>> dto = new UnitDataAnalysis<AnalysisBaseDto<string>>
|
|
||||||
{
|
|
||||||
Code = input.A.Code,
|
|
||||||
AFN = input.AFN_FC.AFN,
|
|
||||||
Fn = input.DT.Fn,
|
|
||||||
Pn = input.DA.Pn,
|
|
||||||
Data = data,
|
|
||||||
ReceivedHexMessage = input.BaseHexMessage.HexMessageString,
|
|
||||||
MessageId = input.MessageId,
|
|
||||||
ReceivedTime = input.ReceivedTime,
|
|
||||||
DensityUnit = DensityUnit.None,
|
|
||||||
TimeDensity = -1,
|
|
||||||
DataType = IOTDBDataTypeConst.Status
|
|
||||||
};
|
|
||||||
result?.Invoke(dto);
|
|
||||||
await _dataStorage.SaveStatusToIotDbAsync<string>(dto);
|
|
||||||
return await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"02_3解析失败:{input.A.Code}-{input.DT.Fn}-{input.BaseHexMessage.HexMessageString},{ex.Message}");
|
|
||||||
}
|
|
||||||
return await Task.FromResult(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using JiShe.CollectBus.Common.Consts;
|
|
||||||
using JiShe.CollectBus.Common.Enums;
|
|
||||||
using JiShe.CollectBus.Common.Extensions;
|
|
||||||
using JiShe.CollectBus.IotSystems.Ammeters;
|
|
||||||
using JiShe.CollectBus.IotSystems.Devices;
|
|
||||||
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.Protocol3761;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Protocol.AnalysisData.AFN_09H
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 5.9.1.2 F1:终端版本信息
|
|
||||||
/// </summary>
|
|
||||||
public class AFN9_F1_Analysis : IAnalysisStrategy<TB3761>
|
|
||||||
{
|
|
||||||
private readonly ILogger<AFN9_F1_Analysis> _logger;
|
|
||||||
|
|
||||||
private readonly DataStorage _dataStorage;
|
|
||||||
|
|
||||||
public AFN9_F1_Analysis(ILogger<AFN9_F1_Analysis> logger, DataStorage dataStorage)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_dataStorage = dataStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> ExecuteAsync(TB3761 input, Action<dynamic>? result = null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(input);
|
|
||||||
ArgumentNullException.ThrowIfNull(input.A.Code);
|
|
||||||
ArgumentNullException.ThrowIfNull(input.UnitData.HexMessageList);
|
|
||||||
var version = AnalysisDataUnit(input.UnitData.HexMessageList);
|
|
||||||
version.AreaCode = input.A.Code?.Substring(0, 4);
|
|
||||||
version.Address = input.A.Code?.Substring(4, 5);
|
|
||||||
var data = new AnalysisBaseDto<AFN9_F1_AnalysisDto?>()
|
|
||||||
{
|
|
||||||
FiledDesc = "终端版本信息",
|
|
||||||
DataValue = version,
|
|
||||||
ItemType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"
|
|
||||||
};
|
|
||||||
// 查询设备信息
|
|
||||||
DeviceInfo? deviceInfo = await _dataStorage.GetDeviceInfoAsync(input.A.Code);
|
|
||||||
if (deviceInfo != null)
|
|
||||||
{
|
|
||||||
data.ProjectId = deviceInfo.ProjectID;
|
|
||||||
data.DeviceId = deviceInfo.FocusId;
|
|
||||||
data.DatabaseBusiID = deviceInfo.DatabaseBusiID;
|
|
||||||
data.DeviceAddress = deviceInfo.FocusAddress;
|
|
||||||
data.DeviceType = MeterTypeEnum.Focus;
|
|
||||||
data.FocusId = deviceInfo.FocusId;
|
|
||||||
}
|
|
||||||
UnitDataAnalysis<AnalysisBaseDto<AFN9_F1_AnalysisDto?>> dto = new UnitDataAnalysis<AnalysisBaseDto<AFN9_F1_AnalysisDto?>>
|
|
||||||
{
|
|
||||||
Code = input.A.Code!,
|
|
||||||
AFN = input.AFN_FC.AFN,
|
|
||||||
Fn = input.DT.Fn,
|
|
||||||
Pn = input.DA.Pn,
|
|
||||||
Data = data,
|
|
||||||
ReceivedHexMessage = input.BaseHexMessage.HexMessageString,
|
|
||||||
MessageId = input.MessageId,
|
|
||||||
ReceivedTime = input.ReceivedTime,
|
|
||||||
DensityUnit = DensityUnit.None,
|
|
||||||
TimeDensity = -1,
|
|
||||||
DataType=IOTDBDataTypeConst.Data
|
|
||||||
};
|
|
||||||
result?.Invoke(dto);
|
|
||||||
await _dataStorage.SaveDataToIotDbAsync(dto);
|
|
||||||
return await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"09_1解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}");
|
|
||||||
}
|
|
||||||
return await Task.FromResult(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AFN9_F1_AnalysisDto AnalysisDataUnit(List<string> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using JiShe.CollectBus.Common.Consts;
|
|
||||||
using JiShe.CollectBus.Common.Enums;
|
|
||||||
using JiShe.CollectBus.Common.Extensions;
|
|
||||||
using JiShe.CollectBus.IotSystems.Ammeters;
|
|
||||||
using JiShe.CollectBus.IotSystems.Devices;
|
|
||||||
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_09H
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 5.9.2.4.9 F9:远程通信模块版本信息(只读解析SIM卡号)
|
|
||||||
/// </summary>
|
|
||||||
public class AFN9_F9_Analysis : IAnalysisStrategy<TB3761>
|
|
||||||
{
|
|
||||||
private readonly ILogger<AFN9_F9_Analysis> _logger;
|
|
||||||
private readonly DataStorage _dataStorage;
|
|
||||||
public AFN9_F9_Analysis(ILogger<AFN9_F9_Analysis> logger, DataStorage dataStorage)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_dataStorage = dataStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> ExecuteAsync(TB3761 input, Action<dynamic>? result = null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(input);
|
|
||||||
ArgumentNullException.ThrowIfNull(input.A.Code);
|
|
||||||
ArgumentNullException.ThrowIfNull(input.UnitData.HexMessageList);
|
|
||||||
var data = new AnalysisBaseDto<string?>()
|
|
||||||
{
|
|
||||||
FiledDesc = "远程通信模块版本信息",
|
|
||||||
DataValue = Encoding.ASCII.GetString(string.Join("", input.UnitData.HexMessageList.Skip(30).Take(20).ToList()).HexToByte()).Replace("\0", ""),
|
|
||||||
ItemType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"
|
|
||||||
};
|
|
||||||
// 查询设备信息
|
|
||||||
DeviceInfo? deviceInfo = await _dataStorage.GetDeviceInfoAsync(input.A.Code);
|
|
||||||
if (deviceInfo != null)
|
|
||||||
{
|
|
||||||
data.ProjectId = deviceInfo.ProjectID;
|
|
||||||
data.DeviceId = deviceInfo.FocusId;
|
|
||||||
data.DatabaseBusiID = deviceInfo.DatabaseBusiID;
|
|
||||||
data.DeviceAddress = deviceInfo.FocusAddress;
|
|
||||||
data.DeviceType = MeterTypeEnum.Focus;
|
|
||||||
data.FocusId = deviceInfo.FocusId;
|
|
||||||
}
|
|
||||||
UnitDataAnalysis<AnalysisBaseDto<string?>> dto = new UnitDataAnalysis<AnalysisBaseDto<string?>>
|
|
||||||
{
|
|
||||||
Code = input.A.Code!,
|
|
||||||
AFN = input.AFN_FC.AFN,
|
|
||||||
Fn = input.DT.Fn,
|
|
||||||
Pn = input.DA.Pn,
|
|
||||||
Data = data, //SIM卡
|
|
||||||
ReceivedHexMessage = input.BaseHexMessage.HexMessageString,
|
|
||||||
MessageId = input.MessageId,
|
|
||||||
ReceivedTime = input.ReceivedTime,
|
|
||||||
DensityUnit = DensityUnit.None,
|
|
||||||
TimeDensity = -1,
|
|
||||||
DataType= IOTDBDataTypeConst.Data
|
|
||||||
};
|
|
||||||
result?.Invoke(dto);
|
|
||||||
await _dataStorage.SaveDataToIotDbAsync(dto);
|
|
||||||
|
|
||||||
return await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"00_1解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}");
|
|
||||||
}
|
|
||||||
return await Task.FromResult(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,193 +0,0 @@
|
|||||||
using JiShe.CollectBus.Common.Consts;
|
|
||||||
using JiShe.CollectBus.Common.Enums;
|
|
||||||
using JiShe.CollectBus.Common.Extensions;
|
|
||||||
using JiShe.CollectBus.IotSystems.Ammeters;
|
|
||||||
using JiShe.CollectBus.IotSystems.Devices;
|
|
||||||
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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 5.10.1.3.1 F10:终端电能表/交流采样装置配置参数
|
|
||||||
/// </summary>
|
|
||||||
internal class AFN10_F10_Analysis : IAnalysisStrategy<TB3761>
|
|
||||||
{
|
|
||||||
private readonly ILogger<AFN10_F10_Analysis> _logger;
|
|
||||||
|
|
||||||
private readonly DataStorage _dataStorage;
|
|
||||||
public AFN10_F10_Analysis(ILogger<AFN10_F10_Analysis> logger, DataStorage dataStorage)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_dataStorage = dataStorage;
|
|
||||||
}
|
|
||||||
public async Task<bool> ExecuteAsync(TB3761 input, Action<dynamic>? result = null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(input);
|
|
||||||
ArgumentNullException.ThrowIfNull(input.A.Code);
|
|
||||||
ArgumentNullException.ThrowIfNull(input.UnitData.HexMessageList);
|
|
||||||
Tuple<int, List<AFN10F10Entity>> tuple = AFN10F10EntityAnalysis(input.UnitData.HexMessageList);
|
|
||||||
|
|
||||||
var data = new AnalysisBaseDto<AFN10_F10_AnalysisDto?>()
|
|
||||||
{
|
|
||||||
FiledDesc = "终端电能表/交流采样装置配置参数",
|
|
||||||
DataValue = new AFN10_F10_AnalysisDto()
|
|
||||||
{
|
|
||||||
AFN10F10Entitys = tuple.Item2,
|
|
||||||
ConfigNum = tuple.Item1
|
|
||||||
},
|
|
||||||
ItemType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"
|
|
||||||
};
|
|
||||||
// 查询设备信息
|
|
||||||
DeviceInfo? deviceInfo = await _dataStorage.GetDeviceInfoAsync(input.A.Code);
|
|
||||||
if (deviceInfo != null)
|
|
||||||
{
|
|
||||||
data.ProjectId = deviceInfo.ProjectID;
|
|
||||||
data.DeviceId = deviceInfo.FocusId;
|
|
||||||
data.DatabaseBusiID = deviceInfo.DatabaseBusiID;
|
|
||||||
data.DeviceAddress = deviceInfo.FocusAddress;
|
|
||||||
data.DeviceType = MeterTypeEnum.Focus;
|
|
||||||
data.FocusId = deviceInfo.FocusId;
|
|
||||||
}
|
|
||||||
UnitDataAnalysis<AnalysisBaseDto<AFN10_F10_AnalysisDto?>> dto = new UnitDataAnalysis<AnalysisBaseDto<AFN10_F10_AnalysisDto?>>
|
|
||||||
{
|
|
||||||
Code = input.A.Code!,
|
|
||||||
AFN = input.AFN_FC.AFN,
|
|
||||||
Fn = input.DT.Fn,
|
|
||||||
Pn = input.DA.Pn,
|
|
||||||
Data = data,
|
|
||||||
ReceivedHexMessage = input.BaseHexMessage.HexMessageString,
|
|
||||||
MessageId = input.MessageId,
|
|
||||||
ReceivedTime = input.ReceivedTime,
|
|
||||||
DensityUnit = DensityUnit.None,
|
|
||||||
TimeDensity = -1,
|
|
||||||
DataType=IOTDBDataTypeConst.Param
|
|
||||||
};
|
|
||||||
result?.Invoke(dto);
|
|
||||||
await _dataStorage.SaveDataToIotDbAsync<AFN10_F10_AnalysisDto?>(dto);
|
|
||||||
return await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"0A_10解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}");
|
|
||||||
}
|
|
||||||
return await Task.FromResult(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Tuple<int, List<AFN10F10Entity>> AFN10F10EntityAnalysis(List<string> hexMessageList)
|
|
||||||
{
|
|
||||||
List<Dictionary<string, string>> meterList = new List<Dictionary<string, string>>();
|
|
||||||
int total = $"{hexMessageList[5]}{hexMessageList[4]}".HexToDec();
|
|
||||||
List<AFN10F10Entity> aFN10F10Entitys = new List<AFN10F10Entity>();
|
|
||||||
for (int i = 0; i < total; i++)
|
|
||||||
{
|
|
||||||
List<string> 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]}",
|
|
||||||
|
|
||||||
};
|
|
||||||
aFN10F10Entity.ComAddress = $"{sArray[11]}{sArray[10]}{sArray[9]}{sArray[8]}{sArray[7]}{sArray[6]}";
|
|
||||||
aFN10F10Entity.ComPwd = $"{sArray[17]}{sArray[16]}{sArray[15]}{sArray[14]}{sArray[13]}{sArray[12]}".Substring(6, 6);
|
|
||||||
aFN10F10Entity.ElectricityRatesNum = sArray[18].HexTo4BinZero().Substring(2, 6).BinToDec();
|
|
||||||
aFN10F10Entity.CollectorAddress = $"{sArray[25]}{sArray[24]}{sArray[23]}{sArray[22]}{sArray[21]}{sArray[20]}";
|
|
||||||
string baudPort = sArray[4].HexTo4BinZero().PadLeft(8, '0'); //波特率和端口号放在一个字节内
|
|
||||||
aFN10F10Entity.BaudRate = GetBaudrate(baudPort.Substring(0, 3));
|
|
||||||
aFN10F10Entity.Port = baudPort.Substring(3, 5).BinToDec();
|
|
||||||
|
|
||||||
string dataDigit = sArray[19].HexTo4BinZero().PadLeft(8, '0'); //有功电能示值整数位及小数位个数
|
|
||||||
aFN10F10Entity.IntegerBitsNum = dataDigit.Substring(4, 2).BinToDec() + 4;
|
|
||||||
aFN10F10Entity.DecimalPlacesNum = dataDigit.Substring(6, 2).BinToDec() + 1;
|
|
||||||
|
|
||||||
string classNo = sArray[26].HexTo4BinZero().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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取波特率
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="binBaud"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取通信协议文本说明
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="protocol"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
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 "其他协议";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
using JiShe.CollectBus.Common.Consts;
|
|
||||||
using JiShe.CollectBus.Common.Enums;
|
|
||||||
using JiShe.CollectBus.Common.Extensions;
|
|
||||||
using JiShe.CollectBus.IotSystems.Ammeters;
|
|
||||||
using JiShe.CollectBus.IotSystems.Devices;
|
|
||||||
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;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Protocol.T37612012.AnalysisData.AFN_0AH
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 5.5.1.3.53 F66:定时上报 2 类数据任务设置
|
|
||||||
/// </summary>
|
|
||||||
public class AFN10_F66_Analysis : IAnalysisStrategy<TB3761>
|
|
||||||
{
|
|
||||||
private readonly ILogger<AFN10_F66_Analysis> _logger;
|
|
||||||
private readonly AnalysisStrategyContext _analysisStrategyContext;
|
|
||||||
private readonly DataStorage _dataStorage;
|
|
||||||
public AFN10_F66_Analysis(ILogger<AFN10_F66_Analysis> logger, AnalysisStrategyContext analysisStrategyContext, DataStorage dataStorage)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_analysisStrategyContext = analysisStrategyContext;
|
|
||||||
_dataStorage = dataStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> ExecuteAsync(TB3761 input, Action<dynamic>? result = null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(input);
|
|
||||||
ArgumentNullException.ThrowIfNull(input.A.Code);
|
|
||||||
ArgumentNullException.ThrowIfNull(input.UnitData.HexMessageList);
|
|
||||||
|
|
||||||
var data = new AnalysisBaseDto<AFN10_F66_AnalysisDto?>()
|
|
||||||
{
|
|
||||||
FiledDesc = "终端电能表/交流采样装置配置参数",
|
|
||||||
DataValue = await GenerateFinalResult(input.UnitData.HexMessageList),
|
|
||||||
ItemType = $"{input.AFN_FC.AFN.HexToDecStr().PadLeft(2, '0')}_{input.DT.Fn}"
|
|
||||||
};
|
|
||||||
// 查询设备信息
|
|
||||||
DeviceInfo? deviceInfo = await _dataStorage.GetDeviceInfoAsync(input.A.Code);
|
|
||||||
if (deviceInfo != null)
|
|
||||||
{
|
|
||||||
data.ProjectId = deviceInfo.ProjectID;
|
|
||||||
data.DeviceId = deviceInfo.FocusId;
|
|
||||||
data.DatabaseBusiID = deviceInfo.DatabaseBusiID;
|
|
||||||
data.DeviceAddress = deviceInfo.FocusAddress;
|
|
||||||
data.DeviceType = MeterTypeEnum.Focus;
|
|
||||||
data.FocusId = deviceInfo.FocusId;
|
|
||||||
}
|
|
||||||
UnitDataAnalysis<AnalysisBaseDto<AFN10_F66_AnalysisDto?>> dto = new UnitDataAnalysis<AnalysisBaseDto<AFN10_F66_AnalysisDto?>>
|
|
||||||
{
|
|
||||||
Code = input.A.Code!,
|
|
||||||
AFN = input.AFN_FC.AFN,
|
|
||||||
Fn = input.DT.Fn,
|
|
||||||
Pn = input.DA.Pn,
|
|
||||||
Data = data,
|
|
||||||
ReceivedHexMessage = input.BaseHexMessage.HexMessageString,
|
|
||||||
MessageId = input.MessageId,
|
|
||||||
ReceivedTime = input.ReceivedTime,
|
|
||||||
DensityUnit = DensityUnit.None,
|
|
||||||
TimeDensity = -1,
|
|
||||||
DataType = IOTDBDataTypeConst.Param
|
|
||||||
};
|
|
||||||
result?.Invoke(dto);
|
|
||||||
await _dataStorage.SaveDataToIotDbAsync<AFN10_F66_AnalysisDto?>(dto);
|
|
||||||
|
|
||||||
return await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"0A_66解析失败:{input.A?.Code}-{input.DT?.Fn ?? 0}-{input?.BaseHexMessage?.HexMessageString},{ex.Message}");
|
|
||||||
}
|
|
||||||
return await Task.FromResult(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<AFN10_F66_AnalysisDto> GenerateFinalResult(List<string> hexMessageList)
|
|
||||||
{
|
|
||||||
AFN10_F66_AnalysisDto entity = new AFN10_F66_AnalysisDto();
|
|
||||||
var cycleBin = hexMessageList[4].HexTo4BinZero().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);
|
|
||||||
|
|
||||||
await _analysisStrategyContext.ExecuteAsync<List<string>>(nameof(Appendix_A1), arrBaseTime, (value) =>
|
|
||||||
{
|
|
||||||
var baseTimeArrStr = (string)value;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user