using AspNetCoreRateLimit;
using Autofac;
using IdentityServer4.AccessTokenValidation;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyModel;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Reflection;
using System.Text;
using Mapster;
using Yitter.IdGenerator;
using FluentValidation;
using FluentValidation.AspNetCore;
using ZhonTai.Admin.Core.Auth;
using ZhonTai.Admin.Tools.Cache;
using ZhonTai.Common.Helpers;
using ZhonTai.Admin.Core.Db;
using ZhonTai.Admin.Core.Extensions;
using ZhonTai.Admin.Core.Filters;
using ZhonTai.Admin.Core.Logs;
using ZhonTai.Admin.Core.RegisterModules;
using System.IO;
using Microsoft.OpenApi.Any;
using Microsoft.AspNetCore.Mvc.Controllers;
using ZhonTai.Admin.Core.Attributes;
using ZhonTai.Admin.Core.Configs;
using ZhonTai.Admin.Core.Consts;
using MapsterMapper;
using ZhonTai.DynamicApi;
using NLog.Web;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc;
using ZhonTai.Admin.Core.Startup;
using ZhonTai.Admin.Core.Conventions;
using FreeSql;
using ZhonTai.Admin.Services.User;
using ZhonTai.Admin.Core.Middlewares;
using ZhonTai.Admin.Core.Dto;
using ZhonTai.DynamicApi.Attributes;
using System.Text.RegularExpressions;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Text.Json.Serialization;
using FreeRedis;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Caching.Distributed;
namespace ZhonTai.Admin.Core;
///
/// 宿主应用
///
public class HostApp
{
readonly HostAppOptions _hostAppOptions;
public HostApp()
{
}
public HostApp(HostAppOptions hostAppOptions)
{
_hostAppOptions = hostAppOptions;
}
///
/// 运行应用
///
///
public void Run(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
//使用NLog日志
builder.Host.UseNLog();
var services = builder.Services;
var env = builder.Environment;
var configuration = builder.Configuration;
var configHelper = new ConfigHelper();
var appConfig = ConfigHelper.Get("appconfig", env.EnvironmentName) ?? new AppConfig();
//添加配置
builder.Configuration.AddJsonFile("./Configs/ratelimitconfig.json", optional: true, reloadOnChange: true);
if (env.EnvironmentName.NotNull())
{
builder.Configuration.AddJsonFile($"./Configs/ratelimitconfig.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
}
builder.Configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
if (env.EnvironmentName.NotNull())
{
builder.Configuration.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
}
var oSSConfigRoot = ConfigHelper.Load("ossconfig", env.EnvironmentName, true);
services.Configure(oSSConfigRoot);
//应用配置
services.AddSingleton(appConfig);
var hostAppContext = new HostAppContext()
{
Services = services,
Environment = env,
Configuration = configuration
};
//使用Autofac容器
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
//配置Autofac容器
builder.Host.ConfigureContainer(builder =>
{
// 控制器注入
builder.RegisterModule(new ControllerModule());
// 单例注入
builder.RegisterModule(new SingleInstanceModule(appConfig));
// 模块注入
builder.RegisterModule(new RegisterModule(appConfig));
_hostAppOptions?.ConfigureAutofacContainer?.Invoke(builder, hostAppContext);
});
//配置Kestrel服务器
builder.WebHost.ConfigureKestrel((context, options) =>
{
//设置应用服务器Kestrel请求体最大为100MB
options.Limits.MaxRequestBodySize = appConfig.MaxRequestBodySize;
});
//访问地址
builder.WebHost.UseUrls(appConfig.Urls);
//配置服务
ConfigureServices(services, env, configuration, configHelper, appConfig);
var app = builder.Build();
//配置中间件
ConfigureMiddleware(app, env, configuration, appConfig);
app.Run();
}
///
/// 实体类型重命名
///
///
///
private string DefaultSchemaIdSelector(Type modelType)
{
if (!modelType.IsConstructedGenericType) return modelType.Name.Replace("[]", "Array");
var prefix = modelType.GetGenericArguments()
.Select(DefaultSchemaIdSelector)
.Aggregate((previous, current) => previous + current);
return modelType.Name.Split('`').First() + prefix;
}
///
/// 配置服务
///
///
///
///
///
///
private void ConfigureServices(IServiceCollection services, IWebHostEnvironment env, IConfiguration configuration, ConfigHelper configHelper, AppConfig appConfig)
{
var hostAppContext = new HostAppContext()
{
Services = services,
Environment = env,
Configuration = configuration
};
_hostAppOptions?.ConfigurePreServices?.Invoke(hostAppContext);
//健康检查
services.AddHealthChecks();
//雪花漂移算法
var idGeneratorOptions = new IdGeneratorOptions(1) { WorkerIdBitLength = 6 };
_hostAppOptions?.ConfigureIdGenerator?.Invoke(idGeneratorOptions);
YitIdHelper.SetIdGenerator(idGeneratorOptions);
//权限处理
services.AddScoped();
// ClaimType不被更改
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
//用户信息
services.AddSingleton();
services.TryAddScoped();
//数据库配置
var dbConfig = ConfigHelper.Get("dbconfig", env.EnvironmentName);
services.AddSingleton(dbConfig);
//添加数据库
if (!_hostAppOptions.CustomInitDb)
{
services.AddDb(env, _hostAppOptions);
}
//上传配置
var uploadConfig = ConfigHelper.Load("uploadconfig", env.EnvironmentName, true);
services.Configure(uploadConfig);
//程序集
Assembly[] assemblies = null;
if(appConfig.AssemblyNames?.Length > 0)
{
assemblies = DependencyContext.Default.RuntimeLibraries
.Where(a => appConfig.AssemblyNames.Contains(a.Name))
.Select(o => Assembly.Load(new AssemblyName(o.Name))).ToArray();
}
#region Mapster 映射配置
services.AddScoped(sp => new Mapper());
if(assemblies?.Length > 0)
{
TypeAdapterConfig.GlobalSettings.Scan(assemblies);
}
#endregion Mapster 映射配置
#region Cors 跨域
services.AddCors(options =>
{
options.AddPolicy(AdminConsts.RequestPolicyName, policy =>
{
var hasOrigins = appConfig.CorUrls?.Length > 0;
if (hasOrigins)
{
policy.WithOrigins(appConfig.CorUrls);
}
else
{
policy.AllowAnyOrigin();
}
policy
.AllowAnyHeader()
.AllowAnyMethod();
if (hasOrigins)
{
policy.AllowCredentials();
}
});
//允许任何源访问Api策略,使用时在控制器或者接口上增加特性[EnableCors(AdminConsts.AllowAnyPolicyName)]
options.AddPolicy(AdminConsts.AllowAnyPolicyName, policy =>
{
policy
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
#endregion Cors 跨域
#region 身份认证授权
var jwtConfig = ConfigHelper.Get("jwtconfig", env.EnvironmentName);
services.TryAddSingleton(jwtConfig);
services.AddAuthentication(options =>
{
options.DefaultScheme = appConfig.IdentityServer.Enable ? IdentityServerAuthenticationDefaults.AuthenticationScheme : JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = nameof(ResponseAuthenticationHandler); //401
options.DefaultForbidScheme = nameof(ResponseAuthenticationHandler); //403
})
.AddJwtBearer(options =>
{
//ids4
if (appConfig.IdentityServer.Enable)
{
options.Authority = appConfig.IdentityServer.Url;
options.RequireHttpsMetadata = appConfig.IdentityServer.RequireHttpsMetadata;
options.Audience = appConfig.IdentityServer.Audience;
}
else
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtConfig.Issuer,
ValidAudience = jwtConfig.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecurityKey)),
ClockSkew = TimeSpan.Zero
};
}
})
.AddScheme(nameof(ResponseAuthenticationHandler), o => { });
#endregion 身份认证授权
#region Swagger Api文档
if (env.IsDevelopment() || appConfig.Swagger.Enable)
{
services.AddSwaggerGen(options =>
{
appConfig.Swagger.Projects?.ForEach(project =>
{
options.SwaggerDoc(project.Code.ToLower(), new OpenApiInfo
{
Title = project.Name,
Version = project.Version,
Description = project.Description
});
});
options.CustomOperationIds(apiDesc =>
{
var controllerAction = apiDesc.ActionDescriptor as ControllerActionDescriptor;
var api = controllerAction.AttributeRouteInfo.Template;
api = Regex.Replace(api, @"[\{\\\/\}]", "-") + "-" + apiDesc.HttpMethod.ToLower();
return api.Replace("--", "-");
});
options.ResolveConflictingActions(apiDescription => apiDescription.First());
options.CustomSchemaIds(modelType => DefaultSchemaIdSelector(modelType));
//支持多分组
options.DocInclusionPredicate((docName, apiDescription) =>
{
var nonGroup = false;
var groupNames = new List();
var dynamicApiAttribute = apiDescription.ActionDescriptor.EndpointMetadata.FirstOrDefault(x => x is DynamicApiAttribute);
if (dynamicApiAttribute != null)
{
var dynamicApi = dynamicApiAttribute as DynamicApiAttribute;
if(dynamicApi.GroupNames?.Length > 0)
{
groupNames.AddRange(dynamicApi.GroupNames);
}
}
var apiGroupAttribute = apiDescription.ActionDescriptor.EndpointMetadata.FirstOrDefault(x => x is ApiGroupAttribute);
if (apiGroupAttribute != null)
{
var apiGroup = apiGroupAttribute as ApiGroupAttribute;
if (apiGroup.GroupNames?.Length > 0)
{
groupNames.AddRange(apiGroup.GroupNames);
}
nonGroup = apiGroup.NonGroup;
}
return docName == apiDescription.GroupName || groupNames.Any(a => a == docName) || nonGroup;
});
string[] xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml");
if (xmlFiles.Length > 0)
{
foreach (var xmlFile in xmlFiles)
{
options.IncludeXmlComments(xmlFile, true);
}
}
var server = new OpenApiServer()
{
Url = appConfig.Swagger.Url,
Description = ""
};
if (appConfig.ApiUI.Footer.Enable)
{
server.Extensions.Add("extensions", new OpenApiObject
{
["copyright"] = new OpenApiString(appConfig.ApiUI.Footer.Content)
});
}
options.AddServer(server);
if(appConfig.Swagger.EnableEnumSchemaFilter)
{
options.SchemaFilter();
}
if(appConfig.Swagger.EnableOrderTagsDocumentFilter)
{
options.DocumentFilter();
}
options.OrderActionsBy(apiDesc =>
{
var order = 0;
var objOrderAttribute = apiDesc.CustomAttributes().FirstOrDefault(x => x is OrderAttribute);
if (objOrderAttribute != null)
{
var orderAttribute = objOrderAttribute as OrderAttribute;
order = orderAttribute.Value;
}
return (int.MaxValue - order).ToString().PadLeft(int.MaxValue.ToString().Length, '0');
});
#region 添加设置Token的按钮
if (appConfig.IdentityServer.Enable)
{
//添加Jwt验证设置
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = "oauth2",
Type = ReferenceType.SecurityScheme
}
},
new List()
}
});
//统一认证
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Description = "oauth2登录授权",
Flows = new OpenApiOAuthFlows
{
Implicit = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri($"{appConfig.IdentityServer.Url}/connect/authorize", UriKind.Absolute),
TokenUrl = new Uri($"{appConfig.IdentityServer.Url}/connect/token", UriKind.Absolute),
Scopes = new Dictionary
{
{ "admin.server.api", "admin后端api" }
}
}
}
});
}
else
{
//添加Jwt验证设置
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = "Bearer",
Type = ReferenceType.SecurityScheme
}
},
new List()
}
});
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "Value: Bearer {token}",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
});
}
#endregion 添加设置Token的按钮
});
}
#endregion Swagger Api文档
#region 操作日志
if (appConfig.Log.Operation)
{
services.AddScoped();
}
#endregion 操作日志
#region 控制器
void mvcConfigure(MvcOptions options)
{
//options.Filters.Add();
options.Filters.Add();
if (appConfig.Validate.Login || appConfig.Validate.Permission)
{
options.Filters.Add();
}
//在具有较高的 Order 值的筛选器之前运行 before 代码
//在具有较高的 Order 值的筛选器之后运行 after 代码
if (appConfig.DynamicApi.FormatResult)
{
options.Filters.Add(20);
}
if (appConfig.Log.Operation)
{
options.Filters.Add(10);
}
//禁止去除ActionAsync后缀
//options.SuppressAsyncSuffixInActionNames = false;
if (env.IsDevelopment() || appConfig.Swagger.Enable)
{
//API分组约定
options.Conventions.Add(new ApiGroupConvention());
}
}
var mvcBuilder = appConfig.AppType switch
{
AppType.Controllers => services.AddControllers(mvcConfigure),
AppType.ControllersWithViews => services.AddControllersWithViews(mvcConfigure),
AppType.MVC => services.AddMvc(mvcConfigure),
_ => services.AddControllers(mvcConfigure)
};
if (assemblies?.Length > 0)
{
foreach (var assembly in assemblies)
{
services.AddValidatorsFromAssembly(assembly);
}
}
services.AddFluentValidationAutoValidation();
mvcBuilder.AddNewtonsoftJson(options =>
{
//忽略循环引用
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
//使用驼峰 首字母小写
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
//设置时间格式
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
})
.AddControllersAsServices();
if (appConfig.Swagger.EnableJsonStringEnumConverter)
mvcBuilder.AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
_hostAppOptions?.ConfigureMvcBuilder?.Invoke(mvcBuilder, hostAppContext);
#endregion 控制器
services.AddHttpClient();
_hostAppOptions?.ConfigureServices?.Invoke(hostAppContext);
#region 缓存
var cacheConfig = ConfigHelper.Get("cacheconfig", env.EnvironmentName);
if (cacheConfig.Type == CacheType.Redis)
{
var redis = new RedisClient(cacheConfig.Redis.ConnectionString)
{
Serialize = JsonConvert.SerializeObject,
Deserialize = JsonConvert.DeserializeObject
};
services.AddSingleton(redis);
services.AddSingleton();
services.AddSingleton(new DistributedCache(redis));
}
else
{
services.AddMemoryCache();
services.AddDistributedMemoryCache();
services.AddSingleton();
}
#endregion 缓存
#region IP限流
if (appConfig.RateLimit)
{
services.AddIpRateLimit(configuration, cacheConfig);
}
#endregion IP限流
//阻止NLog接收状态消息
services.Configure(opts => opts.SuppressStatusMessages = true);
//性能分析
if (appConfig.MiniProfiler)
{
services.AddMiniProfiler();
}
//动态api
services.AddDynamicApi(options =>
{
Assembly[] assemblies = DependencyContext.Default.RuntimeLibraries
.Where(a => a.Name.EndsWith("Service"))
.Select(o => Assembly.Load(new AssemblyName(o.Name))).ToArray();
options.AddAssemblyOptions(assemblies);
options.FormatResult = appConfig.DynamicApi.FormatResult;
options.FormatResultType = typeof(ResultOutput<>);
_hostAppOptions?.ConfigureDynamicApi?.Invoke(options);
});
_hostAppOptions?.ConfigurePostServices?.Invoke(hostAppContext);
}
///
/// 配置中间件
///
///
///
///
///
private void ConfigureMiddleware(WebApplication app, IWebHostEnvironment env, IConfiguration configuration, AppConfig appConfig)
{
var hostAppMiddlewareContext = new HostAppMiddlewareContext()
{
App = app,
Environment = env,
Configuration = configuration
};
_hostAppOptions?.ConfigurePreMiddleware?.Invoke(hostAppMiddlewareContext);
//异常处理
app.UseMiddleware();
//IP限流
if (appConfig.RateLimit)
{
app.UseIpRateLimiting();
}
//性能分析
if (appConfig.MiniProfiler)
{
app.UseMiniProfiler();
}
//静态文件
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseUploadConfig();
//路由
app.UseRouting();
//跨域
app.UseCors(AdminConsts.RequestPolicyName);
//认证
app.UseAuthentication();
//授权
app.UseAuthorization();
//登录用户初始化数据权限
if (appConfig.Validate.Permission)
{
app.Use(async (ctx, next) =>
{
var user = ctx.RequestServices.GetRequiredService();
if (user?.Id > 0)
{
var userService = ctx.RequestServices.GetRequiredService();
await userService.GetDataPermissionAsync();
}
await next();
});
}
//配置端点
app.MapControllers();
_hostAppOptions?.ConfigureMiddleware?.Invoke(hostAppMiddlewareContext);
#region Swagger Api文档
if (env.IsDevelopment() || appConfig.Swagger.Enable)
{
var routePrefix = appConfig.ApiUI.RoutePrefix;
if (!appConfig.ApiUI.Enable && routePrefix.IsNull())
{
routePrefix = appConfig.Swagger.RoutePrefix;
}
var routePath = routePrefix.NotNull() ? $"{routePrefix}/" : "";
app.UseSwagger(optoins =>
{
optoins.RouteTemplate = routePath + optoins.RouteTemplate;
});
app.UseSwaggerUI(options =>
{
options.RoutePrefix = appConfig.Swagger.RoutePrefix;
appConfig.Swagger.Projects?.ForEach(project =>
{
options.SwaggerEndpoint($"/{routePath}swagger/{project.Code.ToLower()}/swagger.json", project.Name);
});
options.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);//折叠Api
//options.DefaultModelsExpandDepth(-1);//不显示Models
if (appConfig.MiniProfiler)
{
options.InjectJavascript("/swagger/mini-profiler.js?v=4.2.22+2.0");
options.InjectStylesheet("/swagger/mini-profiler.css?v=4.2.22+2.0");
}
});
}
#endregion Swagger Api文档
//使用健康检查
if(appConfig.HealthChecks.Enable)
{
app.MapHealthChecks(appConfig.HealthChecks.Path, new HealthCheckOptions()
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
}
_hostAppOptions?.ConfigurePostMiddleware?.Invoke(hostAppMiddlewareContext);
}
}