Compare commits
59 Commits
master
...
feature_定时
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2878087652 | ||
|
|
f2bb529361 | ||
|
|
71f58154ec | ||
|
|
5f77e60eab | ||
|
|
024733062c | ||
|
|
97f8cdae16 | ||
|
|
25989539bf | ||
|
|
53b4e16f2d | ||
|
|
632518c0ce | ||
| 7a4a4e96ae | |||
| 2437d27c93 | |||
|
|
b6d2c7148d | ||
|
|
818b8b8013 | ||
| 42061493dc | |||
| e5487c5206 | |||
| 6f7abfdea2 | |||
|
|
0ec4c803e5 | ||
|
|
ea91622217 | ||
|
|
8f3dea6c2c | ||
| 26c1f52c17 | |||
|
|
04da31f423 | ||
|
|
c88d934feb | ||
| 179b9a2e91 | |||
| 87bf7feff2 | |||
| 8b33ec5400 | |||
| 2d2245fa85 | |||
|
|
4a609b6cf8 | ||
|
|
72c50311b6 | ||
| 7dd833257a | |||
| c999b0a0a9 | |||
| 679086b174 | |||
| db8b0e0b23 | |||
| dbb31d1c60 | |||
| f4b7afae48 | |||
|
|
6393db4dc6 | ||
| 86da556e98 | |||
| 95712c4c0e | |||
|
|
54c1643015 | ||
| f746d84225 | |||
|
|
71a5f7b89b | ||
|
|
91602a95c6 | ||
| 2d2bb0dcc0 | |||
| 2ead6e8242 | |||
| 6adbfe7883 | |||
| 76019141f1 | |||
|
|
27b7452cd1 | ||
|
|
6bc3b31f81 | ||
| ee1f3e3ca9 | |||
| 5d7e7bd7ed | |||
|
|
7a7a68b326 | ||
|
|
4ce1741f7e | ||
| 469ea285f2 | |||
| 3d83cf0ccb | |||
| 6ff97c1c0f | |||
| 77eabfec5d | |||
|
|
e806a127bf | ||
|
|
a78b819233 | ||
| d9491b9b33 | |||
| ead03c6361 |
103
Directory.Build.JiShe.targets
Normal file
103
Directory.Build.JiShe.targets
Normal file
@ -0,0 +1,103 @@
|
||||
<Project>
|
||||
<!-- JiShe.ServicePro包-->
|
||||
<ItemGroup>
|
||||
<PackageReference Update="JiShe.ServicePro.Core" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.CAP" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.CAP.EntityFrameworkCore" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.Localization" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.Oidc" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.TwoFactor" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.Shared.Hosting.Microservices" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.Shared.Hosting.Gateways" Version="$(ServiceProVersion)"/>
|
||||
|
||||
<PackageReference Update="JiShe.ServicePro.BasicManagement.Application" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.BasicManagement.Application.Contracts" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.BasicManagement.Domain" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.BasicManagement.Domain.Shared" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.BasicManagement.EntityFrameworkCore" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.BasicManagement.HttpApi" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.BasicManagement.HttpApi.Client" Version="$(ServiceProVersion)"/>
|
||||
|
||||
|
||||
<PackageReference Update="JiShe.ServicePro.NotificationManagement.Application" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.NotificationManagement.Application.Contracts" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.NotificationManagement.Domain" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.NotificationManagement.Domain.Shared" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.NotificationManagement.EntityFrameworkCore" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.NotificationManagement.HttpApi" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.NotificationManagement.HttpApi.Client" Version="$(ServiceProVersion)"/>
|
||||
|
||||
<PackageReference Update="JiShe.ServicePro.DataDictionaryManagement.Application" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DataDictionaryManagement.Application.Contracts" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DataDictionaryManagement.Domain" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DataDictionaryManagement.Domain.Shared" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DataDictionaryManagement.EntityFrameworkCore" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DataDictionaryManagement.HttpApi" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DataDictionaryManagement.HttpApi.Client" Version="$(ServiceProVersion)"/>
|
||||
|
||||
<PackageReference Update="JiShe.ServicePro.LanguageManagement.Application" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.LanguageManagement.Application.Contracts" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.LanguageManagement.Domain" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.LanguageManagement.Domain.Shared" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.LanguageManagement.EntityFrameworkCore" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.LanguageManagement.HttpApi" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.LanguageManagement.HttpApi.Client" Version="$(ServiceProVersion)"/>
|
||||
|
||||
<PackageReference Update="JiShe.ServicePro.CodeManagement.Application" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.CodeManagement.Application.Contracts" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.CodeManagement.Domain" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.CodeManagement.Domain.Shared" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.CodeManagement.EntityFrameworkCore" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.CodeManagement.HttpApi" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.CodeManagement.HttpApi.Client" Version="$(ServiceProVersion)"/>
|
||||
|
||||
|
||||
<PackageReference Update="JiShe.ServicePro.TemplateManagement.Application" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.TemplateManagement.Application.Contracts" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.TemplateManagement.Domain" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.TemplateManagement.Domain.Shared" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.TemplateManagement.EntityFrameworkCore" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.TemplateManagement.HttpApi" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.TemplateManagement.HttpApi.Client" Version="$(ServiceProVersion)"/>
|
||||
|
||||
<PackageReference Update="JiShe.ServicePro.DynamicMenuManagement.Application" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DynamicMenuManagement.Application.Contracts" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DynamicMenuManagement.Domain" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DynamicMenuManagement.Domain.Shared" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DynamicMenuManagement.EntityFrameworkCore" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DynamicMenuManagement.HttpApi" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DynamicMenuManagement.HttpApi.Client" Version="$(ServiceProVersion)"/>
|
||||
|
||||
|
||||
<PackageReference Update="JiShe.ServicePro.FileManagement.Application" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.FileManagement.Application.Contracts" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.FileManagement.Domain" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.FileManagement.Domain.Shared" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.FileManagement.EntityFrameworkCore" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.FileManagement.HttpApi" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.FileManagement.HttpApi.Client" Version="$(ServiceProVersion)"/>
|
||||
|
||||
|
||||
<PackageReference Update="JiShe.ServicePro.FreeRedisProvider" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.FreeSqlProvider" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.Kafka" Version="$(ServiceProVersion)"/>
|
||||
|
||||
|
||||
<PackageReference Update="JiShe.ServicePro.IoTDBManagement.Application" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.IoTDBManagement.Application.Contracts" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.IoTDBManagement.Domain" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.IoTDBManagement.Domain.Shared" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.IoTDBManagement.EntityFrameworkCore" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.IoTDBManagement.HttpApi" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.IoTDBManagement.HttpApi.Client" Version="$(ServiceProVersion)"/>
|
||||
|
||||
<PackageReference Update="JiShe.ServicePro.DeviceManagement.Application" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DeviceManagement.Application.Contracts" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DeviceManagement.Domain" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DeviceManagement.Domain.Shared" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DeviceManagement.EntityFrameworkCore" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DeviceManagement.HttpApi" Version="$(ServiceProVersion)"/>
|
||||
<PackageReference Update="JiShe.ServicePro.DeviceManagement.HttpApi.Client" Version="$(ServiceProVersion)"/>
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
42
Directory.Build.Microsoft.targets
Normal file
42
Directory.Build.Microsoft.targets
Normal file
@ -0,0 +1,42 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.Extensions.DependencyModel" Version="$(DotnetInfoVersion)"/>
|
||||
<PackageReference Update="Microsoft.Extensions.Diagnostics.HealthChecks" Version="$(DotnetInfoVersion)"/>
|
||||
<PackageReference Update="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(DotnetInfoVersion)"/>
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Tools" Version="$(DotnetInfoVersion)"/>
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Relational" Version="$(DotnetInfoVersion)" />
|
||||
<PackageReference Update="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="$(DotnetInfoVersion)"/>
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Proxies" Version="$(DotnetInfoVersion)"/>
|
||||
<PackageReference Update="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="$(DotnetInfoVersion)"/>
|
||||
<PackageReference Update="Microsoft.Extensions.Caching.StackExchangeRedis" Version="$(DotnetInfoVersion)"/>
|
||||
<PackageReference Update="Microsoft.Extensions.Http.Polly" Version="$(DotnetInfoVersion)"/>
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Abstractions" Version="$(DotnetInfoVersion)"/>
|
||||
<PackageReference Update="Microsoft.Extensions.FileProviders.Embedded" Version="$(DotnetInfoVersion)"/>
|
||||
|
||||
<PackageReference Update="Microsoft.Extensions.Hosting" Version="$(DotnetInfoVersion)" />
|
||||
<PackageReference Update="Microsoft.Extensions.DependencyInjection" Version="$(DotnetInfoVersion)" />
|
||||
<PackageReference Update="Microsoft.Extensions.Http" Version="$(DotnetInfoVersion)"/>
|
||||
|
||||
<PackageReference Update="Microsoft.AspNetCore.Mvc.Core" Version="2.2.0"/>
|
||||
<PackageReference Update="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0"/>
|
||||
<PackageReference Update="Microsoft.CSharp" Version="4.12.0"/>
|
||||
<PackageReference Update="Microsoft.CodeAnalysis.CSharp" Version="4.12.0"/>
|
||||
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||
<PackageReference Update="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="$(DotnetInfoVersion)" />
|
||||
<PackageReference Update="Microsoft.Extensions.FileProviders.Embedded" Version="$(DotnetInfoVersion)" />
|
||||
|
||||
<PackageReference Update="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
|
||||
<PackageReference Update="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" />
|
||||
|
||||
<PackageReference Update="Microsoft.Extensions.Logging" Version="$(DotnetInfoVersion)"/>
|
||||
|
||||
<PackageReference Update="Microsoft.Extensions.Configuration.Abstractions" Version="$(DotnetInfoVersion)"/>
|
||||
<PackageReference Update="Microsoft.Extensions.Hosting.Abstractions" Version="$(DotnetInfoVersion)"/>
|
||||
<PackageReference Update="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(DotnetInfoVersion)"/>
|
||||
|
||||
<PackageReference Update="Microsoft.Extensions.Caching.Memory" Version="$(DotnetInfoVersion)"/>
|
||||
<PackageReference Update="Microsoft.Extensions.Caching.Abstractions" Version="$(DotnetInfoVersion)"/>
|
||||
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
102
Directory.Build.Volo.targets
Normal file
102
Directory.Build.Volo.targets
Normal file
@ -0,0 +1,102 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Volo.Abp.Autofac" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Threading" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Json" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Json.Abstractions" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Validation" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Ddd.Domain" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AutoMapper" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Settings" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.ObjectMapping" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Identity.AspNetCore" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Caching" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.BlobStoring.Aliyun" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.BlobStoring" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.BlobStoring.FileSystem" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.BackgroundJobs" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Authorization.Abstractions" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.BackgroundJobs.HangFire" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AspNetCore.SignalR" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.TestBase" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.EntityFrameworkCore.MySQL" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.PermissionManagement.EntityFrameworkCore" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.SettingManagement.EntityFrameworkCore" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Identity.EntityFrameworkCore" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.BackgroundJobs.EntityFrameworkCore" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AuditLogging.EntityFrameworkCore" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.TenantManagement.EntityFrameworkCore" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.FeatureManagement.EntityFrameworkCore" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AspNetCore.Mvc.Contracts" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Account.Web" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Caching.StackExchangeRedis" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.EntityFrameworkCore.PostgreSql" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AspNetCore.Serilog" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Swashbuckle" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Ddd.Application" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Ddd.Application.Contracts" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Authorization" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Dapper" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AspNetCore.Mvc" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Http.Client" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.MongoDB" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.BackgroundJobs.MongoDB" Version="$(VoloAbpVersion)" />
|
||||
<PackageReference Update="Volo.Abp.AuditLogging.MongoDB" Version="$(VoloAbpVersion)" />
|
||||
<PackageReference Update="Volo.Abp.EntityFrameworkCore.Sqlite" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Http.Client.IdentityModel" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AspNetCore.MultiTenancy" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Account.Application" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Identity.Application" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.PermissionManagement.Application" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.TenantManagement.Application" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.FeatureManagement.Application" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.SettingManagement.Application" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.ObjectExtending" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Account.Application.Contracts" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Identity.Application.Contracts" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.PermissionManagement.Application.Contracts" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.TenantManagement.Application.Contracts" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.FeatureManagement.Application.Contracts" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.SettingManagement.Application.Contracts" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Emailing" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.PermissionManagement.Domain.Identity" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.BackgroundJobs.Domain" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AuditLogging.Domain" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.TenantManagement.Domain" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.FeatureManagement.Domain" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.SettingManagement.Domain" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Identity.Domain" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Identity.Domain.Shared" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.BackgroundJobs.Domain.Shared" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AuditLogging.Domain.Shared" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.TenantManagement.Domain.Shared" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.FeatureManagement.Domain.Shared" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.PermissionManagement.Domain.Shared" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.SettingManagement.Domain.Shared" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Account.HttpApi" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Identity.HttpApi" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.PermissionManagement.HttpApi" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.TenantManagement.HttpApi" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.FeatureManagement.HttpApi" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.SettingManagement.HttpApi" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Account.HttpApi.Client" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Identity.HttpApi.Client" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.PermissionManagement.HttpApi.Client" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.TenantManagement.HttpApi.Client" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.FeatureManagement.HttpApi.Client" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.SettingManagement.HttpApi.Client" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AspNetCore" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Core" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.EntityFrameworkCore" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AspNetCore.TestBase" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.EventBus" Version="$(VoloAbpVersion)"/>
|
||||
<PackageReference Update="Volo.Abp.Localization" Version="$(VoloAbpVersion)" />
|
||||
<PackageReference Update="Volo.Abp.DistributedLocking" Version="$(VoloAbpVersion)" />
|
||||
<PackageReference Update="Volo.Abp.Ddd.Domain.Shared" Version="$(VoloAbpVersion)" />
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
12
Directory.Build.props
Normal file
12
Directory.Build.props
Normal file
@ -0,0 +1,12 @@
|
||||
<Project>
|
||||
<!-- 定义项目加载属性 -->
|
||||
<PropertyGroup>
|
||||
<!--JiShe.ServicePro版本-->
|
||||
<ServiceProVersion>1.0.5.08</ServiceProVersion>
|
||||
<!--Volo Abp 版本-->
|
||||
<VoloAbpVersion>9.1.1</VoloAbpVersion>
|
||||
|
||||
<!--Dotnet Info 版本-->
|
||||
<DotnetInfoVersion>9.0.0</DotnetInfoVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
155
Directory.Build.targets
Normal file
155
Directory.Build.targets
Normal file
@ -0,0 +1,155 @@
|
||||
<Project>
|
||||
|
||||
<Import Project="Directory.Build.Microsoft.targets"/>
|
||||
<Import Project="Directory.Build.Volo.targets"/>
|
||||
<Import Project="Directory.Build.JiShe.targets"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>1.0.1</Version>
|
||||
<Description>JiShe Service Pro</Description>
|
||||
<NoWarn>$(NoWarn);CS1591;CS0436;NU1504</NoWarn>
|
||||
<ServiceProjectType>app</ServiceProjectType>
|
||||
<ImplicitUsings>true</ImplicitUsings>
|
||||
<Authors>湖南集社电子技术有限公司</Authors>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageIcon>icon.png</PackageIcon>
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="$(MSBuildThisFileDirectory)icon.png" Pack="true" PackagePath=""/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Ocelot 网关-->
|
||||
<PackageReference Update="Ocelot" Version="18.0.0"/>
|
||||
<PackageReference Update="Ocelot.Provider.Consul" Version="18.0.0"/>
|
||||
<PackageReference Update="Ocelot.Provider.Polly" Version="18.0.0"/>
|
||||
|
||||
<!--FreeRedis-->
|
||||
<PackageReference Update="FreeRedis" Version="1.3.7"/>
|
||||
<PackageReference Update="FreeRedis.DistributedCache" Version="1.3.7"/>
|
||||
|
||||
<!--FreeSql-->
|
||||
<PackageReference Update="FreeSql.Cloud" Version="2.0.1" />
|
||||
<PackageReference Update="FreeSql.Extensions.JsonMap" Version="3.5.206" />
|
||||
<PackageReference Update="FreeSql" Version="3.5.206"/>
|
||||
<PackageReference Update="FreeSql.Provider.MysqlConnector" Version="3.5.206"/>
|
||||
<PackageReference Update="FreeSql.Provider.Sqlite" Version="3.2.806"/>
|
||||
<PackageReference Update="FreeSql.Provider.SqlServer" Version="3.5.206" />
|
||||
|
||||
|
||||
<!-- 单元测试包-->
|
||||
<PackageReference Update="xunit" Version="2.9.2"/>
|
||||
<PackageReference Update="xunit.extensibility.execution" Version="2.9.2"/>
|
||||
<PackageReference Update="xunit.runner.visualstudio" Version="2.8.2"/>
|
||||
<PackageReference Update="NSubstitute" Version="5.1.0"/>
|
||||
<PackageReference Update="Shouldly" Version="4.2.1"/>
|
||||
<PackageReference Update="coverlet.collector" Version="6.0.0"/>
|
||||
<PackageReference Update="JunitXml.TestLogger" Version="3.0.134"/>
|
||||
<PackageReference Update="AutoFixture.Xunit2" Version="4.18.1"/>
|
||||
|
||||
<!-- Hangfire 后台任务-->
|
||||
<PackageReference Update="Hangfire.Redis.StackExchange" Version="1.9.3"/>
|
||||
|
||||
<!-- CAP 分布式事务-->
|
||||
<PackageReference Update="DotNetCore.CAP" Version="8.3.3"/>
|
||||
<PackageReference Update="DotNetCore.CAP.Dashboard" Version="8.3.3"/>
|
||||
<PackageReference Update="DotNetCore.CAP.Mysql" Version="8.3.3"/>
|
||||
<PackageReference Update="DotNetCore.CAP.RabbitMQ" Version="8.3.3"/>
|
||||
<PackageReference Update="DotNetCore.CAP.InMemoryStorage" Version="8.3.3"/>
|
||||
<PackageReference Update="Savorboard.CAP.InMemoryMessageQueue" Version="8.2.1"/>
|
||||
|
||||
<!-- Swagger-->
|
||||
<PackageReference Update="Swashbuckle.AspNetCore.SwaggerUI" Version="6.8.1"/>
|
||||
<PackageReference Update="Swashbuckle.AspNetCore" Version="6.8.1"/>
|
||||
<PackageReference Update="Swashbuckle.AspNetCore.Annotations" Version="6.5.0"/>
|
||||
|
||||
<!-- Serilog 日志-->
|
||||
<PackageReference Update="Serilog" Version="4.0.2"/>
|
||||
<PackageReference Update="Serilog.Extensions.Logging" Version="8.0.0"/>
|
||||
<PackageReference Update="Serilog.Sinks.Async" Version="2.0.0"/>
|
||||
<PackageReference Update="Serilog.Sinks.File" Version="6.0.0"/>
|
||||
<PackageReference Update="Serilog.Sinks.Console" Version="6.0.0"/>
|
||||
<PackageReference Update="Serilog.AspNetCore" Version="8.0.2"/>
|
||||
<PackageReference Update="Serilog.Exceptions" Version="8.4.0"/>
|
||||
<PackageReference Update="Serilog.Settings.Configuration" Version="8.0.2"/>
|
||||
<PackageReference Update="Serilog.Sinks.Elasticsearch" Version="9.0.3"/>
|
||||
|
||||
|
||||
|
||||
<!-- Magicodes 导入导出Excel-->
|
||||
<PackageReference Update="Magicodes.IE.Excel" Version="2.7.4.5"/>
|
||||
<PackageReference Update="Magicodes.IE.Excel.AspNetCore" Version="2.7.4.5"/>
|
||||
|
||||
<!-- MiniProfiler 查看接口耗时,sql耗时-->
|
||||
<PackageReference Update="MiniProfiler.AspNetCore.Mvc" Version="4.3.8"/>
|
||||
<PackageReference Update="MiniProfiler.EntityFrameworkCore" Version="4.3.8"/>
|
||||
<PackageReference Update="MiniProfiler.Shared" Version="4.3.8"/>
|
||||
|
||||
<!-- Polly 重试机制-->
|
||||
<PackageReference Update="Polly" Version="8.5.2"/>
|
||||
<PackageReference Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
|
||||
<PackageReference Include="Polly.Core" Version="8.5.2" />
|
||||
|
||||
<!-- Octokit 邮件-->
|
||||
<PackageReference Update="Octokit" Version="14.0.0"/>
|
||||
|
||||
<!-- Humanizer 中文格式包-->
|
||||
<PackageReference Update="Humanizer.Core.zh-Hans" Version="2.14.1"/>
|
||||
|
||||
<!-- NEST ElasticSearch-->
|
||||
<PackageReference Update="NEST" Version="7.17.5"/>
|
||||
|
||||
<PackageReference Update="WebApiClientCore" Version="2.1.5" />
|
||||
|
||||
<!-- redis分布式锁-->
|
||||
<PackageReference Update="DistributedLock.Redis" Version="1.0.3" />
|
||||
|
||||
<!-- Humanizer 中文格式包-->
|
||||
<PackageReference Update="Humanizer.Core.zh-Hans" Version="2.14.1"/>
|
||||
|
||||
<PackageReference Update="Scriban" Version="5.4.4" />
|
||||
|
||||
<PackageReference Update="Otp.NET" Version="1.4.0" />
|
||||
<PackageReference Update="QRCoder" Version="1.6.0" />
|
||||
|
||||
|
||||
<!--Flurl-->
|
||||
<PackageReference Update="Flurl.Http" Version="4.0.2" />
|
||||
<PackageReference Update="Mapster" Version="7.4.0" />
|
||||
|
||||
<!--Apache.IoTDB-->
|
||||
<PackageReference Update="Apache.IoTDB" Version="2.0.2" />
|
||||
|
||||
<!--Kafka-->
|
||||
<PackageReference Update="Confluent.Kafka" Version="2.9.0" />
|
||||
<!--MongoDB-->
|
||||
<PackageReference Update="MongoDB.Bson" Version="2.22.0" />
|
||||
<!--Cassandra-->
|
||||
<PackageReference Update="CassandraCSharpDriver" Version="3.22.0" />
|
||||
|
||||
<!--其他-->
|
||||
<PackageReference Update="BenchmarkDotNet" Version="0.15.0" />
|
||||
<PackageReference Update="System.Linq.Dynamic.Core" Version="1.6.4" />
|
||||
<PackageReference Update="JetBrains.Annotations" Version="2024.2.0" />
|
||||
<PackageReference Update="System.Text.Json" Version="9.0.0" />
|
||||
<PackageReference Update="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Update="Swashbuckle.AspNetCore" Version="7.0.0" />
|
||||
<PackageReference Update="Swashbuckle.AspNetCore.Annotations" Version="7.0.0" />
|
||||
<PackageReference Update="Hangfire.Redis.StackExchange" Version="1.9.4" />
|
||||
|
||||
<!--HealthChecks-->
|
||||
<PackageReference Update="AspNetCore.HealthChecks.Kafka" Version="9.0.0" />
|
||||
<PackageReference Update="AspNetCore.HealthChecks.Redis" Version="9.0.0" />
|
||||
<PackageReference Update="AspNetCore.HealthChecks.UI" Version="9.0.0" />
|
||||
<PackageReference Update="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
|
||||
<PackageReference Update="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="9.0.0" />
|
||||
|
||||
<!--TouchSocket-->
|
||||
<PackageReference Update="TouchSocket" Version="3.1.5" />
|
||||
<PackageReference Update="TouchSocket.Hosting" Version="3.1.5" />
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
22
Dockerfile
22
Dockerfile
@ -1,17 +1,21 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
EXPOSE 8080
|
||||
EXPOSE 10500
|
||||
ENV TZ=Asia/Shanghai
|
||||
ENV ASPNETCORE_ENVIRONMENT=Production
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
# WORKDIR /src
|
||||
COPY ["JiShe.CollectBus.Main.sln", "."]
|
||||
COPY ["common.props", "."]
|
||||
COPY ["Directory.Build.JiShe.targets", "."]
|
||||
COPY ["Directory.Build.Microsoft.targets", "."]
|
||||
COPY ["Directory.Build.props", "."]
|
||||
COPY ["Directory.Build.targets", "."]
|
||||
COPY ["Directory.Build.Volo.targets", "."]
|
||||
COPY ["NuGet.Config", "."]
|
||||
COPY ["web/", "web/"]
|
||||
COPY ["modules/", "modules/"]
|
||||
COPY ["services/", "services/"]
|
||||
COPY ["shared/", "shared/"]
|
||||
COPY ["protocols/", "protocols/"]
|
||||
@ -38,15 +42,7 @@ 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"]
|
||||
|
||||
|
||||
|
||||
@ -11,8 +11,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Applicatio
|
||||
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}"
|
||||
@ -21,23 +19,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Common", "
|
||||
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}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.Protocols", "4.Protocols", "{3C3F9DB2-EC97-4464-B49F-BF1A0C2B46DC}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2.Services", "2.Services", "{BA4DA3E7-9AD0-47AD-A0E6-A0BB6700DA23}"
|
||||
EndProject
|
||||
@ -54,6 +40,11 @@ EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "0.Docs", "0.Docs", "{D8346C4C-55B8-43E8-A6B8-E59D56FE6D92}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitignore = .gitignore
|
||||
Directory.Build.JiShe.targets = Directory.Build.JiShe.targets
|
||||
Directory.Build.Microsoft.targets = Directory.Build.Microsoft.targets
|
||||
Directory.Build.props = Directory.Build.props
|
||||
Directory.Build.targets = Directory.Build.targets
|
||||
Directory.Build.Volo.targets = Directory.Build.Volo.targets
|
||||
Dockerfile = Dockerfile
|
||||
NuGet.Config = NuGet.Config
|
||||
PackageAndPublish.bat = PackageAndPublish.bat
|
||||
@ -61,10 +52,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "0.Docs", "0.Docs", "{D8346C
|
||||
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
|
||||
@ -87,10 +74,6 @@ Global
|
||||
{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
|
||||
@ -107,30 +90,10 @@ Global
|
||||
{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
|
||||
@ -147,14 +110,6 @@ Global
|
||||
{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
|
||||
@ -164,23 +119,15 @@ Global
|
||||
{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}
|
||||
|
||||
@ -11,8 +11,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Applicatio
|
||||
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}"
|
||||
@ -21,30 +19,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiShe.CollectBus.Common", "
|
||||
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}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.Protocols", "4.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}"
|
||||
@ -56,24 +40,17 @@ EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "0.Docs", "0.Docs", "{D8346C4C-55B8-43E8-A6B8-E59D56FE6D92}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitignore = .gitignore
|
||||
Directory.Build.JiShe.targets = Directory.Build.JiShe.targets
|
||||
Directory.Build.Microsoft.targets = Directory.Build.Microsoft.targets
|
||||
Directory.Build.props = Directory.Build.props
|
||||
Directory.Build.targets = Directory.Build.targets
|
||||
Directory.Build.Volo.targets = Directory.Build.Volo.targets
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -96,10 +73,6 @@ Global
|
||||
{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
|
||||
@ -116,34 +89,10 @@ Global
|
||||
{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
|
||||
@ -160,30 +109,6 @@ Global
|
||||
{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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -193,28 +118,15 @@ Global
|
||||
{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}
|
||||
{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
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="JiSheTeam" value="http://proget.jisheyun.com:9511/nuget/JiSheTeam/v3/index.json" protocolVersion="3" allowInsecureConnections="true" />
|
||||
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
@ -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>
|
||||
@ -1,64 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JiShe.CollectBus.Cassandra
|
||||
{
|
||||
public class CassandraConfig
|
||||
{
|
||||
public Node[] Nodes { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Keyspace { get; set; }
|
||||
public string ConsistencyLevel { get; set; }
|
||||
public Pooling PoolingOptions { get; set; }
|
||||
public Socket SocketOptions { get; set; }
|
||||
public Query QueryOptions { get; set; }
|
||||
|
||||
public ReplicationStrategy ReplicationStrategy { get; set; }
|
||||
}
|
||||
|
||||
public class Pooling
|
||||
{
|
||||
public int CoreConnectionsPerHost { get; set; }
|
||||
public int MaxConnectionsPerHost { get; set; }
|
||||
public int MaxRequestsPerConnection { get; set; }
|
||||
}
|
||||
|
||||
public class Socket
|
||||
{
|
||||
public int ConnectTimeoutMillis { get; set; }
|
||||
public int ReadTimeoutMillis { get; set; }
|
||||
}
|
||||
|
||||
public class Query
|
||||
{
|
||||
public string ConsistencyLevel { get; set; }
|
||||
public string SerialConsistencyLevel { get; set; }
|
||||
public bool DefaultIdempotence { get; set; }
|
||||
}
|
||||
|
||||
public class ReplicationStrategy
|
||||
{
|
||||
public string Class { get; set; }
|
||||
public DataCenter[] DataCenters { get; set; }
|
||||
}
|
||||
|
||||
public class DataCenter
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int ReplicationFactor { get; set; }
|
||||
public string Strategy { get; set; }
|
||||
}
|
||||
|
||||
public class Node
|
||||
{
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string DataCenter { get; set; }
|
||||
public string Rack { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,154 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Cassandra;
|
||||
using Cassandra.Mapping;
|
||||
using Cassandra.Data.Linq;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using JiShe.CollectBus.Common.Attributes;
|
||||
|
||||
namespace JiShe.CollectBus.Cassandra
|
||||
{
|
||||
public class CassandraProvider : IDisposable, ICassandraProvider, ISingletonDependency
|
||||
{
|
||||
private readonly ILogger<CassandraProvider> _logger;
|
||||
|
||||
public Cluster Instance { get; set; }
|
||||
|
||||
public ISession Session { get; set; }
|
||||
|
||||
public CassandraConfig CassandraConfig { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="logger"></param>
|
||||
public CassandraProvider(
|
||||
IOptions<CassandraConfig> options,
|
||||
ILogger<CassandraProvider> logger)
|
||||
{
|
||||
CassandraConfig = options.Value;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task InitClusterAndSessionAsync()
|
||||
{
|
||||
InitClusterAndSession();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void InitClusterAndSession()
|
||||
{
|
||||
GetCluster((keyspace) =>
|
||||
{
|
||||
GetSession(keyspace);
|
||||
});
|
||||
}
|
||||
|
||||
public Cluster GetCluster(Action<string?>? callback=null)
|
||||
{
|
||||
var clusterBuilder = Cluster.Builder();
|
||||
|
||||
// 添加多个节点
|
||||
foreach (var node in CassandraConfig.Nodes)
|
||||
{
|
||||
clusterBuilder.AddContactPoint(node.Host)
|
||||
.WithPort(node.Port);
|
||||
}
|
||||
|
||||
clusterBuilder.WithCredentials(CassandraConfig.Username, CassandraConfig.Password);
|
||||
|
||||
// 优化连接池配置
|
||||
var poolingOptions = new PoolingOptions()
|
||||
.SetCoreConnectionsPerHost(HostDistance.Local, CassandraConfig.PoolingOptions.CoreConnectionsPerHost)
|
||||
.SetMaxConnectionsPerHost(HostDistance.Local, CassandraConfig.PoolingOptions.MaxConnectionsPerHost)
|
||||
.SetMaxRequestsPerConnection(CassandraConfig.PoolingOptions.MaxRequestsPerConnection)
|
||||
.SetHeartBeatInterval(30000); // 30秒心跳
|
||||
|
||||
clusterBuilder.WithPoolingOptions(poolingOptions);
|
||||
|
||||
// 优化Socket配置
|
||||
var socketOptions = new SocketOptions()
|
||||
.SetConnectTimeoutMillis(CassandraConfig.SocketOptions.ConnectTimeoutMillis)
|
||||
.SetReadTimeoutMillis(CassandraConfig.SocketOptions.ReadTimeoutMillis)
|
||||
.SetTcpNoDelay(true) // 启用Nagle算法
|
||||
.SetKeepAlive(true) // 启用TCP保活
|
||||
.SetReceiveBufferSize(32768) // 32KB接收缓冲区
|
||||
.SetSendBufferSize(32768); // 32KB发送缓冲区
|
||||
|
||||
clusterBuilder.WithSocketOptions(socketOptions);
|
||||
|
||||
// 优化查询选项
|
||||
var queryOptions = new QueryOptions()
|
||||
.SetConsistencyLevel((ConsistencyLevel)Enum.Parse(typeof(ConsistencyLevel), CassandraConfig.QueryOptions.ConsistencyLevel))
|
||||
.SetSerialConsistencyLevel((ConsistencyLevel)Enum.Parse(typeof(ConsistencyLevel), CassandraConfig.QueryOptions.SerialConsistencyLevel))
|
||||
.SetDefaultIdempotence(CassandraConfig.QueryOptions.DefaultIdempotence)
|
||||
.SetPageSize(5000); // 增加页面大小
|
||||
|
||||
clusterBuilder.WithQueryOptions(queryOptions);
|
||||
|
||||
// 启用压缩
|
||||
clusterBuilder.WithCompression(CompressionType.LZ4);
|
||||
|
||||
// 配置重连策略
|
||||
clusterBuilder.WithReconnectionPolicy(new ExponentialReconnectionPolicy(1000, 10 * 60 * 1000));
|
||||
Instance = clusterBuilder.Build();
|
||||
callback?.Invoke(null);
|
||||
return Instance;
|
||||
}
|
||||
|
||||
public ISession GetSession(string? keyspace = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyspace))
|
||||
{
|
||||
keyspace = CassandraConfig.Keyspace;
|
||||
}
|
||||
Session = Instance.Connect();
|
||||
var replication = GetReplicationStrategy();
|
||||
Session.CreateKeyspaceIfNotExists(keyspace, replication);
|
||||
Session.ChangeKeyspace(keyspace);
|
||||
return Session;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> GetReplicationStrategy()
|
||||
{
|
||||
var strategy = CassandraConfig.ReplicationStrategy.Class;
|
||||
var dataCenters = CassandraConfig.ReplicationStrategy.DataCenters;
|
||||
|
||||
switch (strategy)
|
||||
{
|
||||
case "NetworkTopologyStrategy":
|
||||
var networkDic = new Dictionary<string, string> { { "class", "NetworkTopologyStrategy" } };
|
||||
foreach (var dataCenter in dataCenters)
|
||||
{
|
||||
networkDic.Add(dataCenter.Name, dataCenter.ReplicationFactor.ToString());
|
||||
}
|
||||
return networkDic;
|
||||
case "SimpleStrategy":
|
||||
var dic = new Dictionary<string, string> { { "class", "SimpleStrategy" } };
|
||||
if (dataCenters.Length >= 1)
|
||||
{
|
||||
dic.Add("replication_factor", dataCenters[0].ReplicationFactor.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("SimpleStrategy 不支持多个数据中心!");
|
||||
}
|
||||
return dic;
|
||||
default:
|
||||
throw new ArgumentNullException($"Strategy", "Strategy配置错误!");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Instance.Dispose();
|
||||
Session.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,86 +0,0 @@
|
||||
using System.Linq.Expressions;
|
||||
using Cassandra.Mapping;
|
||||
using JiShe.CollectBus.Cassandra.Extensions;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
|
||||
namespace JiShe.CollectBus.Cassandra
|
||||
{
|
||||
public class CassandraRepository<TEntity, TKey>
|
||||
: ICassandraRepository<TEntity, TKey>
|
||||
where TEntity : class, ICassandraEntity<TKey>
|
||||
{
|
||||
private readonly ICassandraProvider _cassandraProvider;
|
||||
public CassandraRepository(ICassandraProvider cassandraProvider, MappingConfiguration mappingConfig)
|
||||
{
|
||||
_cassandraProvider = cassandraProvider;
|
||||
Mapper = new Mapper(cassandraProvider.Session, mappingConfig);
|
||||
cassandraProvider.Session.CreateTable<TEntity>(cassandraProvider.CassandraConfig.Keyspace);
|
||||
}
|
||||
|
||||
public readonly IMapper Mapper;
|
||||
|
||||
public virtual async Task<TEntity> GetAsync(TKey id)
|
||||
{
|
||||
return await GetAsync("WHERE id = ?", id);
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity?> GetAsync(string cql, params object[] args)
|
||||
{
|
||||
return await Mapper.SingleAsync<TEntity?>(cql, args);
|
||||
}
|
||||
|
||||
|
||||
public virtual async Task<TEntity> FirstOrDefaultAsync(TKey id)
|
||||
{
|
||||
return await FirstOrDefaultAsync("WHERE id = ?", id);
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity?> FirstOrDefaultAsync(string cql, params object[] args)
|
||||
{
|
||||
return await Mapper.FirstOrDefaultAsync<TEntity>(cql, args);
|
||||
}
|
||||
|
||||
|
||||
public virtual async Task<List<TEntity>?> GetListAsync(string? cql = null, params object[] args)
|
||||
{
|
||||
return cql.IsNullOrWhiteSpace() ? (await Mapper.FetchAsync<TEntity>()).ToList() : (await Mapper.FetchAsync<TEntity>(cql, args)).ToList();
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity> InsertAsync(TEntity entity)
|
||||
{
|
||||
await Mapper.InsertAsync(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity> UpdateAsync(TEntity entity)
|
||||
{
|
||||
await Mapper.UpdateAsync(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public virtual async Task DeleteAsync(TEntity entity)
|
||||
{
|
||||
await Mapper.DeleteAsync(entity);
|
||||
}
|
||||
|
||||
public virtual async Task DeleteAsync(TKey id)
|
||||
{
|
||||
await Mapper.DeleteAsync<TEntity>("WHERE id = ?", id);
|
||||
}
|
||||
|
||||
public virtual async Task<List<TEntity>> GetPagedListAsync(
|
||||
int skipCount,
|
||||
int maxResultCount,
|
||||
string sorting)
|
||||
{
|
||||
var cql = $"SELECT * FROM {typeof(TEntity).Name.ToLower()}";
|
||||
if (!string.IsNullOrWhiteSpace(sorting))
|
||||
{
|
||||
cql += $" ORDER BY {sorting}";
|
||||
}
|
||||
cql += $" LIMIT {maxResultCount} OFFSET {skipCount}";
|
||||
|
||||
return (await Mapper.FetchAsync<TEntity>(cql)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Autofac;
|
||||
using Volo.Abp.Modularity;
|
||||
|
||||
namespace JiShe.CollectBus.Cassandra
|
||||
{
|
||||
[DependsOn(
|
||||
typeof(AbpAutofacModule)
|
||||
)]
|
||||
public class CollectBusCassandraModule : AbpModule
|
||||
{
|
||||
public override Task ConfigureServicesAsync(ServiceConfigurationContext context)
|
||||
{
|
||||
Configure<CassandraConfig>(context.Services.GetConfiguration().GetSection("Cassandra"));
|
||||
context.AddCassandra();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
|
||||
{
|
||||
await context.UseCassandra();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
using JiShe.CollectBus.Cassandra;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Modularity;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class ApplicationInitializationContextExtensions
|
||||
{
|
||||
public static async Task UseCassandra(this ApplicationInitializationContext context)
|
||||
{
|
||||
var service = context.ServiceProvider;
|
||||
var cassandraProvider = service.GetRequiredService<ICassandraProvider>();
|
||||
await cassandraProvider.InitClusterAndSessionAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static void AddCassandra(this ServiceConfigurationContext context)
|
||||
{
|
||||
context.Services.AddTransient(typeof(ICassandraRepository<,>), typeof(CassandraRepository<,>));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,98 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Cassandra;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using JiShe.CollectBus.Common.Attributes;
|
||||
using Volo.Abp.Data;
|
||||
|
||||
namespace JiShe.CollectBus.Cassandra.Extensions
|
||||
{
|
||||
public static class SessionExtension
|
||||
{
|
||||
public static void CreateTable<TEntity>(this ISession session,string? defaultKeyspace=null) where TEntity : class
|
||||
{
|
||||
var type = typeof(TEntity);
|
||||
var tableAttribute = type.GetCustomAttribute<CassandraTableAttribute>();
|
||||
var tableName = tableAttribute?.Name ?? type.Name.ToLower();
|
||||
var tableKeyspace = session.Keyspace;
|
||||
|
||||
var properties = type.GetProperties();
|
||||
|
||||
// 分区键设置
|
||||
var primaryKey = properties.FirstOrDefault(p => p.GetCustomAttribute<PartitionKeyAttribute>() != null);
|
||||
|
||||
if (primaryKey == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No primary key defined for type {type.Name}");
|
||||
}
|
||||
|
||||
// 集群键设置
|
||||
var clusteringKeys = properties.Where(p => p.GetCustomAttribute<ClusteringKeyAttribute>() != null).Select(a=>a.Name).ToList();
|
||||
var clusteringKeyCql = string.Empty;
|
||||
if (clusteringKeys.Any())
|
||||
{
|
||||
clusteringKeyCql = $", {string.Join(", ", clusteringKeys)}";
|
||||
}
|
||||
|
||||
var cql = new StringBuilder();
|
||||
cql.Append($"CREATE TABLE IF NOT EXISTS {tableKeyspace}.{tableName} (");
|
||||
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
var ignoreAttribute = prop.GetCustomAttribute<CassandraIgnoreAttribute>();
|
||||
if (ignoreAttribute != null) continue;
|
||||
var columnName = prop.Name.ToLower();
|
||||
var cqlType = GetCassandraType(prop.PropertyType);
|
||||
|
||||
cql.Append($"{columnName} {cqlType}, ");
|
||||
}
|
||||
cql.Length -= 2; // Remove last comma and space
|
||||
cql.Append($", PRIMARY KEY (({primaryKey.Name.ToLower()}){clusteringKeyCql}))");
|
||||
|
||||
session.Execute(cql.ToString());
|
||||
}
|
||||
|
||||
private static string GetCassandraType(Type type)
|
||||
{
|
||||
// 基础类型处理
|
||||
switch (Type.GetTypeCode(type))
|
||||
{
|
||||
case TypeCode.String: return "text";
|
||||
case TypeCode.Int32: return "int";
|
||||
case TypeCode.Int64: return "bigint";
|
||||
case TypeCode.Boolean: return "boolean";
|
||||
case TypeCode.DateTime: return "timestamp";
|
||||
case TypeCode.Byte: return "tinyint";
|
||||
}
|
||||
|
||||
if (type == typeof(Guid)) return "uuid";
|
||||
if (type == typeof(DateTimeOffset)) return "timestamp";
|
||||
if (type == typeof(Byte[])) return "blob";
|
||||
if (type == typeof(ExtraPropertyDictionary)) return "map<text,text>";
|
||||
|
||||
// 处理集合类型
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var genericType = type.GetGenericTypeDefinition();
|
||||
var elementType = type.GetGenericArguments()[0];
|
||||
|
||||
if (genericType == typeof(List<>))
|
||||
return $"list<{GetCassandraType(elementType)}>";
|
||||
if (genericType == typeof(HashSet<>))
|
||||
return $"set<{GetCassandraType(elementType)}>";
|
||||
if (genericType == typeof(Nullable<>))
|
||||
return GetCassandraType(elementType);
|
||||
if (genericType == typeof(Dictionary<,>))
|
||||
{
|
||||
var keyType = type.GetGenericArguments()[0];
|
||||
var valueType = type.GetGenericArguments()[1];
|
||||
return $"map<{GetCassandraType(keyType)}, {GetCassandraType(valueType)}>";
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"不支持的类型: {type.Name}");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
using Cassandra;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JiShe.CollectBus.Cassandra
|
||||
{
|
||||
public interface ICassandraProvider
|
||||
{
|
||||
Cluster Instance { get;}
|
||||
|
||||
ISession Session { get;}
|
||||
|
||||
CassandraConfig CassandraConfig { get; }
|
||||
|
||||
ISession GetSession(string? keyspace = null);
|
||||
|
||||
Cluster GetCluster(Action<string?>? callback = null);
|
||||
|
||||
void InitClusterAndSession();
|
||||
|
||||
Task InitClusterAndSessionAsync();
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
|
||||
namespace JiShe.CollectBus.Cassandra
|
||||
{
|
||||
public interface ICassandraRepository<TEntity, TKey> where TEntity : class
|
||||
{
|
||||
Task<TEntity> GetAsync(TKey id);
|
||||
Task<TEntity?> GetAsync(string cql, params object[] args);
|
||||
Task<TEntity> FirstOrDefaultAsync(TKey id);
|
||||
Task<TEntity?> FirstOrDefaultAsync(string cql, params object[] args);
|
||||
Task<List<TEntity>?> GetListAsync(string? cql = null, params object[] args);
|
||||
Task<TEntity> InsertAsync(TEntity entity);
|
||||
Task<TEntity> UpdateAsync(TEntity entity);
|
||||
Task DeleteAsync(TEntity entity);
|
||||
Task DeleteAsync(TKey id);
|
||||
Task<List<TEntity>> GetPagedListAsync(int skipCount, int maxResultCount, string sorting);
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CassandraCSharpDriver" Version="3.22.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
|
||||
<PackageReference Include="Volo.Abp.Autofac" Version="8.3.3" />
|
||||
<PackageReference Include="Volo.Abp.Core" Version="8.3.3" />
|
||||
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="8.3.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\shared\JiShe.CollectBus.Common\JiShe.CollectBus.Common.csproj" />
|
||||
<ProjectReference Include="..\..\shared\JiShe.CollectBus.Domain.Shared\JiShe.CollectBus.Domain.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -1,25 +0,0 @@
|
||||
using JiShe.CollectBus.FreeRedis.Options;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.Modularity;
|
||||
|
||||
namespace JiShe.CollectBus.FreeRedis
|
||||
{
|
||||
public class CollectBusFreeRedisModule : AbpModule
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
var configuration = context.Services.GetConfiguration();
|
||||
|
||||
Configure<FreeRedisOptions>(options =>
|
||||
{
|
||||
configuration.GetSection("Redis").Bind(options);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using FreeRedis;
|
||||
using JiShe.CollectBus.Common.Helpers;
|
||||
using JiShe.CollectBus.FreeRedis.Options;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace JiShe.CollectBus.FreeRedis
|
||||
{
|
||||
|
||||
public class FreeRedisProvider : IFreeRedisProvider, ISingletonDependency
|
||||
{
|
||||
|
||||
private readonly FreeRedisOptions _option;
|
||||
|
||||
/// <summary>
|
||||
/// FreeRedis
|
||||
/// </summary>
|
||||
public FreeRedisProvider(IOptions<FreeRedisOptions> options)
|
||||
{
|
||||
_option = options.Value;
|
||||
GetInstance();
|
||||
}
|
||||
|
||||
public RedisClient Instance { get; set; } = new(string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// 获取 FreeRedis 客户端
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IRedisClient GetInstance()
|
||||
{
|
||||
|
||||
var connectionString = $"{_option.Configuration},defaultdatabase={_option.DefaultDB},MaxPoolSize={_option.MaxPoolSize}";
|
||||
Instance = new RedisClient(connectionString);
|
||||
Instance.Serialize = obj => BusJsonSerializer.Serialize(obj);
|
||||
Instance.Deserialize = (json, type) => BusJsonSerializer.Deserialize(json, type);
|
||||
Instance.Notice += (s, e) => Trace.WriteLine(e.Log);
|
||||
return Instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
using FreeRedis;
|
||||
|
||||
namespace JiShe.CollectBus.FreeRedis
|
||||
{
|
||||
public interface IFreeRedisProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取客户端
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
RedisClient Instance { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FreeRedis" Version="1.3.6" />
|
||||
<PackageReference Include="Volo.Abp" Version="8.3.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\shared\JiShe.CollectBus.Common\JiShe.CollectBus.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -1,26 +0,0 @@
|
||||
namespace JiShe.CollectBus.FreeRedis.Options
|
||||
{
|
||||
public class FreeRedisOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 连接字符串
|
||||
/// </summary>
|
||||
public string? Configuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大连接数
|
||||
/// </summary>
|
||||
public string? MaxPoolSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认数据库
|
||||
/// </summary>
|
||||
public string? DefaultDB { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// HangfireDB
|
||||
/// </summary>
|
||||
public string? HangfireDB { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
using FreeSql;
|
||||
using FreeSql.SqlServer;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.Modularity;
|
||||
|
||||
namespace JiShe.CollectBus.FreeSql
|
||||
{
|
||||
public class CollectBusFreeSqlModule : AbpModule
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
namespace JiShe.CollectBus.FreeSql
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库枚举
|
||||
/// </summary>
|
||||
public enum DbEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 微服务默认数据库,MySQL
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// 预付费数据库,SQLSERVER
|
||||
/// </summary>
|
||||
PrepayDB,
|
||||
|
||||
/// <summary>
|
||||
/// 能耗数据库,SQLSERVER
|
||||
/// </summary>
|
||||
EnergyDB
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
using FreeSql;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace JiShe.CollectBus.FreeSql
|
||||
{
|
||||
public class FreeSqlProvider : IFreeSqlProvider, ISingletonDependency
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FreeSqlProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration.</param>
|
||||
public FreeSqlProvider(IConfiguration configuration)
|
||||
{
|
||||
GetInstance(configuration);
|
||||
}
|
||||
|
||||
public FreeSqlCloud<DbEnum> Instance { get; set; } = new();
|
||||
public FreeSqlCloud<DbEnum> GetInstance(IConfiguration configuration)
|
||||
{
|
||||
Instance = new FreeSqlCloud<DbEnum>
|
||||
{
|
||||
DistributeTrace = log => Console.WriteLine(log.Split('\n')[0].Trim())
|
||||
};
|
||||
|
||||
Instance.Register(DbEnum.EnergyDB, () => new FreeSqlBuilder()
|
||||
.UseConnectionString(DataType.SqlServer, configuration.GetConnectionString(DbEnum.EnergyDB.ToString()))
|
||||
.Build());
|
||||
|
||||
Instance.Register(DbEnum.PrepayDB, () => new FreeSqlBuilder()
|
||||
.UseConnectionString(DataType.SqlServer, configuration.GetConnectionString(DbEnum.PrepayDB.ToString()))
|
||||
.Build());
|
||||
return Instance;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
using FreeSql;
|
||||
|
||||
namespace JiShe.CollectBus.FreeSql
|
||||
{
|
||||
/// <summary>
|
||||
/// FreeSqlProvider服务
|
||||
/// </summary>
|
||||
public interface IFreeSqlProvider
|
||||
{
|
||||
FreeSqlCloud<DbEnum> Instance { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FreeSql.Cloud" Version="1.9.1" />
|
||||
<PackageReference Include="FreeSql.Provider.SqlServer" Version="3.5.102" />
|
||||
<PackageReference Include="Volo.Abp" Version="8.3.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -1,10 +0,0 @@
|
||||
namespace JiShe.CollectBus.IoTDB.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Column分类标记特性(ATTRIBUTE字段),也就是属性字段
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ATTRIBUTEColumnAttribute : System.Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
namespace JiShe.CollectBus.IoTDB.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Column分类标记特性(FIELD字段),数据列字段
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class FIELDColumnAttribute : System.Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
namespace JiShe.CollectBus.IoTDB.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于标识当前实体为单侧点模式,单侧点模式只有一个Filed标识字段,类型是Tuple<string,T>,Item1=>测点名称,Item2=>测点值,泛型
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class SingleMeasuringAttribute : System.Attribute
|
||||
{
|
||||
public string FieldName { get; set;}
|
||||
|
||||
public SingleMeasuringAttribute(string fieldName)
|
||||
{
|
||||
FieldName = fieldName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
namespace JiShe.CollectBus.IoTDB.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Column分类标记特性(TAG字段),标签字段
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class TAGColumnAttribute : System.Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// IoTDB实体存储路径或表名称,一般用于已经明确的存储路径或表名称,例如日志存储
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class TableNameOrTreePathAttribute : System.Attribute
|
||||
{
|
||||
public string TableNameOrTreePath { get; }
|
||||
|
||||
public TableNameOrTreePathAttribute(string tableNameOrTreePath)
|
||||
{
|
||||
TableNameOrTreePath = tableNameOrTreePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
using JiShe.CollectBus.IoTDB.Context;
|
||||
using JiShe.CollectBus.IoTDB.Options;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.Modularity;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB;
|
||||
|
||||
/// <summary>
|
||||
/// CollectBusIoTDBModule
|
||||
/// </summary>
|
||||
public class CollectBusIoTDbModule : AbpModule
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
var configuration = context.Services.GetConfiguration();
|
||||
Configure<IoTDbOptions>(options => { configuration.GetSection(nameof(IoTDbOptions)).Bind(options); });
|
||||
|
||||
//// 注册上下文为Scoped
|
||||
//context.Services.AddScoped<IoTDBRuntimeContext>();
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
using JiShe.CollectBus.IoTDB.Options;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Context
|
||||
{
|
||||
/// <summary>
|
||||
/// IoTDB SessionPool 运行时上下文
|
||||
/// </summary>
|
||||
public class IoTDBRuntimeContext: IScopedDependency//ITransientDependency
|
||||
{
|
||||
private readonly bool _defaultValue;
|
||||
|
||||
public IoTDBRuntimeContext(IOptions<IoTDbOptions> options)
|
||||
{
|
||||
_defaultValue = options.Value.UseTableSessionPoolByDefault;
|
||||
UseTableSessionPool = _defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 存储模型切换标识,是否使用table模型存储, 默认为false,标识tree模型存储
|
||||
/// </summary>
|
||||
public bool UseTableSessionPool { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 重置为默认值
|
||||
/// </summary>
|
||||
public void ResetToDefault()
|
||||
{
|
||||
UseTableSessionPool = _defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
using JiShe.CollectBus.Common.Models;
|
||||
using JiShe.CollectBus.IoTDB.Model;
|
||||
using JiShe.CollectBus.IoTDB.Options;
|
||||
using JiShe.CollectBus.IoTDB.Provider;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// IoTDB数据源,数据库能同时存多个时序模型,但数据是完全隔离的,不能跨时序模型查询,通过连接字符串配置
|
||||
/// </summary>
|
||||
public interface IIoTDbProvider
|
||||
{
|
||||
///// <summary>
|
||||
///// 切换 SessionPool
|
||||
///// </summary>
|
||||
///// <param name="useTableSession">是否使用表模型</param>
|
||||
//void SwitchSessionPool(bool useTableSession);
|
||||
|
||||
IIoTDbProvider GetSessionPool(bool sessionpolType);
|
||||
|
||||
/// <summary>
|
||||
/// 插入数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
Task InsertAsync<T>(T entity) where T : IoTEntity;
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="entities"></param>
|
||||
/// <returns></returns>
|
||||
Task BatchInsertAsync<T>(IEnumerable<T> entities) where T : IoTEntity;
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="deviceMetadata">设备元数据</param>
|
||||
/// <param name="entities"></param>
|
||||
/// <returns></returns>
|
||||
Task BatchInsertAsync<T>(DeviceMetadata deviceMetadata,IEnumerable<T> entities) where T : IoTEntity;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 删除数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
Task<object> DeleteAsync<T>(IoTDBQueryOptions options) where T : IoTEntity;
|
||||
|
||||
/// <summary>
|
||||
/// 获取设备元数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
Task<DeviceMetadata> GetMetadata<T>() where T : IoTEntity;
|
||||
|
||||
/// <summary>
|
||||
/// 查询数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
Task<BusPagedResult<T>> QueryAsync<T>(IoTDBQueryOptions options) where T : IoTEntity, new();
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
namespace JiShe.CollectBus.IoTDB.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// Session 工厂接口
|
||||
/// </summary>
|
||||
public interface IIoTDbSessionFactory:IDisposable
|
||||
{
|
||||
IIoTDbSessionPool GetSessionPool(bool useTableSession);
|
||||
}
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
using Apache.IoTDB.DataStructure;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// Session 连接池
|
||||
/// </summary>
|
||||
public interface IIoTDbSessionPool : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 打开连接池
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task OpenAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 关闭连接池
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task CloseAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 插入数据
|
||||
/// </summary>
|
||||
/// <param name="tablet"></param>
|
||||
/// <returns></returns>
|
||||
Task<int> InsertAsync(Tablet tablet);
|
||||
|
||||
/// <summary>
|
||||
/// 查询数据
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <returns></returns>
|
||||
Task<SessionDataSet> ExecuteQueryStatementAsync(string sql);
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Apache.IoTDB" Version="2.0.2" />
|
||||
<PackageReference Include="Volo.Abp" Version="8.3.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
<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>
|
||||
</Project>
|
||||
@ -1,72 +0,0 @@
|
||||
using JiShe.CollectBus.Common.Attributes;
|
||||
using JiShe.CollectBus.Common.Consts;
|
||||
using JiShe.CollectBus.IoTDB.Attributes;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Model
|
||||
{
|
||||
/// <summary>
|
||||
/// IoT实体基类,此类适用于多个数据测点记录场景,单个测点请使用子类 SingleMeasuring,新增字段只能现有字段末尾添加,否则会导致数据写入失败。
|
||||
/// </summary>
|
||||
public abstract class IoTEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 系统名称
|
||||
/// </summary>
|
||||
[TAGColumn]
|
||||
public string SystemName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 项目编码
|
||||
/// </summary>
|
||||
[TAGColumn]
|
||||
public string ProjectId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据类型
|
||||
/// </summary>
|
||||
[TAGColumn]
|
||||
public string DataType { get; set; } = IOTDBDataTypeConst.Data;
|
||||
|
||||
/// <summary>
|
||||
/// 设备类型集中器、电表、水表、流量计、传感器等
|
||||
/// </summary>
|
||||
[TAGColumn]
|
||||
public string DeviceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备ID,数据生成者,例如集中器ID,电表ID、水表ID、流量计ID、传感器ID等
|
||||
/// </summary>
|
||||
[TAGColumn]
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时标,也就是业务时间戳,单位毫秒,必须通过DateTimeOffset获取
|
||||
/// </summary>
|
||||
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 +0,0 @@
|
||||
using JiShe.CollectBus.Analyzers.Shared;
|
||||
using JiShe.CollectBus.IoTDB.Attributes;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Model
|
||||
{
|
||||
/// <summary>
|
||||
/// Table模型单项数据实体
|
||||
/// </summary>
|
||||
[SourceAnalyzers(EntityTypeEnum.TableModel)]
|
||||
public class TableModelSingleMeasuringEntity<T> : IoTEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 单项数据键值对
|
||||
/// </summary>
|
||||
[SingleMeasuring(nameof(SingleColumn))]
|
||||
public required ValueTuple<string, T> SingleColumn { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
using JiShe.CollectBus.Analyzers.Shared;
|
||||
using JiShe.CollectBus.IoTDB.Attributes;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Model
|
||||
{
|
||||
/// <summary>
|
||||
/// Tree模型单项数据实体
|
||||
/// </summary>
|
||||
[SourceAnalyzers(EntityTypeEnum.TreeModel)]
|
||||
public class TreeModelSingleMeasuringEntity<T> : IoTEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 单项数据键值对
|
||||
/// </summary>
|
||||
[SingleMeasuring(nameof(SingleMeasuring))]
|
||||
public required ValueTuple<string, T> SingleMeasuring { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
namespace JiShe.CollectBus.IoTDB.Options
|
||||
{
|
||||
/// <summary>
|
||||
/// IOTDB配置
|
||||
/// </summary>
|
||||
public class IoTDbOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库名称,表模型才有,树模型为空
|
||||
/// </summary>
|
||||
public string DataBaseName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 集群列表
|
||||
/// </summary>
|
||||
public List<string> ClusterList { get; set; }
|
||||
/// <summary>
|
||||
/// 用户名
|
||||
/// </summary>
|
||||
public string UserName { get; set; }
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 连接池大小
|
||||
/// </summary>
|
||||
public int PoolSize { get; set; } = 8;
|
||||
|
||||
/// <summary>
|
||||
/// 查询时,每次查询的数据量,默认1024
|
||||
/// </summary>
|
||||
public int FetchSize { get; set; } = 1024;
|
||||
|
||||
/// <summary>
|
||||
/// 是否开启调试模式,生产环境请关闭,因为底层的实现方式,可能会导致内存持续增长。
|
||||
/// </summary>
|
||||
public bool OpenDebugMode { get; set;}
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用表模型存储, 默认false,使用tree模型存储
|
||||
/// </summary>
|
||||
public bool UseTableSessionPoolByDefault { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 时区,默认为:"UTC+08:00"
|
||||
/// </summary>
|
||||
public string ZoneId { get; set; } = "UTC+08:00";
|
||||
|
||||
/// <summary>
|
||||
/// 请求超时时间,单位毫秒,默认为:50000
|
||||
/// </summary>
|
||||
public long Timeout { get; set; } = 50000;
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
namespace JiShe.CollectBus.IoTDB.Options
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询条件
|
||||
/// </summary>
|
||||
public class IoTDBQueryOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 表模型的表名称或者树模型的设备路径
|
||||
/// </summary>
|
||||
public required string TableNameOrTreePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分页
|
||||
/// </summary>
|
||||
public int PageIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分页大小
|
||||
/// </summary>
|
||||
public int PageSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 查询条件
|
||||
/// </summary>
|
||||
public List<QueryCondition> Conditions { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
using JiShe.CollectBus.Common.Extensions;
|
||||
using JiShe.CollectBus.Common.Helpers;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Options
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询条件
|
||||
/// </summary>
|
||||
public class QueryCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// 字段
|
||||
/// </summary>
|
||||
public string Field { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作符,>,=,<
|
||||
/// </summary>
|
||||
public string Operator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否数值,如果是数值,则进行数值比较,否则进行字符串比较
|
||||
/// </summary>
|
||||
public bool IsNumber { get; set; } = false;
|
||||
|
||||
private object _rawValue;
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public object Value
|
||||
{
|
||||
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,97 +0,0 @@
|
||||
using Apache.IoTDB;
|
||||
using JiShe.CollectBus.Analyzers.Shared;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Provider
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备元数据
|
||||
/// </summary>
|
||||
public sealed class DeviceMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体类名称
|
||||
/// </summary>
|
||||
public string EntityName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备表名或树路径,如果实体没有添加TableNameOrTreePath,此处为空
|
||||
/// </summary>
|
||||
public string TableNameOrTreePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实体类型枚举
|
||||
/// </summary>
|
||||
public EntityTypeEnum? EntityType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否有单测量值
|
||||
/// </summary>
|
||||
public bool IsSingleMeasuring { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 测量值集合,用于构建Table的测量值,也就是columnNames参数
|
||||
/// </summary>
|
||||
public List<string> ColumnNames { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 列类型集合,用于构建Table的列类型,也就是columnCategories参数
|
||||
/// </summary>
|
||||
public List<ColumnCategory> ColumnCategories { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 值类型集合,用于构建Table的值类型,也就是dataTypes参数
|
||||
/// </summary>
|
||||
public List<TSDataType> DataTypes { get; set; } = 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;
|
||||
}
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
using JiShe.CollectBus.IoTDB.Model;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Provider
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备路径构建器
|
||||
/// </summary>
|
||||
public static class DevicePathBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// 构建设备路径,由于路径的层级约束规范不能是纯数字字符,所以需要做特殊处理。
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetDevicePath<T>(T entity) where T : IoTEntity
|
||||
{
|
||||
return $"root.{entity.SystemName.ToLower()}.`{entity.ProjectId}`.`{entity.DeviceType}`.{entity.DataType}.`{entity.DeviceId}`";
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取表名称
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetTableName<T>() where T : IoTEntity
|
||||
{
|
||||
var type = typeof(T);
|
||||
return $"{type.Name.ToLower()}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取表名称,用作单侧点表模型特殊处理。
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetDeviceTableName<T>(T entity) where T : IoTEntity
|
||||
{
|
||||
return $"{entity.SystemName.ToLower()}.`{entity.ProjectId}`.`{entity.DeviceType}`.`{entity.DeviceId}`";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,981 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Apache.IoTDB;
|
||||
using Apache.IoTDB.DataStructure;
|
||||
using JiShe.CollectBus.Common.Enums;
|
||||
using JiShe.CollectBus.Common.Extensions;
|
||||
using JiShe.CollectBus.Common.Helpers;
|
||||
using JiShe.CollectBus.Common.Models;
|
||||
using JiShe.CollectBus.IoTDB.Attributes;
|
||||
using JiShe.CollectBus.IoTDB.Context;
|
||||
using JiShe.CollectBus.IoTDB.Interface;
|
||||
using JiShe.CollectBus.IoTDB.Model;
|
||||
using JiShe.CollectBus.IoTDB.Options;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// IoTDB数据源
|
||||
/// </summary>
|
||||
public class IoTDbProvider : IIoTDbProvider, ITransientDependency
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Type, DeviceMetadata> MetadataCache = new();
|
||||
private readonly ILogger<IoTDbProvider> _logger;
|
||||
private readonly IIoTDbSessionFactory _sessionFactory;
|
||||
|
||||
/// <summary>
|
||||
/// 存储模型切换标识,是否使用table模型存储, 默认为false,标识tree模型存储
|
||||
/// </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>
|
||||
/// IoTDbProvider
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="sessionFactory"></param>
|
||||
/// <param name="runtimeContext"></param>
|
||||
public IoTDbProvider(
|
||||
ILogger<IoTDbProvider> logger,
|
||||
IIoTDbSessionFactory sessionFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_sessionFactory = sessionFactory;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插入数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public async Task InsertAsync<T>(T entity) where T : IoTEntity
|
||||
{
|
||||
try
|
||||
{
|
||||
var metadata = await GetMetadata<T>();
|
||||
|
||||
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());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"{nameof(InsertAsync)} IoTDB插入{typeof(T).Name}的数据时发生异常");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public async Task BatchInsertAsync<T>(IEnumerable<T> entities) where T : IoTEntity
|
||||
{
|
||||
try
|
||||
{
|
||||
if (entities == null || entities.Count() <= 0)
|
||||
{
|
||||
_logger.LogError($"{nameof(BatchInsertAsync)} 参数异常,-101");
|
||||
return;
|
||||
}
|
||||
var metadata = await GetMetadata<T>();
|
||||
|
||||
var batchSize = 1000;
|
||||
var batches = entities.Chunk(batchSize);
|
||||
|
||||
foreach (var batch in batches)
|
||||
{
|
||||
var tablet = BuildTablet(batch, metadata);
|
||||
if (tablet == null || tablet.Count <= 0)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
_logger.LogError(ex, $"{nameof(BatchInsertAsync)} IoTDB批量插入{typeof(T).Name}的数据时发生异常");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="deviceMetadata">设备元数据</param>
|
||||
/// <param name="entities"></param>
|
||||
/// <returns></returns>
|
||||
public async Task BatchInsertAsync<T>(DeviceMetadata deviceMetadata, IEnumerable<T> entities) where T : IoTEntity
|
||||
{
|
||||
try
|
||||
{
|
||||
var batchSize = 1000;
|
||||
var batches = entities.Chunk(batchSize);
|
||||
|
||||
foreach (var batch in batches)
|
||||
{
|
||||
var tablet = BuildTablet(batch, deviceMetadata);
|
||||
if (tablet == null || tablet.Count <= 0)
|
||||
{
|
||||
_logger.LogError($"{nameof(InsertAsync)} IoTDB插入{typeof(T).Name}的数据时 tablet 为null");
|
||||
return;
|
||||
}
|
||||
foreach (var item in tablet)
|
||||
{
|
||||
await CurrentSession.InsertAsync(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"{nameof(BatchInsertAsync)} IoTDB批量插入{typeof(T).Name}的数据时发生异常");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 删除数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<object> DeleteAsync<T>(IoTDBQueryOptions options) where T : IoTEntity
|
||||
{
|
||||
try
|
||||
{
|
||||
var query = await BuildDeleteSQL<T>(options);
|
||||
var result = await CurrentSession.ExecuteQueryStatementAsync(query);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!result.HasNext())
|
||||
{
|
||||
_logger.LogWarning($"{typeof(T).Name} IoTDB删除{typeof(T).Name}的数据时,没有返回受影响记录数量。");
|
||||
return 0;
|
||||
}
|
||||
|
||||
//获取唯一结果行
|
||||
var row = result.Next();
|
||||
await result.Close();
|
||||
var dataResult = row.Values[0];
|
||||
return dataResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"{nameof(DeleteAsync)} IoTDB删除{typeof(T).Name}的数据时发生异常");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取设备元数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public async Task<DeviceMetadata> GetMetadata<T>() where T : IoTEntity
|
||||
{
|
||||
var accessor = SourceEntityAccessorFactory.GetAccessor<T>();
|
||||
|
||||
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(
|
||||
typeof(T),
|
||||
addValueFactory: t => tmpMetadata, // 如果键不存在,用此值添加
|
||||
updateValueFactory: (t, existingValue) =>
|
||||
{
|
||||
var columns = CollectColumnMetadata(accessor);
|
||||
var metadata = BuildDeviceMetadata(columns, accessor);
|
||||
|
||||
//对现有值 existingValue 进行修改,返回新值
|
||||
string tableNameOrTreePath = string.Empty;
|
||||
var tableNameOrTreePathAttribute = typeof(T).GetCustomAttribute<TableNameOrTreePathAttribute>();
|
||||
if (tableNameOrTreePathAttribute != null)
|
||||
{
|
||||
tableNameOrTreePath = tableNameOrTreePathAttribute.TableNameOrTreePath;
|
||||
}
|
||||
existingValue.ColumnNames = metadata.ColumnNames;
|
||||
existingValue.DataTypes = metadata.DataTypes;
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<BusPagedResult<T>> QueryAsync<T>(IoTDBQueryOptions options) where T : IoTEntity, new()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stopwatch2 = Stopwatch.StartNew();
|
||||
|
||||
var query = await BuildQuerySQL<T>(options);
|
||||
var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query);
|
||||
|
||||
|
||||
var result = new BusPagedResult<T>
|
||||
{
|
||||
TotalCount = await GetTotalCount<T>(options),
|
||||
Items = await ParseResults<T>(sessionDataSet, options.PageSize),
|
||||
PageIndex = options.PageIndex,
|
||||
PageSize = options.PageSize,
|
||||
|
||||
};
|
||||
stopwatch2.Stop();
|
||||
|
||||
//int totalPageCount = (int)Math.Ceiling((double)result.TotalCount / options.PageSize);
|
||||
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CurrentSession.Dispose();
|
||||
_logger.LogError(ex, $"{nameof(QueryAsync)} IoTDB查询{typeof(T).Name}的数据时发生异常");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建Tablet
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="entities">表实体</param>
|
||||
/// <param name="metadata">设备元数据</param></param>
|
||||
/// <returns></returns>
|
||||
private List<Tablet> BuildTablet<T>(IEnumerable<T> entities, DeviceMetadata metadata) where T : IoTEntity
|
||||
{
|
||||
var entitiyList = entities.ToList();
|
||||
if (entitiyList == null || entitiyList.Count <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (metadata.EntityType == null)
|
||||
{
|
||||
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 没有指定,属于异常情况,-101");
|
||||
}
|
||||
|
||||
if (metadata.EntityType == EntityTypeEnum.Other)
|
||||
{
|
||||
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 不属于IoTDB数据模型实体,属于异常情况,-102");
|
||||
}
|
||||
|
||||
if (metadata.EntityType == EntityTypeEnum.TreeModel && UseTableSessionPool == true)
|
||||
{
|
||||
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 tree模型不能使用table模型Session连接,属于异常情况,-103");
|
||||
}
|
||||
else if (metadata.EntityType == EntityTypeEnum.TableModel && UseTableSessionPool == false)
|
||||
{
|
||||
throw new Exception($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 table模型不能使用tree模型Session连接,属于异常情况,-104");
|
||||
}
|
||||
string tableNameOrTreePath = string.Empty;
|
||||
if ( UseTableSessionPool)//表模型
|
||||
{
|
||||
//如果指定了路径
|
||||
if (!string.IsNullOrWhiteSpace(metadata.TableNameOrTreePath))
|
||||
{
|
||||
tableNameOrTreePath = metadata.TableNameOrTreePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
tableNameOrTreePath = DevicePathBuilder.GetTableName<T>();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 获取并转换值
|
||||
values[row][i] = processor.ValueGetter(entity);
|
||||
}
|
||||
}
|
||||
|
||||
return UseTableSessionPool
|
||||
? BuildTableSessionTablet(metadata, tableNameOrTreePath, tempColumnNames, values.Select(d => d.ToList()).ToList(), timestamps.ToList())
|
||||
: BuildSessionTablet(metadata, tableNameOrTreePath, tempColumnNames, values.Select(d => d.ToList()).ToList(), timestamps.ToList());
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 构建tree模型的Tablet
|
||||
/// </summary>
|
||||
/// <param name="metadata">已解析的设备数据元数据</param>
|
||||
/// <param name="devicePath">设备路径</param>
|
||||
/// <param name="columns">数据列集合</param>
|
||||
/// <param name="values">数据集合</param>
|
||||
/// <param name="timestamps">时间戳集合</param>
|
||||
/// <returns></returns>
|
||||
private Tablet BuildSessionTablet(DeviceMetadata metadata, string devicePath, List<string> columns, List<List<object>> values, List<long> timestamps)
|
||||
{
|
||||
//todo 树模型需要去掉TAG类型和ATTRIBUTE类型的字段,只需要保留FIELD类型字段即可
|
||||
|
||||
return new Tablet(
|
||||
devicePath,
|
||||
columns,
|
||||
metadata.DataTypes,
|
||||
values,
|
||||
timestamps
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建表模型的Tablet
|
||||
/// </summary>
|
||||
/// <param name="metadata">已解析的设备数据元数据</param>
|
||||
/// <param name="tableName">表名称</param>
|
||||
/// <param name="columns">数据列集合</param>
|
||||
/// <param name="values">数据集合</param>
|
||||
/// <param name="timestamps">时间戳集合</param>
|
||||
/// <returns></returns>
|
||||
private Tablet BuildTableSessionTablet(DeviceMetadata metadata, string tableName, List<string> columns, List<List<object>> values, List<long> timestamps)
|
||||
{
|
||||
var tablet = new Tablet(
|
||||
tableName,
|
||||
columns,
|
||||
metadata.ColumnCategories,
|
||||
metadata.DataTypes,
|
||||
values,
|
||||
timestamps
|
||||
);
|
||||
|
||||
return tablet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建查询语句
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<string> BuildQuerySQL<T>(IoTDBQueryOptions options) where T : IoTEntity
|
||||
{
|
||||
var metadata = await GetMetadata<T>();
|
||||
var sb = new StringBuilder("SELECT ");
|
||||
sb.AppendJoin(", ", metadata.ColumnNames);
|
||||
sb.Append($" FROM {options.TableNameOrTreePath}");
|
||||
|
||||
if (options.Conditions.Any())
|
||||
{
|
||||
sb.Append(" WHERE ");
|
||||
sb.AppendJoin(" AND ", options.Conditions.Select(TranslateCondition));
|
||||
}
|
||||
|
||||
sb.Append($" LIMIT {options.PageSize} OFFSET {options.PageIndex * options.PageSize}");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建删除语句
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<string> BuildDeleteSQL<T>(IoTDBQueryOptions options) where T : IoTEntity
|
||||
{
|
||||
var metadata = await GetMetadata<T>();
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (!UseTableSessionPool)
|
||||
{
|
||||
sb.Append("DELETE ");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("DROP ");
|
||||
}
|
||||
|
||||
sb.Append($" FROM {options.TableNameOrTreePath}");
|
||||
|
||||
sb.AppendJoin(", ", metadata.ColumnNames);
|
||||
|
||||
if (options.Conditions.Any())
|
||||
{
|
||||
sb.Append(" WHERE ");
|
||||
sb.AppendJoin(" AND ", options.Conditions.Select(TranslateCondition));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将查询条件转换为SQL语句
|
||||
/// </summary>
|
||||
/// <param name="condition"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
private string TranslateCondition(QueryCondition condition)
|
||||
{
|
||||
return condition.Operator switch
|
||||
{
|
||||
">" => $"{condition.Field} > {condition.Value}",
|
||||
"<" => $"{condition.Field} < {condition.Value}",
|
||||
"=" => $"{condition.Field} = {condition.Value}",
|
||||
_ => throw new NotSupportedException($"{nameof(TranslateCondition)} 将查询条件转换为SQL语句时操作符 {condition.Operator} 属于异常情况")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取查询条件的总数量
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<int> GetTotalCount<T>(IoTDBQueryOptions options) where T : IoTEntity
|
||||
{
|
||||
var countQuery = $"SELECT COUNT(*) FROM {options.TableNameOrTreePath}";
|
||||
if (options.Conditions.Any())
|
||||
{
|
||||
countQuery += " WHERE " + string.Join(" AND ", options.Conditions.Select(TranslateCondition));
|
||||
}
|
||||
|
||||
var result = await CurrentSession.ExecuteQueryStatementAsync(countQuery);
|
||||
if (result == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!result.HasNext())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
var count = Convert.ToInt32(result.Next().Values[0]);
|
||||
await result.Close();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析查询结果
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="dataSet"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<IEnumerable<T>> ParseResults<T>(SessionDataSet dataSet, int pageSize) where T : IoTEntity, new()
|
||||
{
|
||||
var results = new List<T>();
|
||||
var metadata = await GetMetadata<T>();
|
||||
|
||||
var accessor = SourceEntityAccessorFactory.GetAccessor<T>();
|
||||
var memberCache = BuildMemberCache(accessor);
|
||||
|
||||
|
||||
while (dataSet.HasNext() && results.Count < pageSize)
|
||||
{
|
||||
var record = dataSet.Next();
|
||||
var entity = new T
|
||||
{
|
||||
Timestamps = record.Timestamps
|
||||
};
|
||||
|
||||
for (int i = 0; i < metadata.Processors.Count; i++)
|
||||
{
|
||||
var value = record.Values[i];
|
||||
if (!(value is System.DBNull))
|
||||
{
|
||||
metadata.Processors[i].ValueSetter(entity, value);
|
||||
}
|
||||
}
|
||||
|
||||
results.Add(entity);
|
||||
|
||||
}
|
||||
await dataSet.Close();
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取设备元数据的列
|
||||
/// </summary>
|
||||
/// <param name="accessor"></param>
|
||||
/// <returns></returns>
|
||||
private List<ColumnInfo> CollectColumnMetadata<T>(ISourceEntityAccessor<T> accessor)
|
||||
{
|
||||
var columns = new List<ColumnInfo>();
|
||||
var memberCache = BuildMemberCache(accessor);
|
||||
|
||||
foreach (var member in accessor.MemberList)
|
||||
{
|
||||
// 过滤元组子项
|
||||
if (member.NameOrPath.Contains(".Item")) continue;
|
||||
|
||||
// 类型名称处理
|
||||
string declaredTypeName = member.DeclaredTypeName;
|
||||
|
||||
// 特性查询优化
|
||||
var attributes = member.CustomAttributes ?? Enumerable.Empty<Attribute>();
|
||||
var tagAttr = attributes.OfType<TAGColumnAttribute>().FirstOrDefault();
|
||||
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);
|
||||
}
|
||||
else if (attrColumn != null)
|
||||
{
|
||||
column = new ColumnInfo(member.NameOrPath, ColumnCategory.ATTRIBUTE, GetDataTypeFromTypeName(member.DeclaredTypeName), false, member.DeclaredTypeName);
|
||||
}
|
||||
else if (fieldColumn != null)
|
||||
{
|
||||
column = new ColumnInfo(member.NameOrPath, ColumnCategory.FIELD, GetDataTypeFromTypeName(member.DeclaredTypeName), false, member.DeclaredTypeName);
|
||||
}
|
||||
|
||||
// 单测模式处理
|
||||
if (singleMeasuringAttr != null && column == null)
|
||||
{
|
||||
var tupleItemKey = $"{member.NameOrPath}.Item2";
|
||||
if (!memberCache.TryGetValue(tupleItemKey, out var tupleMember))
|
||||
{
|
||||
throw new Exception($"{nameof(CollectColumnMetadata)} {accessor.EntityName} {member.NameOrPath} 单侧点属性解析异常");
|
||||
}
|
||||
column = new ColumnInfo(member.NameOrPath, ColumnCategory.FIELD, GetDataTypeFromTypeName(tupleMember.DeclaredTypeName), true, tupleMember.DeclaredTypeName);
|
||||
}
|
||||
|
||||
if (column.HasValue) columns.Add(column.Value);
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建设备元数据
|
||||
/// </summary>
|
||||
/// <param name="typeInfo">待解析的类</param>
|
||||
/// <param name="columns">已处理好的数据列</param>
|
||||
/// <returns></returns>
|
||||
private DeviceMetadata BuildDeviceMetadata<T>(List<ColumnInfo> columns, ISourceEntityAccessor<T> accessor) where T : IoTEntity
|
||||
{
|
||||
var metadata = new DeviceMetadata();
|
||||
|
||||
//先检查是不是单侧点模型
|
||||
if (columns.Any(c => c.IsSingleMeasuring))
|
||||
{
|
||||
metadata.IsSingleMeasuring = true;
|
||||
}
|
||||
|
||||
//按业务逻辑顺序处理(TAG -> ATTRIBUTE -> FIELD)
|
||||
var groupedColumns = columns
|
||||
.GroupBy(c => c.Category)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
ProcessCategory(groupedColumns, ColumnCategory.TAG, metadata);
|
||||
ProcessCategory(groupedColumns, ColumnCategory.ATTRIBUTE, metadata);
|
||||
ProcessCategory(groupedColumns, ColumnCategory.FIELD, metadata);
|
||||
|
||||
// 新增处理器初始化
|
||||
foreach (var item in metadata.ColumnNames)
|
||||
{
|
||||
ColumnInfo column = columns.FirstOrDefault(d => d.Name == item);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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>
|
||||
/// <param name="groupedColumns"></param>
|
||||
/// <param name="category"></param>
|
||||
/// <param name="metadata"></param>
|
||||
private void ProcessCategory(IReadOnlyDictionary<ColumnCategory, List<ColumnInfo>> groupedColumns, ColumnCategory category, DeviceMetadata metadata)
|
||||
{
|
||||
if (groupedColumns.TryGetValue(category, out var cols))
|
||||
{
|
||||
metadata.ColumnNames.AddRange(cols.Select(c => c.Name));
|
||||
metadata.ColumnCategories.AddRange(cols.Select(c => c.Category));
|
||||
metadata.DataTypes.AddRange(cols.Select(c => c.DataType));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据列结构
|
||||
/// </summary>
|
||||
private readonly struct ColumnInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 列名
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 声明的类型的名称
|
||||
/// </summary>
|
||||
public string DeclaredTypeName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否是单测点
|
||||
/// </summary>
|
||||
public bool IsSingleMeasuring { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 列类型
|
||||
/// </summary>
|
||||
public ColumnCategory Category { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据类型
|
||||
/// </summary>
|
||||
public TSDataType DataType { get; }
|
||||
|
||||
public ColumnInfo(string name, ColumnCategory category, TSDataType dataType, bool isSingleMeasuring, string declaredTypeName)
|
||||
{
|
||||
Name = name;
|
||||
Category = category;
|
||||
DataType = dataType;
|
||||
IsSingleMeasuring = isSingleMeasuring;
|
||||
DeclaredTypeName = declaredTypeName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据类型名称获取对应的 IoTDB 数据类型
|
||||
/// </summary>
|
||||
/// <param name="typeName">类型名称(不区分大小写)</param>
|
||||
/// <returns>对应的 TSDataType,默认返回 TSDataType.STRING</returns>
|
||||
private TSDataType GetDataTypeFromTypeName(string typeName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(typeName))
|
||||
return TSDataType.STRING;
|
||||
|
||||
return DataTypeMap.TryGetValue(typeName.Trim(), out var dataType)
|
||||
? dataType
|
||||
: TSDataType.STRING;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据类型名称获取 IoTDB 数据类型
|
||||
/// </summary>
|
||||
private readonly IReadOnlyDictionary<string, TSDataType> DataTypeMap =
|
||||
new Dictionary<string, TSDataType>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["BOOLEAN"] = TSDataType.BOOLEAN,
|
||||
["INT32"] = TSDataType.INT32,
|
||||
["INT64"] = TSDataType.INT64,
|
||||
["FLOAT"] = TSDataType.FLOAT,
|
||||
["DOUBLE"] = TSDataType.DOUBLE,
|
||||
["TEXT"] = TSDataType.TEXT,
|
||||
["NULLTYPE"] = TSDataType.NONE,
|
||||
["DATETIME"] = TSDataType.TIMESTAMP,
|
||||
["DATE"] = TSDataType.DATE,
|
||||
["BLOB"] = TSDataType.BLOB,
|
||||
["DECIMAL"] = TSDataType.DOUBLE,
|
||||
["STRING"] = TSDataType.STRING
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 根据类型名称获取 IoTDB 数据默认值
|
||||
/// </summary>
|
||||
private readonly IReadOnlyDictionary<string, object> DataTypeDefaultValueMap =
|
||||
new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["BOOLEAN"] = false,
|
||||
["INT32"] = 0,
|
||||
["INT64"] = 0,
|
||||
["FLOAT"] = 0.0f,
|
||||
["DOUBLE"] = 0.0d,
|
||||
["TEXT"] = string.Empty,
|
||||
["NULLTYPE"] = null,
|
||||
["DATETIME"] = null,
|
||||
["DATE"] = null,
|
||||
["BLOB"] = null,
|
||||
["DECIMAL"] = "0.0",
|
||||
["STRING"] = string.Empty
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// IoTDB 数据类型与.net类型映射
|
||||
/// </summary>
|
||||
/// <param name="tSDataType"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
private dynamic GetTSDataValue(TSDataType tSDataType, object value) =>
|
||||
tSDataType switch
|
||||
{
|
||||
TSDataType.BOOLEAN => Convert.ToBoolean(value),
|
||||
TSDataType.INT32 => Convert.ToInt32(value),
|
||||
TSDataType.INT64 => Convert.ToInt64(value),
|
||||
TSDataType.FLOAT => Convert.ToSingle(value),
|
||||
TSDataType.DOUBLE => Convert.ToDouble(value),
|
||||
TSDataType.TEXT => Convert.ToString(value),
|
||||
TSDataType.NONE => null,
|
||||
TSDataType.TIMESTAMP => Convert.ToInt64(value),
|
||||
TSDataType.DATE => Convert.ToDateTime(value),
|
||||
TSDataType.BLOB => Convert.ToByte(value),
|
||||
TSDataType.STRING => Convert.ToString(value),
|
||||
_ => Convert.ToString(value)
|
||||
};
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
using JiShe.CollectBus.IoTDB.Interface;
|
||||
using JiShe.CollectBus.IoTDB.Options;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Provider
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 实现带缓存的Session工厂
|
||||
/// </summary>
|
||||
public class IoTDbSessionFactory : IIoTDbSessionFactory, ISingletonDependency
|
||||
{
|
||||
private readonly IoTDbOptions _options;
|
||||
private readonly ConcurrentDictionary<bool, IIoTDbSessionPool> _pools = new();
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// IoTDbSessionFactory
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
public IoTDbSessionFactory(IOptions<IoTDbOptions> options)
|
||||
{
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public IIoTDbSessionPool GetSessionPool(bool useTableSession)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(IoTDbSessionFactory));
|
||||
|
||||
return _pools.GetOrAdd(useTableSession, key =>
|
||||
{
|
||||
var pool = key
|
||||
? (IIoTDbSessionPool)new TableSessionPoolAdapter(_options)
|
||||
: new SessionPoolAdapter(_options);
|
||||
|
||||
pool.OpenAsync().ConfigureAwait(false).GetAwaiter().GetResult(); ;
|
||||
return pool;
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var pool in _pools.Values)
|
||||
{
|
||||
pool.Dispose();
|
||||
}
|
||||
_pools.Clear();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,97 +0,0 @@
|
||||
using Apache.IoTDB;
|
||||
using Apache.IoTDB.DataStructure;
|
||||
using JiShe.CollectBus.IoTDB.Interface;
|
||||
using JiShe.CollectBus.IoTDB.Options;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Provider
|
||||
{
|
||||
/// <summary>
|
||||
/// 树模型连接池
|
||||
/// </summary>
|
||||
public class SessionPoolAdapter : IIoTDbSessionPool
|
||||
{
|
||||
private readonly SessionPool _sessionPool;
|
||||
private readonly IoTDbOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// SessionPoolAdapter
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
public SessionPoolAdapter(IoTDbOptions options)
|
||||
{
|
||||
_options = options;
|
||||
_sessionPool = new SessionPool.Builder()
|
||||
.SetNodeUrl(options.ClusterList)
|
||||
.SetUsername(options.UserName)
|
||||
.SetPassword(options.Password)
|
||||
.SetZoneId(options.ZoneId)
|
||||
.SetFetchSize(options.FetchSize)
|
||||
.SetPoolSize(options.PoolSize)
|
||||
.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开连接池
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task OpenAsync()
|
||||
{
|
||||
await _sessionPool.Open(false);
|
||||
if (_options.OpenDebugMode)
|
||||
{
|
||||
_sessionPool.OpenDebugMode(builder =>
|
||||
{
|
||||
builder.AddConsole();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭连接池
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task CloseAsync()
|
||||
{
|
||||
if (_sessionPool == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await _sessionPool.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入对齐时间序列数据
|
||||
/// </summary>
|
||||
/// <param name="tablet"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<int> InsertAsync(Tablet tablet)
|
||||
{
|
||||
var result = await _sessionPool.InsertAlignedTabletAsync(tablet);
|
||||
if (result != 0)
|
||||
{
|
||||
throw new Exception($"{nameof(SessionPoolAdapter)} Tree模型数据入库没有成功,返回结果为:{result},请检查IoTEntity继承子类属性索引是否有变动。");
|
||||
}
|
||||
//await CloseAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询数据
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<SessionDataSet> ExecuteQueryStatementAsync(string sql)
|
||||
{
|
||||
var result = await _sessionPool.ExecuteQueryStatementAsync(sql, _options.Timeout);
|
||||
//await result.Close();
|
||||
//await CloseAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_sessionPool?.Close().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
using Apache.IoTDB;
|
||||
using Apache.IoTDB.DataStructure;
|
||||
using JiShe.CollectBus.IoTDB.Interface;
|
||||
using JiShe.CollectBus.IoTDB.Options;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Provider
|
||||
{
|
||||
/// <summary>
|
||||
/// 表模型Session连接池
|
||||
/// </summary>
|
||||
public class TableSessionPoolAdapter : IIoTDbSessionPool
|
||||
{
|
||||
private readonly TableSessionPool _sessionPool;
|
||||
private readonly IoTDbOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// TableSessionPoolAdapter
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
public TableSessionPoolAdapter(IoTDbOptions options)
|
||||
{
|
||||
_options = options;
|
||||
_sessionPool = new TableSessionPool.Builder()
|
||||
.SetNodeUrls(options.ClusterList)
|
||||
.SetUsername(options.UserName)
|
||||
.SetPassword(options.Password)
|
||||
.SetZoneId(options.ZoneId)
|
||||
.SetFetchSize(options.FetchSize)
|
||||
.SetPoolSize(options.PoolSize)
|
||||
.SetDatabase(options.DataBaseName)
|
||||
.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开连接池
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task OpenAsync()
|
||||
{
|
||||
await _sessionPool.Open(false);
|
||||
if (_options.OpenDebugMode)
|
||||
{
|
||||
_sessionPool.OpenDebugMode(builder => builder.AddConsole());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭连接池
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task CloseAsync()
|
||||
{
|
||||
if (_sessionPool == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await _sessionPool.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入
|
||||
/// </summary>
|
||||
/// <param name="tablet"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<int> InsertAsync(Tablet tablet)
|
||||
{
|
||||
var result = await _sessionPool.InsertAsync(tablet);
|
||||
if (result != 0)
|
||||
{
|
||||
throw new Exception($"{nameof(TableSessionPoolAdapter)} table模型数据入库没有成功,返回结果为:{result},请检查IoTEntity继承子类属性索引是否有变动。");
|
||||
}
|
||||
|
||||
//await CloseAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询数据
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<SessionDataSet> ExecuteQueryStatementAsync(string sql)
|
||||
{
|
||||
var result = await _sessionPool.ExecuteQueryStatementAsync(sql,_options.Timeout);
|
||||
//await result.Close();
|
||||
//await CloseAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_sessionPool?.Close().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka.Test
|
||||
{
|
||||
public class ConsoleApplicationBuilder: IApplicationBuilder
|
||||
{
|
||||
public IServiceProvider ApplicationServices { get; set; }
|
||||
public IDictionary<string, object> Properties { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
public IFeatureCollection ServerFeatures => throw new NotImplementedException();
|
||||
|
||||
private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new();
|
||||
|
||||
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
|
||||
{
|
||||
_middlewares.Add(middleware);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RequestDelegate Build()
|
||||
{
|
||||
RequestDelegate app = context => Task.CompletedTask;
|
||||
foreach (var middleware in _middlewares)
|
||||
{
|
||||
app = middleware(app);
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
public IApplicationBuilder New()
|
||||
{
|
||||
return new ConsoleApplicationBuilder
|
||||
{
|
||||
ApplicationServices = this.ApplicationServices,
|
||||
Properties = new Dictionary<string, object>(this.Properties)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class HostBuilderExtensions
|
||||
{
|
||||
public static IHostBuilder ConfigureConsoleAppBuilder(
|
||||
this IHostBuilder hostBuilder,
|
||||
Action<IApplicationBuilder> configure)
|
||||
{
|
||||
hostBuilder.ConfigureServices((context, services) =>
|
||||
{
|
||||
// 注册 ConsoleApplicationBuilder 到 DI 容器
|
||||
services.AddSingleton<IApplicationBuilder>(provider =>
|
||||
{
|
||||
var appBuilder = new ConsoleApplicationBuilder
|
||||
{
|
||||
ApplicationServices = provider // 注入服务提供者
|
||||
};
|
||||
configure(appBuilder); // 执行配置委托
|
||||
return appBuilder;
|
||||
});
|
||||
});
|
||||
return hostBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0-preview.3.25171.5" />
|
||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.1" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Volo.Abp.Core" Version="8.3.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\services\JiShe.CollectBus.Domain\JiShe.CollectBus.Domain.csproj" />
|
||||
<ProjectReference Include="..\JiShe.CollectBus.Kafka\JiShe.CollectBus.Kafka.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--注意:基准测试需要引用dll测试-->
|
||||
<!--<ItemGroup>
|
||||
<Reference Include="JiShe.CollectBus.Common">
|
||||
<HintPath>Lib\JiShe.CollectBus.Common.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="JiShe.CollectBus.Kafka">
|
||||
<HintPath>Lib\JiShe.CollectBus.Kafka.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>-->
|
||||
|
||||
</Project>
|
||||
@ -1,121 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using Confluent.Kafka;
|
||||
using JiShe.CollectBus.Common;
|
||||
using JiShe.CollectBus.Kafka.AdminClient;
|
||||
using JiShe.CollectBus.Kafka.Consumer;
|
||||
using JiShe.CollectBus.Kafka.Internal;
|
||||
using JiShe.CollectBus.Kafka.Producer;
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka.Test
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net80)]
|
||||
//[SimpleJob(RuntimeMoniker.NativeAot80)]
|
||||
[RPlotExporter]
|
||||
public class KafkaProduceBenchmark
|
||||
{
|
||||
|
||||
// 每批消息数量
|
||||
[Params(1000, 10000, 100000, 1000000)]
|
||||
public int N;
|
||||
public ServiceProvider _serviceProvider;
|
||||
public IConsumerService _consumerService;
|
||||
public IProducerService _producerService;
|
||||
public string topic = "test-topic1";
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
// 构建配置
|
||||
var config = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json")
|
||||
.Build();
|
||||
// 直接读取配置项
|
||||
var greeting = config["Kafka:ServerTagName"];
|
||||
Console.WriteLine(greeting); // 输出: Hello, World!
|
||||
// 创建服务容器
|
||||
var services = new ServiceCollection();
|
||||
// 注册 IConfiguration 实例
|
||||
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()
|
||||
.ReadFrom.Configuration(config) // 从 appsettings.json 读取配置
|
||||
.CreateLogger();
|
||||
|
||||
// 配置日志系统
|
||||
services.AddLogging(logging =>
|
||||
{
|
||||
logging.ClearProviders();
|
||||
logging.AddSerilog();
|
||||
});
|
||||
services.AddSingleton<IAdminClientService, AdminClientService>();
|
||||
services.AddSingleton<IProducerService, ProducerService>();
|
||||
services.AddSingleton<IConsumerService, ConsumerService>();
|
||||
services.AddSingleton<KafkaPollyPipeline>();
|
||||
services.AddTransient<KafkaSubscribeTest>();
|
||||
|
||||
// 构建ServiceProvider
|
||||
_serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// 获取日志记录器工厂
|
||||
var loggerFactory = _serviceProvider.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger<Program>();
|
||||
logger.LogInformation("程序启动");
|
||||
|
||||
var adminClientService = _serviceProvider.GetRequiredService<IAdminClientService>();
|
||||
|
||||
|
||||
//await adminClientService.DeleteTopicAsync(topic);
|
||||
// 创建 topic
|
||||
//adminClientService.CreateTopicAsync(topic, 3, 3).ConfigureAwait(false).GetAwaiter();
|
||||
|
||||
_consumerService = _serviceProvider.GetRequiredService<IConsumerService>();
|
||||
|
||||
_producerService = _serviceProvider.GetRequiredService<IProducerService>();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task UseAsync()
|
||||
{
|
||||
List<Task> tasks = new();
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
var task = _producerService.ProduceAsync<string>(topic, i.ToString());
|
||||
tasks.Add(task);
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task UseLibrd()
|
||||
{
|
||||
List<Task> tasks = new();
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
var task = _producerService.ProduceAsync<string>(topic, i.ToString(), null);
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
using JiShe.CollectBus.Common.Consts;
|
||||
using JiShe.CollectBus.Common.Enums;
|
||||
using JiShe.CollectBus.Common.Models;
|
||||
using JiShe.CollectBus.IotSystems.MessageReceiveds;
|
||||
using JiShe.CollectBus.Kafka.Attributes;
|
||||
using JiShe.CollectBus.Kafka.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Timing;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka.Test
|
||||
{
|
||||
public class KafkaSubscribeTest: IKafkaSubscribe
|
||||
{
|
||||
[KafkaSubscribe(ProtocolConst.TESTTOPIC, EnableBatch = false, BatchSize = 10)]
|
||||
|
||||
public async Task<ISubscribeAck> KafkaSubscribeAsync(TestTopic obj)
|
||||
//public async Task<ISubscribeAck> KafkaSubscribeAsync(IEnumerable<int> obj)
|
||||
{
|
||||
Console.WriteLine($"收到订阅消息: {JsonSerializer.Serialize(obj)}");
|
||||
return SubscribeAck.Success();
|
||||
}
|
||||
|
||||
|
||||
//[KafkaSubscribe(ProtocolConst.SubscriberLoginIssuedEventName)]
|
||||
////[CapSubscribe(ProtocolConst.SubscriberLoginIssuedEventName)]
|
||||
//public async Task<ISubscribeAck> LoginIssuedEvent(IssuedEventMessage issuedEventMessage)
|
||||
//{
|
||||
// Console.WriteLine($"收到订阅消息: {JsonSerializer.Serialize(issuedEventMessage)}");
|
||||
// return SubscribeAck.Success();
|
||||
//}
|
||||
|
||||
//[KafkaSubscribe(ProtocolConst.SubscriberHeartbeatIssuedEventName)]
|
||||
////[CapSubscribe(ProtocolConst.SubscriberHeartbeatIssuedEventName)]
|
||||
//public async Task<ISubscribeAck> HeartbeatIssuedEvent(IssuedEventMessage issuedEventMessage)
|
||||
//{
|
||||
// Console.WriteLine($"收到订阅消息: {JsonSerializer.Serialize(issuedEventMessage)}");
|
||||
// return SubscribeAck.Success();
|
||||
//}
|
||||
|
||||
//[KafkaSubscribe(ProtocolConst.SubscriberReceivedEventName)]
|
||||
////[CapSubscribe(ProtocolConst.SubscriberReceivedEventName)]
|
||||
//public async Task<ISubscribeAck> ReceivedEvent(MessageReceived receivedMessage)
|
||||
//{
|
||||
// Console.WriteLine($"收到订阅消息: {JsonSerializer.Serialize(receivedMessage)}");
|
||||
// return SubscribeAck.Success();
|
||||
//}
|
||||
|
||||
//[KafkaSubscribe(ProtocolConst.SubscriberHeartbeatReceivedEventName)]
|
||||
////[CapSubscribe(ProtocolConst.SubscriberHeartbeatReceivedEventName)]
|
||||
//public async Task<ISubscribeAck> ReceivedHeartbeatEvent(MessageReceivedHeartbeat receivedHeartbeatMessage)
|
||||
//{
|
||||
// Console.WriteLine($"收到订阅消息: {JsonSerializer.Serialize(receivedHeartbeatMessage)}");
|
||||
// return SubscribeAck.Success();
|
||||
//}
|
||||
|
||||
//[KafkaSubscribe(ProtocolConst.SubscriberLoginReceivedEventName)]
|
||||
////[CapSubscribe(ProtocolConst.SubscriberLoginReceivedEventName)]
|
||||
//public async Task<ISubscribeAck> ReceivedLoginEvent(MessageReceivedLogin receivedLoginMessage)
|
||||
//{
|
||||
// Console.WriteLine($"收到订阅消息: {JsonSerializer.Serialize(receivedLoginMessage)}");
|
||||
// return SubscribeAck.Success();
|
||||
//}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@ -1,197 +0,0 @@
|
||||
// See https://aka.ms/new-console-template for more information
|
||||
using JiShe.CollectBus.Common;
|
||||
using JiShe.CollectBus.Common.Consts;
|
||||
using JiShe.CollectBus.Kafka;
|
||||
using JiShe.CollectBus.Kafka.AdminClient;
|
||||
using JiShe.CollectBus.Kafka.Consumer;
|
||||
using JiShe.CollectBus.Kafka.Internal;
|
||||
using JiShe.CollectBus.Kafka.Producer;
|
||||
using JiShe.CollectBus.Kafka.Test;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
|
||||
#region 基准测试
|
||||
//var summary = BenchmarkRunner.Run<KafkaProduceBenchmark>();
|
||||
//Console.WriteLine("压测完成");
|
||||
//return;
|
||||
#endregion 基准测试
|
||||
|
||||
|
||||
var host = Host.CreateDefaultBuilder(args)
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
// 构建配置
|
||||
var config = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json")
|
||||
.Build();
|
||||
// 直接读取配置项
|
||||
var greeting = config["ServerApplicationOptions:ServerTagName"];
|
||||
Console.WriteLine(greeting); // 输出: Hello, World!
|
||||
|
||||
|
||||
// 创建服务容器
|
||||
//var services = new ServiceCollection();
|
||||
// 注册 IConfiguration 实例
|
||||
services.AddSingleton<IConfiguration>(config);
|
||||
|
||||
// 初始化日志
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(config) // 从 appsettings.json 读取配置
|
||||
.CreateLogger();
|
||||
|
||||
// 配置日志系统
|
||||
services.AddLogging(logging =>
|
||||
{
|
||||
logging.ClearProviders();
|
||||
logging.AddSerilog();
|
||||
});
|
||||
//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<IProducerService, ProducerService>();
|
||||
services.AddSingleton<IConsumerService, ConsumerService>();
|
||||
services.AddSingleton<KafkaPollyPipeline>();
|
||||
services.AddTransient<KafkaSubscribeTest>();
|
||||
|
||||
})
|
||||
.ConfigureConsoleAppBuilder(appBuilder =>
|
||||
{
|
||||
|
||||
})
|
||||
.Build();
|
||||
|
||||
|
||||
await host.StartAsync();
|
||||
var appBuilder = host.Services.GetRequiredService<IApplicationBuilder>();
|
||||
appBuilder.ApplicationServices.UseKafkaSubscribe();
|
||||
|
||||
|
||||
// 构建ServiceProvider
|
||||
//var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// 获取日志记录器工厂
|
||||
var loggerFactory = host.Services.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger<Program>();
|
||||
logger.LogInformation("程序启动");
|
||||
|
||||
|
||||
var _kafkaPollyPipeline = host.Services.GetRequiredService<KafkaPollyPipeline>();
|
||||
if (_kafkaPollyPipeline == null)
|
||||
{
|
||||
logger.LogInformation("KafkaPollyPipeline未注册!");
|
||||
}
|
||||
|
||||
|
||||
var adminClientService = host.Services.GetRequiredService<IAdminClientService>();
|
||||
var configuration = host.Services.GetRequiredService<IConfiguration>();
|
||||
|
||||
var kafkaOptionConfig=host.Services.GetRequiredService<IOptions<ServerApplicationOptions>>();
|
||||
|
||||
string topic = ProtocolConst.TESTTOPIC;
|
||||
//await adminClientService.DeleteTopicAsync(topic);
|
||||
// 创建 topic
|
||||
//await adminClientService.CreateTopicAsync(topic, configuration.GetValue<int>(CommonConst.NumPartitions), 3);
|
||||
|
||||
var consumerService = host.Services.GetRequiredService<IConsumerService>();
|
||||
var producerService = host.Services.GetRequiredService<IProducerService>();
|
||||
//var kafkaOptions = host.Services.GetRequiredService<IOptions<KafkaOptionConfig>>();
|
||||
//await consumerService.SubscribeAsync<object>(topic, (message) =>
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// logger.LogInformation($"消费消息:{message}");
|
||||
// return Task.FromResult(true);
|
||||
|
||||
// }
|
||||
// catch (ConsumeException ex)
|
||||
// {
|
||||
// // 处理消费错误
|
||||
// logger.LogError($"kafka消费异常:{ex.Message}");
|
||||
// }
|
||||
// return Task.FromResult(false);
|
||||
//}, "default");
|
||||
|
||||
//Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
|
||||
//for (int i = 0; i < 3; i++)
|
||||
//{
|
||||
//await consumerService.SubscribeBatchAsync<dynamic>(topic, (message) =>
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// int index = 0;
|
||||
// logger.LogInformation($"消费消息_{index}消费总数:{message.Count()}:{JsonSerializer.Serialize(message)}");
|
||||
// return Task.FromResult(true);
|
||||
|
||||
// }
|
||||
// catch (ConsumeException ex)
|
||||
// {
|
||||
// // 处理消费错误
|
||||
// logger.LogError($"kafka消费异常:{ex.Message}");
|
||||
// }
|
||||
// return Task.FromResult(false);
|
||||
//});
|
||||
//}
|
||||
//stopwatch.Stop();
|
||||
//Console.WriteLine($"耗时: {stopwatch.ElapsedMilliseconds} 毫秒,{stopwatch.ElapsedMilliseconds/1000} 秒");
|
||||
|
||||
|
||||
int num = 1;
|
||||
while (num <= 6)
|
||||
{
|
||||
await producerService.ProduceAsync<TestTopic>(topic, new TestTopic { Topic = topic, Val = num });
|
||||
num++;
|
||||
}
|
||||
|
||||
//int num = 2;
|
||||
//while (num <= 4)
|
||||
//{
|
||||
// await producerService.ProduceAsync<string>(topic, num.ToString());
|
||||
// num++;
|
||||
//}
|
||||
//await Task.Factory.StartNew(async() => {
|
||||
// int num = 0;
|
||||
// while (true)
|
||||
// {
|
||||
// //await producerService.ProduceAsync(topic, new TestTopic { Topic = topic, Val = i });
|
||||
// await producerService.ProduceAsync<string>(topic, num.ToString());
|
||||
// num++;
|
||||
// }
|
||||
//});
|
||||
Console.WriteLine("\n按Esc键退出");
|
||||
while (true)
|
||||
{
|
||||
var key = Console.ReadKey(intercept: true); // intercept:true 隐藏按键显示
|
||||
|
||||
if (key.Key == ConsoleKey.Escape)
|
||||
{
|
||||
await host.StopAsync();
|
||||
Console.WriteLine("\n程序已退出");
|
||||
break;
|
||||
}
|
||||
}
|
||||
(host.Services as IDisposable)?.Dispose();
|
||||
|
||||
|
||||
public class TestTopic
|
||||
{
|
||||
public string Topic { get; set; }
|
||||
public int Val { get; set; }
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"profiles": {
|
||||
"WSL": {
|
||||
"commandName": "WSL2",
|
||||
"distributionName": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,155 +0,0 @@
|
||||
{
|
||||
"Serilog": {
|
||||
"Using": [
|
||||
"Serilog.Sinks.Console",
|
||||
"Serilog.Sinks.File"
|
||||
],
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"Volo.Abp": "Warning",
|
||||
"Hangfire": "Warning",
|
||||
"DotNetCore.CAP": "Warning",
|
||||
"Serilog.AspNetCore": "Information",
|
||||
"Microsoft.EntityFrameworkCore": "Warning",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Microsoft.AspNetCore.Diagnostics.HealthChecks": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Console"
|
||||
},
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"path": "logs/logs-.txt",
|
||||
"rollingInterval": "Day"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"App": {
|
||||
"SelfUrl": "http://localhost:44315",
|
||||
"CorsOrigins": "http://localhost:4200,http://localhost:3100"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"Default": "mongodb://mongo_PmEeF3:lixiao1980@192.168.1.9:27017/JiSheCollectBus?authSource=admin&maxPoolSize=400&minPoolSize=10&waitQueueTimeoutMS=5000",
|
||||
"Kafka": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092",
|
||||
"PrepayDB": "server=118.190.144.92;database=jishe.sysdb;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False",
|
||||
"EnergyDB": "server=118.190.144.92;database=db_energy;uid=sa;pwd=admin@2023;Encrypt=False;Trust Server Certificate=False"
|
||||
},
|
||||
"Redis": {
|
||||
"Configuration": "192.168.1.9:6380,password=1q2w3e!@#,syncTimeout=30000,abortConnect=false,connectTimeout=30000,allowAdmin=true",
|
||||
"MaxPoolSize": "50",
|
||||
"DefaultDB": "14",
|
||||
"HangfireDB": "13"
|
||||
},
|
||||
"Jwt": {
|
||||
"Audience": "JiShe.CollectBus",
|
||||
"SecurityKey": "dzehzRz9a8asdfasfdadfasdfasdfafsdadfasbasdf=",
|
||||
"Issuer": "JiShe.CollectBus",
|
||||
"ExpirationTime": 2
|
||||
},
|
||||
"HealthChecks": {
|
||||
"IsEnable": true,
|
||||
"HealthCheckDatabaseName": "HealthChecks",
|
||||
"EvaluationTimeInSeconds": 10,
|
||||
"MinimumSecondsBetweenFailureNotifications": 60
|
||||
},
|
||||
"SwaggerConfig": [
|
||||
{
|
||||
"GroupName": "Basic",
|
||||
"Title": "【后台管理】基础模块",
|
||||
"Version": "V1"
|
||||
},
|
||||
{
|
||||
"GroupName": "Business",
|
||||
"Title": "【后台管理】业务模块",
|
||||
"Version": "V1"
|
||||
}
|
||||
],
|
||||
"Kafka": {
|
||||
"BootstrapServers": "192.168.1.9:29092,192.168.1.9:39092,192.168.1.9:49092",
|
||||
"EnableFilter": true,
|
||||
"EnableAuthorization": false,
|
||||
"SecurityProtocol": "SaslPlaintext",
|
||||
"SaslMechanism": "Plain",
|
||||
"SaslUserName": "lixiao",
|
||||
"SaslPassword": "lixiao1980",
|
||||
"KafkaReplicationFactor": 3,
|
||||
"NumPartitions": 30,
|
||||
"FirstCollectionTime": "2025-04-22 16:07:00"
|
||||
},
|
||||
"IoTDBOptions": {
|
||||
"UserName": "root",
|
||||
"Password": "root",
|
||||
"ClusterList": [ "192.168.1.9:6667" ],
|
||||
"PoolSize": 32,
|
||||
"DataBaseName": "energy",
|
||||
"OpenDebugMode": true,
|
||||
"UseTableSessionPoolByDefault": false
|
||||
},
|
||||
"Cassandra": {
|
||||
"ReplicationStrategy": {
|
||||
"Class": "NetworkTopologyStrategy", //策略为NetworkTopologyStrategy时才会有多个数据中心,SimpleStrategy用在只有一个数据中心的情况下
|
||||
"DataCenters": [
|
||||
{
|
||||
"Name": "dc1",
|
||||
"ReplicationFactor": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
"Nodes": [
|
||||
{
|
||||
"Host": "192.168.1.9",
|
||||
"Port": 9042,
|
||||
"DataCenter": "dc1",
|
||||
"Rack": "RAC1"
|
||||
},
|
||||
{
|
||||
"Host": "192.168.1.9",
|
||||
"Port": 9043,
|
||||
"DataCenter": "dc1",
|
||||
"Rack": "RAC2"
|
||||
},
|
||||
{
|
||||
"Host": "192.168.1.9",
|
||||
"Port": 9044,
|
||||
"DataCenter": "dc1",
|
||||
"Rack": "RAC2"
|
||||
}
|
||||
],
|
||||
"Username": "admin",
|
||||
"Password": "lixiao1980",
|
||||
"Keyspace": "jishecollectbus",
|
||||
"ConsistencyLevel": "Quorum",
|
||||
"PoolingOptions": {
|
||||
"CoreConnectionsPerHost": 4,
|
||||
"MaxConnectionsPerHost": 8,
|
||||
"MaxRequestsPerConnection": 2000
|
||||
},
|
||||
"SocketOptions": {
|
||||
"ConnectTimeoutMillis": 10000,
|
||||
"ReadTimeoutMillis": 20000
|
||||
},
|
||||
"QueryOptions": {
|
||||
"ConsistencyLevel": "Quorum",
|
||||
"SerialConsistencyLevel": "Serial",
|
||||
"DefaultIdempotence": true
|
||||
}
|
||||
},
|
||||
"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,190 +0,0 @@
|
||||
using Confluent.Kafka;
|
||||
using Confluent.Kafka.Admin;
|
||||
using JiShe.CollectBus.Kafka.Internal;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka.AdminClient;
|
||||
|
||||
public class AdminClientService : IAdminClientService, IDisposable, ISingletonDependency
|
||||
{
|
||||
private readonly ILogger<AdminClientService> _logger;
|
||||
private readonly KafkaOptionConfig _kafkaOptionConfig;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AdminClientService" /> class.
|
||||
/// </summary>
|
||||
/// <param name="configuration"></param>
|
||||
/// <param name="logger"></param>
|
||||
public AdminClientService(IConfiguration configuration, ILogger<AdminClientService> logger, IOptions<KafkaOptionConfig> kafkaOptionConfig)
|
||||
{
|
||||
_logger = logger;
|
||||
_kafkaOptionConfig = kafkaOptionConfig.Value;
|
||||
Instance = GetInstance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the instance.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The instance.
|
||||
/// </value>
|
||||
public IAdminClient Instance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建Kafka主题
|
||||
/// </summary>
|
||||
/// <param name="topic"></param>
|
||||
/// <param name="numPartitions"></param>
|
||||
/// <param name="replicationFactor"></param>
|
||||
/// <returns></returns>
|
||||
public async Task CreateTopicAsync(string topic, int numPartitions, short replicationFactor)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (await CheckTopicAsync(topic)) return;
|
||||
|
||||
|
||||
await Instance.CreateTopicsAsync(new[]
|
||||
{
|
||||
new TopicSpecification
|
||||
{
|
||||
Name = topic,
|
||||
NumPartitions = numPartitions,
|
||||
ReplicationFactor = replicationFactor
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (CreateTopicsException e)
|
||||
{
|
||||
if (e.Results[0].Error.Code != ErrorCode.TopicAlreadyExists) throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除Kafka主题
|
||||
/// </summary>
|
||||
/// <param name="topic"></param>
|
||||
/// <returns></returns>
|
||||
public async Task DeleteTopicAsync(string topic)
|
||||
{
|
||||
await Instance.DeleteTopicsAsync(new[] { topic });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Kafka主题列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<List<string>> ListTopicsAsync()
|
||||
{
|
||||
var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(10));
|
||||
return new List<string>(metadata.Topics.Select(t => t.Topic));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断Kafka主题是否存在
|
||||
/// </summary>
|
||||
/// <param name="topic"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> TopicExistsAsync(string topic)
|
||||
{
|
||||
var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(10));
|
||||
return metadata.Topics.Any(t => t.Topic == topic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测分区是否存在
|
||||
/// </summary>
|
||||
/// <param name="topic"></param>
|
||||
/// <param name="partitions"></param>
|
||||
/// <returns></returns>
|
||||
public Dictionary<int, bool> CheckPartitionsExists(string topic, int[] partitions)
|
||||
{
|
||||
var result = new Dictionary<int, bool>();
|
||||
var metadata = Instance.GetMetadata(topic, TimeSpan.FromSeconds(10));
|
||||
if (metadata.Topics.Count == 0)
|
||||
return partitions.ToDictionary(p => p, p => false);
|
||||
var existingPartitions = metadata.Topics[0].Partitions.Select(p => p.PartitionId).ToHashSet();
|
||||
return partitions.ToDictionary(p => p, p => existingPartitions.Contains(p));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测分区是否存在
|
||||
/// </summary>
|
||||
/// <param name="topic"></param>
|
||||
/// <param name="targetPartition"></param>
|
||||
/// <returns></returns>
|
||||
public bool CheckPartitionsExist(string topic, int targetPartition)
|
||||
{
|
||||
var metadata = Instance.GetMetadata(topic, TimeSpan.FromSeconds(10));
|
||||
if (metadata.Topics.Count == 0)
|
||||
return false;
|
||||
var partitions = metadata.Topics[0].Partitions;
|
||||
return partitions.Any(p => p.PartitionId == targetPartition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取主题的分区数量
|
||||
/// </summary>
|
||||
/// <param name="topic"></param>
|
||||
/// <returns></returns>
|
||||
public int GetTopicPartitionsNum(string topic)
|
||||
{
|
||||
var metadata = Instance.GetMetadata(topic, TimeSpan.FromSeconds(10));
|
||||
if (metadata.Topics.Count == 0)
|
||||
return 0;
|
||||
return metadata.Topics[0].Partitions.Count;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Instance?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IAdminClient GetInstance()
|
||||
{
|
||||
var adminClientConfig = new AdminClientConfig
|
||||
{
|
||||
BootstrapServers = _kafkaOptionConfig.BootstrapServers
|
||||
};
|
||||
if (_kafkaOptionConfig.EnableAuthorization)
|
||||
{
|
||||
adminClientConfig.SecurityProtocol = _kafkaOptionConfig.SecurityProtocol;
|
||||
adminClientConfig.SaslMechanism = _kafkaOptionConfig.SaslMechanism;
|
||||
adminClientConfig.SaslUsername = _kafkaOptionConfig.SaslUserName;
|
||||
adminClientConfig.SaslPassword = _kafkaOptionConfig.SaslPassword;
|
||||
}
|
||||
return new AdminClientBuilder(adminClientConfig).Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the topic asynchronous.
|
||||
/// </summary>
|
||||
/// <param name="topic">The topic.</param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> CheckTopicAsync(string topic)
|
||||
{
|
||||
var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(5));
|
||||
return await Task.FromResult(metadata.Topics.Exists(a => a.Topic == topic));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断Kafka主题是否存在
|
||||
/// </summary>
|
||||
/// <param name="topic">主题名称</param>
|
||||
/// <param name="numPartitions">副本数量,不能高于Brokers数量</param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> CheckTopicAsync(string topic, int numPartitions)
|
||||
{
|
||||
var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(5));
|
||||
if (numPartitions > metadata.Brokers.Count)
|
||||
throw new Exception($"{nameof(CheckTopicAsync)} 主题检查时,副本数量大于了节点数量。");
|
||||
|
||||
return await Task.FromResult(metadata.Topics.Exists(a => a.Topic == topic));
|
||||
}
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka.AdminClient
|
||||
{
|
||||
public interface IAdminClientService
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建Kafka主题
|
||||
/// </summary>
|
||||
/// <param name="topic">主题名称</param>
|
||||
/// <param name="numPartitions">主题分区数量</param>
|
||||
/// <param name="replicationFactor">副本数量,不能高于Brokers数量</param>
|
||||
/// <returns></returns>
|
||||
Task CreateTopicAsync(string topic, int numPartitions, short replicationFactor);
|
||||
|
||||
/// <summary>
|
||||
/// 删除Kafka主题
|
||||
/// </summary>
|
||||
/// <param name="topic"></param>
|
||||
/// <returns></returns>
|
||||
Task DeleteTopicAsync(string topic);
|
||||
|
||||
/// <summary>
|
||||
/// 获取Kafka主题列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<string>> ListTopicsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 判断Kafka主题是否存在
|
||||
/// </summary>
|
||||
/// <param name="topic"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> TopicExistsAsync(string topic);
|
||||
|
||||
/// <summary>
|
||||
/// 检测分区是否存在
|
||||
/// </summary>
|
||||
/// <param name="topic"></param>
|
||||
/// <param name="partitions"></param>
|
||||
/// <returns></returns>
|
||||
Dictionary<int, bool> CheckPartitionsExists(string topic, int[] partitions);
|
||||
|
||||
/// <summary>
|
||||
/// 检测分区是否存在
|
||||
/// </summary>
|
||||
/// <param name="topic"></param>
|
||||
/// <param name="targetPartition"></param>
|
||||
/// <returns></returns>
|
||||
bool CheckPartitionsExist(string topic, int targetPartition);
|
||||
|
||||
/// <summary>
|
||||
/// 获取主题的分区数量
|
||||
/// </summary>
|
||||
/// <param name="topic"></param>
|
||||
/// <returns></returns>
|
||||
int GetTopicPartitionsNum(string topic);
|
||||
}
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
namespace JiShe.CollectBus.Kafka.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class KafkaSubscribeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 订阅主题
|
||||
/// </summary>
|
||||
/// <param name="batchTimeout"></param>
|
||||
public KafkaSubscribeAttribute(string topic)
|
||||
{
|
||||
Topic = topic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 订阅主题
|
||||
/// </summary>
|
||||
public KafkaSubscribeAttribute(string topic, int partition)
|
||||
{
|
||||
Topic = topic;
|
||||
Partition = partition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 订阅的主题
|
||||
/// </summary>
|
||||
public string Topic { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 分区
|
||||
/// </summary>
|
||||
public int Partition { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 消费者组
|
||||
/// </summary>
|
||||
public string? GroupId { get; set; } = null; //"default"
|
||||
|
||||
/// <summary>
|
||||
/// 任务数(默认是多少个分区多少个任务)
|
||||
/// 如设置订阅指定Partition则任务数始终为1
|
||||
/// </summary>
|
||||
public int TaskCount { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 批量处理数量
|
||||
/// </summary>
|
||||
public int BatchSize { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用批量处理
|
||||
/// </summary>
|
||||
public bool EnableBatch { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 批次超时时间
|
||||
/// 格式:("00:05:00")
|
||||
/// </summary>
|
||||
public TimeSpan? BatchTimeout { get; set; } = null;
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
namespace JiShe.CollectBus.Kafka.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class TopicAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TopicAttribute" /> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
public TopicAttribute(string name = "Default")
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public string Name { get; set; }
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
using Confluent.Kafka;
|
||||
using JiShe.CollectBus.Common.Consts;
|
||||
using JiShe.CollectBus.Kafka.Consumer;
|
||||
using JiShe.CollectBus.Kafka.Internal;
|
||||
using JiShe.CollectBus.Kafka.Producer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Modularity;
|
||||
using static Confluent.Kafka.ConfigPropertyNames;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka
|
||||
{
|
||||
public class CollectBusKafkaModule : AbpModule
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
var configuration = context.Services.GetConfiguration();
|
||||
//var kafkaSection = configuration.GetSection(CommonConst.Kafka);
|
||||
//KafkaOptionConfig kafkaOptionConfig = new KafkaOptionConfig ();
|
||||
//kafkaSection.Bind(kafkaOptionConfig);
|
||||
//if (configuration[CommonConst.ServerTagName] != null)
|
||||
//{
|
||||
// kafkaOptionConfig.ServerTagName = configuration[CommonConst.ServerTagName]!;
|
||||
//}
|
||||
//context.Services.AddSingleton(kafkaOptionConfig);
|
||||
|
||||
//context.Services.Configure<KafkaOptionConfig>(context.Services.GetConfiguration().GetSection(CommonConst.Kafka));
|
||||
|
||||
Configure<KafkaOptionConfig>(options =>
|
||||
{
|
||||
configuration.GetSection(CommonConst.Kafka).Bind(options);
|
||||
});
|
||||
|
||||
|
||||
// 注册Producer
|
||||
context.Services.AddSingleton<IProducerService, ProducerService>();
|
||||
// 注册Consumer
|
||||
context.Services.AddSingleton<IConsumerService, ConsumerService>();
|
||||
|
||||
// 注册Polly
|
||||
context.Services.AddSingleton<KafkaPollyPipeline>();
|
||||
|
||||
//context.Services.AddHostedService<HostedService>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在初始化之前,初始化Kafka Topic
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
|
||||
{
|
||||
var app = context.GetApplicationBuilder();
|
||||
app.ApplicationServices.UseInitKafkaTopic();
|
||||
}
|
||||
|
||||
public override void OnApplicationInitialization(ApplicationInitializationContext context)
|
||||
{
|
||||
var app = context.GetApplicationBuilder();
|
||||
// 注册Subscriber
|
||||
app.ApplicationServices.UseKafkaSubscribe();
|
||||
|
||||
// 获取程序集
|
||||
//app.UseKafkaSubscribers(Assembly.Load("JiShe.CollectBus.Application"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,661 +0,0 @@
|
||||
using Confluent.Kafka;
|
||||
using JiShe.CollectBus.Common;
|
||||
using JiShe.CollectBus.Common.Consts;
|
||||
using JiShe.CollectBus.Common.Helpers;
|
||||
using JiShe.CollectBus.Kafka.Internal;
|
||||
using JiShe.CollectBus.Kafka.Serialization;
|
||||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka.Consumer
|
||||
{
|
||||
public class ConsumerService : IConsumerService, IDisposable
|
||||
{
|
||||
private readonly ILogger<ConsumerService> _logger;
|
||||
/// <summary>
|
||||
/// 消费者存储
|
||||
/// Key 格式:{groupId}_{topic}_{TKey}_{TValue}
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, (object Consumer, CancellationTokenSource CTS)>
|
||||
_consumerStore = new();
|
||||
|
||||
/// <summary>
|
||||
/// 消费完或者无数据时的延迟时间
|
||||
/// </summary>
|
||||
private static TimeSpan DelayTime => TimeSpan.FromMilliseconds(100);
|
||||
|
||||
private readonly KafkaOptionConfig _kafkaOptionConfig;
|
||||
|
||||
private readonly ServerApplicationOptions _applicationOptions;
|
||||
|
||||
private readonly KafkaPollyPipeline _kafkaPollyPipeline;
|
||||
|
||||
/// <summary>
|
||||
/// ConsumerService
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="kafkaOptionConfig"></param>
|
||||
public ConsumerService(ILogger<ConsumerService> logger, IOptions<KafkaOptionConfig> kafkaOptionConfig, KafkaPollyPipeline kafkaPollyPipeline, IOptions<ServerApplicationOptions> applicationOptions)
|
||||
{
|
||||
_logger = logger;
|
||||
_kafkaOptionConfig = kafkaOptionConfig.Value;
|
||||
_applicationOptions = applicationOptions.Value;
|
||||
_kafkaPollyPipeline = kafkaPollyPipeline;
|
||||
}
|
||||
|
||||
#region private 私有方法
|
||||
|
||||
/// <summary>
|
||||
/// 创建消费者
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <returns></returns>
|
||||
private IConsumer<TKey, TValue> CreateConsumer<TKey, TValue>(string? groupId = null) where TKey : notnull where TValue : class
|
||||
{
|
||||
var config = BuildConsumerConfig(groupId);
|
||||
return new ConsumerBuilder<TKey, TValue>(config)
|
||||
.SetValueDeserializer(new JsonSerializer<TValue>())
|
||||
.SetLogHandler((_, log) => _logger.LogInformation($"消费者Log: {log.Message}"))
|
||||
.SetErrorHandler((_, e) => _logger.LogError($"消费者错误: {e.Reason}"))
|
||||
.Build();
|
||||
}
|
||||
|
||||
private ConsumerConfig BuildConsumerConfig(string? groupId = null)
|
||||
{
|
||||
var config = new ConsumerConfig
|
||||
{
|
||||
BootstrapServers = _kafkaOptionConfig.BootstrapServers,
|
||||
GroupId = groupId ?? _applicationOptions.ServerTagName,
|
||||
AutoOffsetReset = AutoOffsetReset.Earliest,
|
||||
EnableAutoCommit = false, // 禁止AutoCommit
|
||||
EnablePartitionEof = true, // 启用分区末尾标记
|
||||
//AllowAutoCreateTopics = true, // 启用自动创建
|
||||
FetchMaxBytes = 1024 * 1024 * 50 // 增加拉取大小(50MB)
|
||||
};
|
||||
|
||||
if (_kafkaOptionConfig.EnableAuthorization)
|
||||
{
|
||||
config.SecurityProtocol = _kafkaOptionConfig.SecurityProtocol;
|
||||
config.SaslMechanism = _kafkaOptionConfig.SaslMechanism;
|
||||
config.SaslUsername = _kafkaOptionConfig.SaslUserName;
|
||||
config.SaslPassword = _kafkaOptionConfig.SaslPassword;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 订阅消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="topic"></param>
|
||||
/// <param name="messageHandler"></param>
|
||||
/// <returns></returns>
|
||||
public async Task SubscribeAsync<TKey, TValue>(string topic, Func<TKey, TValue, Task<bool>> messageHandler, string? groupId = null) where TKey : notnull where TValue : class
|
||||
{
|
||||
await SubscribeAsync<TKey, TValue>(new[] { topic }, messageHandler, groupId);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 订阅消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="topic"></param>
|
||||
/// <param name="messageHandler"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 订阅消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="topics"></param>
|
||||
/// <param name="messageHandler"></param>
|
||||
/// <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
|
||||
{
|
||||
try
|
||||
{
|
||||
await _kafkaPollyPipeline.KafkaPipeline.ExecuteAsync(async token =>
|
||||
{
|
||||
|
||||
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)
|
||||
{
|
||||
try
|
||||
{
|
||||
//_logger.LogInformation($"Kafka消费: {string.Join("", topics)} 开始拉取消息....");
|
||||
|
||||
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 DEBUG
|
||||
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
||||
#endif
|
||||
await Task.Delay(DelayTime, cts.Token);
|
||||
continue;
|
||||
}
|
||||
if (_kafkaOptionConfig.EnableFilter)
|
||||
{
|
||||
var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) } };
|
||||
// 检查 Header 是否符合条件
|
||||
if (!headersFilter.Match(result.Message.Headers))
|
||||
{
|
||||
consumer.Commit(result); // 提交偏移量
|
||||
// 跳过消息
|
||||
continue;
|
||||
}
|
||||
}
|
||||
bool sucess = await messageHandler(result.Message.Key, result.Message.Value);
|
||||
if (sucess)
|
||||
consumer.Commit(result); // 手动提交
|
||||
}
|
||||
catch (ConsumeException ex) when (KafkaPollyPipeline.IsRecoverableError(ex))
|
||||
{
|
||||
_logger.LogError(ex, $"{string.Join("、", topics)}消息消费失败: {ex.Error.Reason}");
|
||||
throw; // 抛出异常,以便重试
|
||||
}
|
||||
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;
|
||||
});
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 订阅消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="topics"></param>
|
||||
/// <param name="messageHandler"></param>
|
||||
/// <param name="groupId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task SubscribeAsync<TValue>(string[] topics, Func<TValue, Task<bool>> messageHandler, string? groupId) where TValue : class
|
||||
{
|
||||
try
|
||||
{
|
||||
await _kafkaPollyPipeline.KafkaPipeline.ExecuteAsync(async token =>
|
||||
{
|
||||
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);
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
int count = 0;
|
||||
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)
|
||||
{
|
||||
await Task.Delay(DelayTime, cts.Token);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.IsPartitionEOF)
|
||||
{
|
||||
#if DEBUG
|
||||
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
||||
#endif
|
||||
await Task.Delay(DelayTime, cts.Token);
|
||||
continue;
|
||||
}
|
||||
if (_kafkaOptionConfig.EnableFilter)
|
||||
{
|
||||
var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) } };
|
||||
// 检查 Header 是否符合条件
|
||||
if (!headersFilter.Match(result.Message.Headers))
|
||||
{
|
||||
consumer.Commit(result); // 提交偏移量
|
||||
// 跳过消息
|
||||
continue;
|
||||
}
|
||||
}
|
||||
bool sucess = await messageHandler(result.Message.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, "处理消息时发生未知错误");
|
||||
}
|
||||
}
|
||||
}, cts.Token);
|
||||
await Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 批量订阅消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">消息Key类型</typeparam>
|
||||
/// <typeparam name="TValue">消息Value类型</typeparam>
|
||||
/// <param name="topic">主题</param>
|
||||
/// <param name="messageBatchHandler">批量消息处理函数</param>
|
||||
/// <param name="groupId">消费组ID</param>
|
||||
/// <param name="batchSize">批次大小</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
|
||||
{
|
||||
try
|
||||
{
|
||||
await SubscribeBatchAsync<TKey, TValue>(new[] { topic }, messageBatchHandler, groupId, batchSize, batchTimeout);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量订阅消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">消息Key类型</typeparam>
|
||||
/// <typeparam name="TValue">消息Value类型</typeparam>
|
||||
/// <param name="topics">主题列表</param>
|
||||
/// <param name="messageBatchHandler">批量消息处理函数</param>
|
||||
/// <param name="groupId">消费组ID</param>
|
||||
/// <param name="batchSize">批次大小</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
|
||||
{
|
||||
try
|
||||
{
|
||||
await _kafkaPollyPipeline.KafkaPipeline.ExecuteAsync(async token =>
|
||||
{
|
||||
|
||||
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 (!cts.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 非阻塞快速累积消息
|
||||
while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout)
|
||||
{
|
||||
var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理批次
|
||||
if (messages.Count > 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
var offsetsToCommit = offsetsByPartition
|
||||
.Select(kv => new TopicPartitionOffset(kv.Key, new Offset(kv.Value + 1)))
|
||||
.ToList();
|
||||
consumer.Commit(offsetsToCommit);
|
||||
}
|
||||
messages.Clear();
|
||||
}
|
||||
|
||||
startTime = DateTime.UtcNow;
|
||||
}
|
||||
catch (ConsumeException ex) when (KafkaPollyPipeline.IsRecoverableError(ex))
|
||||
{
|
||||
_logger.LogError(ex, $"{string.Join("、", topics)} 消息消费失败: {ex.Error.Reason}");
|
||||
throw; // 抛出异常,以便重试
|
||||
}
|
||||
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;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 批量订阅消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">消息Value类型</typeparam>
|
||||
/// <param name="topic">主题列表</param>
|
||||
/// <param name="messageBatchHandler">批量消息处理函数</param>
|
||||
/// <param name="groupId">消费组ID</param>
|
||||
/// <param name="batchSize">批次大小</param>
|
||||
/// <param name="batchTimeout">批次超时时间</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
|
||||
{
|
||||
try
|
||||
{
|
||||
await SubscribeBatchAsync(new[] { topic }, messageBatchHandler, groupId, batchSize, batchTimeout, consumeTimeout);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 批量订阅消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">消息Value类型</typeparam>
|
||||
/// <param name="topics">主题列表</param>
|
||||
/// <param name="messageBatchHandler">批量消息处理函数</param>
|
||||
/// <param name="groupId">消费组ID</param>
|
||||
/// <param name="batchSize">批次大小</param>
|
||||
/// <param name="batchTimeout">批次超时时间</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
|
||||
{
|
||||
try
|
||||
{
|
||||
await _kafkaPollyPipeline.KafkaPipeline.ExecuteAsync(async token =>
|
||||
{
|
||||
|
||||
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 (!cts.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 非阻塞快速累积消息
|
||||
while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout)
|
||||
{
|
||||
var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
if (result.IsPartitionEOF)
|
||||
{
|
||||
//_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
||||
await Task.Delay(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);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理批次
|
||||
if (messages.Count > 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
var offsetsToCommit = offsetsByPartition
|
||||
.Select(kv => new TopicPartitionOffset(kv.Key, new Offset(kv.Value + 1)))
|
||||
.ToList();
|
||||
consumer.Commit(offsetsToCommit);
|
||||
}
|
||||
messages.Clear();
|
||||
}
|
||||
|
||||
startTime = DateTime.UtcNow;
|
||||
}
|
||||
catch (ConsumeException ex) when (KafkaPollyPipeline.IsRecoverableError(ex))
|
||||
{
|
||||
_logger.LogError(ex, $"消息消费失败: {ex.Error.Reason}");
|
||||
throw; // 抛出异常,以便重试
|
||||
}
|
||||
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;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 取消消息订阅
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
public void Unsubscribe<TKey, TValue>(string[] topics, string? groupId) where TKey : notnull where TValue : class
|
||||
{
|
||||
try
|
||||
{
|
||||
var consumerKey = $"{groupId}_{string.Join("_", topics)}_{typeof(TKey).Name}_{typeof(TValue).Name}";
|
||||
if (_consumerStore.TryRemove(consumerKey, out var entry))
|
||||
{
|
||||
entry.CTS.Cancel();
|
||||
(entry.Consumer as IDisposable)?.Dispose();
|
||||
entry.CTS.Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var entry in _consumerStore.Values)
|
||||
{
|
||||
entry.CTS.Cancel();
|
||||
(entry.Consumer as IDisposable)?.Dispose();
|
||||
entry.CTS.Dispose();
|
||||
}
|
||||
_consumerStore.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
namespace JiShe.CollectBus.Kafka.Consumer;
|
||||
|
||||
public interface IConsumerService
|
||||
{
|
||||
Task SubscribeAsync<TKey, TValue>(string topic, Func<TKey, TValue, Task<bool>> messageHandler,
|
||||
string? groupId = null) where TKey : notnull where TValue : class;
|
||||
|
||||
/// <summary>
|
||||
/// 订阅消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="topic"></param>
|
||||
/// <param name="messageHandler"></param>
|
||||
/// <returns></returns>
|
||||
Task SubscribeAsync<TValue>(string topic, Func<TValue, Task<bool>> messageHandler, string? groupId = null)
|
||||
where TValue : class;
|
||||
|
||||
Task SubscribeAsync<TKey, TValue>(string[] topics, Func<TKey, TValue, Task<bool>> messageHandler, string? groupId)
|
||||
where TKey : notnull where TValue : class;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 订阅消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="topics"></param>
|
||||
/// <param name="messageHandler"></param>
|
||||
/// <returns></returns>
|
||||
Task SubscribeAsync<TValue>(string[] topics, Func<TValue, Task<bool>> messageHandler, string? groupId = null)
|
||||
where TValue : class;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
void Unsubscribe<TKey, TValue>(string[] topics, string groupId) where TKey : notnull where TValue : class;
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka
|
||||
{
|
||||
public class HostedService : IHostedService, IDisposable
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IServiceProvider _provider;
|
||||
public HostedService(ILogger<HostedService> logger, IServiceProvider provider)
|
||||
{
|
||||
_logger = logger;
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("程序启动");
|
||||
Task.Run(() =>
|
||||
{
|
||||
_provider.UseKafkaSubscribe();
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("结束");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
using Confluent.Kafka;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// 消息头过滤器
|
||||
/// </summary>
|
||||
public class HeadersFilter : Dictionary<string, byte[]>
|
||||
{
|
||||
/// <summary>
|
||||
/// 判断Headers是否匹配
|
||||
/// </summary>
|
||||
/// <param name="headers"></param>
|
||||
/// <returns></returns>
|
||||
public bool Match(Headers headers)
|
||||
{
|
||||
foreach (var kvp in this)
|
||||
if (!headers.TryGetLastBytes(kvp.Key, out var value) || !value.SequenceEqual(kvp.Value))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
namespace JiShe.CollectBus.Kafka.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Kafka订阅者
|
||||
/// <para>
|
||||
/// 订阅者需要继承此接口并需要依赖注入,并使用<see cref="KafkaSubscribeAttribute" />标记
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IKafkaSubscribe
|
||||
{
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
namespace JiShe.CollectBus.Kafka.Internal;
|
||||
|
||||
public interface ISubscribeAck
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否成功标记
|
||||
/// </summary>
|
||||
bool Ack { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
string? Msg { get; set; }
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
using Confluent.Kafka;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka.Internal;
|
||||
|
||||
public class KafkaOptionConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// kafka地址
|
||||
/// </summary>
|
||||
public string BootstrapServers { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// kafka主题副本数量
|
||||
/// </summary>
|
||||
public short KafkaReplicationFactor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// kafka主题分区数量
|
||||
/// </summary>
|
||||
public int NumPartitions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否开启过滤器
|
||||
/// </summary>
|
||||
public bool EnableFilter { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否开启认证
|
||||
/// </summary>
|
||||
public bool EnableAuthorization { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 安全协议
|
||||
/// </summary>
|
||||
public SecurityProtocol SecurityProtocol { get; set; } = SecurityProtocol.SaslPlaintext;
|
||||
|
||||
/// <summary>
|
||||
/// 认证方式
|
||||
/// </summary>
|
||||
public SaslMechanism SaslMechanism { get; set; } = SaslMechanism.Plain;
|
||||
|
||||
/// <summary>
|
||||
/// 用户名
|
||||
/// </summary>
|
||||
public string? SaslUserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
public string? SaslPassword { 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// 反射辅助类
|
||||
/// </summary>
|
||||
public static class ReflectionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 集合类型
|
||||
/// Item1:参数类型
|
||||
/// Item2:集合元素类型
|
||||
/// </summary>
|
||||
public static Tuple<Type, Type?> GetParameterTypeInfo(this MethodInfo method, int parameterIndex = 0)
|
||||
{
|
||||
// 参数校验
|
||||
if (method == null) throw new ArgumentNullException(nameof(method));
|
||||
var parameters = method.GetParameters();
|
||||
if (parameterIndex < 0 || parameterIndex >= parameters.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(parameterIndex));
|
||||
|
||||
var param = parameters[parameterIndex];
|
||||
var paramType = param.ParameterType;
|
||||
Type? elementType = null;
|
||||
|
||||
// 判断是否是集合类型(排除字符串)
|
||||
if (paramType != typeof(string) && IsEnumerableType(paramType))
|
||||
elementType = GetEnumerableElementType(paramType);
|
||||
|
||||
return Tuple.Create(paramType, elementType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否是集合类型(排除字符串)
|
||||
/// </summary>
|
||||
public static bool IsEnumerableType(this Type type)
|
||||
{
|
||||
return type.IsArray
|
||||
|| (type.IsGenericType && type.GetInterfaces()
|
||||
.Any(t => t.IsGenericType
|
||||
&& t.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
|
||||
|| type.GetInterfaces().Any(t => t == typeof(IEnumerable));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取集合元素的类型
|
||||
/// </summary>
|
||||
public static Type? GetEnumerableElementType(this Type type)
|
||||
{
|
||||
// 处理数组类型
|
||||
if (type.IsArray)
|
||||
return type.GetElementType();
|
||||
|
||||
// 处理直接实现IEnumerable<T>的类型(如IEnumerable<int>本身)
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
return type.GetGenericArguments()[0];
|
||||
|
||||
// 处理通过接口实现IEnumerable<T>的泛型集合(如List<T>)
|
||||
var genericEnumerable = type.GetInterfaces()
|
||||
.FirstOrDefault(t => t.IsGenericType
|
||||
&& t.GetGenericTypeDefinition() == typeof(IEnumerable<>));
|
||||
if (genericEnumerable != null)
|
||||
return genericEnumerable.GetGenericArguments()[0];
|
||||
|
||||
// 处理非泛型集合类型(如 ArrayList)
|
||||
if (typeof(IEnumerable).IsAssignableFrom(type) && type == typeof(ArrayList))
|
||||
return typeof(ArrayList);
|
||||
// 返回null表示无法确定元素类型
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否使用强转换
|
||||
/// </summary>
|
||||
/// <param name="targetType"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsConvertType(this Type targetType)
|
||||
{
|
||||
// 处理可空类型
|
||||
var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType;
|
||||
// 情况1:值类型或基元类型(如 int、DateTime)
|
||||
if (underlyingType.IsValueType || underlyingType.IsPrimitive)
|
||||
return true;
|
||||
// 情况2:字符串类型直接赋值
|
||||
if (underlyingType == typeof(string))
|
||||
return true;
|
||||
|
||||
// 情况3:枚举类型处理
|
||||
//else if (underlyingType.IsEnum)
|
||||
//{
|
||||
// if (Enum.IsDefined(underlyingType, msg))
|
||||
// {
|
||||
// convertedValue = Enum.Parse(underlyingType, msg.ToString());
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
//}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
namespace JiShe.CollectBus.Kafka.Internal;
|
||||
|
||||
public class SubscribeResult : ISubscribeAck
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否成功
|
||||
/// </summary>
|
||||
public bool Ack { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
public string? Msg { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
/// <param name="msg">消息</param>
|
||||
public SubscribeResult Success(string? msg = null)
|
||||
{
|
||||
Ack = true;
|
||||
Msg = msg;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 失败
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <returns></returns>
|
||||
public SubscribeResult Fail(string? msg = null)
|
||||
{
|
||||
Msg = msg;
|
||||
Ack = false;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SubscribeAck
|
||||
{
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
/// <param name="msg">消息</param>
|
||||
/// <returns></returns>
|
||||
public static ISubscribeAck Success(string? msg = null)
|
||||
{
|
||||
return new SubscribeResult().Success(msg);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 失败
|
||||
/// </summary>
|
||||
/// <param name="msg">消息</param>
|
||||
/// <returns></returns>
|
||||
public static ISubscribeAck Fail(string? msg = null)
|
||||
{
|
||||
return new SubscribeResult().Fail(msg);
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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.Core" Version="8.3.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\shared\JiShe.CollectBus.Common\JiShe.CollectBus.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -1,404 +0,0 @@
|
||||
using Confluent.Kafka;
|
||||
using JiShe.CollectBus.Common.Consts;
|
||||
using JiShe.CollectBus.Common.Extensions;
|
||||
using JiShe.CollectBus.Common.Helpers;
|
||||
using JiShe.CollectBus.Kafka.AdminClient;
|
||||
using JiShe.CollectBus.Kafka.Attributes;
|
||||
using JiShe.CollectBus.Kafka.Consumer;
|
||||
using JiShe.CollectBus.Kafka.Internal;
|
||||
using JiShe.CollectBus.Kafka.Serialization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka
|
||||
{
|
||||
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)
|
||||
{
|
||||
//初始化主题信息
|
||||
var kafkaAdminClient = provider.GetRequiredService<IAdminClientService>();
|
||||
var kafkaOptions = provider.GetRequiredService<IOptions<KafkaOptionConfig>>();
|
||||
|
||||
var topics = ProtocolConstExtensions.GetAllTopicNamesByIssued();
|
||||
topics.AddRange(ProtocolConstExtensions.GetAllTopicNamesByReceived());
|
||||
|
||||
foreach (var item in topics)
|
||||
{
|
||||
kafkaAdminClient.CreateTopicAsync(item, kafkaOptions.Value.NumPartitions, kafkaOptions.Value.KafkaReplicationFactor).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加Kafka订阅
|
||||
/// </summary>
|
||||
public static void UseKafkaSubscribe(this IServiceProvider provider)
|
||||
{
|
||||
var lifetime = provider.GetRequiredService<IHostApplicationLifetime>();
|
||||
var kafkaOptions = provider.GetRequiredService<IOptions<KafkaOptionConfig>>();
|
||||
lifetime.ApplicationStarted.Register(() =>
|
||||
{
|
||||
var logger = provider.GetRequiredService<ILogger<CollectBusKafkaModule>>();
|
||||
//var threadCount = 0;
|
||||
//var topicCount = 0;
|
||||
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
|
||||
if (string.IsNullOrWhiteSpace(assemblyPath))
|
||||
{
|
||||
logger.LogWarning($"kafka订阅未能找到程序路径");
|
||||
return;
|
||||
}
|
||||
var dllFiles = Directory.GetFiles(assemblyPath, "*.dll");
|
||||
foreach (var file in dllFiles)
|
||||
{
|
||||
// 跳过已加载的程序集
|
||||
var assemblyName = AssemblyName.GetAssemblyName(file);
|
||||
var existingAssembly = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.FirstOrDefault(a => a.GetName().FullName == assemblyName.FullName);
|
||||
var assembly = existingAssembly ?? Assembly.LoadFrom(file);
|
||||
// 实现IKafkaSubscribe接口
|
||||
var subscribeTypes = assembly.GetTypes().Where(type =>
|
||||
typeof(IKafkaSubscribe).IsAssignableFrom(type) &&
|
||||
!type.IsAbstract && !type.IsInterface).ToList();
|
||||
if (subscribeTypes.Count == 0)
|
||||
continue;
|
||||
|
||||
// 并行处理
|
||||
Parallel.ForEach(subscribeTypes, subscribeType =>
|
||||
{
|
||||
var subscribes = provider.GetServices(subscribeType).ToList();
|
||||
Parallel.ForEach(subscribes,subscribe =>
|
||||
{
|
||||
if (subscribe != null)
|
||||
{
|
||||
Tuple<int, int> tuple = BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value);
|
||||
//threadCount += tuple.Item1;
|
||||
//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}线程");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加Kafka订阅
|
||||
/// </summary>
|
||||
public static void UseKafkaSubscribersAsync(this IApplicationBuilder app, Assembly assembly)
|
||||
{
|
||||
var provider = app.ApplicationServices;
|
||||
var lifetime = provider.GetRequiredService<IHostApplicationLifetime>();
|
||||
var kafkaOptions = provider.GetRequiredService<IOptions<KafkaOptionConfig>>();
|
||||
lifetime.ApplicationStarted.Register(() =>
|
||||
{
|
||||
var logger = provider.GetRequiredService<ILogger<CollectBusKafkaModule>>();
|
||||
int threadCount = 0;
|
||||
int topicCount = 0;
|
||||
var subscribeTypes = assembly.GetTypes()
|
||||
.Where(t => typeof(IKafkaSubscribe).IsAssignableFrom(t))
|
||||
.ToList();
|
||||
|
||||
if (subscribeTypes.Count == 0) return;
|
||||
foreach (var subscribeType in subscribeTypes)
|
||||
{
|
||||
var subscribes = provider.GetServices(subscribeType).ToList();
|
||||
subscribes.ForEach(subscribe =>
|
||||
{
|
||||
|
||||
if (subscribe != null)
|
||||
{
|
||||
Tuple<int, int> tuple = BuildKafkaSubscribe(subscribe, provider, logger, kafkaOptions.Value);
|
||||
threadCount += tuple.Item1;
|
||||
topicCount += tuple.Item2;
|
||||
}
|
||||
});
|
||||
}
|
||||
logger.LogInformation($"kafka订阅主题:{topicCount}数,共启动:{threadCount}线程");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建Kafka订阅
|
||||
/// </summary>
|
||||
private static Tuple<int, int> BuildKafkaSubscribe(object subscribe, IServiceProvider provider, ILogger<CollectBusKafkaModule> logger, KafkaOptionConfig kafkaOptionConfig)
|
||||
{
|
||||
var subscribedMethods = subscribe.GetType().GetMethods()
|
||||
.Select(m => new { Method = m, Attribute = m.GetCustomAttribute<KafkaSubscribeAttribute>() })
|
||||
.Where(x => x.Attribute != null)
|
||||
.ToArray();
|
||||
//var configuration = provider.GetRequiredService<IConfiguration>();
|
||||
int threadCount = 0;
|
||||
|
||||
Parallel.ForEach(subscribedMethods, sub =>
|
||||
{
|
||||
Interlocked.Increment(ref _topicSubscribeCount);
|
||||
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;
|
||||
Parallel.For(0,partitionCount, async (partition) =>
|
||||
{
|
||||
Interlocked.Increment(ref _threadCount);
|
||||
//Task.Run(() => StartConsumerAsync(provider, sub.Attribute!, sub.Method, subscribe, logger));
|
||||
//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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动后台消费线程
|
||||
/// </summary>
|
||||
private static async Task StartConsumerAsync(IServiceProvider provider, KafkaSubscribeAttribute attr, MethodInfo method, object subscribe, ILogger<CollectBusKafkaModule> logger)
|
||||
{
|
||||
var consumerService = provider.GetRequiredService<IConsumerService>();
|
||||
|
||||
if (attr.EnableBatch)
|
||||
{
|
||||
Interlocked.Increment(ref _threadStartCount);
|
||||
logger.LogInformation($"kafka开启线程消费:{_threadStartCount}");
|
||||
await consumerService.SubscribeBatchAsync<dynamic>(attr.Topic, async (message) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
#if DEBUG
|
||||
logger.LogInformation($"kafka批量消费消息:{message.Serialize()}");
|
||||
#endif
|
||||
// 处理消息
|
||||
return await ProcessMessageAsync(message.ToList(), method, subscribe);
|
||||
}
|
||||
catch (ConsumeException ex)
|
||||
{
|
||||
// 处理消费错误
|
||||
logger.LogError($"kafka批量消费异常:{ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 处理消费错误
|
||||
logger.LogError($"kafka批量消费异常:{ex.Message}");
|
||||
}
|
||||
return await Task.FromResult(false);
|
||||
}, attr.GroupId, attr.BatchSize, attr.BatchTimeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
Interlocked.Increment(ref _threadStartCount);
|
||||
logger.LogInformation($"kafka开启线程消费:{_threadStartCount}");
|
||||
await consumerService.SubscribeAsync<dynamic>(attr.Topic, async (message) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
#if DEBUG
|
||||
logger.LogInformation($"kafka消费消息:{message}");
|
||||
#endif
|
||||
// 处理消息
|
||||
return await ProcessMessageAsync(new List<object>() { message }, method, subscribe);
|
||||
}
|
||||
catch (ConsumeException ex)
|
||||
{
|
||||
// 处理消费错误
|
||||
logger.LogError($"kafka消费异常:{ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 处理消费错误
|
||||
logger.LogError($"kafka批量消费异常:{ex.Message}");
|
||||
}
|
||||
return await Task.FromResult(false);
|
||||
}, attr.GroupId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 处理消息
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
IList? list = null;
|
||||
Tuple<Type, Type?> tuple = method.GetParameterTypeInfo();
|
||||
bool isEnumerable = false;
|
||||
if (tuple.Item2 != null)
|
||||
{
|
||||
Type listType = typeof(List<>).MakeGenericType(tuple.Item2);
|
||||
list = (IList)Activator.CreateInstance(listType)!;
|
||||
isEnumerable = tuple.Item2.IsConvertType();
|
||||
}
|
||||
else
|
||||
{
|
||||
isEnumerable = tuple.Item1.IsConvertType();
|
||||
}
|
||||
#region 暂时
|
||||
//foreach (var msg in messages)
|
||||
//{
|
||||
// if (tuple.Item2 != null)
|
||||
// {
|
||||
// if (isEnumerable)
|
||||
// {
|
||||
// var parameterType = parameters[0].ParameterType;
|
||||
// var data=messages?.Serialize().Deserialize(parameterType);
|
||||
// messageObj = data!=null? new[] { data }:null;
|
||||
// break;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // 集合类型
|
||||
// var data = msg?.Serialize().Deserialize(tuple.Item2) /*isEnumerable ? Convert.ChangeType(msg, tuple.Item2) : msg?.Serialize().Deserialize(tuple.Item2)*/;
|
||||
// if (data != null)
|
||||
// list?.Add(data);
|
||||
// }
|
||||
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // (dynamic)Convert.ChangeType(msg, tuple.Item1)
|
||||
// using (var stream = new MemoryStream(msg))
|
||||
// {
|
||||
// var data1= System.Text.Json.JsonSerializer.Deserialize(stream, tuple.Item1);
|
||||
// }
|
||||
// var data = isEnumerable ? System.Text.Json.JsonSerializer.Deserialize(msg, tuple.Item1): msg?.ToString()?.Deserialize(tuple.Item1);
|
||||
// if (data != null)
|
||||
// messageObj = new[] { data };
|
||||
// }
|
||||
//}
|
||||
//if (tuple.Item2 != null && list != null && list.Count > 0)
|
||||
//{
|
||||
// messageObj = new[] { list };
|
||||
//}
|
||||
#endregion
|
||||
var parameterDescriptors = method.GetParameters();
|
||||
executeParameters = new object?[parameterDescriptors.Length];
|
||||
for (var i = 0; i < parameterDescriptors.Length; i++)
|
||||
{
|
||||
foreach (var item in messages)
|
||||
{
|
||||
|
||||
object? tempParameter = null;
|
||||
var parameterDescriptor = parameterDescriptors[i];
|
||||
if (KafkaSerialization.IsJsonType(item))
|
||||
{
|
||||
tempParameter = KafkaSerialization.Deserialize(item, tuple.Item2 != null ? tuple.Item2 : parameterDescriptor.ParameterType);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var converter = TypeDescriptor.GetConverter(parameterDescriptor.ParameterType);
|
||||
if (converter.CanConvertFrom(item.GetType()))
|
||||
{
|
||||
tempParameter = converter.ConvertFrom(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parameterDescriptor.ParameterType.IsInstanceOfType(item))
|
||||
tempParameter = item;
|
||||
else
|
||||
tempParameter = Convert.ChangeType(item, parameterDescriptor.ParameterType);
|
||||
}
|
||||
}
|
||||
if (tuple.Item2 == null)
|
||||
{
|
||||
executeParameters[i] = tempParameter;
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(tempParameter);
|
||||
}
|
||||
|
||||
}
|
||||
if (list != null && list.Count > 0)
|
||||
executeParameters[i] = list;
|
||||
}
|
||||
}
|
||||
|
||||
var result = method.Invoke(subscribe, 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)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using Confluent.Kafka;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka.Producer
|
||||
{
|
||||
public interface IProducerService
|
||||
{
|
||||
Task ProduceAsync<TKey, TValue>(string topic, TKey key, TValue value) where TKey : notnull where TValue : class;
|
||||
|
||||
Task ProduceAsync<TValue>(string topic, TValue value) where TValue : class;
|
||||
|
||||
Task ProduceAsync<TKey, TValue>(string topic, TKey key, TValue value, int? partition, Action<DeliveryReport<TKey, TValue>>? deliveryHandler = null) where TKey : notnull where TValue : class;
|
||||
|
||||
Task ProduceAsync<TValue>(string topic, TValue value, int? partition = null, Action<DeliveryReport<Null, TValue>>? deliveryHandler = null) where TValue : class;
|
||||
}
|
||||
}
|
||||
@ -1,265 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Confluent.Kafka;
|
||||
using JiShe.CollectBus.Common;
|
||||
using JiShe.CollectBus.Common.Helpers;
|
||||
using JiShe.CollectBus.Kafka.Consumer;
|
||||
using JiShe.CollectBus.Kafka.Internal;
|
||||
using JiShe.CollectBus.Kafka.Serialization;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka.Producer
|
||||
{
|
||||
public class ProducerService: IProducerService, IDisposable
|
||||
{
|
||||
private readonly ILogger<ProducerService> _logger;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ConcurrentDictionary<Type, object> _producerCache = new();
|
||||
private class KafkaProducer<TKey, TValue> where TKey : notnull where TValue : class { }
|
||||
private readonly KafkaOptionConfig _kafkaOptionConfig;
|
||||
private readonly ServerApplicationOptions _applicationOptions;
|
||||
/// <summary>
|
||||
/// ProducerService
|
||||
/// </summary>
|
||||
/// <param name="configuration"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="kafkaOptionConfig"></param>
|
||||
public ProducerService(IConfiguration configuration,ILogger<ProducerService> logger, IOptions<KafkaOptionConfig> kafkaOptionConfig, IOptions<ServerApplicationOptions> applicationOptions)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
_kafkaOptionConfig = kafkaOptionConfig.Value;
|
||||
_applicationOptions = applicationOptions.Value;
|
||||
}
|
||||
|
||||
#region private 私有方法
|
||||
/// <summary>
|
||||
/// 创建生产者实例
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <returns></returns>
|
||||
private IProducer<TKey, TValue> GetProducer<TKey, TValue>(Type typeKey)
|
||||
{
|
||||
return (IProducer<TKey, TValue>)_producerCache.GetOrAdd(typeKey, _ =>
|
||||
{
|
||||
var config = BuildProducerConfig();
|
||||
return new ProducerBuilder<TKey, TValue>(config)
|
||||
.SetValueSerializer(new JsonSerializer<TValue>()) // Value 使用自定义 JSON 序列化
|
||||
.SetLogHandler((_, msg) => _logger.Log(ConvertLogLevel(msg.Level), msg.Message))
|
||||
.Build();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private ProducerConfig BuildProducerConfig()
|
||||
{
|
||||
var config = new ProducerConfig
|
||||
{
|
||||
BootstrapServers = _kafkaOptionConfig.BootstrapServers,
|
||||
//AllowAutoCreateTopics = true,
|
||||
QueueBufferingMaxKbytes = 4_194_304, // 4_194_304 2_097_151 // 修改缓冲区最大为2GB,默认为1GB
|
||||
QueueBufferingMaxMessages = int.MaxValue, // 缓冲区消息条
|
||||
CompressionType = CompressionType.Lz4, // 配置使用压缩算法LZ4,其他:gzip/snappy/zstd
|
||||
BatchSize = 32_768, // 修改批次大小为32K
|
||||
LingerMs = 20, // 修改等待时间为20ms,默认为5ms
|
||||
Acks = Acks.All, // 表明只有所有副本Broker都收到消息才算提交成功, 可以 Acks.Leader
|
||||
MessageSendMaxRetries = 50, // 消息发送失败最大重试50次
|
||||
MessageTimeoutMs = 120000, // 消息发送超时时间为2分钟,设置值MessageTimeoutMs > LingerMs
|
||||
};
|
||||
|
||||
if (_kafkaOptionConfig.EnableAuthorization)
|
||||
{
|
||||
config.SecurityProtocol = _kafkaOptionConfig.SecurityProtocol;
|
||||
config.SaslMechanism = _kafkaOptionConfig.SaslMechanism;
|
||||
config.SaslUsername = _kafkaOptionConfig.SaslUserName;
|
||||
config.SaslPassword = _kafkaOptionConfig.SaslPassword;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private static LogLevel ConvertLogLevel(SyslogLevel level) => level switch
|
||||
{
|
||||
SyslogLevel.Emergency => LogLevel.Critical,
|
||||
SyslogLevel.Alert => LogLevel.Critical,
|
||||
SyslogLevel.Critical => LogLevel.Critical,
|
||||
SyslogLevel.Error => LogLevel.Error,
|
||||
SyslogLevel.Warning => LogLevel.Warning,
|
||||
SyslogLevel.Notice => LogLevel.Information,
|
||||
SyslogLevel.Info => LogLevel.Information,
|
||||
SyslogLevel.Debug => LogLevel.Debug,
|
||||
_ => LogLevel.None
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 发布消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="topic"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
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>
|
||||
{
|
||||
Key = key,
|
||||
Value = value,
|
||||
Headers = new Headers{
|
||||
{ "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) }
|
||||
}
|
||||
};
|
||||
await producer.ProduceAsync(topic, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发布消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="topic"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
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>
|
||||
{
|
||||
Value = value,
|
||||
Headers = new Headers{
|
||||
{ "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) }
|
||||
}
|
||||
};
|
||||
await producer.ProduceAsync(topic, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发布消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="topic"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="partition"></param>
|
||||
/// <param name="deliveryHandler"></param>
|
||||
/// <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
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = new Message<TKey, TValue>
|
||||
{
|
||||
Key = key,
|
||||
Value = value,
|
||||
Headers = new Headers{
|
||||
{ "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) }
|
||||
}
|
||||
};
|
||||
var typeKey = typeof(KafkaProducer<TKey, TValue>);
|
||||
var producer = GetProducer<TKey, TValue>(typeKey);
|
||||
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)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发布消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="topic"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="partition"></param>
|
||||
/// <param name="deliveryHandler"></param>
|
||||
/// <returns></returns>
|
||||
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>
|
||||
{
|
||||
Value = value,
|
||||
Headers = new Headers{
|
||||
{ "route-key", Encoding.UTF8.GetBytes(_applicationOptions.ServerTagName) }
|
||||
}
|
||||
};
|
||||
var typeKey = typeof(KafkaProducer<Null, TValue>);
|
||||
var producer = GetProducer<Null, TValue>(typeKey);
|
||||
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)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var producer in _producerCache.Values.OfType<IDisposable>())
|
||||
{
|
||||
producer.Dispose();
|
||||
}
|
||||
_producerCache.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,139 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
using Confluent.Kafka;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace JiShe.CollectBus.Kafka.Serialization
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON 序列化器(支持泛型)
|
||||
/// </summary>
|
||||
public class JsonSerializer<T> : ISerializer<T>, IDeserializer<T>
|
||||
{
|
||||
private static readonly JsonSerializerOptions _options = new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||
WriteIndented = false,// 设置格式化输出
|
||||
IncludeFields = true,// 允许反序列化到非公共 setter 和字段
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,// 允许特殊字符
|
||||
IgnoreReadOnlyFields = true,
|
||||
IgnoreReadOnlyProperties = true,
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString, // 允许数字字符串
|
||||
AllowTrailingCommas = true, // 忽略尾随逗号
|
||||
ReadCommentHandling = JsonCommentHandling.Skip, // 忽略注释
|
||||
PropertyNameCaseInsensitive = true, // 属性名称大小写不敏感
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // 属性名称使用驼峰命名规则
|
||||
Converters = { new DateTimeJsonConverter() } // 注册你的自定义转换器,
|
||||
};
|
||||
|
||||
public byte[] Serialize(T data, SerializationContext context)
|
||||
{
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.SerializeToUtf8Bytes(data, _options);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Kafka序列化失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public T Deserialize(ReadOnlySpan<byte> data, bool isNull, SerializationContext context)
|
||||
{
|
||||
if (isNull)
|
||||
return default;
|
||||
try
|
||||
{
|
||||
if (data.IsEmpty)
|
||||
return default;
|
||||
return JsonSerializer.Deserialize<T>(data, _options)!;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Kafka反序列化失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class DateTimeJsonConverter : JsonConverter<DateTime>
|
||||
{
|
||||
private readonly string _dateFormatString;
|
||||
public DateTimeJsonConverter()
|
||||
{
|
||||
_dateFormatString = "yyyy-MM-dd HH:mm:ss";
|
||||
}
|
||||
|
||||
public DateTimeJsonConverter(string dateFormatString)
|
||||
{
|
||||
_dateFormatString = dateFormatString;
|
||||
}
|
||||
|
||||
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return DateTime.Parse(reader.GetString());
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString(_dateFormatString));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class KafkaSerialization
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否是json类型
|
||||
/// </summary>
|
||||
/// <param name="jsonObject"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsJsonType(this object jsonObject)
|
||||
{
|
||||
return jsonObject is JsonElement;
|
||||
}
|
||||
public static object? Deserialize(object value, Type valueType)
|
||||
{
|
||||
try
|
||||
{
|
||||
var _jsonSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||
WriteIndented = false,// 设置格式化输出
|
||||
IncludeFields = true,// 允许反序列化到非公共 setter 和字段
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,// 允许特殊字符
|
||||
IgnoreReadOnlyFields = true,
|
||||
IgnoreReadOnlyProperties = true,
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString, // 允许数字字符串
|
||||
AllowTrailingCommas = true, // 忽略尾随逗号
|
||||
ReadCommentHandling = JsonCommentHandling.Skip, // 忽略注释
|
||||
PropertyNameCaseInsensitive = true, // 属性名称大小写不敏感
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // 属性名称使用驼峰命名规则
|
||||
Converters = { new DateTimeJsonConverter() } // 注册你的自定义转换器,
|
||||
};
|
||||
|
||||
if (value is JsonElement jsonElement)
|
||||
{
|
||||
//return jsonElement.Deserialize(valueType, _jsonSerializerOptions);
|
||||
return JsonSerializer.Deserialize(jsonElement, valueType, _jsonSerializerOptions);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait ContinueOnCapturedContext="false" />
|
||||
</Weavers>
|
||||
@ -1,3 +0,0 @@
|
||||
{
|
||||
"role": "lib.mongodb"
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>JiShe.CollectBus</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="FodyWeavers.xml" />
|
||||
<None Remove="JiShe.CollectBus.MongoDB.abppkg" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Volo.Abp.MongoDB" Version="8.3.3" />
|
||||
<PackageReference Include="Volo.Abp.BackgroundJobs.MongoDB" Version="8.3.3" />
|
||||
<PackageReference Include="Volo.Abp.AuditLogging.MongoDB" Version="8.3.3" />
|
||||
<ProjectReference Include="..\..\services\JiShe.CollectBus.Domain\JiShe.CollectBus.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using JiShe.CollectBus.Data;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MongoDB.Driver;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.MongoDB;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
|
||||
namespace JiShe.CollectBus.MongoDB
|
||||
{
|
||||
public class CollectBusDbSchemaMigrator : ICollectBusDbSchemaMigrator, ITransientDependency
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public CollectBusDbSchemaMigrator(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task MigrateAsync()
|
||||
{
|
||||
var dbContexts = _serviceProvider.GetServices<IAbpMongoDbContext>();
|
||||
var connectionStringResolver = _serviceProvider.GetRequiredService<IConnectionStringResolver>();
|
||||
|
||||
if (_serviceProvider.GetRequiredService<ICurrentTenant>().IsAvailable)
|
||||
{
|
||||
dbContexts = dbContexts.Where(x => !x.GetType().IsDefined(typeof(IgnoreMultiTenancyAttribute)));
|
||||
}
|
||||
|
||||
foreach (var dbContext in dbContexts)
|
||||
{
|
||||
var connectionString =
|
||||
await connectionStringResolver.ResolveAsync(
|
||||
ConnectionStringNameAttribute.GetConnStringName(dbContext.GetType()));
|
||||
var mongoUrl = new MongoUrl(connectionString);
|
||||
var databaseName = mongoUrl.DatabaseName;
|
||||
var client = new MongoClient(mongoUrl);
|
||||
|
||||
if (databaseName.IsNullOrWhiteSpace())
|
||||
{
|
||||
databaseName = ConnectionStringNameAttribute.GetConnStringName(dbContext.GetType());
|
||||
}
|
||||
|
||||
(dbContext as AbpMongoDbContext)?.InitializeCollections(client.GetDatabase(databaseName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
using JiShe.CollectBus.IotSystems.Devices;
|
||||
using JiShe.CollectBus.IotSystems.MessageReceiveds;
|
||||
using JiShe.CollectBus.IotSystems.MeterReadingRecords;
|
||||
using JiShe.CollectBus.IotSystems.Protocols;
|
||||
using JiShe.CollectBus.ShardingStrategy;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization;
|
||||
using MongoDB.Driver;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using JiShe.CollectBus.IotSystems.MessageIssueds;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.MongoDB;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
using JiShe.CollectBus.IotSystems.LogRecord;
|
||||
|
||||
namespace JiShe.CollectBus.MongoDB;
|
||||
|
||||
[IgnoreMultiTenancy]
|
||||
[ConnectionStringName(CollectBusDbProperties.MongoDbConnectionStringName)]
|
||||
public class CollectBusMongoDbContext : AbpMongoDbContext, ICollectBusMongoDbContext
|
||||
{
|
||||
/* Add mongo collections here. Example:
|
||||
* public IMongoCollection<Question> Questions => Collection<Question>();
|
||||
*/
|
||||
|
||||
public IMongoCollection<MessageReceived> MessageReceiveds => Collection<MessageReceived>();
|
||||
public IMongoCollection<MessageReceivedLogin> MessageReceivedLogins => Collection<MessageReceivedLogin>();
|
||||
public IMongoCollection<MessageReceivedHeartbeat> MessageReceivedHeartbeats => Collection<MessageReceivedHeartbeat>();
|
||||
public IMongoCollection<Device> Devices => Collection<Device>();
|
||||
public IMongoCollection<ProtocolInfo> ProtocolInfos => Collection<ProtocolInfo>();
|
||||
|
||||
public IMongoCollection<MessageIssued> MessageIssueds => Collection<MessageIssued>();
|
||||
|
||||
|
||||
protected override void CreateModel(IMongoModelBuilder modelBuilder)
|
||||
{
|
||||
//modelBuilder.Entity<MeterReadingRecords>(builder =>
|
||||
//{
|
||||
// builder.CreateCollectionOptions.Collation = new Collation(locale: "en_US", strength: CollationStrength.Secondary);
|
||||
// builder.ConfigureIndexes(indexes =>
|
||||
// {
|
||||
// indexes.CreateOne(
|
||||
// new CreateIndexModel<BsonDocument>(
|
||||
// Builders<BsonDocument>.IndexKeys.Ascending("MyProperty"),
|
||||
// new CreateIndexOptions { Unique = true }
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
// );
|
||||
|
||||
// //// 创建索引
|
||||
// //builder.ConfigureIndexes(index =>
|
||||
// //{
|
||||
|
||||
|
||||
// // //List<CreateIndexModel<BsonDocument>> createIndexModels = new List<CreateIndexModel<BsonDocument>>();
|
||||
// // //createIndexModels.Add(new CreateIndexModel<BsonDocument>(
|
||||
// // // Builders<BsonDocument>.IndexKeys.Ascending(nameof(MeterReadingRecords)),
|
||||
// // // new CreateIndexOptions
|
||||
// // // {
|
||||
// // // Unique = true
|
||||
// // // }
|
||||
// // // ));
|
||||
|
||||
|
||||
// // //var indexKeys = Builders<BsonDocument>.IndexKeys
|
||||
// // //.Ascending("CreationTime")
|
||||
// // //.Ascending("OrderNumber");
|
||||
|
||||
// // //var indexOptions = new CreateIndexOptions
|
||||
// // //{
|
||||
// // // Background = true,
|
||||
// // // Name = "IX_CreationTime_OrderNumber"
|
||||
// // //};
|
||||
// // //index.CreateOne(
|
||||
// // //new CreateIndexModel<BsonDocument>(indexKeys, indexOptions));
|
||||
|
||||
// // //index.CreateOne(new CreateIndexModel<BsonDocument>(
|
||||
// // // Builders<BsonDocument>.IndexKeys.Ascending(nameof(MeterReadingRecords)),
|
||||
// // // new CreateIndexOptions
|
||||
// // // {
|
||||
// // // Unique = true
|
||||
// // // }
|
||||
// // // ));
|
||||
// //});
|
||||
|
||||
//});
|
||||
|
||||
base.CreateModel(modelBuilder);
|
||||
modelBuilder.ConfigureCollectBus();
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.MongoDB;
|
||||
|
||||
namespace JiShe.CollectBus.MongoDB;
|
||||
|
||||
public static class CollectBusMongoDbContextExtensions
|
||||
{
|
||||
public static void ConfigureCollectBus(
|
||||
this IMongoModelBuilder builder)
|
||||
{
|
||||
Check.NotNull(builder, nameof(builder));
|
||||
}
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
using JiShe.CollectBus.IotSystems.LogRecord;
|
||||
using JiShe.CollectBus.IotSystems.MeterReadingRecords;
|
||||
using JiShe.CollectBus.Repository;
|
||||
using JiShe.CollectBus.Repository.LogRecord;
|
||||
using JiShe.CollectBus.Repository.MeterReadingRecord;
|
||||
using JiShe.CollectBus.ShardingStrategy;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using System;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.AuditLogging.MongoDB;
|
||||
using Volo.Abp.BackgroundJobs.MongoDB;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Domain.Repositories.MongoDB;
|
||||
using Volo.Abp.Modularity;
|
||||
using Volo.Abp.MongoDB;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace JiShe.CollectBus.MongoDB;
|
||||
|
||||
[DependsOn(
|
||||
typeof(CollectBusDomainModule),
|
||||
typeof(AbpMongoDbModule),
|
||||
typeof(AbpBackgroundJobsMongoDbModule),
|
||||
typeof(AbpAuditLoggingMongoDbModule)
|
||||
)]
|
||||
public class CollectBusMongoDbModule : AbpModule
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
context.Services.AddMongoDbContext<CollectBusMongoDbContext>(options =>
|
||||
{
|
||||
options.AddDefaultRepositories(includeAllEntities: true);
|
||||
|
||||
// 注册分表策略
|
||||
context.Services.AddTransient(
|
||||
typeof(IShardingStrategy<>),
|
||||
typeof(DayShardingStrategy<>));
|
||||
|
||||
|
||||
context.Services.AddTransient(typeof(HourShardingStrategy<>));
|
||||
|
||||
//// 分表策略仓储 替换默认仓储
|
||||
//options.AddRepository<MeterReadingRecords, MeterReadingRecordRepository>();
|
||||
|
||||
options.AddRepository<LogRecords, LogRecordRepository>();
|
||||
});
|
||||
context.Services.AddAlwaysDisableUnitOfWorkTransaction();
|
||||
Configure<AbpUnitOfWorkDefaultOptions>(options =>
|
||||
{
|
||||
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.MongoDB;
|
||||
|
||||
namespace JiShe.CollectBus.MongoDB;
|
||||
|
||||
[ConnectionStringName(CollectBusDbProperties.MongoDbConnectionStringName)]
|
||||
public interface ICollectBusMongoDbContext : IAbpMongoDbContext
|
||||
{
|
||||
/* Define mongo collections here. Example:
|
||||
* IMongoCollection<Question> Questions { get; }
|
||||
*/
|
||||
}
|
||||
@ -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,60 +0,0 @@
|
||||
using JiShe.CollectBus.IotSystems.MeterReadingRecords;
|
||||
using MongoDB.Driver;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
||||
namespace JiShe.CollectBus.Repository.MeterReadingRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// 抄读仓储接口
|
||||
/// </summary>
|
||||
public interface IMeterReadingRecordRepository : IRepository<MeterReadingRecords, Guid>
|
||||
{
|
||||
/// <summary>
|
||||
/// 批量插入
|
||||
/// </summary>
|
||||
/// <param name="entities"></param>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
Task InsertManyAsync(List<MeterReadingRecords> entities,
|
||||
DateTime? dateTime);
|
||||
|
||||
/// <summary>
|
||||
/// 单个插入
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
Task<MeterReadingRecords> InsertAsync(MeterReadingRecords entity, DateTime? dateTime);
|
||||
|
||||
/// <summary>
|
||||
/// 单条更新
|
||||
/// </summary>
|
||||
/// <param name="filter">过滤条件,示例:Builders<MeterReadingRecords>.Filter.Eq(x => x.Id, filter.Id)</param>
|
||||
/// <param name="update">包含待更新的内容,示例:Builders<MeterReadingRecords>.Update.Set(x => x.Processed, true).Set(x => x.ProcessedTime, Clock.Now)</param>
|
||||
/// <param name="entity">数据实体,用于获取对应的分片库</param>
|
||||
/// <returns></returns>
|
||||
Task<MeterReadingRecords> UpdateOneAsync(FilterDefinition<MeterReadingRecords> filter, UpdateDefinition<MeterReadingRecords> update, MeterReadingRecords entity);
|
||||
|
||||
/// <summary>
|
||||
/// 单个获取
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
Task<MeterReadingRecords> FirOrDefaultAsync(MeterReadingRecords entity, DateTime? dateTime);
|
||||
|
||||
/// <summary>
|
||||
/// 多集合数据查询
|
||||
/// </summary>
|
||||
/// <param name="startTime"></param>
|
||||
/// <param name="endTime"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<MeterReadingRecords>> ParallelQueryAsync(DateTime startTime, DateTime endTime);
|
||||
}
|
||||
}
|
||||
@ -1,173 +0,0 @@
|
||||
using JiShe.CollectBus.IotSystems.MeterReadingRecords;
|
||||
using JiShe.CollectBus.MongoDB;
|
||||
using JiShe.CollectBus.ShardingStrategy;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.Domain.Repositories.MongoDB;
|
||||
using Volo.Abp.MongoDB;
|
||||
using Volo.Abp.MongoDB.DistributedEvents;
|
||||
using Volo.Abp.Timing;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace JiShe.CollectBus.Repository.MeterReadingRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// 抄读记录仓储
|
||||
/// </summary>
|
||||
public class MeterReadingRecordRepository : MongoDbRepository<CollectBusMongoDbContext, MeterReadingRecords, Guid>, IMeterReadingRecordRepository
|
||||
{
|
||||
|
||||
private readonly IShardingStrategy<MeterReadingRecords> _shardingStrategy;
|
||||
private readonly IMongoDbContextProvider<CollectBusMongoDbContext> _dbContextProvider;
|
||||
|
||||
public MeterReadingRecordRepository(
|
||||
IMongoDbContextProvider<CollectBusMongoDbContext> dbContextProvider,
|
||||
IShardingStrategy<MeterReadingRecords> shardingStrategy
|
||||
)
|
||||
: base(dbContextProvider)
|
||||
{
|
||||
_dbContextProvider = dbContextProvider;
|
||||
_shardingStrategy = shardingStrategy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入
|
||||
/// </summary>
|
||||
/// <param name="entities"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<IEnumerable<MeterReadingRecords>> InsertManyAsync(IEnumerable<MeterReadingRecords> 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<MeterReadingRecords> 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<MeterReadingRecords> InsertAsync(MeterReadingRecords 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<MeterReadingRecords> InsertAsync(MeterReadingRecords entity, DateTime? dateTime)
|
||||
{
|
||||
var collection = await GetShardedCollection(dateTime);
|
||||
await collection.InsertOneAsync(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单条更新
|
||||
/// </summary>
|
||||
/// <param name="filter">过滤条件,示例:Builders<MeterReadingRecords>.Filter.Eq(x => x.Id, filter.Id)</param>
|
||||
/// <param name="update">包含待更新的内容,示例:Builders<MeterReadingRecords>.Update.Set(x => x.Processed, true).Set(x => x.ProcessedTime, Clock.Now)</param>
|
||||
/// <param name="entity">数据实体,用于获取对应的分片库</param>
|
||||
/// <returns></returns>
|
||||
public async Task<MeterReadingRecords> UpdateOneAsync(FilterDefinition<MeterReadingRecords> filter, UpdateDefinition<MeterReadingRecords> update, MeterReadingRecords 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<MeterReadingRecords> FirOrDefaultAsync(MeterReadingRecords entity, DateTime? dateTime)
|
||||
{
|
||||
var collection = await GetShardedCollection(dateTime);
|
||||
var query = await collection.FindAsync(d => d.CreationTime == dateTime.Value && d.AFN == entity.AFN && d.Fn == entity.Fn && d.FocusAddress == entity.FocusAddress);
|
||||
return await query.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多集合数据查询
|
||||
/// </summary>
|
||||
/// <param name="startTime"></param>
|
||||
/// <param name="endTime"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<List<MeterReadingRecords>> ParallelQueryAsync(DateTime startTime, DateTime endTime)
|
||||
{
|
||||
var collectionNames = _shardingStrategy.GetQueryCollectionNames(startTime, endTime);
|
||||
var database = await GetDatabaseAsync();
|
||||
|
||||
|
||||
var tasks = collectionNames.Select(async name =>
|
||||
{
|
||||
var collection = database.GetCollection<MeterReadingRecords>(name);
|
||||
var filter = Builders<MeterReadingRecords>.Filter.And(
|
||||
Builders<MeterReadingRecords>.Filter.Gte(x => x.CreationTime, startTime),
|
||||
Builders<MeterReadingRecords>.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<MeterReadingRecords>> GetShardedCollection(DateTime? dateTime)
|
||||
{
|
||||
var database = await GetDatabaseAsync();
|
||||
string collectionName = string.Empty;
|
||||
|
||||
if (dateTime != null)
|
||||
{
|
||||
collectionName = _shardingStrategy.GetCollectionName(dateTime.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
collectionName = _shardingStrategy.GetCurrentCollectionName();
|
||||
}
|
||||
|
||||
return database.GetCollection<MeterReadingRecords>(collectionName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
using JiShe.CollectBus.Common.Enums;
|
||||
using JiShe.CollectBus.Common.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace JiShe.CollectBus.ShardingStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 按天分表策略
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
public class DayShardingStrategy<TEntity> : IShardingStrategy<TEntity>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取指定时间对应的集合名
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public string GetCollectionName(DateTime dateTime)
|
||||
{
|
||||
var baseName = typeof(TEntity).Name;
|
||||
return $"{baseName}_{dateTime.GetDataTableShardingStrategy(TableTimeStrategyEnum.DayShardingStrategy)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前时间对应的集合名
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetCurrentCollectionName()
|
||||
{
|
||||
var baseName = typeof(TEntity).Name;
|
||||
return $"{baseName}_{DateTime.Now.GetDataTableShardingStrategy(TableTimeStrategyEnum.DayShardingStrategy)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于查询时确定目标集合
|
||||
/// </summary>
|
||||
/// <param name="startTime"></param>
|
||||
/// <param name="endTime"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<string> GetQueryCollectionNames(DateTime? startTime, DateTime? endTime)
|
||||
{
|
||||
var months = new List<string>();
|
||||
var current = startTime ?? DateTime.MinValue;
|
||||
var end = endTime ?? DateTime.MaxValue;
|
||||
var baseName = typeof(TEntity).Name;
|
||||
|
||||
while (current <= end)
|
||||
{
|
||||
months.Add($"{baseName}_{current.GetDataTableShardingStrategy(TableTimeStrategyEnum.DayShardingStrategy)}");
|
||||
current = current.AddMonths(1);
|
||||
}
|
||||
|
||||
return months.Distinct();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,36 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JiShe.CollectBus.ShardingStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据存储分片策略
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
public interface IShardingStrategy<TEntity>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取指定时间对应的集合名
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetCollectionName(DateTime dateTime);
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前时间对应的集合名
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetCurrentCollectionName();
|
||||
|
||||
/// <summary>
|
||||
/// 用于查询时确定目标集合
|
||||
/// </summary>
|
||||
/// <param name="startTime"></param>
|
||||
/// <param name="endTime"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<string> GetQueryCollectionNames(DateTime? startTime = null,
|
||||
DateTime? endTime = null);
|
||||
}
|
||||
}
|
||||
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