committed by
GitHub
231 changed files with 12886 additions and 796 deletions
@ -0,0 +1,141 @@ |
|||||
|
{ |
||||
|
"App": { |
||||
|
"SelfUrl": "http://localhost:44385/", |
||||
|
"CorsOrigins": "http://localhost:4200,http://localhost:9528,http://127.0.0.1:63898" |
||||
|
}, |
||||
|
"AppSelfUrl": "http://localhost:44385/", |
||||
|
"ConnectionStrings": { |
||||
|
"Default": "Server=127.0.0.1;Database=IdentityServer;User Id=root;Password=123456", |
||||
|
"AbpIdentity": "Server=127.0.0.1;Database=IdentityServer;User Id=root;Password=123456", |
||||
|
"AbpIdentityServer": "Server=127.0.0.1;Database=IdentityServer;User Id=root;Password=123456", |
||||
|
"AbpTenantManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", |
||||
|
"AbpSettingManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", |
||||
|
"AbpPermissionManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", |
||||
|
"AbpFeatureManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456" |
||||
|
}, |
||||
|
"Identity": { |
||||
|
"Password": { |
||||
|
"RequiredLength": 6, |
||||
|
"RequiredUniqueChars": 0, |
||||
|
"RequireNonAlphanumeric": false, |
||||
|
"RequireLowercase": false, |
||||
|
"RequireUppercase": false, |
||||
|
"RequireDigit": false |
||||
|
}, |
||||
|
"Lockout": { |
||||
|
"AllowedForNewUsers": false, |
||||
|
"LockoutDuration": 5, |
||||
|
"MaxFailedAccessAttempts": 5 |
||||
|
}, |
||||
|
"SignIn": { |
||||
|
"RequireConfirmedEmail": false, |
||||
|
"RequireConfirmedPhoneNumber": false |
||||
|
} |
||||
|
}, |
||||
|
"CAP": { |
||||
|
"EventBus": { |
||||
|
"DefaultGroup": "AuthServer", |
||||
|
"Version": "v1", |
||||
|
"FailedRetryInterval": 300, |
||||
|
"FailedRetryCount": 10 |
||||
|
}, |
||||
|
"RabbitMQ": { |
||||
|
"HostName": "127.0.0.1", |
||||
|
"Port": 5672, |
||||
|
"UserName": "admin", |
||||
|
"Password": "admin", |
||||
|
"ExchangeName": "LINGYUN.AbpApplication", |
||||
|
"VirtualHost": "multi.service.test" |
||||
|
} |
||||
|
}, |
||||
|
"RedisCache": { |
||||
|
"ConnectString": "127.0.0.1", |
||||
|
"RedisPrefix": "AuthServer" |
||||
|
}, |
||||
|
"AuthServer": { |
||||
|
"Authority": "http://localhost:44385/", |
||||
|
"ApiName": "auth-service" |
||||
|
}, |
||||
|
"WeChat": { |
||||
|
"Auth": { |
||||
|
"AppId": "微信AppId", |
||||
|
"AppSecret": "微信AppSecret" |
||||
|
}, |
||||
|
"Signature": { |
||||
|
"RequestPath": "微信开发者中心填写的验证地址", |
||||
|
"Token": "微信开发者中心填写的Token" |
||||
|
} |
||||
|
}, |
||||
|
"IdentityServer": { |
||||
|
"Clients": { |
||||
|
"AuthManagement": { |
||||
|
"ClientId": "auth-management", |
||||
|
"RootUrl": "http://localhost:44313/" |
||||
|
}, |
||||
|
"AuthVueAdmin": { |
||||
|
"ClientId": "vue-admin-element" |
||||
|
}, |
||||
|
"AuthApiGateway": { |
||||
|
"ClientId": "apigateway-host-client" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"Serilog": { |
||||
|
"MinimumLevel": { |
||||
|
"Default": "Information", |
||||
|
"Override": { |
||||
|
"System": "Warning", |
||||
|
"Microsoft": "Warning", |
||||
|
"DotNetCore": "Information" |
||||
|
} |
||||
|
}, |
||||
|
"Enrich": [ "FromLogContext", "WithProcessId", "WithThreadId" ], |
||||
|
"WriteTo": [ |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Debug-.log", |
||||
|
"restrictedToMinimumLevel": "Debug", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Info-.log", |
||||
|
"restrictedToMinimumLevel": "Information", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Warn-.log", |
||||
|
"restrictedToMinimumLevel": "Warning", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Error-.log", |
||||
|
"restrictedToMinimumLevel": "Error", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Fatal-.log", |
||||
|
"restrictedToMinimumLevel": "Fatal", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,110 @@ |
|||||
|
{ |
||||
|
"ConnectionStrings": { |
||||
|
"Default": "Server=127.0.0.1;Database=Messages;User Id=root;Password=123456", |
||||
|
"MessageService": "Server=127.0.0.1;Database=Messages;User Id=root;Password=123456", |
||||
|
"AbpTenantManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", |
||||
|
"AbpSettingManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", |
||||
|
"AbpPermissionManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456" |
||||
|
}, |
||||
|
"RedisCache": { |
||||
|
"RedisPrefix": "Platform_Test_Cache", |
||||
|
"ConnectString": "127.0.0.1" |
||||
|
}, |
||||
|
"AuthServer": { |
||||
|
"Authority": "http://localhost:44385/", |
||||
|
"ApiName": "auth-service" |
||||
|
}, |
||||
|
"Hangfire": { |
||||
|
"MySql": { |
||||
|
"Connection": "Server=127.0.0.1;Database=Messages;User Id=root;Password=123456;Allow User Variables=true", |
||||
|
"TablePrefix": "AppHangfire" |
||||
|
} |
||||
|
}, |
||||
|
"WeChat": { |
||||
|
"Auth": { |
||||
|
"AppId": "你自己的微信AppId", |
||||
|
"AppSecret": "你自己的微信AppSecret" |
||||
|
} |
||||
|
}, |
||||
|
"Notifications": { |
||||
|
"WeChat": { |
||||
|
"WeApp": { |
||||
|
"DefaultWeAppState": "formal" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"CAP": { |
||||
|
"EventBus": { |
||||
|
"DefaultGroup": "MessageService", |
||||
|
"Version": "v1", |
||||
|
"FailedRetryInterval": 300, |
||||
|
"FailedRetryCount": 10 |
||||
|
}, |
||||
|
"RabbitMQ": { |
||||
|
"HostName": "127.0.0.1", |
||||
|
"Port": 5672, |
||||
|
"UserName": "admin", |
||||
|
"Password": "admin", |
||||
|
"ExchangeName": "LINGYUN.AbpApplication", |
||||
|
"VirtualHost": "multi.service.test" |
||||
|
} |
||||
|
}, |
||||
|
"Serilog": { |
||||
|
"MinimumLevel": { |
||||
|
"Default": "Debug", |
||||
|
"Override": { |
||||
|
"Microsoft.EntityFrameworkCore": "Debug", |
||||
|
"System": "Warning", |
||||
|
"Microsoft": "Warning" |
||||
|
} |
||||
|
}, |
||||
|
"Enrich": [ "FromLogContext", "WithProcessId", "WithThreadId" ], |
||||
|
"WriteTo": [ |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Debug-.log", |
||||
|
"restrictedToMinimumLevel": "Debug", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Info-.log", |
||||
|
"restrictedToMinimumLevel": "Information", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Warn-.log", |
||||
|
"restrictedToMinimumLevel": "Warning", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Error-.log", |
||||
|
"restrictedToMinimumLevel": "Error", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Fatal-.log", |
||||
|
"restrictedToMinimumLevel": "Fatal", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
{ |
||||
|
"ApiGateway": { |
||||
|
"AppId": "TEST-APP"//这里是用于Ocelot主机去API服务器获取参数的标识,指定需要获取什么类型的网关配置 |
||||
|
}, |
||||
|
"RemoteServices": { |
||||
|
"ApiGateway": { |
||||
|
"BaseUrl": "http://localhost:30001/",//Ocelot Api代理地址,参见Volo.Abp.HttpClient |
||||
|
"IdentityClient": "apigateway-host-client" |
||||
|
} |
||||
|
}, |
||||
|
"IdentityClients": {//这里是用于Ocelot主机去API服务器获取参数授权用的,参见Volo.Abp.HttpClient.IdentityModel |
||||
|
"apigateway-host-client": { |
||||
|
"Authority": "http://localhost:44385", |
||||
|
"RequireHttps": false, |
||||
|
"GrantType": "client_credentials", |
||||
|
"ClientId": "apigateway-host-client", |
||||
|
"Scope": "apigateway-service", |
||||
|
"ClientSecret": "1q2w3e*", |
||||
|
"UserName": "ocelotHost", |
||||
|
"UserPassword": "Ocelot1." |
||||
|
} |
||||
|
}, |
||||
|
"EnabledDynamicOcelot": true, |
||||
|
"CAP": { |
||||
|
"EventBus": { |
||||
|
"DefaultGroup": "ApiGateway-Host", |
||||
|
"Version": "v1", |
||||
|
"FailedRetryInterval": 300, |
||||
|
"FailedRetryCount": 10 |
||||
|
}, |
||||
|
"RabbitMQ": { |
||||
|
"HostName": "127.0.0.1", |
||||
|
"Port": 5672, |
||||
|
"UserName": "admin", |
||||
|
"Password": "admin", |
||||
|
"ExchangeName": "LINGYUN.ApiGateway", |
||||
|
"VirtualHost": "multi.service.test" |
||||
|
} |
||||
|
}, |
||||
|
"AuthServer": { |
||||
|
"Host": "http://localhost:44385/",//填写你的IdentityServer服务器地址 |
||||
|
"ApiName": "apigateway-service",//填写你的IdentityServer服务器注册的ApiName |
||||
|
"ApiSecret": "defj98734htgrb90365D23"//填写你的IdentityServer服务器注册的ApiSecret |
||||
|
}, |
||||
|
"Serilog": { |
||||
|
"MinimumLevel": { |
||||
|
"Default": "Debug", |
||||
|
"Override": { |
||||
|
"System": "Warning", |
||||
|
"Microsoft": "Warning" |
||||
|
} |
||||
|
}, |
||||
|
"Enrich": [ "FromLogContext", "WithProcessId", "WithThreadId" ], |
||||
|
"WriteTo": [ |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Debug-.log", |
||||
|
"restrictedToMinimumLevel": "Debug", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Info-.log", |
||||
|
"restrictedToMinimumLevel": "Information", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Warn-.log", |
||||
|
"restrictedToMinimumLevel": "Warning", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Error-.log", |
||||
|
"restrictedToMinimumLevel": "Error", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Fatal-.log", |
||||
|
"restrictedToMinimumLevel": "Fatal", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,91 @@ |
|||||
|
{ |
||||
|
"ConnectionStrings": { |
||||
|
"Default": "Server=127.0.0.1;Database=ApiGateway;User Id=root;Password=123456", |
||||
|
"ApiGateway": "Server=127.0.0.1;Database=ApiGateway;User Id=root;Password=123456", |
||||
|
"AbpTenantManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", |
||||
|
"AbpSettingManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", |
||||
|
"AbpPermissionManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456" |
||||
|
}, |
||||
|
"RedisCache": { |
||||
|
"RedisPrefix": "ApiGateway_Test_Cache", |
||||
|
"ConnectString": "127.0.0.1" |
||||
|
}, |
||||
|
"CAP": { |
||||
|
"EventBus": { |
||||
|
"DefaultGroup": "ApiGateway-Admin", |
||||
|
"Version": "v1", |
||||
|
"FailedRetryInterval": 300, |
||||
|
"FailedRetryCount": 10 |
||||
|
}, |
||||
|
"RabbitMQ": { |
||||
|
"HostName": "127.0.0.1", |
||||
|
"Port": 5672, |
||||
|
"UserName": "admin", |
||||
|
"Password": "admin", |
||||
|
"ExchangeName": "LINGYUN.ApiGateway", |
||||
|
"VirtualHost": "multi.service.test" |
||||
|
} |
||||
|
}, |
||||
|
"AuthServer": { |
||||
|
"Host": "http://localhost:44385/", |
||||
|
"ApiName": "apigateway-service", |
||||
|
"ApiSecret": "defj98734htgrb90365D23" |
||||
|
}, |
||||
|
"Serilog": { |
||||
|
"MinimumLevel": { |
||||
|
"Default": "Debug", |
||||
|
"Override": { |
||||
|
"System": "Warning", |
||||
|
"Microsoft": "Warning" |
||||
|
} |
||||
|
}, |
||||
|
"Enrich": [ "FromLogContext", "WithProcessId", "WithThreadId" ], |
||||
|
"WriteTo": [ |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Debug-.log", |
||||
|
"restrictedToMinimumLevel": "Debug", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Info-.log", |
||||
|
"restrictedToMinimumLevel": "Information", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Warn-.log", |
||||
|
"restrictedToMinimumLevel": "Warning", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Error-.log", |
||||
|
"restrictedToMinimumLevel": "Error", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Fatal-.log", |
||||
|
"restrictedToMinimumLevel": "Fatal", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,133 @@ |
|||||
|
{ |
||||
|
"ConnectionStrings": { |
||||
|
"Default": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", |
||||
|
"AbpIdentity": "Server=127.0.0.1;Database=IdentityServer;User Id=root;Password=123456", |
||||
|
"AbpIdentityServer": "Server=127.0.0.1;Database=IdentityServer;User Id=root;Password=123456", |
||||
|
"AbpTenantManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", |
||||
|
"AbpSettingManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", |
||||
|
"AbpPermissionManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456" |
||||
|
}, |
||||
|
"Location": { |
||||
|
"Baidu": { |
||||
|
"AccessKey": "你自己的百度地图WebAPI Key", |
||||
|
"ExtensionsRoad": true, |
||||
|
"ExtensionsTown": true, |
||||
|
"ExtensionsPoi": "1", |
||||
|
"VisableErrorToClient": true |
||||
|
} |
||||
|
}, |
||||
|
"Aliyun": { |
||||
|
"Sms": { |
||||
|
"RegionId": "cn-hangzhou", |
||||
|
"Domain": "dysmsapi.aliyuncs.com", |
||||
|
"Version": "2017-05-25", |
||||
|
"AccessKeyId": "你自己的阿里云Sms服务Key", |
||||
|
"AccessKeySecret": "你自己的阿里云Sms服务KeySecret", |
||||
|
"DefaultSignName": "你自己的阿里云Sms服务签名", |
||||
|
"DefaultTemplateCode": "你自己的阿里云Sms服务模板", |
||||
|
"DeveloperPhoneNumber": "你自己的手机号码,用于开发模式统一接收短信的手机号", |
||||
|
"VisableErrorToClient": true |
||||
|
} |
||||
|
}, |
||||
|
"Identity": { |
||||
|
"Password": { |
||||
|
"RequiredLength": 6, |
||||
|
"RequiredUniqueChars": 0, |
||||
|
"RequireNonAlphanumeric": false, |
||||
|
"RequireLowercase": false, |
||||
|
"RequireUppercase": false, |
||||
|
"RequireDigit": false |
||||
|
}, |
||||
|
"Lockout": { |
||||
|
"AllowedForNewUsers": false, |
||||
|
"LockoutDuration": 5, |
||||
|
"MaxFailedAccessAttempts": 5 |
||||
|
}, |
||||
|
"SignIn": { |
||||
|
"RequireConfirmedEmail": false, |
||||
|
"RequireConfirmedPhoneNumber": false |
||||
|
} |
||||
|
}, |
||||
|
"CAP": { |
||||
|
"EventBus": { |
||||
|
"DefaultGroup": "Platform", |
||||
|
"Version": "v1", |
||||
|
"FailedRetryInterval": 300, |
||||
|
"FailedRetryCount": 10 |
||||
|
}, |
||||
|
"RabbitMQ": { |
||||
|
"HostName": "127.0.0.1", |
||||
|
"Port": 5672, |
||||
|
"UserName": "admin", |
||||
|
"Password": "admin", |
||||
|
"ExchangeName": "LINGYUN.AbpApplication", |
||||
|
"VirtualHost": "multi.service.test" |
||||
|
} |
||||
|
}, |
||||
|
"RedisCache": { |
||||
|
"RedisPrefix": "Platform_Test_Cache", |
||||
|
"ConnectString": "127.0.0.1" |
||||
|
}, |
||||
|
"AuthServer": { |
||||
|
"Authority": "http://localhost:44385/", |
||||
|
"ApiName": "auth-service" |
||||
|
}, |
||||
|
"Serilog": { |
||||
|
"MinimumLevel": { |
||||
|
"Default": "Debug", |
||||
|
"Override": { |
||||
|
"Microsoft.EntityFrameworkCore": "Debug", |
||||
|
"System": "Warning", |
||||
|
"Microsoft": "Warning" |
||||
|
} |
||||
|
}, |
||||
|
"Enrich": [ "FromLogContext", "WithProcessId", "WithThreadId" ], |
||||
|
"WriteTo": [ |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Debug-.log", |
||||
|
"restrictedToMinimumLevel": "Debug", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Info-.log", |
||||
|
"restrictedToMinimumLevel": "Information", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Warn-.log", |
||||
|
"restrictedToMinimumLevel": "Warning", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Error-.log", |
||||
|
"restrictedToMinimumLevel": "Error", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "File", |
||||
|
"Args": { |
||||
|
"path": "Logs/Fatal-.log", |
||||
|
"restrictedToMinimumLevel": "Fatal", |
||||
|
"rollingInterval": "Day", |
||||
|
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.Identity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Account |
||||
|
{ |
||||
|
public class WeChatRegisterDto |
||||
|
{ |
||||
|
[Required] |
||||
|
public string Code { get; set; } |
||||
|
|
||||
|
[DisableAuditing] |
||||
|
[DataType(DataType.Password)] |
||||
|
[Required] |
||||
|
[StringLength(IdentityUserConsts.MaxPasswordLength)] |
||||
|
public string Password { get; set; } |
||||
|
|
||||
|
[StringLength(IdentityUserConsts.MaxNameLength)] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
[StringLength(IdentityUserConsts.MaxUserNameLength)] |
||||
|
public string UserName { get; set; } |
||||
|
|
||||
|
[EmailAddress] |
||||
|
[StringLength(IdentityUserConsts.MaxEmailLength)] |
||||
|
public string EmailAddress { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> |
||||
|
<Version>2.9.0</Version> |
||||
|
<Authors>LINGYUN</Authors> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
||||
|
<OutputPath>D:\LocalNuget</OutputPath> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Core" Version="2.9.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,15 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Aliyun.Authorization |
||||
|
{ |
||||
|
public class AbpAliyunAuthorizationModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
|
||||
|
Configure<AbpAliyunOptions>(configuration.GetSection("Aliyun:Auth")); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
namespace LINGYUN.Abp.Aliyun.Authorization |
||||
|
{ |
||||
|
public class AbpAliyunOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 访问标识
|
||||
|
/// </summary>
|
||||
|
public string AccessKeyId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 访问密钥
|
||||
|
/// </summary>
|
||||
|
public string AccessKeySecret { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> |
||||
|
<Version>2.9.0</Version> |
||||
|
<Authors>LINGYUN</Authors> |
||||
|
<Company /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
||||
|
<OutputPath>D:\LocalNuget</OutputPath> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.HangFire" Version="2.9.0" /> |
||||
|
<PackageReference Include="Volo.Abp.BackgroundJobs" Version="2.9.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,34 @@ |
|||||
|
using Hangfire; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
using Volo.Abp.Hangfire; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundJobs.Hangfire |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpBackgroundJobsAbstractionsModule), |
||||
|
typeof(AbpHangfireModule) |
||||
|
)] |
||||
|
public class AbpBackgroundJobsHangfireModule : AbpModule |
||||
|
{ |
||||
|
public override void OnPreApplicationInitialization(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundJobOptions>>().Value; |
||||
|
if (!options.IsJobExecutionEnabled) |
||||
|
{ |
||||
|
var hangfireOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpHangfireOptions>>().Value; |
||||
|
hangfireOptions.BackgroundJobServerFactory = CreateOnlyEnqueueJobServer; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private BackgroundJobServer CreateOnlyEnqueueJobServer(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
serviceProvider.GetRequiredService<JobStorage>(); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using Hangfire; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundJobs.Hangfire |
||||
|
{ |
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class HangfireBackgroundJobManager : IBackgroundJobManager, ITransientDependency |
||||
|
{ |
||||
|
public virtual Task<string> EnqueueAsync<TArgs>(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, |
||||
|
TimeSpan? delay = null) |
||||
|
{ |
||||
|
if (!delay.HasValue) |
||||
|
{ |
||||
|
return Task.FromResult( |
||||
|
BackgroundJob.Enqueue<HangfireJobExecutionAdapter<TArgs>>( |
||||
|
adapter => adapter.Execute(args) |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return Task.FromResult( |
||||
|
BackgroundJob.Schedule<HangfireJobExecutionAdapter<TArgs>>( |
||||
|
adapter => adapter.Execute(args), |
||||
|
delay.Value |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundJobs.Hangfire |
||||
|
{ |
||||
|
public class HangfireJobExecutionAdapter<TArgs> |
||||
|
{ |
||||
|
protected AbpBackgroundJobOptions Options { get; } |
||||
|
protected IServiceScopeFactory ServiceScopeFactory { get; } |
||||
|
protected IBackgroundJobExecuter JobExecuter { get; } |
||||
|
|
||||
|
public HangfireJobExecutionAdapter( |
||||
|
IOptions<AbpBackgroundJobOptions> options, |
||||
|
IBackgroundJobExecuter jobExecuter, |
||||
|
IServiceScopeFactory serviceScopeFactory) |
||||
|
{ |
||||
|
JobExecuter = jobExecuter; |
||||
|
ServiceScopeFactory = serviceScopeFactory; |
||||
|
Options = options.Value; |
||||
|
} |
||||
|
|
||||
|
public void Execute(TArgs args) |
||||
|
{ |
||||
|
if (!Options.IsJobExecutionEnabled) |
||||
|
{ |
||||
|
throw new AbpException( |
||||
|
"Background job execution is disabled. " + |
||||
|
"This method should not be called! " + |
||||
|
"If you want to enable the background job execution, " + |
||||
|
$"set {nameof(AbpBackgroundJobOptions)}.{nameof(AbpBackgroundJobOptions.IsJobExecutionEnabled)} to true! " + |
||||
|
"If you've intentionally disabled job execution and this seems a bug, please report it." |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
using (var scope = ServiceScopeFactory.CreateScope()) |
||||
|
{ |
||||
|
var jobType = Options.GetJob(typeof(TArgs)).JobType; |
||||
|
var context = new JobExecutionContext(scope.ServiceProvider, jobType, args); |
||||
|
AsyncHelper.RunSync(() => JobExecuter.ExecuteAsync(context)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using Hangfire; |
||||
|
using JetBrains.Annotations; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundJobs.Hangfire |
||||
|
{ |
||||
|
public static class IBackgroundJobManagerExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 后台作业进入周期性队列
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TArgs">作业参数类型</typeparam>
|
||||
|
/// <param name="backgroundJobManager">后台作业管理器</param>
|
||||
|
/// <param name="cron">Cron表达式</param>
|
||||
|
/// <param name="args">作业参数</param>
|
||||
|
/// <returns></returns>
|
||||
|
public static Task EnqueueAsync<TArgs>( |
||||
|
this IBackgroundJobManager backgroundJobManager, |
||||
|
[NotNull] string cron, |
||||
|
TArgs args |
||||
|
) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(cron, nameof(cron)); |
||||
|
Check.NotNull(args, nameof(args)); |
||||
|
|
||||
|
var jobName = BackgroundJobNameAttribute.GetName<TArgs>(); |
||||
|
|
||||
|
RecurringJob.AddOrUpdate<HangfireJobExecutionAdapter<TArgs>>(jobName, adapter => adapter.Execute(args), cron); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
using Hangfire; |
||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundJobs |
||||
|
{ |
||||
|
public class CronGenerator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 周期性为分钟的任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="interval">执行周期的间隔,默认为每分钟一次</param>
|
||||
|
/// <returns></returns>
|
||||
|
public static string Minute(int interval = 1) |
||||
|
{ |
||||
|
return $"1 0/{interval} * * * ? "; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 周期性为小时的任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="minute">第几分钟开始,默认为第一分钟</param>
|
||||
|
/// <param name="interval">执行周期的间隔,默认为每小时一次</param>
|
||||
|
/// <returns></returns>
|
||||
|
public static string Hour(int minute = 1, int interval = 1) |
||||
|
{ |
||||
|
return $"1 {minute} 0/ {interval} * * ? "; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 周期性为天的任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="hour">第几小时开始,默认从1点开始</param>
|
||||
|
/// <param name="minute">第几分钟开始,默认从第1分钟开始</param>
|
||||
|
/// <param name="interval">执行周期的间隔,默认为每天一次</param>
|
||||
|
/// <returns></returns>
|
||||
|
public static string Day(int hour = 1, int minute = 1, int interval = 1) |
||||
|
{ |
||||
|
return $"1 {minute} {hour} 1/ {interval} * ? "; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 周期性为周的任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="dayOfWeek">星期几开始,默认从星期一点开始</param>
|
||||
|
/// <param name="hour">第几小时开始,默认从1点开始</param>
|
||||
|
/// <param name="minute">第几分钟开始,默认从第1分钟开始</param>
|
||||
|
/// <returns></returns>
|
||||
|
public static string Week(DayOfWeek dayOfWeek = DayOfWeek.Monday, int hour = 1, int minute = 1) |
||||
|
{ |
||||
|
return Cron.Weekly(dayOfWeek, hour, minute); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 周期性为月的任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="day">几号开始,默认从一号开始</param>
|
||||
|
/// <param name="hour">第几小时开始,默认从1点开始</param>
|
||||
|
/// <param name="minute">第几分钟开始,默认从第1分钟开始</param>
|
||||
|
/// <returns></returns>
|
||||
|
public static string Month(int day = 1, int hour = 1, int minute = 1) |
||||
|
{ |
||||
|
return Cron.Monthly(day, hour, minute); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 周期性为年的任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="month">几月开始,默认从一月开始</param>
|
||||
|
/// <param name="day">几号开始,默认从一号开始</param>
|
||||
|
/// <param name="hour">第几小时开始,默认从1点开始</param>
|
||||
|
/// <param name="minute">第几分钟开始,默认从第1分钟开始</param>
|
||||
|
/// <returns></returns>
|
||||
|
public static string Year(int month = 1, int day = 1, int hour = 1, int minute = 1) |
||||
|
{ |
||||
|
return Cron.Yearly(month, day, hour, minute); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> |
||||
|
<Version>2.9.0</Version> |
||||
|
<Authors>LINGYUN</Authors> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
||||
|
<OutputPath>D:\LocalNuget</OutputPath> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.BackgroundJobs" Version="2.9.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,43 @@ |
|||||
|
namespace LINGYUN.Abp.BackgroundJobs |
||||
|
{ |
||||
|
public class RetryAsyncBackgroundJobArgs<TArgs> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 重试次数
|
||||
|
/// </summary>
|
||||
|
public int RetryCount { get; set; } = 0; |
||||
|
/// <summary>
|
||||
|
/// 重试间隔(毫秒)
|
||||
|
/// 默认 300000ms = 5min
|
||||
|
/// </summary>
|
||||
|
public double RetryIntervalMillisecond { get; set; } = 300000d; |
||||
|
/// <summary>
|
||||
|
/// 最大重试次数
|
||||
|
/// 默认 20
|
||||
|
/// </summary>
|
||||
|
public int MaxRetryCount { get; set; } = 20; |
||||
|
/// <summary>
|
||||
|
/// 作业参数
|
||||
|
/// </summary>
|
||||
|
public TArgs JobArgs { get; set; } |
||||
|
|
||||
|
public RetryAsyncBackgroundJobArgs() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public RetryAsyncBackgroundJobArgs(TArgs jobArgs) |
||||
|
{ |
||||
|
JobArgs = jobArgs; |
||||
|
} |
||||
|
|
||||
|
public RetryAsyncBackgroundJobArgs(TArgs jobArgs, int retryCount = 0, double interval = 300000d, int maxRetryCount = 20) |
||||
|
{ |
||||
|
JobArgs = jobArgs; |
||||
|
|
||||
|
RetryCount = retryCount; |
||||
|
RetryIntervalMillisecond = interval; |
||||
|
MaxRetryCount = maxRetryCount; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundJobs |
||||
|
{ |
||||
|
public abstract class RetryAsyncBackgroundJobBase<TArgs> : IAsyncBackgroundJob<RetryAsyncBackgroundJobArgs<TArgs>> |
||||
|
{ |
||||
|
public ILogger<RetryAsyncBackgroundJobBase<TArgs>> Logger { get; set; } |
||||
|
|
||||
|
protected IBackgroundJobManager BackgroundJobManager { get; } |
||||
|
|
||||
|
protected RetryAsyncBackgroundJobBase( |
||||
|
IBackgroundJobManager backgroundJobManager) |
||||
|
{ |
||||
|
BackgroundJobManager = backgroundJobManager; |
||||
|
|
||||
|
Logger = NullLogger<RetryAsyncBackgroundJobBase<TArgs>>.Instance; |
||||
|
} |
||||
|
|
||||
|
public async Task ExecuteAsync(RetryAsyncBackgroundJobArgs<TArgs> args) |
||||
|
{ |
||||
|
if (args.RetryCount > args.MaxRetryCount) |
||||
|
{ |
||||
|
Logger.LogWarning("Job has failed and the maximum number of retries has been reached. The failure callback is about to enter"); |
||||
|
// 任务执行失败次数已达上限,调用用户定义回调,并不再执行
|
||||
|
await OnJobExecuteFailedAsync(args.JobArgs); |
||||
|
return; |
||||
|
} |
||||
|
try |
||||
|
{ |
||||
|
// 执行任务
|
||||
|
await ExecuteAsync(args.JobArgs, args.RetryCount); |
||||
|
// 执行完成后回调
|
||||
|
await OnJobExecuteCompletedAsync(args.JobArgs); |
||||
|
} |
||||
|
catch(Exception ex) |
||||
|
{ |
||||
|
Logger.LogWarning("Job execution has failed and a retry is imminent"); |
||||
|
Logger.LogWarning("Job running error:{0}", ex.Message); |
||||
|
|
||||
|
// 每次重试 间隔时间增加1.1倍
|
||||
|
var retryInterval = args.RetryIntervalMillisecond * 1.1; |
||||
|
var retryJobArgs = new RetryAsyncBackgroundJobArgs<TArgs>(args.JobArgs, |
||||
|
args.RetryCount + 1, retryInterval, args.MaxRetryCount); |
||||
|
|
||||
|
Logger.LogDebug("Job task is queued for the next execution"); |
||||
|
|
||||
|
// 计算优先级
|
||||
|
BackgroundJobPriority priority = BackgroundJobPriority.Normal; |
||||
|
|
||||
|
if (args.RetryCount <= (args.MaxRetryCount / 2) && |
||||
|
args.RetryCount > (args.MaxRetryCount / 3)) |
||||
|
{ |
||||
|
priority = BackgroundJobPriority.BelowNormal; |
||||
|
} |
||||
|
else if (args.RetryCount > (args.MaxRetryCount / 1.5)) |
||||
|
{ |
||||
|
priority = BackgroundJobPriority.Low; |
||||
|
} |
||||
|
// 延迟入队,等待下一次运行
|
||||
|
await BackgroundJobManager.EnqueueAsync(retryJobArgs, priority, delay: TimeSpan.FromMilliseconds(retryInterval)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected abstract Task ExecuteAsync(TArgs args, int retryCount); |
||||
|
|
||||
|
protected virtual Task OnJobExecuteFailedAsync(TArgs args) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
protected virtual Task OnJobExecuteCompletedAsync(TArgs args) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> |
||||
|
<Version>2.9.0</Version> |
||||
|
<Authors>LINGYUN</Authors> |
||||
|
<Description>阿里云Oss对象存储Abp集成</Description> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
||||
|
<OutputPath>D:\LocalNuget</OutputPath> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.10.0" /> |
||||
|
<PackageReference Include="Volo.Abp.BlobStoring" Version="2.9.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Aliyun.Authorization\LINGYUN.Abp.Aliyun.Authorization.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,35 @@ |
|||||
|
using LINGYUN.Abp.Aliyun.Authorization; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Aliyun |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpBlobStoringModule), |
||||
|
typeof(AbpAliyunAuthorizationModule))] |
||||
|
public class AbpBlobStoringAliyunModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
|
||||
|
Configure<AbpBlobStoringOptions>(options => |
||||
|
{ |
||||
|
context.Services.ExecutePreConfiguredActions(options); |
||||
|
options.Containers.ConfigureAll((containerName, containerConfiguration) => |
||||
|
{ |
||||
|
containerConfiguration.UseAliyun(aliyun => |
||||
|
{ |
||||
|
aliyun.BucketName = configuration[AliyunBlobProviderConfigurationNames.BucketName] ?? ""; |
||||
|
aliyun.CreateBucketIfNotExists = configuration.GetSection(AliyunBlobProviderConfigurationNames.CreateBucketIfNotExists).Get<bool>(); |
||||
|
aliyun.CreateBucketReferer = configuration.GetSection(AliyunBlobProviderConfigurationNames.CreateBucketReferer).Get<List<string>>(); |
||||
|
aliyun.Endpoint = configuration[AliyunBlobProviderConfigurationNames.Endpoint]; |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Aliyun |
||||
|
{ |
||||
|
public static class AliyunBlobContainerConfigurationExtensions |
||||
|
{ |
||||
|
public static AliyunBlobProviderConfiguration GetAliyunConfiguration( |
||||
|
this BlobContainerConfiguration containerConfiguration) |
||||
|
{ |
||||
|
return new AliyunBlobProviderConfiguration(containerConfiguration); |
||||
|
} |
||||
|
|
||||
|
public static BlobContainerConfiguration UseAliyun( |
||||
|
this BlobContainerConfiguration containerConfiguration, |
||||
|
Action<AliyunBlobProviderConfiguration> aliyunConfigureAction) |
||||
|
{ |
||||
|
containerConfiguration.ProviderType = typeof(AliyunBlobProvider); |
||||
|
|
||||
|
aliyunConfigureAction(new AliyunBlobProviderConfiguration(containerConfiguration)); |
||||
|
|
||||
|
return containerConfiguration; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,148 @@ |
|||||
|
using Aliyun.OSS; |
||||
|
using LINGYUN.Abp.Aliyun.Authorization; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Aliyun |
||||
|
{ |
||||
|
public class AliyunBlobProvider : BlobProviderBase, ITransientDependency |
||||
|
{ |
||||
|
protected AbpAliyunOptions Options { get; } |
||||
|
protected IAliyunBlobNameCalculator AliyunBlobNameCalculator { get; } |
||||
|
|
||||
|
public AliyunBlobProvider( |
||||
|
IOptions<AbpAliyunOptions> options, |
||||
|
IAliyunBlobNameCalculator aliyunBlobNameCalculator) |
||||
|
{ |
||||
|
Options = options.Value; |
||||
|
AliyunBlobNameCalculator = aliyunBlobNameCalculator; |
||||
|
} |
||||
|
|
||||
|
public override async Task<bool> DeleteAsync(BlobProviderDeleteArgs args) |
||||
|
{ |
||||
|
var blobName = AliyunBlobNameCalculator.Calculate(args); |
||||
|
|
||||
|
if (await BlobExistsAsync(args, blobName)) |
||||
|
{ |
||||
|
var ossClient = GetOssClient(args); |
||||
|
|
||||
|
return ossClient.DeleteObject(GetBucketName(args), blobName).DeleteMarker; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public override async Task<bool> ExistsAsync(BlobProviderExistsArgs args) |
||||
|
{ |
||||
|
var blobName = AliyunBlobNameCalculator.Calculate(args); |
||||
|
|
||||
|
return await BlobExistsAsync(args, blobName); |
||||
|
} |
||||
|
|
||||
|
public override async Task<Stream> GetOrNullAsync(BlobProviderGetArgs args) |
||||
|
{ |
||||
|
var blobName = AliyunBlobNameCalculator.Calculate(args); |
||||
|
|
||||
|
if (!await BlobExistsAsync(args, blobName)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var ossClient = GetOssClient(args); |
||||
|
var ossObject = ossClient.GetObject(GetBucketName(args), blobName); |
||||
|
// 返回原始结果才会调用 Stream.ReadAsync();
|
||||
|
return ossObject.Content; |
||||
|
} |
||||
|
|
||||
|
public override async Task SaveAsync(BlobProviderSaveArgs args) |
||||
|
{ |
||||
|
var blobName = AliyunBlobNameCalculator.Calculate(args); |
||||
|
var configuration = args.Configuration.GetAliyunConfiguration(); |
||||
|
if (!args.OverrideExisting && await BlobExistsAsync(args, blobName)) |
||||
|
{ |
||||
|
throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the bucketName '{GetBucketName(args)}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); |
||||
|
} |
||||
|
|
||||
|
if (configuration.CreateBucketIfNotExists) |
||||
|
{ |
||||
|
await CreateBucketIfNotExists(args, configuration.CreateBucketReferer); |
||||
|
} |
||||
|
|
||||
|
var bucketName = GetBucketName(args); |
||||
|
var ossClient = GetOssClient(args); |
||||
|
|
||||
|
if (args.OverrideExisting && await BlobExistsAsync(args, blobName)) |
||||
|
{ |
||||
|
ossClient.DeleteObject(bucketName, blobName); |
||||
|
} |
||||
|
|
||||
|
ossClient.PutObject(bucketName, blobName, args.BlobStream); |
||||
|
} |
||||
|
|
||||
|
protected virtual OssClient GetOssClient(BlobProviderArgs args) |
||||
|
{ |
||||
|
var configuration = args.Configuration.GetAliyunConfiguration(); |
||||
|
var ossClient = new OssClient(configuration.Endpoint, Options.AccessKeyId, Options.AccessKeySecret); |
||||
|
return ossClient; |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task CreateBucketIfNotExists(BlobProviderArgs args, IList<string> refererList = null) |
||||
|
{ |
||||
|
if (! await BucketExistsAsync(args)) |
||||
|
{ |
||||
|
var ossClient = GetOssClient(args); |
||||
|
var bucketName = GetBucketName(args); |
||||
|
|
||||
|
var request = new CreateBucketRequest(bucketName) |
||||
|
{ |
||||
|
//设置存储空间访问权限ACL。
|
||||
|
ACL = CannedAccessControlList.PublicReadWrite, |
||||
|
//设置数据容灾类型。
|
||||
|
DataRedundancyType = DataRedundancyType.ZRS |
||||
|
}; |
||||
|
|
||||
|
ossClient.CreateBucket(request); |
||||
|
|
||||
|
if (refererList != null && refererList.Count > 0) |
||||
|
{ |
||||
|
var srq = new SetBucketRefererRequest(bucketName, refererList); |
||||
|
ossClient.SetBucketReferer(srq); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<bool> BlobExistsAsync(BlobProviderArgs args, string blobName) |
||||
|
{ |
||||
|
var ossClient = GetOssClient(args); |
||||
|
var bucketExists = await BucketExistsAsync(args); |
||||
|
if (bucketExists) |
||||
|
{ |
||||
|
var objectExists = ossClient.DoesObjectExist(GetBucketName(args), blobName); |
||||
|
|
||||
|
return objectExists; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private Task<bool> BucketExistsAsync(BlobProviderArgs args) |
||||
|
{ |
||||
|
var ossClient = GetOssClient(args); |
||||
|
var bucketExists = ossClient.DoesBucketExist(GetBucketName(args)); |
||||
|
|
||||
|
return Task.FromResult(bucketExists); |
||||
|
} |
||||
|
|
||||
|
private static string GetBucketName(BlobProviderArgs args) |
||||
|
{ |
||||
|
var configuration = args.Configuration.GetAliyunConfiguration(); |
||||
|
return configuration.BucketName.IsNullOrWhiteSpace() |
||||
|
? args.ContainerName |
||||
|
: configuration.BucketName; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Aliyun |
||||
|
{ |
||||
|
public class AliyunBlobProviderConfiguration |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 数据中心
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 详见 https://help.aliyun.com/document_detail/31837.html?spm=a2c4g.11186623.2.14.417cd47eLc9LHc#concept-zt4-cvy-5db
|
||||
|
/// </remarks>
|
||||
|
public string Endpoint |
||||
|
{ |
||||
|
get => _containerConfiguration.GetConfiguration<string>(AliyunBlobProviderConfigurationNames.Endpoint); |
||||
|
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.Endpoint, Check.NotNullOrWhiteSpace(value, nameof(value))); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 命名空间
|
||||
|
/// </summary>
|
||||
|
public string BucketName |
||||
|
{ |
||||
|
get => _containerConfiguration.GetConfiguration<string>(AliyunBlobProviderConfigurationNames.BucketName); |
||||
|
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.BucketName, Check.NotNullOrWhiteSpace(value, nameof(value))); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 命名空间不存在是否创建
|
||||
|
/// </summary>
|
||||
|
public bool CreateBucketIfNotExists |
||||
|
{ |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.CreateBucketIfNotExists, false); |
||||
|
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.CreateBucketIfNotExists, value); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 创建命名空间时防盗链列表
|
||||
|
/// </summary>
|
||||
|
public List<string> CreateBucketReferer |
||||
|
{ |
||||
|
get => _containerConfiguration.GetConfiguration<List<string>>(AliyunBlobProviderConfigurationNames.CreateBucketReferer); |
||||
|
set |
||||
|
{ |
||||
|
if (value == null) |
||||
|
{ |
||||
|
_containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.CreateBucketReferer, new List<string>()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.CreateBucketReferer, value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private readonly BlobContainerConfiguration _containerConfiguration; |
||||
|
|
||||
|
public AliyunBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) |
||||
|
{ |
||||
|
_containerConfiguration = containerConfiguration; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
namespace LINGYUN.Abp.BlobStoring.Aliyun |
||||
|
{ |
||||
|
public static class AliyunBlobProviderConfigurationNames |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 数据中心
|
||||
|
/// </summary>
|
||||
|
public const string Endpoint = "Aliyun:OSS:Endpoint"; |
||||
|
/// <summary>
|
||||
|
/// 命名空间
|
||||
|
/// </summary>
|
||||
|
public const string BucketName = "Aliyun:OSS:BucketName"; |
||||
|
/// <summary>
|
||||
|
/// 命名空间不存在是否创建
|
||||
|
/// </summary>
|
||||
|
public const string CreateBucketIfNotExists = "Aliyun:OSS:CreateBucketIfNotExists"; |
||||
|
/// <summary>
|
||||
|
/// 创建命名空间时防盗链列表
|
||||
|
/// </summary>
|
||||
|
public const string CreateBucketReferer = "Aliyun:OSS:CreateBucketReferer"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using Volo.Abp.BlobStoring; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Aliyun |
||||
|
{ |
||||
|
public class DefaultAliyunBlobNameCalculator : IAliyunBlobNameCalculator, ITransientDependency |
||||
|
{ |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
|
||||
|
public DefaultAliyunBlobNameCalculator( |
||||
|
ICurrentTenant currentTenant) |
||||
|
{ |
||||
|
CurrentTenant = currentTenant; |
||||
|
} |
||||
|
|
||||
|
public string Calculate(BlobProviderArgs args) |
||||
|
{ |
||||
|
return CurrentTenant.Id == null |
||||
|
? $"host/{args.BlobName}" |
||||
|
: $"tenants/{CurrentTenant.Id.Value:D}/{args.BlobName}"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.BlobStoring; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BlobStoring.Aliyun |
||||
|
{ |
||||
|
public interface IAliyunBlobNameCalculator |
||||
|
{ |
||||
|
string Calculate(BlobProviderArgs args); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance> |
||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> |
||||
|
<Version>2.9.0</Version> |
||||
|
<Company>LINGYUN</Company> |
||||
|
<Authors>LINGYUN</Authors> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
||||
|
<OutputPath>D:\LocalNuget</OutputPath> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Hangfire.MySql.Core" Version="2.2.5" /> |
||||
|
<PackageReference Include="Volo.Abp.HangFire" Version="2.9.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,38 @@ |
|||||
|
using Hangfire; |
||||
|
using Hangfire.MySql.Core; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Hangfire; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Hangfire.Storage.MySql |
||||
|
{ |
||||
|
[DependsOn(typeof(AbpHangfireModule))] |
||||
|
public class AbpHangfireMySqlStorageModule : AbpModule |
||||
|
{ |
||||
|
private MySqlStorage _jobStorage; |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
|
||||
|
var mysqlStorageOptions = new MySqlStorageOptions(); |
||||
|
configuration.GetSection("Hangfire:MySql").Bind(mysqlStorageOptions); |
||||
|
|
||||
|
var hangfireMySqlConfiguration = configuration.GetSection("Hangfire:MySql:Connection"); |
||||
|
var hangfireMySqlCon = hangfireMySqlConfiguration.Exists() |
||||
|
? hangfireMySqlConfiguration.Value : configuration.GetConnectionString("Default"); |
||||
|
|
||||
|
_jobStorage = new MySqlStorage(hangfireMySqlCon, mysqlStorageOptions); |
||||
|
context.Services.AddSingleton<JobStorage, MySqlStorage>(fac => |
||||
|
{ |
||||
|
return _jobStorage; |
||||
|
}); |
||||
|
|
||||
|
context.Services.AddHangfire(config => |
||||
|
{ |
||||
|
config.UseStorage(_jobStorage); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> |
||||
|
<Version>2.9.0</Version> |
||||
|
<Authors>LINGYUN</Authors> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
||||
|
<OutputPath>D:\LocalNuget</OutputPath> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="HangFire.SqlServer" Version="1.7.11" /> |
||||
|
<PackageReference Include="Volo.Abp.HangFire" Version="2.9.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,38 @@ |
|||||
|
using Hangfire; |
||||
|
using Hangfire.SqlServer; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Hangfire; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Hangfire.Storage.SqlServer |
||||
|
{ |
||||
|
[DependsOn(typeof(AbpHangfireModule))] |
||||
|
public class AbpHangfireSqlServerStorageModule : AbpModule |
||||
|
{ |
||||
|
private SqlServerStorage _jobStorage; |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
|
||||
|
var sqlserverStorageOptions = new SqlServerStorageOptions(); |
||||
|
configuration.GetSection("Hangfire:SqlServer").Bind(sqlserverStorageOptions); |
||||
|
|
||||
|
var hangfireSqlServerConfiguration = configuration.GetSection("Hangfire:SqlServer:Connection"); |
||||
|
var hangfireSqlServerCon = hangfireSqlServerConfiguration.Exists() |
||||
|
? hangfireSqlServerConfiguration.Value : configuration.GetConnectionString("Default"); |
||||
|
|
||||
|
_jobStorage = new SqlServerStorage(hangfireSqlServerCon, sqlserverStorageOptions); |
||||
|
context.Services.AddSingleton<JobStorage, SqlServerStorage>(fac => |
||||
|
{ |
||||
|
return _jobStorage; |
||||
|
}); |
||||
|
|
||||
|
context.Services.AddHangfire(config => |
||||
|
{ |
||||
|
config.UseStorage(_jobStorage); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,8 +0,0 @@ |
|||||
using System; |
|
||||
|
|
||||
namespace LINGYUN.Abp.IdentityServer.WeChatValidator |
|
||||
{ |
|
||||
public class Class1 |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,44 @@ |
|||||
|
using LINGYUN.Abp.IdentityServer.WeChatValidator; |
||||
|
using LINGYUN.Abp.WeChat.Authorization; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.IdentityServer; |
||||
|
using Volo.Abp.IdentityServer.Localization; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.IdentityServer |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpWeChatAuthorizationModule), |
||||
|
typeof(AbpIdentityServerDomainModule))] |
||||
|
public class AbpIdentityServerWeChatValidatorModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<IIdentityServerBuilder>(builder => |
||||
|
{ |
||||
|
builder.AddExtensionGrantValidator<WeChatTokenGrantValidator>(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
|
||||
|
Configure<WeChatSignatureOptions>(configuration.GetSection("WeChat:Signature")); |
||||
|
|
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpIdentityServerWeChatValidatorModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Get<AbpIdentityServerResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/IdentityServer/Localization/WeChatValidator"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"InvalidGrant:GrantTypeInvalid": "The type of authorization that is not allowed!", |
||||
|
"InvalidGrant:WeChatTokenInvalid": "WeChat authentication failed!", |
||||
|
"InvalidGrant:WeChatCodeNotFound": "The code obtained when WeChat is logged in is empty or does not exist!", |
||||
|
"InvalidGrant:WeChatNotRegister": "User WeChat account not registed!" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"InvalidGrant:GrantTypeInvalid": "不被允许的授权类型!", |
||||
|
"InvalidGrant:WeChatTokenInvalid": "微信认证失败!", |
||||
|
"InvalidGrant:WeChatCodeNotFound": "微信登录时获取的 code 为空或不存在!", |
||||
|
"InvalidGrant:WeChatNotRegister": "用户微信账号未绑定!" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Security.Cryptography; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.IdentityServer |
||||
|
{ |
||||
|
public class WeChatSignatureMiddleware : IMiddleware, ITransientDependency |
||||
|
{ |
||||
|
protected WeChatSignatureOptions Options { get; } |
||||
|
public WeChatSignatureMiddleware(IOptions<WeChatSignatureOptions> options) |
||||
|
{ |
||||
|
Options = options.Value; |
||||
|
} |
||||
|
|
||||
|
public async Task InvokeAsync(HttpContext context, RequestDelegate next) |
||||
|
{ |
||||
|
if (context.Request.Path.HasValue) |
||||
|
{ |
||||
|
var requestPath = context.Request.Path.Value; |
||||
|
// 访问地址是否与定义的地址匹配
|
||||
|
if (requestPath.Equals(Options.RequestPath)) |
||||
|
{ |
||||
|
var timestamp = context.Request.Query["timestamp"]; |
||||
|
var nonce = context.Request.Query["nonce"]; |
||||
|
var signature = context.Request.Query["signature"]; |
||||
|
var echostr = context.Request.Query["echostr"]; |
||||
|
// 验证消息合法性
|
||||
|
var check = CheckWeChatSignature(Options.Token, timestamp, nonce, signature); |
||||
|
if (check) |
||||
|
{ |
||||
|
// 验证通过需要把微信服务器传递的字符原封不动传回
|
||||
|
await context.Response.WriteAsync(echostr); |
||||
|
return; |
||||
|
} |
||||
|
// 微信消息验证不通过
|
||||
|
throw new AbpException("Invalid wechat signature"); |
||||
|
} |
||||
|
} |
||||
|
// 不属于微信的消息进入下一个中间件
|
||||
|
await next(context); |
||||
|
} |
||||
|
|
||||
|
protected bool CheckWeChatSignature(string token, string timestamp, string nonce, string signature) |
||||
|
{ |
||||
|
var al = new ArrayList |
||||
|
{ |
||||
|
token, |
||||
|
timestamp, |
||||
|
nonce |
||||
|
}; |
||||
|
// step1 排序
|
||||
|
al.Sort(); |
||||
|
string signatureStr = string.Empty; |
||||
|
// step2 拼接
|
||||
|
for (int i = 0; i < al.Count; i++) |
||||
|
{ |
||||
|
signatureStr += al[i]; |
||||
|
} |
||||
|
// step3 SHA1加密
|
||||
|
using var sha1 = new SHA1CryptoServiceProvider(); |
||||
|
byte[] bytes_in = Encoding.ASCII.GetBytes(signatureStr); |
||||
|
byte[] bytes_out = sha1.ComputeHash(bytes_in); |
||||
|
string result = BitConverter.ToString(bytes_out).Replace("-", ""); |
||||
|
// step4 比对
|
||||
|
if (result.Equals(signature, StringComparison.CurrentCultureIgnoreCase)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
namespace LINGYUN.Abp.IdentityServer |
||||
|
{ |
||||
|
public class WeChatSignatureOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 微信服务器请求路径
|
||||
|
/// 填写在微信开发者中心配置的地址
|
||||
|
/// </summary>
|
||||
|
public string RequestPath { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 微信服务器请求token
|
||||
|
/// 填写在微信开发者中心配置的token
|
||||
|
/// </summary>
|
||||
|
public string Token { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
using IdentityModel; |
||||
|
using IdentityServer4.Events; |
||||
|
using IdentityServer4.Models; |
||||
|
using IdentityServer4.Services; |
||||
|
using IdentityServer4.Validation; |
||||
|
using LINGYUN.Abp.WeChat.Authorization; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.Extensions.Localization; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Net.Http; |
||||
|
using System.Security.Claims; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.IdentityServer.Localization; |
||||
|
using Volo.Abp.Security.Claims; |
||||
|
using IdentityUser = Volo.Abp.Identity.IdentityUser; |
||||
|
|
||||
|
namespace LINGYUN.Abp.IdentityServer.WeChatValidator |
||||
|
{ |
||||
|
public class WeChatTokenGrantValidator : IExtensionGrantValidator |
||||
|
{ |
||||
|
protected ILogger<WeChatTokenGrantValidator> Logger { get; } |
||||
|
protected AbpWeChatOptions Options { get; } |
||||
|
protected IHttpClientFactory HttpClientFactory{ get; } |
||||
|
protected IEventService EventService { get; } |
||||
|
protected IWeChatOpenIdFinder WeChatOpenIdFinder { get; } |
||||
|
protected IIdentityUserRepository UserRepository { get; } |
||||
|
protected UserManager<IdentityUser> UserManager { get; } |
||||
|
protected SignInManager<IdentityUser> SignInManager { get; } |
||||
|
protected IStringLocalizer<AbpIdentityServerResource> Localizer { get; } |
||||
|
protected PhoneNumberTokenProvider<IdentityUser> PhoneNumberTokenProvider { get; } |
||||
|
|
||||
|
|
||||
|
public WeChatTokenGrantValidator( |
||||
|
IEventService eventService, |
||||
|
IWeChatOpenIdFinder weChatOpenIdFinder, |
||||
|
IHttpClientFactory httpClientFactory, |
||||
|
UserManager<IdentityUser> userManager, |
||||
|
IIdentityUserRepository userRepository, |
||||
|
SignInManager<IdentityUser> signInManager, |
||||
|
IStringLocalizer<AbpIdentityServerResource> stringLocalizer, |
||||
|
PhoneNumberTokenProvider<IdentityUser> phoneNumberTokenProvider, |
||||
|
IOptions<AbpWeChatOptions> options, |
||||
|
ILogger<WeChatTokenGrantValidator> logger) |
||||
|
{ |
||||
|
Logger = logger; |
||||
|
Options = options.Value; |
||||
|
|
||||
|
EventService = eventService; |
||||
|
UserManager = userManager; |
||||
|
SignInManager = signInManager; |
||||
|
Localizer = stringLocalizer; |
||||
|
UserRepository = userRepository; |
||||
|
WeChatOpenIdFinder = weChatOpenIdFinder; |
||||
|
HttpClientFactory = httpClientFactory; |
||||
|
PhoneNumberTokenProvider = phoneNumberTokenProvider; |
||||
|
} |
||||
|
|
||||
|
public string GrantType => WeChatValidatorConsts.WeChatValidatorGrantTypeName; |
||||
|
|
||||
|
public async Task ValidateAsync(ExtensionGrantValidationContext context) |
||||
|
{ |
||||
|
var raw = context.Request.Raw; |
||||
|
var credential = raw.Get(OidcConstants.TokenRequest.GrantType); |
||||
|
if (credential == null || !credential.Equals(GrantType)) |
||||
|
{ |
||||
|
Logger.LogWarning("Invalid grant type: not allowed"); |
||||
|
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, |
||||
|
Localizer["InvalidGrant:GrantTypeInvalid"]); |
||||
|
return; |
||||
|
} |
||||
|
var wechatCode = raw.Get(WeChatValidatorConsts.WeChatValidatorTokenName); |
||||
|
if (wechatCode.IsNullOrWhiteSpace() || wechatCode.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
Logger.LogWarning("Invalid grant type: wechat code not found"); |
||||
|
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, |
||||
|
Localizer["InvalidGrant:WeChatCodeNotFound"]); |
||||
|
return; |
||||
|
} |
||||
|
var whchatOpenId = await WeChatOpenIdFinder.FindAsync(wechatCode); |
||||
|
var currentUser = await UserManager.FindByLoginAsync("WeChat", whchatOpenId.OpenId); |
||||
|
if(currentUser == null) |
||||
|
{ |
||||
|
Logger.LogWarning("Invalid grant type: wechat openid: {0} not register", whchatOpenId.OpenId); |
||||
|
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, |
||||
|
Localizer["InvalidGrant:WeChatNotRegister"]); |
||||
|
return; |
||||
|
} |
||||
|
var sub = await UserManager.GetUserIdAsync(currentUser); |
||||
|
|
||||
|
var additionalClaims = new List<Claim>(); |
||||
|
if (currentUser.TenantId.HasValue) |
||||
|
{ |
||||
|
additionalClaims.Add(new Claim(AbpClaimTypes.TenantId, currentUser.TenantId?.ToString())); |
||||
|
} |
||||
|
additionalClaims.Add(new Claim(WeChatValidatorConsts.ClaimTypes.OpenId, whchatOpenId.OpenId)); |
||||
|
|
||||
|
await EventService.RaiseAsync(new UserLoginSuccessEvent(currentUser.UserName, whchatOpenId.OpenId, null)); |
||||
|
context.Result = new GrantValidationResult(sub, |
||||
|
WeChatValidatorConsts.AuthenticationMethods.BasedWeChatAuthentication, additionalClaims.ToArray()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
namespace LINGYUN.Abp.IdentityServer.WeChatValidator |
||||
|
{ |
||||
|
public class WeChatValidatorConsts |
||||
|
{ |
||||
|
public const string WeChatValidatorClientName = "WeChatValidator"; |
||||
|
|
||||
|
public const string WeChatValidatorGrantTypeName = "wechat"; |
||||
|
|
||||
|
public const string WeChatValidatorTokenName = "code"; |
||||
|
|
||||
|
public class ClaimTypes |
||||
|
{ |
||||
|
public const string OpenId = "wx-openid"; |
||||
|
} |
||||
|
|
||||
|
public class AuthenticationMethods |
||||
|
{ |
||||
|
public const string BasedWeChatAuthentication = "wca"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using LINGYUN.Abp.IdentityServer; |
||||
|
|
||||
|
namespace Microsoft.AspNetCore.Builder |
||||
|
{ |
||||
|
public static class IdentityServerApplicationBuilderExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 启用中间件可以处理微信服务器消息
|
||||
|
/// 用于验证消息是否来自于微信服务器
|
||||
|
/// </summary>
|
||||
|
/// <param name="builder"></param>
|
||||
|
/// <remarks>
|
||||
|
/// 也可以用Controller的形式来实现
|
||||
|
/// </remarks>
|
||||
|
/// <returns></returns>
|
||||
|
public static IApplicationBuilder UseWeChatSignature(this IApplicationBuilder builder) |
||||
|
{ |
||||
|
builder.UseMiddleware<WeChatSignatureMiddleware>(); |
||||
|
return builder; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance> |
||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> |
||||
|
<Version>2.9.0</Version> |
||||
|
<Description>通知接口的微信小程序发布者实现</Description> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
||||
|
<OutputPath>D:\LocalNuget</OutputPath> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.WeChat.Authorization\LINGYUN.Abp.WeChat.Authorization.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Notifications\LINGYUN.Abp.Notifications.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,30 @@ |
|||||
|
using LINGYUN.Abp.WeChat.Authorization; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Polly; |
||||
|
using System; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpWeChatAuthorizationModule), |
||||
|
typeof(AbpNotificationModule))] |
||||
|
public class AbpNotificationsWeChatWeAppModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
Configure<AbpWeChatWeAppNotificationOptions>(configuration.GetSection("Notifications:WeChat:WeApp")); |
||||
|
|
||||
|
// TODO:是否有必要启用重试机制?
|
||||
|
context.Services.AddHttpClient(WeChatWeAppNotificationSender.SendNotificationClientName) |
||||
|
.AddTransientHttpErrorPolicy(builder => |
||||
|
builder.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)))); |
||||
|
|
||||
|
Configure<AbpNotificationOptions>(options => |
||||
|
{ |
||||
|
options.PublishProviders.Add<WeChatWeAppNotificationPublishProvider>(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
||||
|
{ |
||||
|
public class AbpWeChatWeAppNotificationOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 默认消息头部标记
|
||||
|
/// </summary>
|
||||
|
public string DefaultMsgPrefix { get; set; } = "[wx]"; |
||||
|
/// <summary>
|
||||
|
/// 默认小程序模板
|
||||
|
/// </summary>
|
||||
|
public string DefaultTemplateId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 默认跳转小程序类型
|
||||
|
/// </summary>
|
||||
|
public string DefaultWeAppState { get; set; } = "developer"; |
||||
|
/// <summary>
|
||||
|
/// 默认小程序语言
|
||||
|
/// </summary>
|
||||
|
public string DefaultWeAppLanguage { get; set; } = "zh_CN"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
||||
|
{ |
||||
|
public interface IWeChatWeAppNotificationSender |
||||
|
{ |
||||
|
Task SendAsync(WeChatWeAppSendNotificationData notificationData); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 微信小程序消息推送提供者
|
||||
|
/// </summary>
|
||||
|
public class WeChatWeAppNotificationPublishProvider : NotificationPublishProvider |
||||
|
{ |
||||
|
public override string Name => "WeChat.WeApp"; |
||||
|
|
||||
|
protected IWeChatWeAppNotificationSender NotificationSender { get; } |
||||
|
protected AbpWeChatWeAppNotificationOptions Options { get; } |
||||
|
public WeChatWeAppNotificationPublishProvider( |
||||
|
IServiceProvider serviceProvider, |
||||
|
IWeChatWeAppNotificationSender notificationSender, |
||||
|
IOptions<AbpWeChatWeAppNotificationOptions> options) |
||||
|
: base(serviceProvider) |
||||
|
{ |
||||
|
Options = options.Value; |
||||
|
NotificationSender = notificationSender; |
||||
|
} |
||||
|
|
||||
|
public override async Task PublishAsync(NotificationInfo notification, IEnumerable<UserIdentifier> identifiers) |
||||
|
{ |
||||
|
// step1 默认微信openid绑定的就是username,
|
||||
|
// 如果不是,需要自行处理openid获取逻辑
|
||||
|
|
||||
|
// step2 调用微信消息推送接口
|
||||
|
|
||||
|
foreach (var identifier in identifiers) |
||||
|
{ |
||||
|
await SendWeChatTemplateMessagAsync(notification, identifier); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task SendWeChatTemplateMessagAsync(NotificationInfo notification, UserIdentifier identifier) |
||||
|
{ |
||||
|
var templateId = GetOrDefaultTemplateId(notification.Data); |
||||
|
Logger.LogDebug($"Get wechat weapp template id: {templateId}"); |
||||
|
|
||||
|
var redirect = GetOrDefault(notification.Data, "RedirectPage", null); |
||||
|
Logger.LogDebug($"Get wechat weapp redirect page: {redirect ?? "null"}"); |
||||
|
|
||||
|
var weAppState = GetOrDefault(notification.Data, "WeAppState", Options.DefaultWeAppState); |
||||
|
Logger.LogDebug($"Get wechat weapp state: {weAppState ?? null}"); |
||||
|
|
||||
|
var weAppLang = GetOrDefault(notification.Data, "WeAppLanguage", Options.DefaultWeAppLanguage); |
||||
|
Logger.LogDebug($"Get wechat weapp language: {weAppLang ?? null}"); |
||||
|
|
||||
|
var weChatWeAppNotificationData = new WeChatWeAppSendNotificationData(identifier.UserName, |
||||
|
templateId, redirect, weAppState, weAppLang); |
||||
|
|
||||
|
|
||||
|
// 写入模板数据
|
||||
|
weChatWeAppNotificationData.WriteStandardData(NotificationData.ToStandardData(Options.DefaultMsgPrefix, notification.Data)); |
||||
|
// weChatWeAppNotificationData.WriteData(Options.DefaultMsgPrefix, notification.Data.Properties);
|
||||
|
|
||||
|
Logger.LogDebug($"Sending wechat weapp notification: {notification.Name}"); |
||||
|
// 发送小程序订阅消息
|
||||
|
await NotificationSender.SendAsync(weChatWeAppNotificationData); |
||||
|
} |
||||
|
|
||||
|
protected string GetOrDefaultTemplateId(NotificationData data) |
||||
|
{ |
||||
|
return GetOrDefault(data, "TemplateId", Options.DefaultTemplateId); |
||||
|
} |
||||
|
|
||||
|
protected string GetOrDefault(NotificationData data, string key, string defaultValue) |
||||
|
{ |
||||
|
if (data.Properties.TryGetValue(key, out object value)) |
||||
|
{ |
||||
|
// 取得了数据就删除对应键值
|
||||
|
// data.Properties.Remove(key);
|
||||
|
return value.ToString(); |
||||
|
} |
||||
|
return defaultValue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,108 @@ |
|||||
|
using LINGYUN.Abp.WeChat.Authorization; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using Newtonsoft.Json; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
||||
|
{ |
||||
|
public class WeChatWeAppNotificationSender : IWeChatWeAppNotificationSender, ITransientDependency |
||||
|
{ |
||||
|
public const string SendNotificationClientName = "WeChatWeAppSendNotificationClient"; |
||||
|
public ILogger<WeChatWeAppNotificationSender> Logger { get; set; } |
||||
|
protected IHttpClientFactory HttpClientFactory { get; } |
||||
|
protected IJsonSerializer JsonSerializer { get; } |
||||
|
protected IWeChatTokenProvider WeChatTokenProvider { get; } |
||||
|
public WeChatWeAppNotificationSender( |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
IHttpClientFactory httpClientFactory, |
||||
|
IWeChatTokenProvider weChatTokenProvider) |
||||
|
{ |
||||
|
JsonSerializer = jsonSerializer; |
||||
|
HttpClientFactory = httpClientFactory; |
||||
|
WeChatTokenProvider = weChatTokenProvider; |
||||
|
|
||||
|
Logger = NullLogger<WeChatWeAppNotificationSender>.Instance; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task SendAsync(WeChatWeAppSendNotificationData notificationData) |
||||
|
{ |
||||
|
var weChatToken = await WeChatTokenProvider.GetTokenAsync(); |
||||
|
var requestParamters = new Dictionary<string, string> |
||||
|
{ |
||||
|
{ "access_token", weChatToken.AccessToken } |
||||
|
}; |
||||
|
var weChatSendNotificationUrl = "https://api.weixin.qq.com"; |
||||
|
var weChatSendNotificationPath = "/cgi-bin/message/subscribe/send"; |
||||
|
var requestUrl = BuildRequestUrl(weChatSendNotificationUrl, weChatSendNotificationPath, requestParamters); |
||||
|
var responseContent = await MakeRequestAndGetResultAsync(requestUrl, notificationData); |
||||
|
var weChatSenNotificationResponse = JsonSerializer.Deserialize<WeChatSendNotificationResponse>(responseContent); |
||||
|
|
||||
|
if (!weChatSenNotificationResponse.IsSuccessed) |
||||
|
{ |
||||
|
Logger.LogWarning("Send wechat we app subscribe message failed"); |
||||
|
Logger.LogWarning($"Error code: {weChatSenNotificationResponse.ErrorCode}, message: {weChatSenNotificationResponse.ErrorMessage}"); |
||||
|
} |
||||
|
// 失败是否抛出异常
|
||||
|
// weChatSenNotificationResponse.ThrowIfNotSuccess();
|
||||
|
} |
||||
|
protected virtual async Task<string> MakeRequestAndGetResultAsync(string url, WeChatWeAppSendNotificationData notificationData) |
||||
|
{ |
||||
|
var client = HttpClientFactory.CreateClient(SendNotificationClientName); |
||||
|
var sendDataContent = JsonSerializer.Serialize(notificationData); |
||||
|
var requestContent = new StringContent(sendDataContent); |
||||
|
var requestMessage = new HttpRequestMessage(HttpMethod.Post, url) |
||||
|
{ |
||||
|
Content = requestContent |
||||
|
}; |
||||
|
|
||||
|
var response = await client.SendAsync(requestMessage); |
||||
|
if (!response.IsSuccessStatusCode) |
||||
|
{ |
||||
|
throw new AbpException($"WeChat send subscribe message http request service returns error! HttpStatusCode: {response.StatusCode}, ReasonPhrase: {response.ReasonPhrase}"); |
||||
|
} |
||||
|
var resultContent = await response.Content.ReadAsStringAsync(); |
||||
|
|
||||
|
return resultContent; |
||||
|
} |
||||
|
|
||||
|
protected virtual string BuildRequestUrl(string uri, string path, IDictionary<string, string> paramters) |
||||
|
{ |
||||
|
var requestUrlBuilder = new StringBuilder(128); |
||||
|
requestUrlBuilder.Append(uri); |
||||
|
requestUrlBuilder.Append(path).Append("?"); |
||||
|
foreach (var paramter in paramters) |
||||
|
{ |
||||
|
requestUrlBuilder.AppendFormat("{0}={1}", paramter.Key, paramter.Value); |
||||
|
requestUrlBuilder.Append("&"); |
||||
|
} |
||||
|
requestUrlBuilder.Remove(requestUrlBuilder.Length - 1, 1); |
||||
|
return requestUrlBuilder.ToString(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class WeChatSendNotificationResponse |
||||
|
{ |
||||
|
[JsonProperty("errcode")] |
||||
|
public int ErrorCode { get; set; } |
||||
|
|
||||
|
[JsonProperty("errmsg")] |
||||
|
public string ErrorMessage { get; set; } |
||||
|
|
||||
|
public bool IsSuccessed => ErrorCode == 0; |
||||
|
|
||||
|
public void ThrowIfNotSuccess() |
||||
|
{ |
||||
|
if (ErrorCode != 0) |
||||
|
{ |
||||
|
throw new AbpException($"Send wechat weapp notification error:{ErrorMessage}"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,103 @@ |
|||||
|
#pragma warning disable IDE1006 // 禁止编译器提示
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
||||
|
{ |
||||
|
public class WeChatWeAppSendNotificationData |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 接收者(用户)的 openid
|
||||
|
/// </summary>
|
||||
|
public string touser { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 所需下发的订阅模板id
|
||||
|
/// </summary>
|
||||
|
public string template_id { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 点击模板卡片后的跳转页面,仅限本小程序内的页面。
|
||||
|
/// 支持带参数,(示例index?foo=bar)。
|
||||
|
/// 该字段不填则模板无跳转
|
||||
|
/// </summary>
|
||||
|
public string page { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 跳转小程序类型:
|
||||
|
/// developer为开发版;trial为体验版;formal为正式版;
|
||||
|
/// 默认为正式版
|
||||
|
/// </summary>
|
||||
|
public string miniprogram_state { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 进入小程序查看”的语言类型,
|
||||
|
/// 支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),
|
||||
|
/// 默认为zh_CN
|
||||
|
/// </summary>
|
||||
|
public string lang { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 模板内容,
|
||||
|
/// 格式形如 { "key1": { "value": any }, "key2": { "value": any } }
|
||||
|
/// </summary>
|
||||
|
public Dictionary<string, WeChatNotificationData> data { get; set; } |
||||
|
|
||||
|
public WeChatWeAppSendNotificationData() { } |
||||
|
public WeChatWeAppSendNotificationData(string openId, string templateId, string redirectPage = "", |
||||
|
string state = "formal", string miniLang = "zh_CN") |
||||
|
{ |
||||
|
touser = openId; |
||||
|
template_id = templateId; |
||||
|
page = redirectPage; |
||||
|
miniprogram_state = state; |
||||
|
lang = miniLang; |
||||
|
|
||||
|
data = new Dictionary<string, WeChatNotificationData>(); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 写入标准数据
|
||||
|
/// </summary>
|
||||
|
/// <param name="writeData"></param>
|
||||
|
/// <returns></returns>
|
||||
|
public WeChatWeAppSendNotificationData WriteStandardData(NotificationData writeData) |
||||
|
{ |
||||
|
foreach (var kv in writeData.Properties) |
||||
|
{ |
||||
|
if (!data.ContainsKey(kv.Key)) |
||||
|
{ |
||||
|
data.Add(kv.Key, new WeChatNotificationData(kv.Value)); |
||||
|
} |
||||
|
} |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public WeChatWeAppSendNotificationData WriteData(string prefix, string key, object value) |
||||
|
{ |
||||
|
// 只截取符合标记的数据
|
||||
|
if (key.StartsWith(prefix)) |
||||
|
{ |
||||
|
key = key.Replace(prefix, ""); |
||||
|
if (!data.ContainsKey(key)) |
||||
|
{ |
||||
|
data.Add(key, new WeChatNotificationData(value)); |
||||
|
} |
||||
|
} |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public WeChatWeAppSendNotificationData WriteData(string prefix, IDictionary<string, object> setData) |
||||
|
{ |
||||
|
foreach(var kv in setData) |
||||
|
{ |
||||
|
WriteData(prefix, kv.Key, kv.Value); |
||||
|
} |
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class WeChatNotificationData |
||||
|
{ |
||||
|
public object Value { get; } |
||||
|
|
||||
|
public WeChatNotificationData(object value) |
||||
|
{ |
||||
|
Value = value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
#pragma warning restore IDE1006
|
||||
@ -1,14 +1,47 @@ |
|||||
using LINGYUN.Abp.Notifications.Internal; |
using LINGYUN.Abp.Notifications.Internal; |
||||
using Microsoft.Extensions.DependencyInjection; |
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
using Volo.Abp.Json; |
||||
using Volo.Abp.Modularity; |
using Volo.Abp.Modularity; |
||||
|
|
||||
namespace LINGYUN.Abp.Notifications |
namespace LINGYUN.Abp.Notifications |
||||
{ |
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpBackgroundJobsModule), |
||||
|
typeof(AbpJsonModule))] |
||||
public class AbpNotificationModule : AbpModule |
public class AbpNotificationModule : AbpModule |
||||
{ |
{ |
||||
|
|
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
AutoAddDefinitionProviders(context.Services); |
||||
|
} |
||||
|
|
||||
public override void ConfigureServices(ServiceConfigurationContext context) |
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
{ |
{ |
||||
context.Services.AddTransient<INotificationDispatcher, DefaultNotificationDispatcher>(); |
context.Services.AddTransient<INotificationDispatcher, DefaultNotificationDispatcher>(); |
||||
} |
} |
||||
|
|
||||
|
private static void AutoAddDefinitionProviders(IServiceCollection services) |
||||
|
{ |
||||
|
var definitionProviders = new List<Type>(); |
||||
|
|
||||
|
services.OnRegistred(context => |
||||
|
{ |
||||
|
if (typeof(INotificationDefinitionProvider).IsAssignableFrom(context.ImplementationType)) |
||||
|
{ |
||||
|
definitionProviders.Add(context.ImplementationType); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
services.Configure<AbpNotificationOptions>(options => |
||||
|
{ |
||||
|
services.ExecutePreConfiguredActions(options); |
||||
|
options.DefinitionProviders.AddIfNotContains(definitionProviders); |
||||
|
}); |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,16 @@ |
|||||
|
using Volo.Abp.Collections; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public class AbpNotificationOptions |
||||
|
{ |
||||
|
public ITypeList<INotificationDefinitionProvider> DefinitionProviders { get; } |
||||
|
|
||||
|
public ITypeList<INotificationPublishProvider> PublishProviders { get; } |
||||
|
public AbpNotificationOptions() |
||||
|
{ |
||||
|
PublishProviders = new TypeList<INotificationPublishProvider>(); |
||||
|
DefinitionProviders = new TypeList<INotificationDefinitionProvider>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public interface INotificationDefinitionContext |
||||
|
{ |
||||
|
NotificationDefinition GetOrNull(string category); |
||||
|
|
||||
|
void Add(params NotificationDefinition[] definitions); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public interface INotificationDefinitionManager |
||||
|
{ |
||||
|
[NotNull] |
||||
|
NotificationDefinition Get([NotNull] string category); |
||||
|
|
||||
|
IReadOnlyList<NotificationDefinition> GetAll(); |
||||
|
|
||||
|
NotificationDefinition GetOrNull(string category); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public interface INotificationDefinitionProvider |
||||
|
{ |
||||
|
void Define(INotificationDefinitionContext context); |
||||
|
} |
||||
|
} |
||||
@ -1,9 +1,33 @@ |
|||||
using System.Threading.Tasks; |
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
namespace LINGYUN.Abp.Notifications |
namespace LINGYUN.Abp.Notifications |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// 通知发送者接口
|
||||
|
/// </summary>
|
||||
public interface INotificationDispatcher |
public interface INotificationDispatcher |
||||
{ |
{ |
||||
Task DispatcheAsync(NotificationInfo notification); |
/// <summary>
|
||||
|
/// 发送通知
|
||||
|
/// </summary>
|
||||
|
/// <param name="notificationName">通知名称</param>
|
||||
|
/// <param name="data">数据</param>
|
||||
|
/// <param name="tenantId">租户</param>
|
||||
|
/// <param name="notificationSeverity">级别</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task DispatchAsync(NotificationName notificationName, NotificationData data, Guid? tenantId = null, |
||||
|
NotificationSeverity notificationSeverity = NotificationSeverity.Info); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 发送通知事件
|
||||
|
/// </summary>
|
||||
|
/// <param name="notificationName">通知名称</param>
|
||||
|
/// <param name="data">数据</param>
|
||||
|
/// <param name="tenantId">租户</param>
|
||||
|
/// <param name="notificationSeverity">级别</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task DispatchEventAsync(NotificationName notificationName, NotificationData data, Guid? tenantId = null, |
||||
|
NotificationSeverity notificationSeverity = NotificationSeverity.Info); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,12 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public interface INotificationPublishProvider |
||||
|
{ |
||||
|
string Name { get; } |
||||
|
|
||||
|
Task PublishAsync(NotificationInfo notification, IEnumerable<UserIdentifier> identifiers); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public interface INotificationPublishProviderManager |
||||
|
{ |
||||
|
List<INotificationPublishProvider> Providers { get; } |
||||
|
} |
||||
|
} |
||||
@ -1,11 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace LINGYUN.Abp.Notifications |
|
||||
{ |
|
||||
public interface INotificationPublisher |
|
||||
{ |
|
||||
Task PublishAsync(NotificationInfo notification, IEnumerable<Guid> userIds); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,81 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 通知订阅管理器
|
||||
|
/// </summary>
|
||||
|
public interface INotificationSubscriptionManager |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 是否已订阅
|
||||
|
/// </summary>
|
||||
|
/// <param name="tenantId">租户</param>
|
||||
|
/// <param name="userId">用户标识</param>
|
||||
|
/// <param name="notificationName">通知名称</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> IsSubscribedAsync(Guid? tenantId, Guid userId, string notificationName); |
||||
|
/// <summary>
|
||||
|
/// 订阅通知
|
||||
|
/// </summary>
|
||||
|
/// <param name="tenantId">租户</param>
|
||||
|
/// <param name="identifier">用户标识</param>
|
||||
|
/// <param name="notificationName">通知名称</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task SubscribeAsync(Guid? tenantId, UserIdentifier identifier, string notificationName); |
||||
|
/// <summary>
|
||||
|
/// 订阅通知
|
||||
|
/// </summary>
|
||||
|
/// <param name="tenantId">租户</param>
|
||||
|
/// <param name="identifiers">用户标识列表</param>
|
||||
|
/// <param name="notificationName">通知名称</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task SubscribeAsync(Guid? tenantId, IEnumerable<UserIdentifier> identifiers, string notificationName); |
||||
|
/// <summary>
|
||||
|
/// 取消所有用户订阅
|
||||
|
/// </summary>
|
||||
|
/// <param name="tenantId">租户</param>
|
||||
|
/// <param name="notificationName">通知名称</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task UnsubscribeAllAsync(Guid? tenantId, string notificationName); |
||||
|
/// <summary>
|
||||
|
/// 取消订阅
|
||||
|
/// </summary>
|
||||
|
/// <param name="tenantId">租户</param>
|
||||
|
/// <param name="identifier">用户标识</param>
|
||||
|
/// <param name="notificationName">通知名称</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task UnsubscribeAsync(Guid? tenantId, UserIdentifier identifier, string notificationName); |
||||
|
/// <summary>
|
||||
|
/// 取消订阅
|
||||
|
/// </summary>
|
||||
|
/// <param name="tenantId">租户</param>
|
||||
|
/// <param name="identifiers">用户标识列表</param>
|
||||
|
/// <param name="notificationName">通知名称</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task UnsubscribeAsync(Guid? tenantId, IEnumerable<UserIdentifier> identifiers, string notificationName); |
||||
|
/// <summary>
|
||||
|
/// 获取通知被订阅用户列表
|
||||
|
/// </summary>
|
||||
|
/// <param name="tenantId">租户</param>
|
||||
|
/// <param name="notificationName">通知名称</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<List<NotificationSubscriptionInfo>> GetSubscriptionsAsync(Guid? tenantId, string notificationName); |
||||
|
/// <summary>
|
||||
|
/// 获取用户订阅列表
|
||||
|
/// </summary>
|
||||
|
/// <param name="tenantId">租户</param>
|
||||
|
/// <param name="userId">用户标识</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync(Guid? tenantId, Guid userId); |
||||
|
/// <summary>
|
||||
|
/// 获取用户订阅列表
|
||||
|
/// </summary>
|
||||
|
/// <param name="tenantId">租户</param>
|
||||
|
/// <param name="userName">用户名</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync(Guid? tenantId, string userName); |
||||
|
} |
||||
|
} |
||||
@ -1,34 +1,210 @@ |
|||||
using System.Linq; |
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
using Volo.Abp.EventBus.Distributed; |
||||
|
|
||||
namespace LINGYUN.Abp.Notifications.Internal |
namespace LINGYUN.Abp.Notifications.Internal |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Implements <see cref="INotificationDispatcher"/>.
|
||||
|
/// </summary>
|
||||
internal class DefaultNotificationDispatcher : INotificationDispatcher |
internal class DefaultNotificationDispatcher : INotificationDispatcher |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Reference to <see cref="ILogger<DefaultNotificationDispatcher>"/>.
|
||||
|
/// </summary>
|
||||
|
public ILogger<DefaultNotificationDispatcher> Logger { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Reference to <see cref="IDistributedEventBus"/>.
|
||||
|
/// </summary>
|
||||
|
public IDistributedEventBus DistributedEventBus { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Reference to <see cref="IBackgroundJobManager"/>.
|
||||
|
/// </summary>
|
||||
|
private readonly IBackgroundJobManager _backgroundJobManager; |
||||
|
/// <summary>
|
||||
|
/// Reference to <see cref="INotificationStore"/>.
|
||||
|
/// </summary>
|
||||
private readonly INotificationStore _notificationStore; |
private readonly INotificationStore _notificationStore; |
||||
private readonly INotificationPublisher _notificationPublisher; |
/// <summary>
|
||||
|
/// Reference to <see cref="INotificationDefinitionManager"/>.
|
||||
|
/// </summary>
|
||||
|
private readonly INotificationDefinitionManager _notificationDefinitionManager; |
||||
|
/// <summary>
|
||||
|
/// Reference to <see cref="INotificationPublishProviderManager"/>.
|
||||
|
/// </summary>
|
||||
|
private readonly INotificationPublishProviderManager _notificationPublishProviderManager; |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="DefaultNotificationDispatcher"/> class.
|
||||
|
/// </summary>
|
||||
public DefaultNotificationDispatcher( |
public DefaultNotificationDispatcher( |
||||
|
IBackgroundJobManager backgroundJobManager, |
||||
|
|
||||
INotificationStore notificationStore, |
INotificationStore notificationStore, |
||||
INotificationPublisher notificationPublisher) |
INotificationDefinitionManager notificationDefinitionManager, |
||||
|
INotificationPublishProviderManager notificationPublishProviderManager) |
||||
{ |
{ |
||||
|
_backgroundJobManager = backgroundJobManager; |
||||
|
|
||||
_notificationStore = notificationStore; |
_notificationStore = notificationStore; |
||||
_notificationPublisher = notificationPublisher; |
_notificationDefinitionManager = notificationDefinitionManager; |
||||
|
_notificationPublishProviderManager = notificationPublishProviderManager; |
||||
|
|
||||
|
DistributedEventBus = NullDistributedEventBus.Instance; |
||||
|
Logger = NullLogger<DefaultNotificationDispatcher>.Instance; |
||||
} |
} |
||||
|
/// <summary>
|
||||
|
/// 发送通知
|
||||
|
/// </summary>
|
||||
|
/// <param name="notificationName">通知名称</param>
|
||||
|
/// <param name="data">通知数据</param>
|
||||
|
/// <param name="tenantId">租户</param>
|
||||
|
/// <param name="notificationSeverity">级别</param>
|
||||
|
/// <returns></returns>
|
||||
|
public virtual async Task DispatchAsync(NotificationName notificationName, NotificationData data, Guid? tenantId = null, |
||||
|
NotificationSeverity notificationSeverity = NotificationSeverity.Info) |
||||
|
{ |
||||
|
// 获取自定义的通知
|
||||
|
var defineNotification = _notificationDefinitionManager.Get(notificationName.CateGory); |
||||
|
|
||||
|
//// 没有定义的通知,应该也要能发布、订阅,
|
||||
|
//// 比如订单之类的,是以订单编号为通知名称,这是动态的,没法自定义
|
||||
|
//if(defineNotification == null)
|
||||
|
//{
|
||||
|
// defineNotification = new NotificationDefinition(notificationName.CateGory);
|
||||
|
//}
|
||||
|
|
||||
|
var notificationInfo = new NotificationInfo |
||||
|
{ |
||||
|
CateGory = notificationName.CateGory, |
||||
|
Name = notificationName.Name, |
||||
|
CreationTime = DateTime.Now, |
||||
|
NotificationSeverity = notificationSeverity, |
||||
|
Lifetime = defineNotification.NotificationLifetime, |
||||
|
NotificationType = defineNotification.NotificationType, |
||||
|
TenantId = tenantId, |
||||
|
Data = data |
||||
|
}; |
||||
|
|
||||
public async Task DispatcheAsync(NotificationInfo notification) |
var providers = Enumerable |
||||
|
.Reverse(_notificationPublishProviderManager.Providers); |
||||
|
|
||||
|
if (defineNotification.Providers.Any()) |
||||
|
{ |
||||
|
providers = providers.Where(p => defineNotification.Providers.Contains(p.Name)); |
||||
|
} |
||||
|
|
||||
|
await PublishFromProvidersAsync(providers, notificationInfo); |
||||
|
|
||||
|
if (notificationInfo.Lifetime == NotificationLifetime.OnlyOne) |
||||
|
{ |
||||
|
// 一次性通知在发送完成后就取消用户订阅
|
||||
|
await _notificationStore.DeleteAllUserSubscriptionAsync(notificationInfo.TenantId, |
||||
|
notificationInfo.Name); |
||||
|
} |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 发送通知事件
|
||||
|
/// </summary>
|
||||
|
/// <param name="notificationName"></param>
|
||||
|
/// <param name="data"></param>
|
||||
|
/// <param name="tenantId"></param>
|
||||
|
/// <param name="notificationSeverity"></param>
|
||||
|
/// <returns></returns>
|
||||
|
public virtual async Task DispatchEventAsync(NotificationName notificationName, NotificationData data, Guid? tenantId = null, |
||||
|
NotificationSeverity notificationSeverity = NotificationSeverity.Info) |
||||
{ |
{ |
||||
|
// 获取自定义的通知
|
||||
|
var defineNotification = _notificationDefinitionManager.Get(notificationName.CateGory); |
||||
|
|
||||
|
var notificationEventData = new NotificationEventData |
||||
|
{ |
||||
|
CateGory = notificationName.CateGory, |
||||
|
Name = notificationName.Name, |
||||
|
CreationTime = DateTime.Now, |
||||
|
NotificationSeverity = notificationSeverity, |
||||
|
Lifetime = defineNotification.NotificationLifetime, |
||||
|
NotificationType = defineNotification.NotificationType, |
||||
|
TenantId = tenantId, |
||||
|
Data = data |
||||
|
}; |
||||
|
// 发布分布式通知事件,让消息中心统一处理
|
||||
|
await DistributedEventBus.PublishAsync(notificationEventData); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 指定提供者发布通知
|
||||
|
/// </summary>
|
||||
|
/// <param name="providers">提供者列表</param>
|
||||
|
/// <param name="notificationInfo">通知信息</param>
|
||||
|
/// <returns></returns>
|
||||
|
protected async Task PublishFromProvidersAsync(IEnumerable<INotificationPublishProvider> providers, |
||||
|
NotificationInfo notificationInfo) |
||||
|
{ |
||||
|
Logger.LogDebug($"Persistent notification {notificationInfo.Name}"); |
||||
// 持久化通知
|
// 持久化通知
|
||||
await _notificationStore.InsertNotificationAsync(notification); |
await _notificationStore.InsertNotificationAsync(notificationInfo); |
||||
|
|
||||
|
Logger.LogDebug($"Gets a list of user subscriptions {notificationInfo.Name}"); |
||||
// 获取用户订阅列表
|
// 获取用户订阅列表
|
||||
var userSubscriptions = await _notificationStore.GetSubscriptionsAsync(notification.TenantId, notification.Name); |
var userSubscriptions = await _notificationStore.GetSubscriptionsAsync(notificationInfo.TenantId, notificationInfo.Name); |
||||
|
|
||||
|
Logger.LogDebug($"Persistent user notifications {notificationInfo.Name}"); |
||||
// 持久化用户通知
|
// 持久化用户通知
|
||||
var subscriptionUserIds = userSubscriptions.Select(us => us.UserId); |
var subscriptionUserIdentifiers = userSubscriptions.Select(us => new UserIdentifier(us.UserId, us.UserName)); |
||||
await _notificationStore.InsertUserNotificationsAsync(notification, subscriptionUserIds); |
|
||||
|
await _notificationStore.InsertUserNotificationsAsync(notificationInfo, |
||||
|
subscriptionUserIdentifiers.Select(u => u.UserId)); |
||||
|
|
||||
|
// 发布通知
|
||||
|
foreach (var provider in providers) |
||||
|
{ |
||||
|
await PublishAsync(provider, notificationInfo, subscriptionUserIdentifiers); |
||||
|
} |
||||
|
|
||||
|
// TODO: 需要计算队列大小,根据情况是否需要并行发布消息
|
||||
|
//Parallel.ForEach(providers, async (provider) =>
|
||||
|
//{
|
||||
|
// await PublishAsync(provider, notificationInfo, subscriptionUserIdentifiers);
|
||||
|
//});
|
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 发布通知
|
||||
|
/// </summary>
|
||||
|
/// <param name="provider">通知发布者</param>
|
||||
|
/// <param name="notificationInfo">通知信息</param>
|
||||
|
/// <param name="subscriptionUserIdentifiers">订阅用户列表</param>
|
||||
|
/// <returns></returns>
|
||||
|
protected async Task PublishAsync(INotificationPublishProvider provider, NotificationInfo notificationInfo, |
||||
|
IEnumerable<UserIdentifier> subscriptionUserIdentifiers) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
Logger.LogDebug($"Sending notification with provider {provider.Name}"); |
||||
|
|
||||
|
// 发布
|
||||
|
await provider.PublishAsync(notificationInfo, subscriptionUserIdentifiers); |
||||
|
|
||||
|
Logger.LogDebug($"Send notification {notificationInfo.Name} with provider {provider.Name} was successful"); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
Logger.LogWarning($"Send notification error with provider {provider.Name}"); |
||||
|
Logger.LogWarning($"Error message:{ex.Message}"); |
||||
|
|
||||
|
Logger.LogTrace(ex, $"Send notification error with provider { provider.Name}"); |
||||
|
|
||||
// 发布用户通知
|
Logger.LogDebug($"Send notification error, notification {notificationInfo.Name} entry queue"); |
||||
await _notificationPublisher.PublishAsync(notification, subscriptionUserIds); |
// 发送失败的消息进入后台队列
|
||||
|
await _backgroundJobManager.EnqueueAsync( |
||||
|
new NotificationPublishJobArgs(notificationInfo.GetId(), |
||||
|
provider.GetType().AssemblyQualifiedName, |
||||
|
subscriptionUserIdentifiers.ToList(), |
||||
|
notificationInfo.TenantId)); |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,70 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Internal |
||||
|
{ |
||||
|
internal class NotificationSubscriptionManager : INotificationSubscriptionManager, ITransientDependency |
||||
|
{ |
||||
|
private readonly INotificationStore _store; |
||||
|
|
||||
|
public NotificationSubscriptionManager( |
||||
|
INotificationStore store) |
||||
|
{ |
||||
|
_store = store; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<List<NotificationSubscriptionInfo>> GetSubscriptionsAsync(Guid? tenantId, string notificationName) |
||||
|
{ |
||||
|
return await _store.GetSubscriptionsAsync(tenantId, notificationName); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync(Guid? tenantId, Guid userId) |
||||
|
{ |
||||
|
return await _store.GetUserSubscriptionsAsync(tenantId, userId); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync(Guid? tenantId, string userName) |
||||
|
{ |
||||
|
return await _store.GetUserSubscriptionsAsync(tenantId, userName); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> IsSubscribedAsync(Guid? tenantId, Guid userId, string notificationName) |
||||
|
{ |
||||
|
return await _store.IsSubscribedAsync(tenantId, userId, notificationName); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task SubscribeAsync(Guid? tenantId, UserIdentifier identifier, string notificationName) |
||||
|
{ |
||||
|
if (await IsSubscribedAsync(tenantId, identifier.UserId, notificationName)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
await _store.InsertUserSubscriptionAsync(tenantId, identifier, notificationName); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task SubscribeAsync(Guid? tenantId, IEnumerable<UserIdentifier> identifiers, string notificationName) |
||||
|
{ |
||||
|
foreach(var identifier in identifiers) |
||||
|
{ |
||||
|
await SubscribeAsync(tenantId, identifier, notificationName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task UnsubscribeAsync(Guid? tenantId, UserIdentifier identifier, string notificationName) |
||||
|
{ |
||||
|
await _store.DeleteUserSubscriptionAsync(tenantId, identifier.UserId, notificationName); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task UnsubscribeAllAsync(Guid? tenantId, string notificationName) |
||||
|
{ |
||||
|
await _store.DeleteAllUserSubscriptionAsync(tenantId, notificationName); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task UnsubscribeAsync(Guid? tenantId, IEnumerable<UserIdentifier> identifiers, string notificationName) |
||||
|
{ |
||||
|
await _store.DeleteUserSubscriptionAsync(tenantId, identifiers, notificationName); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
/* |
||||
|
* 通知系统的设计不应该定死通知名称 |
||||
|
* 而是规范通知的一些属性,因此不应该是自定义通知名称,而是定义通知的类目,类似于Catalog |
||||
|
* 或者Prefix |
||||
|
* |
||||
|
*/ |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public class NotificationDefinition |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 通知类目
|
||||
|
/// </summary>
|
||||
|
[NotNull] |
||||
|
public string CateGory { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 通知显示名称
|
||||
|
/// </summary>
|
||||
|
[NotNull] |
||||
|
public ILocalizableString DisplayName |
||||
|
{ |
||||
|
get => _displayName; |
||||
|
set => _displayName = Check.NotNull(value, nameof(value)); |
||||
|
} |
||||
|
private ILocalizableString _displayName; |
||||
|
/// <summary>
|
||||
|
/// 通知说明
|
||||
|
/// </summary>
|
||||
|
[CanBeNull] |
||||
|
public ILocalizableString Description { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 允许客户端显示订阅
|
||||
|
/// </summary>
|
||||
|
public bool AllowSubscriptionToClients { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 存活类型
|
||||
|
/// </summary>
|
||||
|
public NotificationLifetime NotificationLifetime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 通知类型
|
||||
|
/// </summary>
|
||||
|
public NotificationType NotificationType { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 通知提供者
|
||||
|
/// </summary>
|
||||
|
public List<string> Providers { get; } |
||||
|
|
||||
|
public NotificationDefinition( |
||||
|
string category, |
||||
|
ILocalizableString displayName = null, |
||||
|
ILocalizableString description = null, |
||||
|
NotificationType notificationType = NotificationType.Application, |
||||
|
NotificationLifetime lifetime = NotificationLifetime.Persistent, |
||||
|
bool allowSubscriptionToClients = false) |
||||
|
{ |
||||
|
CateGory = category; |
||||
|
DisplayName = displayName ?? new FixedLocalizableString(category); |
||||
|
Description = description; |
||||
|
NotificationLifetime = lifetime; |
||||
|
NotificationType = notificationType; |
||||
|
AllowSubscriptionToClients = allowSubscriptionToClients; |
||||
|
|
||||
|
Providers = new List<string>(); |
||||
|
} |
||||
|
|
||||
|
public virtual NotificationDefinition WithProviders(params string[] providers) |
||||
|
{ |
||||
|
if (!providers.IsNullOrEmpty()) |
||||
|
{ |
||||
|
Providers.AddRange(providers); |
||||
|
} |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public class NotificationDefinitionContext : INotificationDefinitionContext |
||||
|
{ |
||||
|
protected Dictionary<string, NotificationDefinition> Notifications { get; } |
||||
|
|
||||
|
public NotificationDefinitionContext(Dictionary<string, NotificationDefinition> notifications) |
||||
|
{ |
||||
|
Notifications = notifications; |
||||
|
} |
||||
|
|
||||
|
public void Add(params NotificationDefinition[] definitions) |
||||
|
{ |
||||
|
if (definitions.IsNullOrEmpty()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
foreach (var definition in definitions) |
||||
|
{ |
||||
|
Notifications[definition.CateGory] = definition; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public NotificationDefinition GetOrNull(string category) |
||||
|
{ |
||||
|
return Notifications.GetOrDefault(category); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Linq; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public class NotificationDefinitionManager : INotificationDefinitionManager, ISingletonDependency |
||||
|
{ |
||||
|
protected Lazy<IDictionary<string, NotificationDefinition>> NotificationDefinitions { get; } |
||||
|
|
||||
|
protected AbpNotificationOptions Options { get; } |
||||
|
|
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public NotificationDefinitionManager( |
||||
|
IOptions<AbpNotificationOptions> options, |
||||
|
IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
ServiceProvider = serviceProvider; |
||||
|
Options = options.Value; |
||||
|
|
||||
|
NotificationDefinitions = new Lazy<IDictionary<string, NotificationDefinition>>(CreateNotificationDefinitions, true); |
||||
|
} |
||||
|
|
||||
|
public virtual NotificationDefinition Get([NotNull] string category) |
||||
|
{ |
||||
|
Check.NotNull(category, nameof(category)); |
||||
|
|
||||
|
var notification = GetOrNull(category); |
||||
|
|
||||
|
if (notification == null) |
||||
|
{ |
||||
|
throw new AbpException("Undefined notification category: " + category); |
||||
|
} |
||||
|
|
||||
|
return notification; |
||||
|
} |
||||
|
|
||||
|
public virtual IReadOnlyList<NotificationDefinition> GetAll() |
||||
|
{ |
||||
|
return NotificationDefinitions.Value.Values.ToImmutableList(); |
||||
|
} |
||||
|
|
||||
|
public virtual NotificationDefinition GetOrNull(string category) |
||||
|
{ |
||||
|
return NotificationDefinitions.Value.GetOrDefault(category); |
||||
|
} |
||||
|
|
||||
|
protected virtual IDictionary<string, NotificationDefinition> CreateNotificationDefinitions() |
||||
|
{ |
||||
|
var notifications = new Dictionary<string, NotificationDefinition>(); |
||||
|
|
||||
|
using (var scope = ServiceProvider.CreateScope()) |
||||
|
{ |
||||
|
var providers = Options |
||||
|
.DefinitionProviders |
||||
|
.Select(p => scope.ServiceProvider.GetRequiredService(p) as INotificationDefinitionProvider) |
||||
|
.ToList(); |
||||
|
|
||||
|
foreach (var provider in providers) |
||||
|
{ |
||||
|
provider.Define(new NotificationDefinitionContext(notifications)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return notifications; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public abstract class NotificationDefinitionProvider : INotificationDefinitionProvider, ITransientDependency |
||||
|
{ |
||||
|
public abstract void Define(INotificationDefinitionContext context); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public class NotificationEventData |
||||
|
{ |
||||
|
public Guid? TenantId { get; set; } |
||||
|
public string CateGory { get; set; } |
||||
|
public string Name { get; set; } |
||||
|
public string Id { get; set; } |
||||
|
public NotificationData Data { get; set; } |
||||
|
public DateTime CreationTime { get; set; } |
||||
|
public NotificationLifetime Lifetime { get; set; } |
||||
|
public NotificationType NotificationType { get; set; } |
||||
|
public NotificationSeverity NotificationSeverity { get; set; } |
||||
|
|
||||
|
public NotificationEventData() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public NotificationInfo ToNotificationInfo() |
||||
|
{ |
||||
|
return new NotificationInfo |
||||
|
{ |
||||
|
NotificationSeverity = NotificationSeverity, |
||||
|
CreationTime = CreationTime, |
||||
|
Data = Data, |
||||
|
Id = Id, |
||||
|
Name = Name, |
||||
|
CateGory = CateGory, |
||||
|
NotificationType = NotificationType, |
||||
|
Lifetime = Lifetime, |
||||
|
TenantId = TenantId |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 通知存活时间
|
||||
|
/// 发送之后取消用户订阅,类似于微信小程序
|
||||
|
/// </summary>
|
||||
|
public enum NotificationLifetime |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 持久化
|
||||
|
/// </summary>
|
||||
|
Persistent = 0, |
||||
|
/// <summary>
|
||||
|
/// 一次性
|
||||
|
/// </summary>
|
||||
|
OnlyOne = 1 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public class NotificationName |
||||
|
{ |
||||
|
public string CateGory { get; } |
||||
|
public string Name { get; } |
||||
|
|
||||
|
public NotificationName(string cateGory, string name) |
||||
|
{ |
||||
|
Name = name; |
||||
|
CateGory = cateGory; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public static class NotificationNameNormalizer |
||||
|
{ |
||||
|
public static NotificationName NormalizerName(string name) |
||||
|
{ |
||||
|
return new NotificationName(name, name); |
||||
|
} |
||||
|
public static NotificationName NormalizerName(string category, string name) |
||||
|
{ |
||||
|
var notifyName = string.Concat(category, ":", name); |
||||
|
return new NotificationName(category, notifyName); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public class NotificationPublishJob : AsyncBackgroundJob<NotificationPublishJobArgs>, ITransientDependency |
||||
|
{ |
||||
|
protected INotificationStore Store { get; } |
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public NotificationPublishJob( |
||||
|
INotificationStore store, |
||||
|
IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
Store = store; |
||||
|
ServiceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public override async Task ExecuteAsync(NotificationPublishJobArgs args) |
||||
|
{ |
||||
|
var providerType = Type.GetType(args.ProviderType); |
||||
|
|
||||
|
if (ServiceProvider.GetRequiredService(providerType) is INotificationPublishProvider publishProvider) |
||||
|
{ |
||||
|
var notification = await Store.GetNotificationOrNullAsync(args.TenantId, args.NotificationId); |
||||
|
|
||||
|
await publishProvider.PublishAsync(notification, args.UserIdentifiers); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public class NotificationPublishJobArgs |
||||
|
{ |
||||
|
public Guid? TenantId { get; set; } |
||||
|
public long NotificationId { get; set; } |
||||
|
public string ProviderType { get; set; } |
||||
|
public List<UserIdentifier> UserIdentifiers { get; set; } |
||||
|
public NotificationPublishJobArgs(long id, string providerType, List<UserIdentifier> userIdentifiers, Guid? tenantId = null ) |
||||
|
{ |
||||
|
NotificationId = id; |
||||
|
ProviderType = providerType; |
||||
|
UserIdentifiers = userIdentifiers; |
||||
|
TenantId = tenantId; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public abstract class NotificationPublishProvider : INotificationPublishProvider, ITransientDependency |
||||
|
{ |
||||
|
public abstract string Name { get; } |
||||
|
|
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
protected readonly object ServiceProviderLock = new object(); |
||||
|
|
||||
|
public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory); |
||||
|
private ILoggerFactory _loggerFactory; |
||||
|
|
||||
|
protected ILogger Logger => _lazyLogger.Value; |
||||
|
private Lazy<ILogger> _lazyLogger => new Lazy<ILogger>(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true); |
||||
|
|
||||
|
|
||||
|
protected TService LazyGetRequiredService<TService>(ref TService reference) |
||||
|
{ |
||||
|
if (reference == null) |
||||
|
{ |
||||
|
lock (ServiceProviderLock) |
||||
|
{ |
||||
|
if (reference == null) |
||||
|
{ |
||||
|
reference = ServiceProvider.GetRequiredService<TService>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return reference; |
||||
|
} |
||||
|
|
||||
|
protected NotificationPublishProvider(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
ServiceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public abstract Task PublishAsync(NotificationInfo notification, IEnumerable<UserIdentifier> identifiers); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public class NotificationPublishProviderManager : INotificationPublishProviderManager, ISingletonDependency |
||||
|
{ |
||||
|
public List<INotificationPublishProvider> Providers => _lazyProviders.Value; |
||||
|
|
||||
|
protected AbpNotificationOptions Options { get; } |
||||
|
|
||||
|
private readonly Lazy<List<INotificationPublishProvider>> _lazyProviders; |
||||
|
|
||||
|
public NotificationPublishProviderManager( |
||||
|
IServiceProvider serviceProvider, |
||||
|
IOptions<AbpNotificationOptions> options) |
||||
|
{ |
||||
|
Options = options.Value; |
||||
|
|
||||
|
_lazyProviders = new Lazy<List<INotificationPublishProvider>>( |
||||
|
() => Options |
||||
|
.PublishProviders |
||||
|
.Select(type => serviceProvider.GetRequiredService(type) as INotificationPublishProvider) |
||||
|
.ToList(), |
||||
|
true |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public class UserIdentifier |
||||
|
{ |
||||
|
public Guid UserId { get; set; } |
||||
|
public string UserName { get; set; } |
||||
|
|
||||
|
public UserIdentifier(Guid userId, string userName) |
||||
|
{ |
||||
|
UserId = userId; |
||||
|
UserName = userName; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue