HostApp.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. using AspNetCoreRateLimit;
  2. using Autofac;
  3. using IdentityServer4.AccessTokenValidation;
  4. using Microsoft.AspNetCore.Authentication;
  5. using Microsoft.AspNetCore.Authentication.JwtBearer;
  6. using Microsoft.AspNetCore.Builder;
  7. using Microsoft.AspNetCore.Hosting;
  8. using Microsoft.AspNetCore.Http;
  9. using Microsoft.Extensions.Configuration;
  10. using Microsoft.Extensions.DependencyInjection;
  11. using Microsoft.Extensions.DependencyInjection.Extensions;
  12. using Microsoft.Extensions.Hosting;
  13. using Microsoft.Extensions.DependencyModel;
  14. using Microsoft.IdentityModel.Tokens;
  15. using Microsoft.OpenApi.Models;
  16. using Newtonsoft.Json;
  17. using Newtonsoft.Json.Serialization;
  18. using System;
  19. using System.Collections.Generic;
  20. using System.IdentityModel.Tokens.Jwt;
  21. using System.Linq;
  22. using System.Reflection;
  23. using System.Text;
  24. using Mapster;
  25. using Yitter.IdGenerator;
  26. using FluentValidation;
  27. using FluentValidation.AspNetCore;
  28. using ZhonTai.Admin.Core.Auth;
  29. using ZhonTai.Admin.Tools.Cache;
  30. using ZhonTai.Common.Helpers;
  31. using ZhonTai.Admin.Core.Db;
  32. using ZhonTai.Admin.Core.Extensions;
  33. using ZhonTai.Admin.Core.Filters;
  34. using ZhonTai.Admin.Core.Logs;
  35. using ZhonTai.Admin.Core.RegisterModules;
  36. using System.IO;
  37. using Microsoft.OpenApi.Any;
  38. using Microsoft.AspNetCore.Mvc.Controllers;
  39. using ZhonTai.Admin.Core.Attributes;
  40. using ZhonTai.Admin.Core.Configs;
  41. using ZhonTai.Admin.Core.Consts;
  42. using MapsterMapper;
  43. using ZhonTai.DynamicApi;
  44. using NLog.Web;
  45. using Autofac.Extensions.DependencyInjection;
  46. using Microsoft.AspNetCore.Mvc;
  47. using ZhonTai.Admin.Core.Startup;
  48. using ZhonTai.Admin.Core.Conventions;
  49. using FreeSql;
  50. using ZhonTai.Admin.Core.Db.Transaction;
  51. using ZhonTai.Admin.Services.User;
  52. namespace ZhonTai.Admin.Core;
  53. public class HostApp
  54. {
  55. readonly HostAppOptions _hostAppOptions;
  56. public HostApp()
  57. {
  58. }
  59. public HostApp(HostAppOptions hostAppOptions)
  60. {
  61. _hostAppOptions = hostAppOptions;
  62. }
  63. public void Run(string[] args)
  64. {
  65. var builder = WebApplication.CreateBuilder(args);
  66. //使用NLog日志
  67. builder.Host.UseNLog();
  68. //添加配置
  69. builder.Host.ConfigureAppConfiguration((context, builder) =>
  70. {
  71. builder.AddJsonFile("./Configs/ratelimitconfig.json", optional: true, reloadOnChange: true);
  72. if (context.HostingEnvironment.EnvironmentName.NotNull())
  73. {
  74. builder.AddJsonFile($"./Configs/ratelimitconfig.{context.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true);
  75. }
  76. builder.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
  77. if (context.HostingEnvironment.EnvironmentName.NotNull())
  78. {
  79. builder.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true);
  80. }
  81. });
  82. var services = builder.Services;
  83. var env = builder.Environment;
  84. var configuration = builder.Configuration;
  85. var configHelper = new ConfigHelper();
  86. var appConfig = ConfigHelper.Get<AppConfig>("appconfig", env.EnvironmentName) ?? new AppConfig();
  87. //应用配置
  88. services.AddSingleton(appConfig);
  89. //使用Autofac容器
  90. builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
  91. //配置Autofac容器
  92. builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
  93. {
  94. // 控制器注入
  95. builder.RegisterModule(new ControllerModule());
  96. // 单例注入
  97. builder.RegisterModule(new SingleInstanceModule(appConfig));
  98. // 仓储注入
  99. builder.RegisterModule(new RepositoryModule(appConfig));
  100. // 服务注入
  101. builder.RegisterModule(new ServiceModule(appConfig));
  102. });
  103. //配置Kestrel服务器
  104. builder.WebHost.ConfigureKestrel((context, options) =>
  105. {
  106. //设置应用服务器Kestrel请求体最大为100MB
  107. options.Limits.MaxRequestBodySize = 1024 * 1024 * 100;
  108. });
  109. //访问地址
  110. builder.WebHost.UseUrls(appConfig.Urls);
  111. //配置服务
  112. ConfigureServices(services, env, configuration, configHelper, appConfig);
  113. var app = builder.Build();
  114. //配置中间件
  115. ConfigureMiddleware(app, env, configuration, appConfig);
  116. app.Run();
  117. }
  118. /// <summary>
  119. /// 配置服务
  120. /// </summary>
  121. /// <param name="services"></param>
  122. /// <param name="env"></param>
  123. /// <param name="configuration"></param>
  124. /// <param name="configHelper"></param>
  125. /// <param name="appConfig"></param>
  126. private void ConfigureServices(IServiceCollection services, IWebHostEnvironment env, IConfiguration configuration, ConfigHelper configHelper, AppConfig appConfig)
  127. {
  128. var hostAppContext = new HostAppContext()
  129. {
  130. Services = services,
  131. Environment = env,
  132. Configuration = configuration
  133. };
  134. _hostAppOptions?.ConfigurePreServices?.Invoke(hostAppContext);
  135. //雪花漂移算法
  136. var idGeneratorOptions = new IdGeneratorOptions(1) { WorkerIdBitLength = 6 };
  137. _hostAppOptions?.ConfigureIdGenerator?.Invoke(idGeneratorOptions);
  138. YitIdHelper.SetIdGenerator(idGeneratorOptions);
  139. //权限处理
  140. services.AddScoped<IPermissionHandler, PermissionHandler>();
  141. // ClaimType不被更改
  142. JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
  143. //用户信息
  144. services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
  145. services.TryAddScoped<IUser, User>();
  146. //数据库配置
  147. var dbConfig = ConfigHelper.Get<DbConfig>("dbconfig", env.EnvironmentName);
  148. services.AddSingleton(dbConfig);
  149. //添加数据库
  150. if (!_hostAppOptions.CustomInitDb)
  151. {
  152. services.AddDb(env, _hostAppOptions);
  153. }
  154. //上传配置
  155. var uploadConfig = ConfigHelper.Load("uploadconfig", env.EnvironmentName, true);
  156. services.Configure<UploadConfig>(uploadConfig);
  157. //程序集
  158. Assembly[] assemblies = null;
  159. if(appConfig.AssemblyNames?.Length > 0)
  160. {
  161. assemblies = DependencyContext.Default.RuntimeLibraries
  162. .Where(a => appConfig.AssemblyNames.Contains(a.Name))
  163. .Select(o => Assembly.Load(new AssemblyName(o.Name))).ToArray();
  164. }
  165. #region Mapster 映射配置
  166. services.AddScoped<IMapper>(sp => new Mapper());
  167. if(assemblies?.Length > 0)
  168. {
  169. TypeAdapterConfig.GlobalSettings.Scan(assemblies);
  170. }
  171. #endregion Mapster 映射配置
  172. #region Cors 跨域
  173. services.AddCors(options =>
  174. {
  175. options.AddPolicy(AdminConsts.RequestPolicyName, policy =>
  176. {
  177. var hasOrigins = appConfig.CorUrls?.Length > 0;
  178. if (hasOrigins)
  179. {
  180. policy.WithOrigins(appConfig.CorUrls);
  181. }
  182. else
  183. {
  184. policy.AllowAnyOrigin();
  185. }
  186. policy
  187. .AllowAnyHeader()
  188. .AllowAnyMethod();
  189. if (hasOrigins)
  190. {
  191. policy.AllowCredentials();
  192. }
  193. });
  194. //允许任何源访问Api策略,使用时在控制器或者接口上增加特性[EnableCors(AdminConsts.AllowAnyPolicyName)]
  195. options.AddPolicy(AdminConsts.AllowAnyPolicyName, policy =>
  196. {
  197. policy
  198. .AllowAnyOrigin()
  199. .AllowAnyHeader()
  200. .AllowAnyMethod();
  201. });
  202. });
  203. #endregion Cors 跨域
  204. #region 身份认证授权
  205. var jwtConfig = ConfigHelper.Get<JwtConfig>("jwtconfig", env.EnvironmentName);
  206. services.TryAddSingleton(jwtConfig);
  207. if (appConfig.IdentityServer.Enable)
  208. {
  209. //is4
  210. services.AddAuthentication(options =>
  211. {
  212. options.DefaultScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
  213. options.DefaultChallengeScheme = nameof(ResponseAuthenticationHandler); //401
  214. options.DefaultForbidScheme = nameof(ResponseAuthenticationHandler); //403
  215. })
  216. .AddJwtBearer(options =>
  217. {
  218. options.Authority = appConfig.IdentityServer.Url;
  219. options.RequireHttpsMetadata = false;
  220. options.Audience = "admin.server.api";
  221. })
  222. .AddScheme<AuthenticationSchemeOptions, ResponseAuthenticationHandler>(nameof(ResponseAuthenticationHandler), o => { });
  223. }
  224. else
  225. {
  226. //jwt
  227. services.AddAuthentication(options =>
  228. {
  229. options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
  230. options.DefaultChallengeScheme = nameof(ResponseAuthenticationHandler); //401
  231. options.DefaultForbidScheme = nameof(ResponseAuthenticationHandler); //403
  232. })
  233. .AddJwtBearer(options =>
  234. {
  235. options.TokenValidationParameters = new TokenValidationParameters
  236. {
  237. ValidateIssuer = true,
  238. ValidateAudience = true,
  239. ValidateLifetime = true,
  240. ValidateIssuerSigningKey = true,
  241. ValidIssuer = jwtConfig.Issuer,
  242. ValidAudience = jwtConfig.Audience,
  243. IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecurityKey)),
  244. ClockSkew = TimeSpan.Zero
  245. };
  246. })
  247. .AddScheme<AuthenticationSchemeOptions, ResponseAuthenticationHandler>(nameof(ResponseAuthenticationHandler), o => { });
  248. }
  249. #endregion 身份认证授权
  250. #region Swagger Api文档
  251. if (env.IsDevelopment() || appConfig.Swagger.Enable)
  252. {
  253. services.AddSwaggerGen(options =>
  254. {
  255. appConfig.Swagger.Projects?.ForEach(project =>
  256. {
  257. options.SwaggerDoc(project.Code.ToLower(), new OpenApiInfo
  258. {
  259. Title = project.Name,
  260. Version = project.Version,
  261. Description = project.Description
  262. });
  263. //c.OrderActionsBy(o => o.RelativePath);
  264. });
  265. options.SchemaFilter<EnumSchemaFilter>();
  266. options.CustomOperationIds(apiDesc =>
  267. {
  268. var controllerAction = apiDesc.ActionDescriptor as ControllerActionDescriptor;
  269. return controllerAction.ControllerName + "-" + controllerAction.ActionName;
  270. });
  271. options.ResolveConflictingActions(apiDescription => apiDescription.First());
  272. options.CustomSchemaIds(x => x.FullName);
  273. //options.DocInclusionPredicate((docName, description) => true);
  274. string[] xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml");
  275. if (xmlFiles.Length > 0)
  276. {
  277. foreach (var xmlFile in xmlFiles)
  278. {
  279. options.IncludeXmlComments(xmlFile, true);
  280. }
  281. }
  282. var server = new OpenApiServer()
  283. {
  284. Url = appConfig.Swagger.Url,
  285. Description = ""
  286. };
  287. server.Extensions.Add("extensions", new OpenApiObject
  288. {
  289. ["copyright"] = new OpenApiString(appConfig.ApiUI.Footer.Content)
  290. });
  291. options.AddServer(server);
  292. #region 添加设置Token的按钮
  293. if (appConfig.IdentityServer.Enable)
  294. {
  295. //添加Jwt验证设置
  296. options.AddSecurityRequirement(new OpenApiSecurityRequirement()
  297. {
  298. {
  299. new OpenApiSecurityScheme
  300. {
  301. Reference = new OpenApiReference
  302. {
  303. Id = "oauth2",
  304. Type = ReferenceType.SecurityScheme
  305. }
  306. },
  307. new List<string>()
  308. }
  309. });
  310. //统一认证
  311. options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
  312. {
  313. Type = SecuritySchemeType.OAuth2,
  314. Description = "oauth2登录授权",
  315. Flows = new OpenApiOAuthFlows
  316. {
  317. Implicit = new OpenApiOAuthFlow
  318. {
  319. AuthorizationUrl = new Uri($"{appConfig.IdentityServer.Url}/connect/authorize"),
  320. Scopes = new Dictionary<string, string>
  321. {
  322. { "admin.server.api", "admin后端api" }
  323. }
  324. }
  325. }
  326. });
  327. }
  328. else
  329. {
  330. //添加Jwt验证设置
  331. options.AddSecurityRequirement(new OpenApiSecurityRequirement()
  332. {
  333. {
  334. new OpenApiSecurityScheme
  335. {
  336. Reference = new OpenApiReference
  337. {
  338. Id = "Bearer",
  339. Type = ReferenceType.SecurityScheme
  340. }
  341. },
  342. new List<string>()
  343. }
  344. });
  345. options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
  346. {
  347. Description = "Value: Bearer {token}",
  348. Name = "Authorization",
  349. In = ParameterLocation.Header,
  350. Type = SecuritySchemeType.ApiKey
  351. });
  352. }
  353. #endregion 添加设置Token的按钮
  354. });
  355. }
  356. #endregion Swagger Api文档
  357. #region 操作日志
  358. if (appConfig.Log.Operation)
  359. {
  360. services.AddScoped<ILogHandler, LogHandler>();
  361. }
  362. #endregion 操作日志
  363. #region 控制器
  364. void mvcConfigure(MvcOptions options)
  365. {
  366. options.Filters.Add<ControllerExceptionFilter>();
  367. options.Filters.Add<ValidateInputFilter>();
  368. if(appConfig.Validate.Login || appConfig.Validate.Permission)
  369. {
  370. options.Filters.Add<ValidatePermissionAttribute>();
  371. }
  372. if (appConfig.Log.Operation)
  373. {
  374. options.Filters.Add<ControllerLogFilter>();
  375. }
  376. //禁止去除ActionAsync后缀
  377. //options.SuppressAsyncSuffixInActionNames = false;
  378. if (env.IsDevelopment() || appConfig.Swagger.Enable)
  379. {
  380. //API分组约定
  381. options.Conventions.Add(new ApiGroupConvention());
  382. }
  383. }
  384. var mvcBuilder = appConfig.AppType switch
  385. {
  386. AppType.Controllers => services.AddControllers(mvcConfigure),
  387. AppType.ControllersWithViews => services.AddControllersWithViews(mvcConfigure),
  388. AppType.MVC => services.AddMvc(mvcConfigure),
  389. _ => services.AddControllers(mvcConfigure)
  390. };
  391. if (assemblies?.Length > 0)
  392. {
  393. foreach (var assembly in assemblies)
  394. {
  395. services.AddValidatorsFromAssembly(assembly);
  396. }
  397. }
  398. services.AddFluentValidationAutoValidation();
  399. mvcBuilder.AddNewtonsoftJson(options =>
  400. {
  401. //忽略循环引用
  402. options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
  403. //使用驼峰 首字母小写
  404. options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
  405. //设置时间格式
  406. options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
  407. })
  408. .AddControllersAsServices();
  409. #endregion 控制器
  410. services.AddHttpClient();
  411. _hostAppOptions?.ConfigureServices?.Invoke(hostAppContext);
  412. #region 缓存
  413. var cacheConfig = ConfigHelper.Get<CacheConfig>("cacheconfig", env.EnvironmentName);
  414. if (cacheConfig.Type == CacheType.Redis)
  415. {
  416. var csredis = new CSRedis.CSRedisClient(cacheConfig.Redis.ConnectionString);
  417. RedisHelper.Initialization(csredis);
  418. services.AddSingleton<ICacheTool, RedisCacheTool>();
  419. }
  420. else
  421. {
  422. services.AddMemoryCache();
  423. services.AddSingleton<ICacheTool, MemoryCacheTool>();
  424. }
  425. #endregion 缓存
  426. #region IP限流
  427. if (appConfig.RateLimit)
  428. {
  429. services.AddIpRateLimit(configuration, cacheConfig);
  430. }
  431. #endregion IP限流
  432. //阻止NLog接收状态消息
  433. services.Configure<ConsoleLifetimeOptions>(opts => opts.SuppressStatusMessages = true);
  434. //性能分析
  435. if (appConfig.MiniProfiler)
  436. {
  437. services.AddMiniProfiler();
  438. }
  439. //动态api
  440. services.AddDynamicApi(options =>
  441. {
  442. Assembly[] assemblies = DependencyContext.Default.RuntimeLibraries
  443. .Where(a => a.Name.EndsWith("Service"))
  444. .Select(o => Assembly.Load(new AssemblyName(o.Name))).ToArray();
  445. options.AddAssemblyOptions(assemblies);
  446. _hostAppOptions?.ConfigureDynamicApi?.Invoke(options);
  447. });
  448. _hostAppOptions?.ConfigurePostServices?.Invoke(hostAppContext);
  449. }
  450. /// <summary>
  451. /// 配置中间件
  452. /// </summary>
  453. /// <param name="app"></param>
  454. /// <param name="env"></param>
  455. /// <param name="configuration"></param>
  456. /// <param name="appConfig"></param>
  457. private void ConfigureMiddleware(WebApplication app, IWebHostEnvironment env, IConfiguration configuration, AppConfig appConfig)
  458. {
  459. var hostAppMiddlewareContext = new HostAppMiddlewareContext()
  460. {
  461. App = app,
  462. Environment = env,
  463. Configuration = configuration
  464. };
  465. _hostAppOptions?.ConfigurePreMiddleware?.Invoke(hostAppMiddlewareContext);
  466. //IP限流
  467. if (appConfig.RateLimit)
  468. {
  469. app.UseIpRateLimiting();
  470. }
  471. //性能分析
  472. if (appConfig.MiniProfiler)
  473. {
  474. app.UseMiniProfiler();
  475. }
  476. //异常
  477. app.UseExceptionHandler("/Error");
  478. //静态文件
  479. app.UseDefaultFiles();
  480. app.UseStaticFiles();
  481. app.UseUploadConfig();
  482. //路由
  483. app.UseRouting();
  484. //跨域
  485. app.UseCors(AdminConsts.RequestPolicyName);
  486. //认证
  487. app.UseAuthentication();
  488. //授权
  489. app.UseAuthorization();
  490. //初始化登录用户数据权限
  491. app.Use(async (ctx, next) =>
  492. {
  493. var user = ctx.RequestServices.GetRequiredService<IUser>();
  494. if (user?.Id > 0)
  495. {
  496. var userService = ctx.RequestServices.GetRequiredService<IUserService>();
  497. await userService.GetDataPermissionAsync();
  498. }
  499. await next();
  500. });
  501. //配置端点
  502. app.UseEndpoints(endpoints =>
  503. {
  504. endpoints.MapControllers();
  505. });
  506. _hostAppOptions?.ConfigureMiddleware?.Invoke(hostAppMiddlewareContext);
  507. #region Swagger Api文档
  508. if (env.IsDevelopment() || appConfig.Swagger.Enable)
  509. {
  510. app.UseSwagger();
  511. app.UseSwaggerUI(c =>
  512. {
  513. appConfig.Swagger.Projects?.ForEach(project =>
  514. {
  515. c.SwaggerEndpoint($"/swagger/{project.Code.ToLower()}/swagger.json", project.Name);
  516. });
  517. c.RoutePrefix = "swagger";//直接根目录访问,如果是IIS发布可以注释该语句,并打开launchSettings.launchUrl
  518. c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);//折叠Api
  519. //c.DefaultModelsExpandDepth(-1);//不显示Models
  520. if (appConfig.MiniProfiler)
  521. {
  522. c.InjectJavascript("/swagger/mini-profiler.js?v=4.2.22+2.0");
  523. c.InjectStylesheet("/swagger/mini-profiler.css?v=4.2.22+2.0");
  524. }
  525. });
  526. }
  527. #endregion Swagger Api文档
  528. //数据库日志
  529. //var log = LogManager.GetLogger("db");
  530. //var ei = new LogEventInfo(LogLevel.Error, "", "错误信息");
  531. //ei.Properties["id"] = YitIdHelper.NextId();
  532. //log.Log(ei);
  533. _hostAppOptions?.ConfigurePostMiddleware?.Invoke(hostAppMiddlewareContext);
  534. }
  535. }