51 changed files with 1154 additions and 1036 deletions
@ -0,0 +1,44 @@ |
|||||
|
namespace Lion.AbpPro.AspNetCore; |
||||
|
|
||||
|
public class AbpProAspNetCoreConsts |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 默认跨域策略名称
|
||||
|
/// </summary>
|
||||
|
public const string DefaultCorsPolicyName = "Default"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Cookies名称
|
||||
|
/// </summary>
|
||||
|
public const string DefaultCookieName = "Lion.AbpPro.Http.Api"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 网关配置节名称
|
||||
|
/// </summary>
|
||||
|
public const string Gateway = "Gateway"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 跨域配置节名称
|
||||
|
/// </summary>
|
||||
|
public const string Cors = "Cors"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// MiniProfiler配置节名称
|
||||
|
/// </summary>
|
||||
|
public const string MiniProfiler = "MiniProfiler"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 多租户配置节名称
|
||||
|
/// </summary>
|
||||
|
public const string MultiTenancy = "MultiTenancy"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Swagger配置节名称
|
||||
|
/// </summary>
|
||||
|
public const string Swagger = "Swagger"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 审计配置节名称
|
||||
|
/// </summary>
|
||||
|
public const string Audit = "Audit"; |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using Lion.AbpPro.AspNetCore.Options; |
||||
|
|
||||
|
namespace Lion.AbpPro.AspNetCore; |
||||
|
|
||||
|
public class AbpProAspNetCoreModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var s = context.Configuration.GetSection(AbpProAspNetCoreConsts.Cors); |
||||
|
context.Services.Configure<AbpProGatewayOptions>(context.Configuration.GetSection(AbpProAspNetCoreConsts.Gateway)); |
||||
|
context.Services.Configure<AbpProCorsOptions>(context.Configuration.GetSection(AbpProAspNetCoreConsts.Cors)); |
||||
|
context.Services.Configure<AbpProMiniProfilerOptions>(context.Configuration.GetSection(AbpProAspNetCoreConsts.MiniProfiler)); |
||||
|
context.Services.Configure<AbpProMultiTenancyOptions>(context.Configuration.GetSection(AbpProAspNetCoreConsts.MultiTenancy)); |
||||
|
context.Services.Configure<AbpProSwaggerOptions>(context.Configuration.GetSection(AbpProAspNetCoreConsts.Swagger)); |
||||
|
context.Services.Configure<AbpProAuditOptions>(context.Configuration.GetSection(AbpProAspNetCoreConsts.Audit)); |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
namespace Lion.AbpPro.AspNetCore.Options; |
||||
|
|
||||
|
public class AbpProAuditOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 是否启用
|
||||
|
/// </summary>
|
||||
|
public bool Enabled { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 应用名称
|
||||
|
/// </summary>
|
||||
|
public string ApplicationName { get; set; } |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
namespace Lion.AbpPro.AspNetCore.Options; |
||||
|
|
||||
|
public class AbpProCorsOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 是否启用
|
||||
|
/// </summary>
|
||||
|
public bool Enabled { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 跨越设置,多个用英文逗号隔开。
|
||||
|
/// </summary>
|
||||
|
public string CorsOrigins { get; set; } |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
namespace Lion.AbpPro.AspNetCore.Options; |
||||
|
|
||||
|
public class AbpProGatewayOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 是否启用
|
||||
|
/// </summary>
|
||||
|
public bool Enabled { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// consul服务地址
|
||||
|
/// </summary>
|
||||
|
public string ConsulServiceUrl { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// consul服务名称
|
||||
|
/// </summary>
|
||||
|
public string ServiceName { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 健康检查地址
|
||||
|
/// </summary>
|
||||
|
public string HealthUrl { get; set; } |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
namespace Lion.AbpPro.AspNetCore.Options; |
||||
|
|
||||
|
public class AbpProJwtOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// JWT令牌颁发者(Issuer)
|
||||
|
/// 用于标识令牌的颁发机构
|
||||
|
/// </summary>
|
||||
|
public string Issuer { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// JWT令牌受众(Audience)
|
||||
|
/// 指定令牌的目标接收方
|
||||
|
/// </summary>
|
||||
|
public string Audience { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// JWT安全密钥
|
||||
|
/// 用于签名和验证JWT令牌的密钥
|
||||
|
/// </summary>
|
||||
|
public string SecurityKey { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// JWT令牌过期时间(分钟)
|
||||
|
/// 令牌在颁发后多少分钟过期
|
||||
|
/// </summary>
|
||||
|
public int ExpirationTime { get; set; } |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
namespace Lion.AbpPro.AspNetCore.Options; |
||||
|
|
||||
|
public class AbpProMiniProfilerOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 是否启用
|
||||
|
/// </summary>
|
||||
|
public bool Enabled { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// path
|
||||
|
/// </summary>
|
||||
|
public string RouteBasePath { get; set; } = "/profiler"; |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace Lion.AbpPro.AspNetCore.Options; |
||||
|
|
||||
|
public class AbpProMultiTenancyOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 是否启用
|
||||
|
/// </summary>
|
||||
|
public bool Enabled { get; set; } |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace Lion.AbpPro.AspNetCore.Options; |
||||
|
|
||||
|
public class AbpProSwaggerOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 是否启用
|
||||
|
/// </summary>
|
||||
|
public bool Enabled { get; set; } |
||||
|
} |
||||
@ -0,0 +1,143 @@ |
|||||
|
using Consul; |
||||
|
using Lion.AbpPro.AspNetCore; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Volo.Abp.Timing; |
||||
|
using System.Threading.Tasks; |
||||
|
using Lion.AbpPro.AspNetCore.Options; |
||||
|
using Microsoft.AspNetCore.Localization; |
||||
|
using Swashbuckle.AspNetCore.SwaggerUI; |
||||
|
|
||||
|
namespace Microsoft.AspNetCore.Builder; |
||||
|
|
||||
|
public static class ApplicationBuilderExtensions |
||||
|
{ |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// consul服务
|
||||
|
/// </summary>
|
||||
|
public static IApplicationBuilder UseAbpProConsul(this IApplicationBuilder app) |
||||
|
{ |
||||
|
// 获取 AbpProGatewayOptions 配置
|
||||
|
var consulOptions = app.ApplicationServices.GetRequiredService<IOptions<AbpProGatewayOptions>>().Value; |
||||
|
if (!consulOptions.Enabled) |
||||
|
return app; |
||||
|
|
||||
|
var appLifetime = app.ApplicationServices.GetService<IHostApplicationLifetime>(); |
||||
|
using var scope = app.ApplicationServices.CreateScope(); |
||||
|
var clock = scope.ServiceProvider.GetService<IClock>(); |
||||
|
var appUrl = new Uri(consulOptions.ConsulServiceUrl, UriKind.Absolute); |
||||
|
|
||||
|
var client = scope.ServiceProvider.GetService<IConsulClient>(); |
||||
|
|
||||
|
var consulServiceRegistration = new AgentServiceRegistration |
||||
|
{ |
||||
|
Name = consulOptions.ServiceName, |
||||
|
ID = $"{consulOptions.ServiceName}:{clock.Now:yyyyMMddHHmmssfff}", |
||||
|
Address = appUrl.Host, |
||||
|
Port = appUrl.Port, |
||||
|
Check = new AgentServiceCheck |
||||
|
{ |
||||
|
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5), //服务停止多久后注销
|
||||
|
Interval = TimeSpan.FromSeconds(3), //健康检查时间间隔,或者称为心跳 间隔
|
||||
|
HTTP = consulOptions.HealthUrl, //健康检查地址
|
||||
|
Timeout = TimeSpan.FromSeconds(15) //超时时间
|
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
client.Agent.ServiceRegister(consulServiceRegistration); |
||||
|
appLifetime.ApplicationStopping.Register(() => { client.Agent.ServiceDeregister(consulServiceRegistration.ID); }); |
||||
|
return app; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Ocelot服务
|
||||
|
/// </summary>
|
||||
|
public static IApplicationBuilder UseAbpProOcelot(this IApplicationBuilder app) |
||||
|
{ |
||||
|
// 获取 AbpProGatewayOptions 配置
|
||||
|
var consulOptions = app.ApplicationServices.GetRequiredService<IOptions<AbpProGatewayOptions>>().Value; |
||||
|
if (!consulOptions.Enabled) |
||||
|
return app; |
||||
|
app.UseOcelot().Wait(); |
||||
|
return app; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 多语言中间件
|
||||
|
/// <remarks>浏览器传递的请求头:Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6而abp钟简体中文为:zh-Hans</remarks>
|
||||
|
/// </summary>
|
||||
|
public static IApplicationBuilder UseAbpProRequestLocalization(this IApplicationBuilder app) |
||||
|
{ |
||||
|
return app.UseAbpRequestLocalization(options => |
||||
|
{ |
||||
|
// 移除自带header解析器
|
||||
|
options.RequestCultureProviders.RemoveAll(provider => provider is AcceptLanguageHeaderRequestCultureProvider); |
||||
|
// 添加header解析器
|
||||
|
options.RequestCultureProviders.Add(new AbpProAcceptLanguageHeaderRequestCultureProvider()); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// SwaggerUI
|
||||
|
/// </summary>
|
||||
|
// ReSharper disable once InconsistentNaming
|
||||
|
public static IApplicationBuilder UseAbpProSwaggerUI(this IApplicationBuilder app, string endpoint, string name) |
||||
|
{ |
||||
|
var swaggerOptions = app.ApplicationServices.GetRequiredService<IOptions<AbpProSwaggerOptions>>().Value; |
||||
|
if (!swaggerOptions.Enabled) return app; |
||||
|
app.UseSwagger(); |
||||
|
app.UseAbpSwaggerUI(options => |
||||
|
{ |
||||
|
options.SwaggerEndpoint(endpoint, name); |
||||
|
options.DocExpansion(DocExpansion.None); |
||||
|
options.DefaultModelsExpandDepth(-1); |
||||
|
}); |
||||
|
return app; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// MiniProfiler
|
||||
|
/// </summary>
|
||||
|
public static IApplicationBuilder UseAbpProMiniProfiler(this IApplicationBuilder app) |
||||
|
{ |
||||
|
var miniProfilerOptions = app.ApplicationServices.GetRequiredService<IOptions<AbpProMiniProfilerOptions>>().Value; |
||||
|
if (!miniProfilerOptions.Enabled) return app; |
||||
|
app.UseMiniProfiler(); |
||||
|
return app; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// MultiTenancy
|
||||
|
/// </summary>
|
||||
|
public static IApplicationBuilder UseAbpProMultiTenancy(this IApplicationBuilder app) |
||||
|
{ |
||||
|
var abpProMultiTenancyOptions = app.ApplicationServices.GetRequiredService<IOptions<AbpProMultiTenancyOptions>>().Value; |
||||
|
if (!abpProMultiTenancyOptions.Enabled) return app; |
||||
|
app.UseMultiTenancy(); |
||||
|
return app; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 跨域设置
|
||||
|
/// </summary>
|
||||
|
public static IApplicationBuilder UseAbpProCors(this IApplicationBuilder app) |
||||
|
{ |
||||
|
var corsOptions = app.ApplicationServices.GetRequiredService<IOptions<AbpProCorsOptions>>().Value; |
||||
|
if (!corsOptions.Enabled) return app; |
||||
|
app.UseCors(AbpProAspNetCoreConsts.DefaultCorsPolicyName); |
||||
|
return app; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 审计日志
|
||||
|
/// </summary>
|
||||
|
public static IApplicationBuilder UseAbpProAuditing(this IApplicationBuilder app) |
||||
|
{ |
||||
|
var auditOptions = app.ApplicationServices.GetRequiredService<IOptions<AbpProAuditOptions>>().Value; |
||||
|
if (!auditOptions.Enabled) return app; |
||||
|
app.UseAuditing(); |
||||
|
return app; |
||||
|
} |
||||
|
} |
||||
@ -1,4 +1,6 @@ |
|||||
namespace Microsoft.AspNetCore.Localization; |
using Microsoft.Extensions.Primitives; |
||||
|
|
||||
|
namespace Microsoft.AspNetCore.Localization; |
||||
|
|
||||
public class AbpProAcceptLanguageHeaderRequestCultureProvider : AcceptLanguageHeaderRequestCultureProvider |
public class AbpProAcceptLanguageHeaderRequestCultureProvider : AcceptLanguageHeaderRequestCultureProvider |
||||
{ |
{ |
||||
@ -0,0 +1,35 @@ |
|||||
|
namespace Microsoft.AspNetCore.Mvc.Filters; |
||||
|
|
||||
|
public class AbpProResultFilter : IResultFilter, ITransientDependency |
||||
|
{ |
||||
|
public void OnResultExecuting(ResultExecutingContext context) |
||||
|
{ |
||||
|
// 如果是page 直接return
|
||||
|
if (context.ActionDescriptor.IsPageAction()) return; |
||||
|
|
||||
|
var controllerHasDontWrapResultAttribute = |
||||
|
context.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.GetCustomAttributes(typeof(WrapResultAttribute), true).Any(); |
||||
|
var controllerActionHasDontWrapResultAttribute = context.ActionDescriptor.GetMethodInfo().GetCustomAttributes(typeof(WrapResultAttribute), true).Any(); |
||||
|
if (!controllerHasDontWrapResultAttribute && !controllerActionHasDontWrapResultAttribute) return; |
||||
|
|
||||
|
context.HttpContext.Response.StatusCode = 200; |
||||
|
var result = new WrapResult<object>(); |
||||
|
if (context.Result is not EmptyResult) |
||||
|
{ |
||||
|
result.SetSuccess(((ObjectResult)context.Result).Value); |
||||
|
} |
||||
|
|
||||
|
var jsonSerializer = context.GetRequiredService<IJsonSerializer>(); |
||||
|
|
||||
|
context.Result = new ContentResult() |
||||
|
{ |
||||
|
StatusCode = (int)HttpStatusCode.OK, |
||||
|
ContentType = "application/json;charset=utf-8", |
||||
|
Content = jsonSerializer.Serialize(result) |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public void OnResultExecuted(ResultExecutedContext context) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,338 @@ |
|||||
|
using Consul; |
||||
|
using Lion.AbpPro.AspNetCore; |
||||
|
using Lion.AbpPro.AspNetCore.Options; |
||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer; |
||||
|
using Microsoft.AspNetCore.Mvc.Filters; |
||||
|
using Microsoft.IdentityModel.Tokens; |
||||
|
using Swagger; |
||||
|
using Volo.Abp.AspNetCore.Auditing; |
||||
|
using Volo.Abp.AspNetCore.MultiTenancy; |
||||
|
using Volo.Abp.AspNetCore.Mvc.AntiForgery; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace Microsoft.Extensions.DependencyInjection; |
||||
|
|
||||
|
public static class ServiceCollectionExtensions |
||||
|
{ |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Ocelot配置
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProOcelot(this IServiceCollection service) |
||||
|
{ |
||||
|
var configuration = service.GetConfiguration(); |
||||
|
service.AddOcelot(configuration).AddConsul().AddPolly(); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
public static IServiceCollection AddAbpProConsul(this IServiceCollection service) |
||||
|
{ |
||||
|
var consulOptions = service.GetRequiredService<IOptions<AbpProGatewayOptions>>().Value; |
||||
|
if (!consulOptions.Enabled) |
||||
|
return service; |
||||
|
|
||||
|
service.AddSingleton<IConsulClient>(p => new ConsulClient(config => { config.Address = new Uri(consulOptions.ConsulServiceUrl); })); |
||||
|
|
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 健康检查
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProHealthChecks(this IServiceCollection service) |
||||
|
{ |
||||
|
// TODO 检查数据库和redis是否正常 AspNetCore.HealthChecks.Redis AspNetCore.HealthChecks.MySql
|
||||
|
// context.Services.AddHealthChecks().AddRedis(redisConnectionString).AddMySql(connectString);
|
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 配置租户解析
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProTenantResolvers(this IServiceCollection service) |
||||
|
{ |
||||
|
service.Configure<AbpTenantResolveOptions>(options => |
||||
|
{ |
||||
|
options.TenantResolvers.Clear(); |
||||
|
// 只保留通过请求头解析租户
|
||||
|
// options.TenantResolvers.Add(new QueryStringTenantResolveContributor());
|
||||
|
// options.TenantResolvers.Add(new RouteTenantResolveContributor());
|
||||
|
options.TenantResolvers.Add(new HeaderTenantResolveContributor()); |
||||
|
// options.TenantResolvers.Add(new CookieTenantResolveContributor());
|
||||
|
}); |
||||
|
|
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 多语言配置
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProLocalization(this IServiceCollection service) |
||||
|
{ |
||||
|
service.Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Languages.Add(new LanguageInfo("ar", "ar", "العربية")); |
||||
|
options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština")); |
||||
|
options.Languages.Add(new LanguageInfo("en", "en", "English")); |
||||
|
options.Languages.Add(new LanguageInfo("en-GB", "en-GB", "English (UK)")); |
||||
|
options.Languages.Add(new LanguageInfo("fr", "fr", "Français")); |
||||
|
options.Languages.Add(new LanguageInfo("hu", "hu", "Magyar")); |
||||
|
options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português")); |
||||
|
options.Languages.Add(new LanguageInfo("ru", "ru", "Русский")); |
||||
|
options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe")); |
||||
|
options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); |
||||
|
options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁體中文")); |
||||
|
}); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 配置跨域
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProCors(this IServiceCollection service) |
||||
|
{ |
||||
|
var corsOptions = service.GetRequiredService<IOptions<AbpProCorsOptions>>().Value; |
||||
|
if (!corsOptions.Enabled) return service; |
||||
|
|
||||
|
service.AddCors(options => |
||||
|
{ |
||||
|
options.AddPolicy(AbpProAspNetCoreConsts.DefaultCorsPolicyName, builder => |
||||
|
{ |
||||
|
builder |
||||
|
.WithOrigins( |
||||
|
corsOptions.CorsOrigins |
||||
|
.Split(",", StringSplitOptions.RemoveEmptyEntries) |
||||
|
.Select(o => o.RemovePostFix("/")) |
||||
|
.ToArray() |
||||
|
) |
||||
|
//.WithAbpExposedHeaders()
|
||||
|
.SetIsOriginAllowedToAllowWildcardSubdomains() |
||||
|
.AllowAnyHeader() |
||||
|
.AllowAnyMethod() |
||||
|
//.AllowCredentials()
|
||||
|
// https://www.cnblogs.com/JulianHuang/p/14225515.html
|
||||
|
// https://learn.microsoft.com/zh-cn/aspnet/core/security/cors?view=aspnetcore-7.0
|
||||
|
.SetPreflightMaxAge((TimeSpan.FromHours(24))); |
||||
|
}); |
||||
|
}); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 阻止跨站点请求伪造
|
||||
|
/// https://docs.microsoft.com/zh-cn/aspnet/core/security/anti-request-forgery?view=aspnetcore-6.0
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProAntiForgery(this IServiceCollection service) |
||||
|
{ |
||||
|
service.Configure<AbpAntiForgeryOptions>(options => { options.AutoValidate = false; }); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 异常处理
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProExceptions(this IServiceCollection service) |
||||
|
{ |
||||
|
service.AddMvc(options => |
||||
|
{ |
||||
|
options.Filters.Add(typeof(AbpProExceptionFilter)); |
||||
|
options.Filters.Add(typeof(AbpProResultFilter)); |
||||
|
} |
||||
|
); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 添加swagger
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProSwagger(this IServiceCollection service, string name, string version = "v1") |
||||
|
{ |
||||
|
var swaggerOptions = service.GetRequiredService<IOptions<AbpProSwaggerOptions>>().Value; |
||||
|
if (!swaggerOptions.Enabled) return service; |
||||
|
|
||||
|
service.AddSwaggerGen(options => |
||||
|
{ |
||||
|
// 文件下载类型
|
||||
|
options.MapType<FileContentResult>(() => new OpenApiSchema() { Type = "file" }); |
||||
|
options.SwaggerDoc(name, new OpenApiInfo { Title = name, Version = version }); |
||||
|
options.DocInclusionPredicate((docName, description) => true); |
||||
|
options.EnableAnnotations(); // 启用注解
|
||||
|
options.DocumentFilter<HiddenAbpDefaultApiFilter>(); |
||||
|
options.SchemaFilter<EnumSchemaFilter>(); |
||||
|
// 加载所有xml注释,这里会导致swagger加载有点缓慢
|
||||
|
var xmlPaths = Directory.GetFiles(AppContext.BaseDirectory, "*.xml"); |
||||
|
foreach (var xml in xmlPaths) |
||||
|
{ |
||||
|
options.IncludeXmlComments(xml, true); |
||||
|
} |
||||
|
|
||||
|
options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, |
||||
|
new OpenApiSecurityScheme() |
||||
|
{ |
||||
|
Description = "直接在下框输入JWT生成的Token", |
||||
|
Name = "Authorization", |
||||
|
In = ParameterLocation.Header, |
||||
|
Type = SecuritySchemeType.Http, |
||||
|
Scheme = JwtBearerDefaults.AuthenticationScheme, |
||||
|
BearerFormat = "JWT" |
||||
|
}); |
||||
|
options.AddSecurityRequirement(new OpenApiSecurityRequirement |
||||
|
{ |
||||
|
{ |
||||
|
new OpenApiSecurityScheme |
||||
|
{ |
||||
|
Reference = new OpenApiReference |
||||
|
{ |
||||
|
Type = ReferenceType.SecurityScheme, Id = "Bearer" |
||||
|
} |
||||
|
}, |
||||
|
new List<string>() |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
options.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme() |
||||
|
{ |
||||
|
Type = SecuritySchemeType.ApiKey, |
||||
|
In = ParameterLocation.Header, |
||||
|
Name = "Accept-Language", |
||||
|
Description = "多语言设置,系统预设语言有zh-Hans、en,默认为zh-Hans", |
||||
|
}); |
||||
|
|
||||
|
options.AddSecurityRequirement(new OpenApiSecurityRequirement |
||||
|
{ |
||||
|
{ |
||||
|
new OpenApiSecurityScheme |
||||
|
{ |
||||
|
Reference = new OpenApiReference |
||||
|
{ Type = ReferenceType.SecurityScheme, Id = "ApiKey" } |
||||
|
}, |
||||
|
Array.Empty<string>() |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 配置MiniProfiler
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProMiniProfiler(this IServiceCollection service) |
||||
|
{ |
||||
|
var options = service.GetRequiredService<IOptions<AbpProMiniProfilerOptions>>().Value; |
||||
|
if (!options.Enabled) return service; |
||||
|
service.AddMiniProfiler(opt => opt.RouteBasePath = "/profiler").AddEntityFramework(); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 添加多租户支持
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProMultiTenancy(this IServiceCollection service) |
||||
|
{ |
||||
|
var multiTenancyOptions = service.GetRequiredService<IOptions<AbpProMultiTenancyOptions>>().Value; |
||||
|
service.Configure<AbpMultiTenancyOptions>(options => { options.IsEnabled = multiTenancyOptions.Enabled; }); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 配置JWT
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProAuthentication(this IServiceCollection service) |
||||
|
{ |
||||
|
var jwtOptions = service.GetRequiredService<IOptions<AbpProJwtOptions>>().Value; |
||||
|
|
||||
|
service.AddAuthentication(options => |
||||
|
{ |
||||
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; |
||||
|
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; |
||||
|
}) |
||||
|
.AddJwtBearer(options => |
||||
|
{ |
||||
|
options.TokenValidationParameters = |
||||
|
new TokenValidationParameters() |
||||
|
{ |
||||
|
// 是否开启签名认证
|
||||
|
ValidateIssuerSigningKey = true, |
||||
|
ValidateIssuer = true, |
||||
|
ValidateAudience = true, |
||||
|
ValidateLifetime = true, |
||||
|
ClockSkew = TimeSpan.Zero, |
||||
|
ValidIssuer = jwtOptions.Issuer, |
||||
|
ValidAudience = jwtOptions.Audience, |
||||
|
IssuerSigningKey = |
||||
|
new SymmetricSecurityKey( |
||||
|
Encoding.ASCII.GetBytes(jwtOptions.SecurityKey)) |
||||
|
}; |
||||
|
|
||||
|
options.Events = new JwtBearerEvents |
||||
|
{ |
||||
|
OnMessageReceived = currentContext => |
||||
|
{ |
||||
|
var path = currentContext.HttpContext.Request.Path; |
||||
|
if (path.StartsWithSegments("/login")) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
var accessToken = string.Empty; |
||||
|
if (currentContext.HttpContext.Request.Headers.ContainsKey("Authorization")) |
||||
|
{ |
||||
|
accessToken = currentContext.HttpContext.Request.Headers["Authorization"]; |
||||
|
if (!string.IsNullOrWhiteSpace(accessToken)) |
||||
|
{ |
||||
|
accessToken = accessToken.Split(" ").LastOrDefault(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (accessToken.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
accessToken = currentContext.Request.Query["access_token"].FirstOrDefault(); |
||||
|
} |
||||
|
|
||||
|
if (accessToken.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
accessToken = currentContext.Request.Cookies[AbpProAspNetCoreConsts.DefaultCookieName]; |
||||
|
} |
||||
|
|
||||
|
currentContext.Token = accessToken; |
||||
|
currentContext.Request.Headers.Remove("Authorization"); |
||||
|
currentContext.Request.Headers.Append("Authorization", $"Bearer {accessToken}"); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 审计日志
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProAuditLog(this IServiceCollection service) |
||||
|
{ |
||||
|
var auditOptions = service.GetRequiredService<IOptions<AbpProAuditOptions>>().Value; |
||||
|
service.Configure<AbpAuditingOptions> |
||||
|
(options => |
||||
|
{ |
||||
|
options.IsEnabled = auditOptions.Enabled; |
||||
|
options.EntityHistorySelectors.AddAllEntities(); |
||||
|
options.ApplicationName = auditOptions.ApplicationName; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
service.Configure<AbpAspNetCoreAuditingOptions>(options => |
||||
|
{ |
||||
|
options.IgnoredUrls.Add("/AuditLogs/page"); |
||||
|
options.IgnoredUrls.Add("/hangfire/stats"); |
||||
|
options.IgnoredUrls.Add("/hangfire/recurring/trigger"); |
||||
|
options.IgnoredUrls.Add("/cap"); |
||||
|
options.IgnoredUrls.Add("/"); |
||||
|
}); |
||||
|
return service; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,118 @@ |
|||||
|
namespace Serilog; |
||||
|
|
||||
|
public static class SerilogToEsExtensions |
||||
|
{ |
||||
|
public static void SetSerilogConfiguration(LoggerConfiguration loggerConfiguration, IConfiguration configuration) |
||||
|
{ |
||||
|
// 默认读取 configuration 中 "Serilog" 节点下的配置
|
||||
|
loggerConfiguration |
||||
|
.ReadFrom.Configuration(configuration) |
||||
|
.Enrich.FromLogContext(); |
||||
|
|
||||
|
var writeToElasticSearch = configuration.GetValue("ElasticSearch:Enabled", false); |
||||
|
|
||||
|
|
||||
|
// LogToElasticSearch:Enabled = true 才输出至ES
|
||||
|
if (!writeToElasticSearch) |
||||
|
return; |
||||
|
|
||||
|
var applicationName = "Lion.AbpPro.HttpApi.Host"; |
||||
|
|
||||
|
var esUrl = configuration["ElasticSearch:Url"]; |
||||
|
// 需要设置ES URL
|
||||
|
if (string.IsNullOrEmpty(esUrl)) |
||||
|
return; |
||||
|
|
||||
|
|
||||
|
var indexFormat = configuration["ElasticSearch:IndexFormat"]; |
||||
|
|
||||
|
// 需要设置ES URL
|
||||
|
if (string.IsNullOrEmpty(indexFormat)) |
||||
|
return; |
||||
|
|
||||
|
var esUserName = configuration["ElasticSearch:UserName"]; |
||||
|
var esPassword = configuration["ElasticSearch:Password"]; |
||||
|
|
||||
|
loggerConfiguration.Enrich.FromLogContext().Enrich.WithExceptionDetails().WriteTo |
||||
|
.Elasticsearch(BuildElasticSearchSinkOptions(esUrl, indexFormat, esUserName, esPassword)); |
||||
|
loggerConfiguration.Enrich.WithProperty("Application", applicationName); |
||||
|
} |
||||
|
|
||||
|
// 创建Es连接
|
||||
|
private static ElasticsearchSinkOptions BuildElasticSearchSinkOptions( |
||||
|
string url, |
||||
|
string indexFormat, |
||||
|
string userName, |
||||
|
string password) |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(userName)) |
||||
|
{ |
||||
|
return new ElasticsearchSinkOptions(new Uri(url)) |
||||
|
{ |
||||
|
AutoRegisterTemplate = true, |
||||
|
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7, |
||||
|
IndexFormat = indexFormat |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
return new ElasticsearchSinkOptions(new Uri(url)) |
||||
|
{ |
||||
|
AutoRegisterTemplate = true, |
||||
|
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7, |
||||
|
IndexFormat = indexFormat, |
||||
|
ModifyConnectionSettings = x => x.BasicAuthentication(userName, password) |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext) |
||||
|
{ |
||||
|
var request = httpContext.Request; |
||||
|
|
||||
|
// 为每个请求都设置通用的属性
|
||||
|
diagnosticContext.Set("Host", request.Host); |
||||
|
diagnosticContext.Set("Protocol", request.Protocol); |
||||
|
diagnosticContext.Set("Scheme", request.Scheme); |
||||
|
diagnosticContext.Set("RemoteIpAddress", httpContext.Connection.RemoteIpAddress); |
||||
|
// 如果要记录 Request Body 或 Response Body
|
||||
|
// 参考 https://stackoverflow.com/questions/60076922/serilog-logging-web-api-methods-adding-context-properties-inside-middleware
|
||||
|
string requestBody = ReadRequestBody(httpContext.Request).Result; |
||||
|
if (!string.IsNullOrEmpty(requestBody)) |
||||
|
{ |
||||
|
diagnosticContext.Set("RequestBody", requestBody); |
||||
|
} |
||||
|
|
||||
|
// string responseBody = ReadResponseBody(httpContext.Response).Result;
|
||||
|
// if (!string.IsNullOrEmpty(responseBody))
|
||||
|
// {
|
||||
|
// diagnosticContext.Set("ResponseBody", requestBody);
|
||||
|
// }
|
||||
|
|
||||
|
if (request.QueryString.HasValue) |
||||
|
{ |
||||
|
diagnosticContext.Set("QueryString", request?.QueryString.Value ?? string.Empty); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static async Task<string> ReadRequestBody(HttpRequest request) |
||||
|
{ |
||||
|
HttpRequestRewindExtensions.EnableBuffering(request); |
||||
|
|
||||
|
var body = request.Body; |
||||
|
var buffer = new byte[Convert.ToInt32(request.ContentLength)]; |
||||
|
await request.Body.ReadAsync(buffer, 0, buffer.Length); |
||||
|
string requestBody = Encoding.UTF8.GetString(buffer); |
||||
|
body.Seek(0, SeekOrigin.Begin); |
||||
|
request.Body = body; |
||||
|
|
||||
|
return $"{requestBody}"; |
||||
|
} |
||||
|
|
||||
|
private static async Task<string> ReadResponseBody(HttpResponse response) |
||||
|
{ |
||||
|
response.Body.Seek(0, SeekOrigin.Begin); |
||||
|
string responseBody = await new StreamReader(response.Body).ReadToEndAsync(); |
||||
|
response.Body.Seek(0, SeekOrigin.Begin); |
||||
|
|
||||
|
return $"{responseBody}"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
namespace Swagger; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 在使用nswag的时候,原生默认的api导致生产的代理类存在问题
|
||||
|
/// 所有隐藏原生的api,重写路由
|
||||
|
/// </summary>
|
||||
|
public class HiddenAbpDefaultApiFilter : IDocumentFilter |
||||
|
{ |
||||
|
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) |
||||
|
{ |
||||
|
foreach (ApiDescription apiDescription in context.ApiDescriptions) |
||||
|
{ |
||||
|
if (apiDescription.TryGetMethodInfo(out MethodInfo method)) |
||||
|
{ |
||||
|
string key = "/" + apiDescription.RelativePath; |
||||
|
var reuslt = IsHidden(key); |
||||
|
if (reuslt) swaggerDoc.Paths.Remove(key); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private bool IsHidden(string key) |
||||
|
{ |
||||
|
var list = GetHiddenAbpDefaultApiList(); |
||||
|
foreach (var item in list) |
||||
|
{ |
||||
|
if (key.Contains(item)) return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private List<string> GetHiddenAbpDefaultApiList() |
||||
|
{ |
||||
|
return new List<string>() { |
||||
|
"/api/abp/multi-tenancy/tenants", |
||||
|
"/api/account", |
||||
|
"/api/feature-management/features", |
||||
|
"/api/permission-management/permissions", |
||||
|
"/api/identity/my-profile", |
||||
|
"/api/identity", |
||||
|
"/api/multi-tenancy/tenants", |
||||
|
"/api/setting-management/emailing", |
||||
|
"/configuration", |
||||
|
"/outputcache" |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -1,293 +0,0 @@ |
|||||
using Medallion.Threading; |
|
||||
using Medallion.Threading.Redis; |
|
||||
using Volo.Abp.BlobStoring; |
|
||||
using Volo.Abp.BlobStoring.FileSystem; |
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
|
||||
|
|
||||
namespace Lion.AbpPro; |
|
||||
|
|
||||
public partial class AbpProHttpApiHostModule |
|
||||
{ |
|
||||
private void ConfigureHangfire(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
var redisStorageOptions = new RedisStorageOptions() |
|
||||
{ |
|
||||
Db = context.Services.GetConfiguration().GetValue<int>("Hangfire:Redis:DB") |
|
||||
}; |
|
||||
|
|
||||
Configure<AbpBackgroundJobOptions>(options => { options.IsJobExecutionEnabled = true; }); |
|
||||
|
|
||||
context.Services.AddHangfire(config => |
|
||||
{ |
|
||||
config.UseRedisStorage( |
|
||||
context.Services.GetConfiguration().GetValue<string>("Hangfire:Redis:Host"), redisStorageOptions) |
|
||||
.WithJobExpirationTimeout(TimeSpan.FromDays(7)); |
|
||||
var delaysInSeconds = new[] { 10, 60, 60 * 3 }; // 重试时间间隔
|
|
||||
const int Attempts = 3; // 重试次数
|
|
||||
config.UseFilter(new AutomaticRetryAttribute() { Attempts = Attempts, DelaysInSeconds = delaysInSeconds }); |
|
||||
//config.UseFilter(new AutoDeleteAfterSuccessAttribute(TimeSpan.FromDays(7)));
|
|
||||
config.UseFilter(new JobRetryLastFilter(Attempts)); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 配置MiniProfiler
|
|
||||
/// </summary>
|
|
||||
private void ConfigureMiniProfiler(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
if (context.Services.GetConfiguration().GetValue("MiniProfiler:Enabled", false)) |
|
||||
{ |
|
||||
context.Services.AddMiniProfiler(options => options.RouteBasePath = "/profiler").AddEntityFramework(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 配置JWT
|
|
||||
/// </summary>
|
|
||||
private void ConfigureJwtAuthentication(ServiceConfigurationContext context, IConfiguration configuration) |
|
||||
{ |
|
||||
context.Services.AddAuthentication(options => |
|
||||
{ |
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; |
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; |
|
||||
}) |
|
||||
.AddJwtBearer(options => |
|
||||
{ |
|
||||
options.TokenValidationParameters = |
|
||||
new TokenValidationParameters() |
|
||||
{ |
|
||||
// 是否开启签名认证
|
|
||||
ValidateIssuerSigningKey = true, |
|
||||
ValidateIssuer = true, |
|
||||
ValidateAudience = true, |
|
||||
ValidateLifetime = true, |
|
||||
ClockSkew = TimeSpan.Zero, |
|
||||
ValidIssuer = configuration["Jwt:Issuer"], |
|
||||
ValidAudience = configuration["Jwt:Audience"], |
|
||||
IssuerSigningKey = |
|
||||
new SymmetricSecurityKey( |
|
||||
Encoding.ASCII.GetBytes(configuration["Jwt:SecurityKey"])) |
|
||||
}; |
|
||||
|
|
||||
options.Events = new JwtBearerEvents |
|
||||
{ |
|
||||
OnMessageReceived = currentContext => |
|
||||
{ |
|
||||
var path = currentContext.HttpContext.Request.Path; |
|
||||
if (path.StartsWithSegments("/login")) |
|
||||
{ |
|
||||
return Task.CompletedTask; |
|
||||
} |
|
||||
|
|
||||
var accessToken = string.Empty; |
|
||||
if (currentContext.HttpContext.Request.Headers.ContainsKey("Authorization")) |
|
||||
{ |
|
||||
accessToken = currentContext.HttpContext.Request.Headers["Authorization"]; |
|
||||
if (!string.IsNullOrWhiteSpace(accessToken)) |
|
||||
{ |
|
||||
accessToken = accessToken.Split(" ").LastOrDefault(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (accessToken.IsNullOrWhiteSpace()) |
|
||||
{ |
|
||||
accessToken = currentContext.Request.Query["access_token"].FirstOrDefault(); |
|
||||
} |
|
||||
|
|
||||
if (accessToken.IsNullOrWhiteSpace()) |
|
||||
{ |
|
||||
accessToken = currentContext.Request.Cookies[AbpProHttpApiHostConst.DefaultCookieName]; |
|
||||
} |
|
||||
|
|
||||
currentContext.Token = accessToken; |
|
||||
currentContext.Request.Headers.Remove("Authorization"); |
|
||||
currentContext.Request.Headers.Append("Authorization", $"Bearer {accessToken}"); |
|
||||
|
|
||||
return Task.CompletedTask; |
|
||||
} |
|
||||
}; |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Redis缓存
|
|
||||
/// </summary>
|
|
||||
private void ConfigureCache(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
Configure<AbpDistributedCacheOptions>( |
|
||||
options => { options.KeyPrefix = "AbpPro:"; }); |
|
||||
var configuration = context.Services.GetConfiguration(); |
|
||||
var redis = ConnectionMultiplexer.Connect(configuration.GetValue<string>("Redis:Configuration")); |
|
||||
context.Services |
|
||||
.AddDataProtection() |
|
||||
.PersistKeysToStackExchangeRedis(redis, "AbpPro-Protection-Keys"); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 配置Identity
|
|
||||
/// </summary>
|
|
||||
private void ConfigureIdentity(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
context.Services.Configure<IdentityOptions>(options => { options.Lockout = new LockoutOptions() { AllowedForNewUsers = false }; }); |
|
||||
} |
|
||||
|
|
||||
private void ConfigurationSignalR(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
context.Services |
|
||||
.AddSignalR() |
|
||||
.AddStackExchangeRedis(context.Services.GetConfiguration().GetValue<string>("Redis:Configuration"), |
|
||||
options => { options.Configuration.ChannelPrefix = "Lion.AbpPro"; }); |
|
||||
} |
|
||||
|
|
||||
private void ConfigureSwaggerServices(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
context.Services.AddSwaggerGen( |
|
||||
options => |
|
||||
{ |
|
||||
// 文件下载类型
|
|
||||
options.MapType<FileContentResult>(() => new OpenApiSchema() { Type = "file" }); |
|
||||
|
|
||||
options.SwaggerDoc("AbpPro", |
|
||||
new OpenApiInfo { Title = "AbpPro API", Version = "v1" }); |
|
||||
options.DocInclusionPredicate((docName, description) => true); |
|
||||
options.EnableAnnotations(); // 启用注解
|
|
||||
options.DocumentFilter<HiddenAbpDefaultApiFilter>(); |
|
||||
options.SchemaFilter<EnumSchemaFilter>(); |
|
||||
// 加载所有xml注释,这里会导致swagger加载有点缓慢
|
|
||||
var xmlPaths = Directory.GetFiles(AppContext.BaseDirectory, "*.xml"); |
|
||||
foreach (var xml in xmlPaths) |
|
||||
{ |
|
||||
options.IncludeXmlComments(xml, true); |
|
||||
} |
|
||||
|
|
||||
options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, |
|
||||
new OpenApiSecurityScheme() |
|
||||
{ |
|
||||
Description = "直接在下框输入JWT生成的Token", |
|
||||
Name = "Authorization", |
|
||||
In = ParameterLocation.Header, |
|
||||
Type = SecuritySchemeType.Http, |
|
||||
Scheme = JwtBearerDefaults.AuthenticationScheme, |
|
||||
BearerFormat = "JWT" |
|
||||
}); |
|
||||
options.AddSecurityRequirement(new OpenApiSecurityRequirement |
|
||||
{ |
|
||||
{ |
|
||||
new OpenApiSecurityScheme |
|
||||
{ |
|
||||
Reference = new OpenApiReference |
|
||||
{ |
|
||||
Type = ReferenceType.SecurityScheme, Id = "Bearer" |
|
||||
} |
|
||||
}, |
|
||||
new List<string>() |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
options.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme() |
|
||||
{ |
|
||||
Type = SecuritySchemeType.ApiKey, |
|
||||
In = ParameterLocation.Header, |
|
||||
Name = "Accept-Language", |
|
||||
Description = "多语言设置,系统预设语言有zh-Hans、en,默认为zh-Hans", |
|
||||
}); |
|
||||
|
|
||||
options.AddSecurityRequirement(new OpenApiSecurityRequirement |
|
||||
{ |
|
||||
{ |
|
||||
new OpenApiSecurityScheme |
|
||||
{ |
|
||||
Reference = new OpenApiReference |
|
||||
{ Type = ReferenceType.SecurityScheme, Id = "ApiKey" } |
|
||||
}, |
|
||||
Array.Empty<string>() |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
private void ConfigureCap(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
var configuration = context.Services.GetConfiguration(); |
|
||||
context.AddAbpCap(capOptions => |
|
||||
{ |
|
||||
capOptions.SetCapDbConnectionString(configuration["ConnectionStrings:Default"]); |
|
||||
capOptions.UseEntityFramework<AbpProDbContext>(); |
|
||||
capOptions.UseRabbitMQ(option => |
|
||||
{ |
|
||||
option.HostName = configuration.GetValue<string>("Cap:RabbitMq:HostName"); |
|
||||
option.UserName = configuration.GetValue<string>("Cap:RabbitMq:UserName"); |
|
||||
option.Password = configuration.GetValue<string>("Cap:RabbitMq:Password"); |
|
||||
option.Port = configuration.GetValue<int>("Cap:RabbitMq:Port"); |
|
||||
}); |
|
||||
capOptions.UseDashboard(options => |
|
||||
{ |
|
||||
options.AuthorizationPolicy = AbpProCapPermissions.CapManagement.Cap; |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 审计日志
|
|
||||
/// </summary>
|
|
||||
private void ConfigureAuditLog(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
Configure<AbpAuditingOptions> |
|
||||
( |
|
||||
options => |
|
||||
{ |
|
||||
options.IsEnabled = true; |
|
||||
options.EntityHistorySelectors.AddAllEntities(); |
|
||||
options.ApplicationName = "Lion.AbpPro"; |
|
||||
} |
|
||||
); |
|
||||
|
|
||||
Configure<AbpAspNetCoreAuditingOptions>( |
|
||||
options => |
|
||||
{ |
|
||||
options.IgnoredUrls.Add("/AuditLogs/page"); |
|
||||
options.IgnoredUrls.Add("/hangfire/stats"); |
|
||||
options.IgnoredUrls.Add("/hangfire/recurring/trigger"); |
|
||||
options.IgnoredUrls.Add("/cap"); |
|
||||
options.IgnoredUrls.Add("/"); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private void ConfigurationMultiTenancy() |
|
||||
{ |
|
||||
Configure<AbpMultiTenancyOptions>(options => { options.IsEnabled = MultiTenancyConsts.IsEnabled; }); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 配置redis分布式锁
|
|
||||
/// </summary>
|
|
||||
private void ConfigurationDistributedLocking(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
var configuration = context.Services.GetConfiguration(); |
|
||||
var connectionString = configuration.GetValue<string>("Redis:Configuration"); |
|
||||
context.Services.AddSingleton<IDistributedLockProvider>(sp => |
|
||||
{ |
|
||||
var connection = ConnectionMultiplexer.Connect(connectionString); |
|
||||
return new RedisDistributedSynchronizationProvider(connection.GetDatabase()); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 配置blob设置
|
|
||||
/// </summary>
|
|
||||
private void ConfigureBlobStorage() |
|
||||
{ |
|
||||
Configure<AbpBlobStoringOptions>(options => |
|
||||
{ |
|
||||
options.Containers.ConfigureDefault(container => |
|
||||
{ |
|
||||
container.UseFileSystem(fileSystem => |
|
||||
{ |
|
||||
fileSystem.BasePath = "C:\\my-files"; |
|
||||
}); |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,113 @@ |
|||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
|
||||
|
namespace Microsoft.Extensions.DependencyInjection; |
||||
|
|
||||
|
public static class ServiceCollectionExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 注册Redis缓存
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProRedis(this IServiceCollection service) |
||||
|
{ |
||||
|
service.Configure<AbpDistributedCacheOptions>(options => { options.KeyPrefix = "AbpPro:"; }); |
||||
|
var configuration = service.GetConfiguration(); |
||||
|
var redis = ConnectionMultiplexer.Connect(configuration.GetValue<string>("Redis:Configuration")); |
||||
|
service |
||||
|
.AddDataProtection() |
||||
|
.PersistKeysToStackExchangeRedis(redis, "AbpPro-Protection-Keys"); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 注册redis分布式锁
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProRedisDistributedLocking(this IServiceCollection service) |
||||
|
{ |
||||
|
var configuration = service.GetConfiguration(); |
||||
|
var connectionString = configuration.GetValue<string>("Redis:Configuration"); |
||||
|
service.AddSingleton<IDistributedLockProvider>(sp => |
||||
|
{ |
||||
|
var connection = ConnectionMultiplexer.Connect(connectionString); |
||||
|
return new RedisDistributedSynchronizationProvider(connection.GetDatabase()); |
||||
|
}); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 注册Identity
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProIdentity(this IServiceCollection service) |
||||
|
{ |
||||
|
service.Configure<IdentityOptions>(options => { options.Lockout = new LockoutOptions() { AllowedForNewUsers = false }; }); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 注册SignalR
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProSignalR(this IServiceCollection service) |
||||
|
{ |
||||
|
service |
||||
|
.AddSignalR() |
||||
|
.AddStackExchangeRedis(service.GetConfiguration().GetValue<string>("Redis:Configuration"), |
||||
|
options => { options.Configuration.ChannelPrefix = "Lion.AbpPro"; }); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 注册blob设置
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProBlobStorage(this IServiceCollection service) |
||||
|
{ |
||||
|
service.Configure<AbpBlobStoringOptions>(options => { options.Containers.ConfigureDefault(container => { container.UseFileSystem(fileSystem => { fileSystem.BasePath = "C:\\my-files"; }); }); }); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 注册cap
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProCap(this IServiceCollection service) |
||||
|
{ |
||||
|
var configuration = service.GetConfiguration(); |
||||
|
service.AddAbpCap(capOptions => |
||||
|
{ |
||||
|
capOptions.SetCapDbConnectionString(configuration["ConnectionStrings:Default"]); |
||||
|
capOptions.UseEntityFramework<AbpProDbContext>(); |
||||
|
capOptions.UseRabbitMQ(option => |
||||
|
{ |
||||
|
option.HostName = configuration.GetValue<string>("Cap:RabbitMq:HostName"); |
||||
|
option.UserName = configuration.GetValue<string>("Cap:RabbitMq:UserName"); |
||||
|
option.Password = configuration.GetValue<string>("Cap:RabbitMq:Password"); |
||||
|
option.Port = configuration.GetValue<int>("Cap:RabbitMq:Port"); |
||||
|
}); |
||||
|
capOptions.UseDashboard(options => { options.AuthorizationPolicy = AbpProCapPermissions.CapManagement.Cap; }); |
||||
|
}); |
||||
|
return service; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 注册hangfire
|
||||
|
/// </summary>
|
||||
|
public static IServiceCollection AddAbpProHangfire(this IServiceCollection service) |
||||
|
{ |
||||
|
var redisStorageOptions = new RedisStorageOptions() |
||||
|
{ |
||||
|
Db = service.GetConfiguration().GetValue<int>("Hangfire:Redis:DB") |
||||
|
}; |
||||
|
|
||||
|
service.Configure<AbpBackgroundJobOptions>(options => { options.IsJobExecutionEnabled = true; }); |
||||
|
|
||||
|
service.AddHangfire(config => |
||||
|
{ |
||||
|
config.UseRedisStorage( |
||||
|
service.GetConfiguration().GetValue<string>("Hangfire:Redis:Host"), redisStorageOptions) |
||||
|
.WithJobExpirationTimeout(TimeSpan.FromDays(7)); |
||||
|
var delaysInSeconds = new[] { 10, 60, 60 * 3 }; // 重试时间间隔
|
||||
|
const int attempts = 3; // 重试次数
|
||||
|
config.UseFilter(new AutomaticRetryAttribute() { Attempts = 3, DelaysInSeconds = delaysInSeconds }); |
||||
|
//config.UseFilter(new AutoDeleteAfterSuccessAttribute(TimeSpan.FromDays(7)));
|
||||
|
config.UseFilter(new JobRetryLastFilter(attempts)); |
||||
|
}); |
||||
|
return service; |
||||
|
} |
||||
|
} |
||||
@ -1,9 +0,0 @@ |
|||||
// Global using directives
|
|
||||
|
|
||||
global using Microsoft.Extensions.DependencyInjection; |
|
||||
global using Ocelot.DependencyInjection; |
|
||||
global using Ocelot.Provider.Consul; |
|
||||
global using Ocelot.Provider.Polly; |
|
||||
global using Volo.Abp.Autofac; |
|
||||
global using Volo.Abp.Modularity; |
|
||||
global using Volo.Abp.Swashbuckle; |
|
||||
@ -1,21 +0,0 @@ |
|||||
<Project Sdk="Microsoft.NET.Sdk"> |
|
||||
|
|
||||
<PropertyGroup> |
|
||||
<TargetFramework>net9.0</TargetFramework> |
|
||||
<RootNamespace /> |
|
||||
</PropertyGroup> |
|
||||
<ItemGroup> |
|
||||
<PackageReference Include="Ocelot" /> |
|
||||
<PackageReference Include="Ocelot.Provider.Consul" /> |
|
||||
<PackageReference Include="Ocelot.Provider.Polly" /> |
|
||||
<PackageReference Include="Volo.Abp" /> |
|
||||
<PackageReference Include="Volo.Abp.AspNetCore" /> |
|
||||
<PackageReference Include="Volo.Abp.Autofac" /> |
|
||||
<PackageReference Include="Volo.Abp.Core" /> |
|
||||
<PackageReference Include="Volo.Abp.Swashbuckle" /> |
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" /> |
|
||||
</ItemGroup> |
|
||||
<ItemGroup> |
|
||||
<Content Remove="$(UserProfile)\.nuget\packages\*\*\contentFiles\any\*\*.abppkg*.json" /> |
|
||||
</ItemGroup> |
|
||||
</Project> |
|
||||
@ -1,32 +0,0 @@ |
|||||
namespace Lion.AbpPro |
|
||||
{ |
|
||||
[DependsOn( |
|
||||
typeof(AbpSwashbuckleModule), |
|
||||
typeof(AbpAutofacModule))] |
|
||||
public class AbpProSharedHostingGatewayModule : AbpModule |
|
||||
{ |
|
||||
|
|
||||
public override void ConfigureServices(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
ConfigureOcelot(context); |
|
||||
ConfigureHealthChecks(context); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Ocelot配置
|
|
||||
/// </summary>
|
|
||||
private static void ConfigureOcelot(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
var configuration = context.Services.GetConfiguration(); |
|
||||
context.Services.AddOcelot(configuration).AddConsul().AddPolly(); |
|
||||
} |
|
||||
/// <summary>
|
|
||||
/// 健康检查
|
|
||||
/// </summary>
|
|
||||
/// <param name="context"></param>
|
|
||||
private void ConfigureHealthChecks(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
context.Services.AddHealthChecks(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,140 +0,0 @@ |
|||||
using Microsoft.AspNetCore.DataProtection; |
|
||||
using StackExchange.Redis; |
|
||||
using Volo.Abp.AspNetCore.MultiTenancy; |
|
||||
using Volo.Abp.Caching; |
|
||||
using Volo.Abp.MultiTenancy; |
|
||||
using Volo.Abp.Settings; |
|
||||
|
|
||||
namespace Lion.AbpPro; |
|
||||
|
|
||||
[DependsOn( |
|
||||
typeof(AbpSwashbuckleModule), |
|
||||
typeof(AbpAutofacModule), |
|
||||
typeof(AbpProCoreModule))] |
|
||||
public class AbpProSharedHostingMicroserviceModule : AbpModule |
|
||||
{ |
|
||||
private const string DefaultCorsPolicyName = "Default"; |
|
||||
|
|
||||
public override void ConfigureServices(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
var configuration = context.Services.GetConfiguration(); |
|
||||
context.Services.AddConsulConfig(configuration); |
|
||||
ConfigureHealthChecks(context); |
|
||||
ConfigureLocalization(); |
|
||||
ConfigureCors(context); |
|
||||
ConfigureConsul(context, configuration); |
|
||||
ConfigAntiForgery(); |
|
||||
ConfigureAbpExceptions(context); |
|
||||
ConfigureTenantResolvers(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 异常处理
|
|
||||
/// </summary>
|
|
||||
private void ConfigureAbpExceptions(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
context.Services.AddMvc |
|
||||
( |
|
||||
options => |
|
||||
{ |
|
||||
options.Filters.Add(typeof(AbpProExceptionFilter)); |
|
||||
options.Filters.Add(typeof(AbpProResultFilter)); |
|
||||
} |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 阻止跨站点请求伪造
|
|
||||
/// https://docs.microsoft.com/zh-cn/aspnet/core/security/anti-request-forgery?view=aspnetcore-6.0
|
|
||||
/// </summary>
|
|
||||
private void ConfigAntiForgery() |
|
||||
{ |
|
||||
Configure<AbpAntiForgeryOptions>(options => { options.AutoValidate = false; }); |
|
||||
} |
|
||||
|
|
||||
private void ConfigureConsul(ServiceConfigurationContext context, IConfiguration configuration) |
|
||||
{ |
|
||||
if (configuration.GetValue<bool>("Consul:Enabled", false)) |
|
||||
{ |
|
||||
context.Services.AddConsulConfig(configuration); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 配置跨域
|
|
||||
/// </summary>
|
|
||||
private void ConfigureCors(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
var configuration = context.Services.GetConfiguration(); |
|
||||
context.Services.AddCors(options => |
|
||||
{ |
|
||||
options.AddPolicy(DefaultCorsPolicyName, builder => |
|
||||
{ |
|
||||
builder |
|
||||
.WithOrigins( |
|
||||
configuration["App:CorsOrigins"] |
|
||||
.Split(",", StringSplitOptions.RemoveEmptyEntries) |
|
||||
.Select(o => o.RemovePostFix("/")) |
|
||||
.ToArray() |
|
||||
) |
|
||||
//.WithAbpExposedHeaders()
|
|
||||
.SetIsOriginAllowedToAllowWildcardSubdomains() |
|
||||
.AllowAnyHeader() |
|
||||
.AllowAnyMethod() |
|
||||
//.AllowCredentials()
|
|
||||
// https://www.cnblogs.com/JulianHuang/p/14225515.html
|
|
||||
// https://learn.microsoft.com/zh-cn/aspnet/core/security/cors?view=aspnetcore-7.0
|
|
||||
.SetPreflightMaxAge((TimeSpan.FromHours(24))); |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 多语言配置
|
|
||||
/// </summary>
|
|
||||
private void ConfigureLocalization() |
|
||||
{ |
|
||||
Configure<AbpLocalizationOptions>(options => |
|
||||
{ |
|
||||
options.Languages.Add(new LanguageInfo("ar", "ar", "العربية")); |
|
||||
options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština")); |
|
||||
options.Languages.Add(new LanguageInfo("en", "en", "English")); |
|
||||
options.Languages.Add(new LanguageInfo("en-GB", "en-GB", "English (UK)")); |
|
||||
options.Languages.Add(new LanguageInfo("fr", "fr", "Français")); |
|
||||
options.Languages.Add(new LanguageInfo("hu", "hu", "Magyar")); |
|
||||
options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português")); |
|
||||
options.Languages.Add(new LanguageInfo("ru", "ru", "Русский")); |
|
||||
options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe")); |
|
||||
options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); |
|
||||
options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁體中文")); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 健康检查
|
|
||||
/// </summary>
|
|
||||
private void ConfigureHealthChecks(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
// TODO 检查数据库和redis是否正常 AspNetCore.HealthChecks.Redis AspNetCore.HealthChecks.MySql
|
|
||||
// context.Services.AddHealthChecks().AddRedis(redisConnectionString).AddMySql(connectString);
|
|
||||
context.Services.AddHealthChecks(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 配置租户解析
|
|
||||
/// </summary>
|
|
||||
private void ConfigureTenantResolvers() |
|
||||
{ |
|
||||
Configure<AbpTenantResolveOptions>(options => |
|
||||
{ |
|
||||
options.TenantResolvers.Clear(); |
|
||||
// 只保留通过请求头解析租户
|
|
||||
// options.TenantResolvers.Add(new QueryStringTenantResolveContributor());
|
|
||||
// options.TenantResolvers.Add(new RouteTenantResolveContributor());
|
|
||||
options.TenantResolvers.Add(new HeaderTenantResolveContributor()); |
|
||||
// options.TenantResolvers.Add(new CookieTenantResolveContributor());
|
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
@ -1,79 +0,0 @@ |
|||||
using Microsoft.AspNetCore.RequestLog; |
|
||||
|
|
||||
namespace Microsoft.AspNetCore.Builder |
|
||||
{ |
|
||||
public static class ApplicationBuilderExtensions |
|
||||
{ |
|
||||
public static string UseConsul(this IApplicationBuilder app) |
|
||||
{ |
|
||||
var appLifetime = app.ApplicationServices.GetService<IHostApplicationLifetime>(); |
|
||||
using var scope = app.ApplicationServices.CreateScope(); |
|
||||
var configuration = scope.ServiceProvider.GetService<IConfiguration>(); |
|
||||
|
|
||||
bool isEnabled = configuration.GetValue<bool>("Consul:Enabled"); |
|
||||
string serviceName = configuration.GetValue<string>("Consul:Service"); |
|
||||
var appString = configuration.GetValue<string>("App:SelfUrl"); |
|
||||
Uri appUrl = new Uri(appString, UriKind.Absolute); |
|
||||
|
|
||||
if (!isEnabled) |
|
||||
return String.Empty; |
|
||||
|
|
||||
Guid serviceId = Guid.NewGuid(); |
|
||||
string consulServiceId = $"{serviceName}:{serviceId}"; |
|
||||
|
|
||||
var client = scope.ServiceProvider.GetService<IConsulClient>(); |
|
||||
|
|
||||
var consulServiceRegistration = new AgentServiceRegistration |
|
||||
{ |
|
||||
Name = serviceName, |
|
||||
ID = consulServiceId, |
|
||||
Address = appUrl.Host, |
|
||||
Port = appUrl.Port, |
|
||||
Check = new AgentServiceCheck |
|
||||
{ |
|
||||
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5), //服务停止多久后注销
|
|
||||
Interval = TimeSpan.FromSeconds(3), //健康检查时间间隔,或者称为心跳 间隔
|
|
||||
HTTP = $"http://{appUrl.Host}:{appUrl.Port}/health", //健康检查地址
|
|
||||
Timeout = TimeSpan.FromSeconds(15) //超时时间
|
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
client.Agent.ServiceRegister(consulServiceRegistration); |
|
||||
appLifetime.ApplicationStopping.Register(() => { client.Agent.ServiceDeregister(consulServiceRegistration.ID); }); |
|
||||
|
|
||||
return consulServiceId; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 记录请求响应日志
|
|
||||
/// </summary>
|
|
||||
/// <returns></returns>
|
|
||||
public static IApplicationBuilder UseRequestLog(this IApplicationBuilder app) |
|
||||
{ |
|
||||
return app.UseMiddleware<RequestLogMiddleware>(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 多语言中间件
|
|
||||
/// <remarks>浏览器传递的请求头:Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6而abp钟简体中文为:zh-Hans</remarks>
|
|
||||
/// <example>
|
|
||||
/// app.UseAbpProRequestLocalization();
|
|
||||
/// </example>
|
|
||||
/// </summary>
|
|
||||
public static IApplicationBuilder UseAbpProRequestLocalization(this IApplicationBuilder app) |
|
||||
{ |
|
||||
if (app == null) |
|
||||
{ |
|
||||
throw new ArgumentNullException(nameof(app)); |
|
||||
} |
|
||||
|
|
||||
return app.UseAbpRequestLocalization(options => |
|
||||
{ |
|
||||
// 移除自带header解析器
|
|
||||
options.RequestCultureProviders.RemoveAll(provider=> provider is AcceptLanguageHeaderRequestCultureProvider); |
|
||||
// 添加header解析器
|
|
||||
options.RequestCultureProviders.Add(new AbpProAcceptLanguageHeaderRequestCultureProvider()); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,38 +0,0 @@ |
|||||
using System.Text.Json; |
|
||||
|
|
||||
namespace Microsoft.AspNetCore.Mvc.Filters; |
|
||||
|
|
||||
public class AbpProResultFilter : IResultFilter, ITransientDependency |
|
||||
{ |
|
||||
public void OnResultExecuting(ResultExecutingContext context) |
|
||||
{ |
|
||||
// 如果是page 直接return
|
|
||||
if (context.ActionDescriptor.IsPageAction()) return; |
|
||||
|
|
||||
var controllerHasDontWrapResultAttribute = |
|
||||
context.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.GetCustomAttributes(typeof(WrapResultAttribute), true).Any(); |
|
||||
var controllerActionHasDontWrapResultAttribute = context.ActionDescriptor.GetMethodInfo().GetCustomAttributes(typeof(WrapResultAttribute), true).Any(); |
|
||||
if (controllerHasDontWrapResultAttribute || controllerActionHasDontWrapResultAttribute) |
|
||||
{ |
|
||||
context.HttpContext.Response.StatusCode = 200; |
|
||||
var result = new WrapResult<object>(); |
|
||||
if (context.Result is not EmptyResult) |
|
||||
{ |
|
||||
result.SetSuccess(((ObjectResult)context.Result).Value); |
|
||||
} |
|
||||
|
|
||||
var jsonSerializer = context.GetService<IJsonSerializer>(); |
|
||||
|
|
||||
context.Result = new ContentResult() |
|
||||
{ |
|
||||
StatusCode = (int)HttpStatusCode.OK, |
|
||||
ContentType = "application/json;charset=utf-8", |
|
||||
Content = jsonSerializer.Serialize(result) |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void OnResultExecuted(ResultExecutedContext context) |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
@ -1,77 +0,0 @@ |
|||||
namespace Microsoft.AspNetCore.RequestLog; |
|
||||
|
|
||||
public class RequestLogMiddleware |
|
||||
{ |
|
||||
private readonly RequestDelegate _next; |
|
||||
private readonly ILogger<RequestLogMiddleware> _logger; |
|
||||
|
|
||||
public RequestLogMiddleware(RequestDelegate next, |
|
||||
ILogger<RequestLogMiddleware> logger) |
|
||||
{ |
|
||||
_next = next; |
|
||||
_logger = logger; |
|
||||
} |
|
||||
|
|
||||
public async Task InvokeAsync(HttpContext context) |
|
||||
{ |
|
||||
context.Request.EnableBuffering(); |
|
||||
var originalBody = context.Response.Body; |
|
||||
if (context.Request.Path.ToString().ToLower().Contains("swagger") |
|
||||
|| context.Request.Path.ToString().ToLower().Contains("login") |
|
||||
|| context.Request.Path.ToString().ToLower().Contains("monitor") |
|
||||
|| context.Request.Path.ToString().ToLower().Contains("cap") |
|
||||
|| context.Request.Path.ToString().ToLower().Contains("hangfire") |
|
||||
|| context.Request.Path.ToString() == "/" |
|
||||
) |
|
||||
{ |
|
||||
await _next(context); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
var logRequestId = Guid.NewGuid().ToString(); |
|
||||
await RequestDataLog(context, logRequestId); |
|
||||
using (var ms = new MemoryStream()) |
|
||||
{ |
|
||||
context.Response.Body = ms; |
|
||||
await _next(context); |
|
||||
ResponseDataLog(ms, logRequestId); |
|
||||
ms.Position = 0; |
|
||||
await ms.CopyToAsync(originalBody); |
|
||||
} |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
// 记录异常
|
|
||||
_logger.LogError(ex.Message + "" + ex.InnerException); |
|
||||
} |
|
||||
finally |
|
||||
{ |
|
||||
context.Response.Body = originalBody; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private async Task RequestDataLog(HttpContext context, |
|
||||
string requestId) |
|
||||
{ |
|
||||
var request = context.Request; |
|
||||
var body = new StreamReader(request.Body); |
|
||||
var requestData = $" 请求路径:{request.Path}\r\n 请求Body参数:{await body.ReadToEndAsync()}"; |
|
||||
_logger.LogInformation($"日志中间件[Request],LogRequestId:{requestId}:请求接口信息:{requestData}"); |
|
||||
request.Body.Position = 0; |
|
||||
} |
|
||||
|
|
||||
private void ResponseDataLog(MemoryStream ms, string requestId) |
|
||||
{ |
|
||||
ms.Position = 0; |
|
||||
var responseBody = new StreamReader(ms).ReadToEnd(); |
|
||||
// 去除 Html
|
|
||||
var isHtml = Regex.IsMatch(responseBody, "<[^>]+>"); |
|
||||
if (!isHtml && !string.IsNullOrEmpty(responseBody)) |
|
||||
{ |
|
||||
_logger.LogInformation($"日志中间件[Response],LogRequestId:{requestId}:响应接口信息:{responseBody}"); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,15 +0,0 @@ |
|||||
namespace Microsoft.Extensions.DependencyInjection |
|
||||
{ |
|
||||
public static class ServiceCollectionExtensions |
|
||||
{ |
|
||||
public static IServiceCollection AddConsulConfig(this IServiceCollection services, IConfiguration configuration) |
|
||||
{ |
|
||||
services.AddSingleton<IConsulClient>(p => new ConsulClient(consulConfig => |
|
||||
{ |
|
||||
var address = configuration.GetValue<string>("Consul:Host"); |
|
||||
consulConfig.Address = new Uri(address); |
|
||||
})); |
|
||||
return services; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,122 +0,0 @@ |
|||||
using System.Diagnostics.CodeAnalysis; |
|
||||
|
|
||||
namespace Serilog |
|
||||
{ |
|
||||
[SuppressMessage("Reliability", "CA2022:避免使用 \"Stream.Read\" 进行不准确读取")] |
|
||||
public static class SerilogToEsExtensions |
|
||||
{ |
|
||||
public static void SetSerilogConfiguration(LoggerConfiguration loggerConfiguration, IConfiguration configuration) |
|
||||
{ |
|
||||
// 默认读取 configuration 中 "Serilog" 节点下的配置
|
|
||||
loggerConfiguration |
|
||||
.ReadFrom.Configuration(configuration) |
|
||||
.Enrich.FromLogContext(); |
|
||||
|
|
||||
var writeToElasticSearch = configuration.GetValue("ElasticSearch:Enabled", false); |
|
||||
|
|
||||
|
|
||||
// LogToElasticSearch:Enabled = true 才输出至ES
|
|
||||
if (!writeToElasticSearch) |
|
||||
return; |
|
||||
|
|
||||
var applicationName = "Lion.AbpPro.HttpApi.Host"; |
|
||||
|
|
||||
var esUrl = configuration["ElasticSearch:Url"]; |
|
||||
// 需要设置ES URL
|
|
||||
if (string.IsNullOrEmpty(esUrl)) |
|
||||
return; |
|
||||
|
|
||||
|
|
||||
var indexFormat = configuration["ElasticSearch:IndexFormat"]; |
|
||||
|
|
||||
// 需要设置ES URL
|
|
||||
if (string.IsNullOrEmpty(indexFormat)) |
|
||||
return; |
|
||||
|
|
||||
var esUserName = configuration["ElasticSearch:UserName"]; |
|
||||
var esPassword = configuration["ElasticSearch:Password"]; |
|
||||
|
|
||||
loggerConfiguration.Enrich.FromLogContext().Enrich.WithExceptionDetails().WriteTo |
|
||||
.Elasticsearch(BuildElasticSearchSinkOptions(esUrl, indexFormat, esUserName, esPassword)); |
|
||||
loggerConfiguration.Enrich.WithProperty("Application", applicationName); |
|
||||
} |
|
||||
|
|
||||
// 创建Es连接
|
|
||||
private static ElasticsearchSinkOptions BuildElasticSearchSinkOptions( |
|
||||
string url, |
|
||||
string indexFormat, |
|
||||
string userName, |
|
||||
string password) |
|
||||
{ |
|
||||
if (string.IsNullOrEmpty(userName)) |
|
||||
{ |
|
||||
return new ElasticsearchSinkOptions(new Uri(url)) |
|
||||
{ |
|
||||
AutoRegisterTemplate = true, |
|
||||
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7, |
|
||||
IndexFormat = indexFormat |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
return new ElasticsearchSinkOptions(new Uri(url)) |
|
||||
{ |
|
||||
AutoRegisterTemplate = true, |
|
||||
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7, |
|
||||
IndexFormat = indexFormat, |
|
||||
ModifyConnectionSettings = x => x.BasicAuthentication(userName, password) |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext) |
|
||||
{ |
|
||||
var request = httpContext.Request; |
|
||||
|
|
||||
// 为每个请求都设置通用的属性
|
|
||||
diagnosticContext.Set("Host", request.Host); |
|
||||
diagnosticContext.Set("Protocol", request.Protocol); |
|
||||
diagnosticContext.Set("Scheme", request.Scheme); |
|
||||
diagnosticContext.Set("RemoteIpAddress", httpContext.Connection.RemoteIpAddress); |
|
||||
// 如果要记录 Request Body 或 Response Body
|
|
||||
// 参考 https://stackoverflow.com/questions/60076922/serilog-logging-web-api-methods-adding-context-properties-inside-middleware
|
|
||||
string requestBody = ReadRequestBody(httpContext.Request).Result; |
|
||||
if (!string.IsNullOrEmpty(requestBody)) |
|
||||
{ |
|
||||
diagnosticContext.Set("RequestBody", requestBody); |
|
||||
} |
|
||||
|
|
||||
// string responseBody = ReadResponseBody(httpContext.Response).Result;
|
|
||||
// if (!string.IsNullOrEmpty(responseBody))
|
|
||||
// {
|
|
||||
// diagnosticContext.Set("ResponseBody", requestBody);
|
|
||||
// }
|
|
||||
|
|
||||
if (request.QueryString.HasValue) |
|
||||
{ |
|
||||
diagnosticContext.Set("QueryString", request.QueryString.Value); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static async Task<string> ReadRequestBody(HttpRequest request) |
|
||||
{ |
|
||||
HttpRequestRewindExtensions.EnableBuffering(request); |
|
||||
|
|
||||
var body = request.Body; |
|
||||
var buffer = new byte[Convert.ToInt32(request.ContentLength)]; |
|
||||
await request.Body.ReadAsync(buffer, 0, buffer.Length); |
|
||||
string requestBody = Encoding.UTF8.GetString(buffer); |
|
||||
body.Seek(0, SeekOrigin.Begin); |
|
||||
request.Body = body; |
|
||||
|
|
||||
return $"{requestBody}"; |
|
||||
} |
|
||||
|
|
||||
private static async Task<string> ReadResponseBody(HttpResponse response) |
|
||||
{ |
|
||||
response.Body.Seek(0, SeekOrigin.Begin); |
|
||||
string responseBody = await new StreamReader(response.Body).ReadToEndAsync(); |
|
||||
response.Body.Seek(0, SeekOrigin.Begin); |
|
||||
|
|
||||
return $"{responseBody}"; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,49 +0,0 @@ |
|||||
namespace Swagger |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 在使用nswag的时候,原生默认的api导致生产的代理类存在问题
|
|
||||
/// 所有隐藏原生的api,重写路由
|
|
||||
/// </summary>
|
|
||||
public class HiddenAbpDefaultApiFilter : IDocumentFilter |
|
||||
{ |
|
||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) |
|
||||
{ |
|
||||
foreach (ApiDescription apiDescription in context.ApiDescriptions) |
|
||||
{ |
|
||||
if (apiDescription.TryGetMethodInfo(out MethodInfo method)) |
|
||||
{ |
|
||||
string key = "/" + apiDescription.RelativePath; |
|
||||
var reuslt = IsHidden(key); |
|
||||
if (reuslt) swaggerDoc.Paths.Remove(key); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private bool IsHidden(string key) |
|
||||
{ |
|
||||
var list = GetHiddenAbpDefaultApiList(); |
|
||||
foreach (var item in list) |
|
||||
{ |
|
||||
if (key.Contains(item)) return true; |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
private List<string> GetHiddenAbpDefaultApiList() |
|
||||
{ |
|
||||
return new List<string>() { |
|
||||
"/api/abp/multi-tenancy/tenants", |
|
||||
"/api/account", |
|
||||
"/api/feature-management/features", |
|
||||
"/api/permission-management/permissions", |
|
||||
"/api/identity/my-profile", |
|
||||
"/api/identity", |
|
||||
"/api/multi-tenancy/tenants", |
|
||||
"/api/setting-management/emailing", |
|
||||
"/configuration", |
|
||||
"/outputcache" |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,64 +0,0 @@ |
|||||
namespace Volo.Abp |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 修改Abp 返回状态码
|
|
||||
/// 原因: 抛出BusinessException异常 不应该抛403异常
|
|
||||
/// </summary>
|
|
||||
public class DefaultHttpExceptionStatusCodeFinder : IHttpExceptionStatusCodeFinder, ITransientDependency |
|
||||
{ |
|
||||
protected AbpExceptionHttpStatusCodeOptions Options { get; } |
|
||||
|
|
||||
public DefaultHttpExceptionStatusCodeFinder( |
|
||||
IOptions<AbpExceptionHttpStatusCodeOptions> options) |
|
||||
{ |
|
||||
Options = options.Value; |
|
||||
} |
|
||||
|
|
||||
public HttpStatusCode GetStatusCode(HttpContext httpContext, Exception exception) |
|
||||
{ |
|
||||
if (exception is IHasHttpStatusCode exceptionWithHttpStatusCode && |
|
||||
exceptionWithHttpStatusCode.HttpStatusCode > 0) |
|
||||
{ |
|
||||
return (HttpStatusCode)exceptionWithHttpStatusCode.HttpStatusCode; |
|
||||
} |
|
||||
|
|
||||
if (exception is IHasErrorCode exceptionWithErrorCode && |
|
||||
!exceptionWithErrorCode.Code.IsNullOrWhiteSpace()) |
|
||||
{ |
|
||||
if (Options.ErrorCodeToHttpStatusCodeMappings.TryGetValue(exceptionWithErrorCode.Code, out var status)) |
|
||||
{ |
|
||||
return status; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (exception is AbpAuthorizationException) |
|
||||
{ |
|
||||
return HttpStatusCode.Forbidden; |
|
||||
} |
|
||||
|
|
||||
//TODO: Handle SecurityException..?
|
|
||||
|
|
||||
if (exception is AbpValidationException) |
|
||||
{ |
|
||||
return HttpStatusCode.BadRequest; |
|
||||
} |
|
||||
|
|
||||
if (exception is EntityNotFoundException) |
|
||||
{ |
|
||||
return HttpStatusCode.NotFound; |
|
||||
} |
|
||||
|
|
||||
if (exception is NotImplementedException) |
|
||||
{ |
|
||||
return HttpStatusCode.NotImplemented; |
|
||||
} |
|
||||
|
|
||||
if (exception is IBusinessException) |
|
||||
{ |
|
||||
return HttpStatusCode.InternalServerError; |
|
||||
} |
|
||||
|
|
||||
return HttpStatusCode.InternalServerError; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue