| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 | using Autofac;using Autofac.Extensions.DependencyInjection;using Microsoft.AspNetCore.Builder;using Microsoft.AspNetCore.Hosting;using Microsoft.AspNetCore.Http;using Microsoft.AspNetCore.Http.Features;using Microsoft.AspNetCore.Mvc.ApplicationParts;using Microsoft.AspNetCore.Mvc.Controllers;using Microsoft.AspNetCore.Server.Kestrel.Core;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.DependencyInjection.Extensions;using Microsoft.Extensions.FileProviders;using Microsoft.Extensions.Hosting;using Microsoft.Extensions.Logging;using Microsoft.OpenApi.Models;using Newtonsoft.Json.Converters;using Newtonsoft.Json.Serialization;using Serilog;using Serilog.Extensions.Logging;using System.Diagnostics;using System.Reflection;namespace DW5S.WebApi{    /// <summary>    ///     /// </summary>    public static class WebApiHelper    {        private static CancellationTokenSource _cts;        /// <summary>        /// 启动AspNetCore WebAPI        /// (如果Controller所在程序集未被代码使用,且入口Exe被发布为自包含程序,会找不到Controller)        /// </summary>        /// <param name="_localPort">本地端口</param>        /// <param name="controllerXmlName">Controller所在程序集XML描述文档名称,默认使用入口程序生成的xml</param>        /// <param name="dtoXmlName">DTO所在程序集XML描述文档,默认使用入口程序生成的xml</param>        /// <param name="staticDir">要启用的静态目录预览及文件下载的目录(已经包含upload、download、logs三个目录)</param>        /// <param name="prefix">使用DI注入时程序集的前缀</param>        /// <exception cref="Exception"></exception>        public static void Start(int _localPort, string dtoXmlName = null, string[] staticDir = null, string controllerXmlName = null, string prefix = "ips")        {            _cts = new CancellationTokenSource();            if (controllerXmlName == null)                controllerXmlName = $"{AppDomain.CurrentDomain.FriendlyName}.xml";            if (dtoXmlName == null)                dtoXmlName = $"{AppDomain.CurrentDomain.FriendlyName}.xml";            List<string> listDir = new List<string>();            if (staticDir != null)            {                listDir.AddRange(staticDir.Select(p => p.ToLower()).Distinct());            }            if (!listDir.Contains("upload"))            {                listDir.Add("upload");            }            if (!listDir.Contains("download"))            {                listDir.Add("download");            }            if (!listDir.Contains("logs"))            {                listDir.Add("logs");            }            staticDir = listDir.ToArray();            var assemblies = AppDomain.CurrentDomain.GetAllAssemblies(prefix);            if (assemblies == null)            {                throw new Exception($"未扫描到{prefix}前缀的程序集");            }            foreach (var item in assemblies)            {                Console.WriteLine($"已加载DI注入程序集[{item.FullName}]");            }            var controllerAssemblies = assemblies.Where(p => p.GetTypes().Any(q =>            {                if (q.Name.EndsWith("Controller") && q.IsSubclassOf(typeof(BaseController)))                    return true;                return false;            })).ToList();            if (controllerAssemblies == null || !controllerAssemblies.Any())            {                throw new Exception("未找到Controller所在的程序集");            }            var builder = WebApplication.CreateBuilder();            builder.Services.AddRouting(t => t.LowercaseUrls = true);//全部路由默认显示小写            #region 请求大小限制200MB及Http2支持            builder.WebHost.ConfigureKestrel(options =>            {                options.Limits.MaxRequestBodySize = 200 << 20;//200MB                options.ListenAnyIP(Convert.ToInt32(_localPort), listenOptions =>                {                    listenOptions.Protocols = HttpProtocols.Http1AndHttp2;                });            });            builder.Services.Configure<FormOptions>(options =>            {                options.MultipartBodyLengthLimit = 200 << 20;//通过表单上传最大200MB            });            #endregion            #region 初始化日志            //取消默认的日志Provider            builder.Logging.ClearProviders();            builder.Host.UseSerilog((builderContext, config) =>            {                var basePath = AppContext.BaseDirectory;                var outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff}[线程={ThreadId}][{Level:u3}]{Message:lj}{NewLine}\t{Exception}";                config.Enrich.FromLogContext()                    .Enrich.With(new SerilogEnricher())                    .WriteTo.Console(outputTemplate: outputTemplate)                    .WriteTo.Logger(p => p.Filter.ByIncludingOnly(e => e.Level == Serilog.Events.LogEventLevel.Information)                        .WriteTo.File(Path.Combine(basePath, "Logs", "Info", ".log"), rollingInterval: Serilog.RollingInterval.Day, outputTemplate: outputTemplate))                    .WriteTo.Logger(p => p.Filter.ByIncludingOnly(e => e.Level == Serilog.Events.LogEventLevel.Warning)                        .WriteTo.File(Path.Combine(basePath, "Logs", "Warning", ".log"), rollingInterval: Serilog.RollingInterval.Day, outputTemplate: outputTemplate))                    .WriteTo.Logger(p => p.Filter.ByIncludingOnly(e => e.Level == Serilog.Events.LogEventLevel.Error)                        .WriteTo.File(Path.Combine(basePath, "Logs", "Error", ".log"), rollingInterval: Serilog.RollingInterval.Day, outputTemplate: outputTemplate));            });            builder.Logging.AddSerilog();            #endregion            #region 启用静态文件缓存和压缩(已屏蔽,采集文件压缩率不高)            //builder.Services.AddResponseCaching();            //builder.Services.AddResponseCompression();            #endregion            #region 注入常用服务            //系统缓存,可以其它地方使用IMemoryCache接口            builder.Services.AddMemoryCache();            //http上下文            builder.Services.AddSingleton<IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>();            //HttpClient            builder.Services.AddHttpClient();//IHttpClientFactory            builder.Services.AddGrpc();            //builder.Services.AddGrpcClient<object>(p=>            //{            //    p.Address = new Uri("http://127.0.0.1:16001");            //}).ConfigureChannel(p =>            //{            //});            builder.Services.AddEndpointsApiExplorer();            builder.Services.AddSwaggerGen(c =>            {                c.SwaggerDoc("v1", new OpenApiInfo                {                    Version = "v1",                    Title = $"{Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.FileName)}Http接口文档",                });                var basePath = AppDomain.CurrentDomain.BaseDirectory;                var xmlPath = Path.Combine(basePath, controllerXmlName);//Controller xml                c.IncludeXmlComments(xmlPath, true);                xmlPath = Path.Combine(basePath, "Ips.Library.WebApi.xml");//BaesController xml                c.IncludeXmlComments(xmlPath, true);                c.OrderActionsBy(o => o.RelativePath);                xmlPath = Path.Combine(basePath, dtoXmlName);//dto xml                c.IncludeXmlComments(xmlPath);            });            #endregion            #region 注入Controller、Service、Repository、HostedService            //让Controller的实例由autofac创建,而不是由dotnet框架创建,以便在控制器中使用autofac高级功能            builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());            //将框架默认IOC容器替换为Autofac容器            builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());            var hostBuilder = builder.Host.ConfigureContainer<Autofac.ContainerBuilder>(builder =>            {                //瞬态注入Controllerer,每次接口请求都创建了新的对象                builder.RegisterAssemblyTypes(assemblies)                .Where(type => type.Name.EndsWith("Controller"))                  .PropertiesAutowired((propInfo, instance) =>//Controller中的属性支持自动注入                    propInfo.Name.EndsWith("Autowired") || //以Autowired名称结尾的属性自动注入                    propInfo.GetCustomAttribute<AutowiredAttribute>() != null)//带有Autowired特性的属性自动注入                .AsSelf()//接口的默认实现                .InstancePerDependency();                //单例注入以Service或Repository结尾的类(有继承的类不会注入)                builder.RegisterAssemblyTypes(assemblies)                .Where(type =>                {                    if (!type.Name.EndsWith("Service") && !type.Name.EndsWith("Repository"))                    {                        return false;                    }                    if (type.BaseType != typeof(object))                    {                        return false;                    }                    return true;                })                .PropertiesAutowired((propInfo, instance) =>//Service中的属性支持自动注入                    propInfo.Name.EndsWith("Autowired") || //以Autowired名称结尾的属性自动注入                    propInfo.GetCustomAttribute<AutowiredAttribute>() != null)//带有Autowired特性的属性自动注入                .AsSelf()//接口的默认实现                .SingleInstance();                //注入后台服务                builder.RegisterAssemblyTypes(assemblies)                .Where(type => type.Name.EndsWith("Service") && type.IsSubclassOf(typeof(BackgroundService)))                .PropertiesAutowired((propInfo, instance) =>//BackgroundService类中的属性支持自动注入                    propInfo.Name.EndsWith("Autowired") || //以Autowired名称结尾的属性自动注入                    propInfo.GetCustomAttribute<AutowiredAttribute>() != null)//带有Autowired特性的属性自动注入                .As<IHostedService>()//后台服务方式注入                .InstancePerDependency();            });            #endregion            #region 注入HostedService后台服务(已屏蔽,在后面使用了autofac自动注入)            //builder.Services.AddHostedService<UploadClearService>();//执行初始化等操作            #endregion            builder.Services.AddControllers(c =>            {                //过滤器                //c.Filters.Add<LogActionFilter>();            })            //这种方式不方便加载Controller分布在不同dll中的情况,因此使用了ConfigureApplicationPartManager            //.AddApplicationPart(controllerAssemblies.First())            .ConfigureApplicationPartManager(apm =>            {                foreach (var item in controllerAssemblies)                {                    var part = new AssemblyPart(item);                    apm.ApplicationParts.Add(part);                }            })            .AddNewtonsoftJson(options =>            {                //修改属性名称的序列化方式,首字母小写                options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();                //修改时间的序列化方式                options.SerializerSettings.Converters.Add(new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" });            });            var app = builder.Build();            #region 启用静态资源访问            foreach (var item in staticDir)            {                //静态文件物理路径                if (string.IsNullOrWhiteSpace(item)) continue;                string path;                if (item.Contains(":"))                    path = item;//绝对路径                else                    path = Path.Combine(AppContext.BaseDirectory, item);//相对路径                Directory.CreateDirectory(path);                //静态资源缓存时间(300秒,600秒)                var cachePeriod = app.Environment.IsDevelopment() ? "300" : "600";                //启用静态文件路由                app.UseStaticFiles(new StaticFileOptions                {                    ServeUnknownFileTypes = true,                    FileProvider = new PhysicalFileProvider(path),                    RequestPath = $"/{Path.GetFileName(item)}",                    OnPrepareResponse = ctx =>                    {                        ctx.Context.Response.Headers.Append("Cache-Control", $"public, max-age={cachePeriod}");                    }                });                //启用目录浏览所有文件                app.UseDirectoryBrowser(new DirectoryBrowserOptions()                {                    FileProvider = new PhysicalFileProvider(path),                    RequestPath = $"/{Path.GetFileName(item)}"                });            }            app.UseResponseCaching();            #endregion            //app.MapGrpcService<FileService>();            app.Urls.Add($"http://+:{_localPort}");            app.UseSwagger();            app.UseSwaggerUI(c =>            {                c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1版本");            });            app.UseExceptionHandler("/Home/Error");            //app.UseAuthentication();//身份验证            app.UseAuthorization(); //授权            //app.UseMiddleware<ExceptionHandlingMiddleware>();//全局异常处理            app.MapControllers();            //app.Map("/", () => "必须通过GRPC客户端访问此接口");            app.RunAsync(_cts.Token);        }        /// <summary>        /// 结束WebAPI        /// </summary>        public static void Stop()        {            _cts?.Cancel();        }    }}
 |