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 Microsoft.Extensions.DependencyInjection; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.Json; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Notifications |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpBackgroundJobsModule), |
|||
typeof(AbpJsonModule))] |
|||
public class AbpNotificationModule : AbpModule |
|||
{ |
|||
|
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
AutoAddDefinitionProviders(context.Services); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
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 |
|||
{ |
|||
/// <summary>
|
|||
/// 通知发送者接口
|
|||
/// </summary>
|
|||
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 Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
|
|||
namespace LINGYUN.Abp.Notifications.Internal |
|||
{ |
|||
/// <summary>
|
|||
/// Implements <see cref="INotificationDispatcher"/>.
|
|||
/// </summary>
|
|||
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 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( |
|||
IBackgroundJobManager backgroundJobManager, |
|||
|
|||
INotificationStore notificationStore, |
|||
INotificationPublisher notificationPublisher) |
|||
INotificationDefinitionManager notificationDefinitionManager, |
|||
INotificationPublishProviderManager notificationPublishProviderManager) |
|||
{ |
|||
_backgroundJobManager = backgroundJobManager; |
|||
|
|||
_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); |
|||
await _notificationStore.InsertUserNotificationsAsync(notification, subscriptionUserIds); |
|||
var subscriptionUserIdentifiers = userSubscriptions.Select(us => new UserIdentifier(us.UserId, us.UserName)); |
|||
|
|||
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}"); |
|||
|
|||
// 发布用户通知
|
|||
await _notificationPublisher.PublishAsync(notification, subscriptionUserIds); |
|||
Logger.LogDebug($"Send notification error, notification {notificationInfo.Name} entry queue"); |
|||
// 发送失败的消息进入后台队列
|
|||
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