WebApiHelper.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. using Autofac;
  2. using Autofac.Extensions.DependencyInjection;
  3. using Microsoft.AspNetCore.Builder;
  4. using Microsoft.AspNetCore.Hosting;
  5. using Microsoft.AspNetCore.Http;
  6. using Microsoft.AspNetCore.Http.Features;
  7. using Microsoft.AspNetCore.Mvc.ApplicationParts;
  8. using Microsoft.AspNetCore.Mvc.Controllers;
  9. using Microsoft.AspNetCore.Server.Kestrel.Core;
  10. using Microsoft.Extensions.DependencyInjection;
  11. using Microsoft.Extensions.DependencyInjection.Extensions;
  12. using Microsoft.Extensions.FileProviders;
  13. using Microsoft.Extensions.Hosting;
  14. using Microsoft.Extensions.Logging;
  15. using Microsoft.OpenApi.Models;
  16. using Newtonsoft.Json.Converters;
  17. using Newtonsoft.Json.Serialization;
  18. using Serilog;
  19. using Serilog.Extensions.Logging;
  20. using System.Diagnostics;
  21. using System.Reflection;
  22. namespace DW5S.WebApi
  23. {
  24. /// <summary>
  25. ///
  26. /// </summary>
  27. public static class WebApiHelper
  28. {
  29. private static CancellationTokenSource _cts;
  30. /// <summary>
  31. /// 启动AspNetCore WebAPI
  32. /// (如果Controller所在程序集未被代码使用,且入口Exe被发布为自包含程序,会找不到Controller)
  33. /// </summary>
  34. /// <param name="_localPort">本地端口</param>
  35. /// <param name="controllerXmlName">Controller所在程序集XML描述文档名称,默认使用入口程序生成的xml</param>
  36. /// <param name="dtoXmlName">DTO所在程序集XML描述文档,默认使用入口程序生成的xml</param>
  37. /// <param name="staticDir">要启用的静态目录预览及文件下载的目录(已经包含upload、download、logs三个目录)</param>
  38. /// <param name="prefix">使用DI注入时程序集的前缀</param>
  39. /// <exception cref="Exception"></exception>
  40. public static void Start(int _localPort, string dtoXmlName = null, string[] staticDir = null, string controllerXmlName = null, string prefix = "ips")
  41. {
  42. _cts = new CancellationTokenSource();
  43. if (controllerXmlName == null)
  44. controllerXmlName = $"{AppDomain.CurrentDomain.FriendlyName}.xml";
  45. if (dtoXmlName == null)
  46. dtoXmlName = $"{AppDomain.CurrentDomain.FriendlyName}.xml";
  47. List<string> listDir = new List<string>();
  48. if (staticDir != null)
  49. {
  50. listDir.AddRange(staticDir.Select(p => p.ToLower()).Distinct());
  51. }
  52. if (!listDir.Contains("upload"))
  53. {
  54. listDir.Add("upload");
  55. }
  56. if (!listDir.Contains("download"))
  57. {
  58. listDir.Add("download");
  59. }
  60. if (!listDir.Contains("logs"))
  61. {
  62. listDir.Add("logs");
  63. }
  64. staticDir = listDir.ToArray();
  65. var assemblies = AppDomain.CurrentDomain.GetAllAssemblies(prefix);
  66. if (assemblies == null)
  67. {
  68. throw new Exception($"未扫描到{prefix}前缀的程序集");
  69. }
  70. foreach (var item in assemblies)
  71. {
  72. Console.WriteLine($"已加载DI注入程序集[{item.FullName}]");
  73. }
  74. var controllerAssemblies = assemblies.Where(p => p.GetTypes().Any(q =>
  75. {
  76. if (q.Name.EndsWith("Controller") && q.IsSubclassOf(typeof(BaseController)))
  77. return true;
  78. return false;
  79. })).ToList();
  80. if (controllerAssemblies == null || !controllerAssemblies.Any())
  81. {
  82. throw new Exception("未找到Controller所在的程序集");
  83. }
  84. var builder = WebApplication.CreateBuilder();
  85. builder.Services.AddRouting(t => t.LowercaseUrls = true);//全部路由默认显示小写
  86. #region 请求大小限制200MB及Http2支持
  87. builder.WebHost.ConfigureKestrel(options =>
  88. {
  89. options.Limits.MaxRequestBodySize = 200 << 20;//200MB
  90. options.ListenAnyIP(Convert.ToInt32(_localPort), listenOptions =>
  91. {
  92. listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
  93. });
  94. });
  95. builder.Services.Configure<FormOptions>(options =>
  96. {
  97. options.MultipartBodyLengthLimit = 200 << 20;//通过表单上传最大200MB
  98. });
  99. #endregion
  100. #region 初始化日志
  101. //取消默认的日志Provider
  102. builder.Logging.ClearProviders();
  103. builder.Host.UseSerilog((builderContext, config) =>
  104. {
  105. var basePath = AppContext.BaseDirectory;
  106. var outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff}[线程={ThreadId}][{Level:u3}]{Message:lj}{NewLine}\t{Exception}";
  107. config.Enrich.FromLogContext()
  108. .Enrich.With(new SerilogEnricher())
  109. .WriteTo.Console(outputTemplate: outputTemplate)
  110. .WriteTo.Logger(p => p.Filter.ByIncludingOnly(e => e.Level == Serilog.Events.LogEventLevel.Information)
  111. .WriteTo.File(Path.Combine(basePath, "Logs", "Info", ".log"), rollingInterval: Serilog.RollingInterval.Day, outputTemplate: outputTemplate))
  112. .WriteTo.Logger(p => p.Filter.ByIncludingOnly(e => e.Level == Serilog.Events.LogEventLevel.Warning)
  113. .WriteTo.File(Path.Combine(basePath, "Logs", "Warning", ".log"), rollingInterval: Serilog.RollingInterval.Day, outputTemplate: outputTemplate))
  114. .WriteTo.Logger(p => p.Filter.ByIncludingOnly(e => e.Level == Serilog.Events.LogEventLevel.Error)
  115. .WriteTo.File(Path.Combine(basePath, "Logs", "Error", ".log"), rollingInterval: Serilog.RollingInterval.Day, outputTemplate: outputTemplate));
  116. });
  117. builder.Logging.AddSerilog();
  118. #endregion
  119. #region 启用静态文件缓存和压缩(已屏蔽,采集文件压缩率不高)
  120. //builder.Services.AddResponseCaching();
  121. //builder.Services.AddResponseCompression();
  122. #endregion
  123. #region 注入常用服务
  124. //系统缓存,可以其它地方使用IMemoryCache接口
  125. builder.Services.AddMemoryCache();
  126. //http上下文
  127. builder.Services.AddSingleton<IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>();
  128. //HttpClient
  129. builder.Services.AddHttpClient();//IHttpClientFactory
  130. builder.Services.AddGrpc();
  131. //builder.Services.AddGrpcClient<object>(p=>
  132. //{
  133. // p.Address = new Uri("http://127.0.0.1:16001");
  134. //}).ConfigureChannel(p =>
  135. //{
  136. //});
  137. builder.Services.AddEndpointsApiExplorer();
  138. builder.Services.AddSwaggerGen(c =>
  139. {
  140. c.SwaggerDoc("v1", new OpenApiInfo
  141. {
  142. Version = "v1",
  143. Title = $"{Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.FileName)}Http接口文档",
  144. });
  145. var basePath = AppDomain.CurrentDomain.BaseDirectory;
  146. var xmlPath = Path.Combine(basePath, controllerXmlName);//Controller xml
  147. c.IncludeXmlComments(xmlPath, true);
  148. xmlPath = Path.Combine(basePath, "Ips.Library.WebApi.xml");//BaesController xml
  149. c.IncludeXmlComments(xmlPath, true);
  150. c.OrderActionsBy(o => o.RelativePath);
  151. xmlPath = Path.Combine(basePath, dtoXmlName);//dto xml
  152. c.IncludeXmlComments(xmlPath);
  153. });
  154. #endregion
  155. #region 注入Controller、Service、Repository、HostedService
  156. //让Controller的实例由autofac创建,而不是由dotnet框架创建,以便在控制器中使用autofac高级功能
  157. builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
  158. //将框架默认IOC容器替换为Autofac容器
  159. builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
  160. var hostBuilder = builder.Host.ConfigureContainer<Autofac.ContainerBuilder>(builder =>
  161. {
  162. //瞬态注入Controllerer,每次接口请求都创建了新的对象
  163. builder.RegisterAssemblyTypes(assemblies)
  164. .Where(type => type.Name.EndsWith("Controller"))
  165. .PropertiesAutowired((propInfo, instance) =>//Controller中的属性支持自动注入
  166. propInfo.Name.EndsWith("Autowired") || //以Autowired名称结尾的属性自动注入
  167. propInfo.GetCustomAttribute<AutowiredAttribute>() != null)//带有Autowired特性的属性自动注入
  168. .AsSelf()//接口的默认实现
  169. .InstancePerDependency();
  170. //单例注入以Service或Repository结尾的类(有继承的类不会注入)
  171. builder.RegisterAssemblyTypes(assemblies)
  172. .Where(type =>
  173. {
  174. if (!type.Name.EndsWith("Service") && !type.Name.EndsWith("Repository"))
  175. {
  176. return false;
  177. }
  178. if (type.BaseType != typeof(object))
  179. {
  180. return false;
  181. }
  182. return true;
  183. })
  184. .PropertiesAutowired((propInfo, instance) =>//Service中的属性支持自动注入
  185. propInfo.Name.EndsWith("Autowired") || //以Autowired名称结尾的属性自动注入
  186. propInfo.GetCustomAttribute<AutowiredAttribute>() != null)//带有Autowired特性的属性自动注入
  187. .AsSelf()//接口的默认实现
  188. .SingleInstance();
  189. //注入后台服务
  190. builder.RegisterAssemblyTypes(assemblies)
  191. .Where(type => type.Name.EndsWith("Service") && type.IsSubclassOf(typeof(BackgroundService)))
  192. .PropertiesAutowired((propInfo, instance) =>//BackgroundService类中的属性支持自动注入
  193. propInfo.Name.EndsWith("Autowired") || //以Autowired名称结尾的属性自动注入
  194. propInfo.GetCustomAttribute<AutowiredAttribute>() != null)//带有Autowired特性的属性自动注入
  195. .As<IHostedService>()//后台服务方式注入
  196. .InstancePerDependency();
  197. });
  198. #endregion
  199. #region 注入HostedService后台服务(已屏蔽,在后面使用了autofac自动注入)
  200. //builder.Services.AddHostedService<UploadClearService>();//执行初始化等操作
  201. #endregion
  202. builder.Services.AddControllers(c =>
  203. {
  204. //过滤器
  205. //c.Filters.Add<LogActionFilter>();
  206. })
  207. //这种方式不方便加载Controller分布在不同dll中的情况,因此使用了ConfigureApplicationPartManager
  208. //.AddApplicationPart(controllerAssemblies.First())
  209. .ConfigureApplicationPartManager(apm =>
  210. {
  211. foreach (var item in controllerAssemblies)
  212. {
  213. var part = new AssemblyPart(item);
  214. apm.ApplicationParts.Add(part);
  215. }
  216. })
  217. .AddNewtonsoftJson(options =>
  218. {
  219. //修改属性名称的序列化方式,首字母小写
  220. options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
  221. //修改时间的序列化方式
  222. options.SerializerSettings.Converters.Add(new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" });
  223. });
  224. var app = builder.Build();
  225. #region 启用静态资源访问
  226. foreach (var item in staticDir)
  227. {
  228. //静态文件物理路径
  229. if (string.IsNullOrWhiteSpace(item)) continue;
  230. string path;
  231. if (item.Contains(":"))
  232. path = item;//绝对路径
  233. else
  234. path = Path.Combine(AppContext.BaseDirectory, item);//相对路径
  235. Directory.CreateDirectory(path);
  236. //静态资源缓存时间(300秒,600秒)
  237. var cachePeriod = app.Environment.IsDevelopment() ? "300" : "600";
  238. //启用静态文件路由
  239. app.UseStaticFiles(new StaticFileOptions
  240. {
  241. ServeUnknownFileTypes = true,
  242. FileProvider = new PhysicalFileProvider(path),
  243. RequestPath = $"/{Path.GetFileName(item)}",
  244. OnPrepareResponse = ctx =>
  245. {
  246. ctx.Context.Response.Headers.Append("Cache-Control", $"public, max-age={cachePeriod}");
  247. }
  248. });
  249. //启用目录浏览所有文件
  250. app.UseDirectoryBrowser(new DirectoryBrowserOptions()
  251. {
  252. FileProvider = new PhysicalFileProvider(path),
  253. RequestPath = $"/{Path.GetFileName(item)}"
  254. });
  255. }
  256. app.UseResponseCaching();
  257. #endregion
  258. //app.MapGrpcService<FileService>();
  259. app.Urls.Add($"http://+:{_localPort}");
  260. app.UseSwagger();
  261. app.UseSwaggerUI(c =>
  262. {
  263. c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1版本");
  264. });
  265. app.UseExceptionHandler("/Home/Error");
  266. //app.UseAuthentication();//身份验证
  267. app.UseAuthorization(); //授权
  268. //app.UseMiddleware<ExceptionHandlingMiddleware>();//全局异常处理
  269. app.MapControllers();
  270. //app.Map("/", () => "必须通过GRPC客户端访问此接口");
  271. app.RunAsync(_cts.Token);
  272. }
  273. /// <summary>
  274. /// 结束WebAPI
  275. /// </summary>
  276. public static void Stop()
  277. {
  278. _cts?.Cancel();
  279. }
  280. }
  281. }