| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636 | using System;using System.Collections.Concurrent;using System.Collections.Generic;using System.ComponentModel;using System.ComponentModel.DataAnnotations;using System.IO;using System.Linq;using System.Net;using System.Net.Http;using System.Reflection;using System.Runtime.Remoting.Contexts;using System.Text;using System.Threading;using System.Web.Http;using System.Web.Http.Description;using System.Web.Http.Filters;using System.Web.Http.Metadata;using System.Web.Http.Routing;using System.Web.Http.Validation;using System.Web.Http.Validation.Providers;using System.Xml;using System.Xml.Linq;using Autofac;using Autofac.Integration.Owin;using Microsoft.Owin;using Microsoft.Owin.Cors;using Newtonsoft.Json;using Newtonsoft.Json.Serialization;using Owin;using Swashbuckle.Application;using Swashbuckle.Swagger;using Autofac.Integration.WebApi;using Autofac.Core;using Microsoft.Owin.FileSystems;using Microsoft.Owin.StaticFiles;using System.Threading.Tasks;using System.Diagnostics;using System.Web.Http.Controllers;using Microsoft.Owin.Hosting;[assembly: OwinStartup(typeof(XdCxRhDW.WebApi.Startup))]namespace XdCxRhDW.WebApi{    /// <summary>    /// WebApi启动类    /// </summary>    public class Startup    {        private static List<IDisposable> svrs = new List<IDisposable>();        private static List<HttpConfiguration> configs = new List<HttpConfiguration>();        private static string _controllerXmlName { get; set; }        private static string _dtoXmlName { get; set; }        internal static string _timeZoneUtc;        /// <summary>        /// 启动http服务,会自动关闭之前启动的服务        /// </summary>        /// <param name="port"></param>        /// <param name="controllerXmlName">controller所在程序集xml文件名称</param>        /// <param name="dtoXmlName">dto所在程序集xml文件名称</param>        /// <param name="timeZoneUtc">时区</param>        public static void Start(int port, string controllerXmlName, string dtoXmlName, string timeZoneUtc = "UTC+08:00")        {            //不要删除这句代码,VS引用优化检测到没有使用Microsoft.Owin.Host.HttpListener.dll则可能不会将此dll复制到本地导致http服务启动失败            Console.WriteLine(typeof(Microsoft.Owin.Host.HttpListener.OwinHttpListener));            _timeZoneUtc = timeZoneUtc;            _controllerXmlName = controllerXmlName;            _dtoXmlName = dtoXmlName;            foreach (var item in svrs)            {                try                {                    item.Dispose();                }                catch                {                }            }            foreach (var item in configs)            {                try                {                    item.Filters.Clear();                    item.Services.Dispose();                    item.Routes.Dispose();                    item.Formatters.Clear();                    item.Dispose();                }                catch                {                }            }            svrs.Clear();            configs.Clear();            StartOptions options = new StartOptions();            options.Urls.Add($"http://+:{port}");            var svr = WebApp.Start<Startup>(options);            svrs.Add(svr);            GC.Collect();        }        /// <summary>        ///         /// </summary>        /// <param name="app"></param>        public void Configuration(IAppBuilder app)        {            //启用目录浏览和静态文件            Directory.CreateDirectory("wwwroot");            var physicalFileSystem = new PhysicalFileSystem(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot"));//目录浏览物理地址            var options = new FileServerOptions            {                RequestPath = new PathString("/wwwroot"),//目录浏览地址                EnableDefaultFiles = true,                EnableDirectoryBrowsing = true,//启用目录浏览                FileSystem = physicalFileSystem            };            options.StaticFileOptions.FileSystem = physicalFileSystem;            options.StaticFileOptions.ServeUnknownFileTypes = true;//允许下载wwwroot中的所有类型文件            app.UseFileServer(options);            HttpConfiguration config = new HttpConfiguration();            configs.Add(config);            IEnumerable<ModelValidatorProvider> modelValidatorProviders = config.Services.GetModelValidatorProviders();            DataAnnotationsModelValidatorProvider provider = (DataAnnotationsModelValidatorProvider)                    modelValidatorProviders.Single(x => x is DataAnnotationsModelValidatorProvider);            //var provider2 = (DataMemberModelValidatorProvider)            //  modelValidatorProviders.Single(x => x is DataMemberModelValidatorProvider);            provider.RegisterDefaultValidatableObjectAdapter(typeof(CustomModelValidator));            JsonSerializerSettings setting = new JsonSerializerSettings()            {                //日期类型默认格式化处理                DateFormatHandling = DateFormatHandling.MicrosoftDateFormat,                DateFormatString = "yyyy-MM-dd HH:mm:ss.fff",                //驼峰样式                //ContractResolver = new CamelCasePropertyNamesContractResolver(),                //空值处理                //NullValueHandling = NullValueHandling.Ignore,                //设置序列化的最大层数                MaxDepth = 10,                //解决json序列化时的循环引用问题                ReferenceLoopHandling = ReferenceLoopHandling.Ignore            };            config.Formatters.JsonFormatter.SerializerSettings = setting;            config.Formatters.Remove(config.Formatters.XmlFormatter);            config.Filters.Add(new HandlerErrorAttribute());            config.Filters.Add(new ValidateFilter());            //config.Routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = @"\d+" });            config.Routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");            //config.Routes.MapHttpRoute("DefaultApiGet", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });            //config.Routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new { action = "Post" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) });            ConfigureSwagger(config);            //添加路由路径            config.MapHttpAttributeRoutes();            var builder = new ContainerBuilder();            var controllerAssemblys = AppDomain.CurrentDomain.GetAssemblies().Where(p =>                p.GetTypes().Any(t => t.BaseType == typeof(BaseController))).ToArray();            builder.RegisterApiControllers(controllerAssemblys);            var serviceTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(p => p.GetTypes())                .Where(p => p.Namespace != null && p.Namespace.EndsWith(".Service")).ToList();            foreach (var serviceType in serviceTypes)            {                //单例模式注入Service                builder.RegisterType(serviceType).SingleInstance();            }            var container = builder.Build();            config.DependencyResolver = new AutofacWebApiDependencyResolver(container);            GlobalConfiguration.Configuration.DependencyResolver = config.DependencyResolver;            AutofacUtil.Container = container;            //app.UseAutofacLifetimeScopeInjector(container);            app.UseAutofacMiddleware(container);            app.UseAutofacWebApi(config);            app.UseCors(CorsOptions.AllowAll);            app.UseWebApi(config);        }        private static void ConfigureSwagger(HttpConfiguration config)        {            var thisAssembly = typeof(Startup).Assembly;            string exeName = Assembly.GetEntryAssembly().GetName().Name;            config.EnableSwagger(c =>             {                 c.IgnoreObsoleteActions();//忽略过时的方法                 c.IgnoreObsoleteProperties();//忽略过时的属性                 c.PrettyPrint();//漂亮缩进                 c.SingleApiVersion("v1", $"{exeName}Http接口");                 c.ApiKey("123456");                 var webApiXmlPath0 = $"{AppDomain.CurrentDomain.BaseDirectory}{System.Reflection.Assembly.GetAssembly(typeof(Startup)).GetName().Name}.xml";                 c.IncludeXmlComments(webApiXmlPath0);//WebApi模型描述                 var webApiXmlPath1 = $"{AppDomain.CurrentDomain.BaseDirectory}{Path.GetFileNameWithoutExtension(_dtoXmlName)}.xml";                 c.IncludeXmlComments(webApiXmlPath1);//dto模型描述                 var webApiXmlPath2 = $"{AppDomain.CurrentDomain.BaseDirectory}{Path.GetFileNameWithoutExtension(_controllerXmlName)}.xml";                 c.IncludeXmlComments(webApiXmlPath2);//控制器中方法描述                 var webApiXmlPath3 = $"{AppDomain.CurrentDomain.BaseDirectory}{typeof(AjaxResult).Assembly.GetName().Name}.xml";                 c.IncludeXmlComments(webApiXmlPath3);//返回值描述                 //控制器本身描述                 string controllerXmlPath1 = $"{AppDomain.CurrentDomain.BaseDirectory}{Path.GetFileNameWithoutExtension(_controllerXmlName)}.xml";                 c.CustomProvider(defaultProvider => new SwaggerControllerDescProvider(defaultProvider,new string[] { webApiXmlPath0, controllerXmlPath1 }));                 c.OperationFilter<FileUploadOperation>();                 c.SchemaFilter<SwaggerEnumFilter>();                 c.SchemaFilter<SwaggerDefalutValueFilter>();             })             .EnableSwaggerUi(c =>             {                 c.InjectJavaScript(thisAssembly, $"{thisAssembly.GetName().Name}.Swagger.js");                 //c.DocumentTitle($"{exeName}Http接口");             });        }        class FileUploadOperation : IOperationFilter        {            public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)            {                if (operation.parameters == null)                {                    operation.parameters = new List<Swashbuckle.Swagger.Parameter>();                }                var requestAttributes = apiDescription.GetControllerAndActionAttributes<SwaggerFormAttribute>();                foreach (var attr in requestAttributes)                {                    operation.parameters.Add(new Swashbuckle.Swagger.Parameter                    {                        description = attr.Description,                        name = attr.Name,                        @in = "formData",                        required = true,                        type = "file",                    });                    operation.consumes.Add("multipart/form-data");                }            }        }        class ValidateFilter : IActionFilter        {            public bool AllowMultiple { get; }            public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)            {                if (!actionContext.ModelState.IsValid)                {                    string msg = "";                    var err = actionContext.ModelState.Values?.Last()?.Errors?.Last();                    if (err != null)                    {                        if (!string.IsNullOrWhiteSpace(err.ErrorMessage))                            msg = err.ErrorMessage;                        else                            msg = err.Exception.Message;                    }                    return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)                    {                        Content = new StringContent(                        JsonConvert.SerializeObject(                        new AjaxResult                        {                            code = 0,                            data = null,                            msg = msg,                        }), Encoding.UTF8, "application/json")                    });                }                return continuation();            }        }        class HandlerErrorAttribute : ExceptionFilterAttribute        {            /// <summary>            /// 控制器方法中出现异常,会调用该方法捕获异常            /// </summary>            /// <param name="context">提供使用</param>            public override void OnException(HttpActionExecutedContext context)            {                if (context.Exception.GetType() != typeof(System.OperationCanceledException))                    Serilog.Log.Error(context.Exception, context.Exception.Message);                else                    return;                base.OnException(context);                string msg = context.Exception.Message;                if (context.Exception.GetType() == typeof(FileNotFoundException))                {                    //防止程序路径泄露到前端                    msg = "未能找到文件" + context.Exception.Message.Substring(context.Exception.Message.LastIndexOf("\\") + 1);                }                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.OK)                {                    Content = new StringContent(                   JsonConvert.SerializeObject(                    new AjaxResult                    {                        code = 0,                        data = null,                        msg = msg                    }), Encoding.UTF8, "application/json")                });            }        };        class SwaggerControllerDescProvider : ISwaggerProvider        {            private readonly ISwaggerProvider _swaggerProvider;            private static ConcurrentDictionary<string, SwaggerDocument> _cache = new ConcurrentDictionary<string, SwaggerDocument>();            private readonly string[] _xml;            /// <summary>            ///             /// </summary>            /// <param name="swaggerProvider"></param>            /// <param name="xml">xml文档路径</param>            public SwaggerControllerDescProvider(ISwaggerProvider swaggerProvider, string[] xml)            {                _swaggerProvider = swaggerProvider;                _xml = xml;            }            public SwaggerDocument GetSwagger(string rootUrl, string apiVersion)            {                var cacheKey = string.Format("{0}_{1}", rootUrl, apiVersion);                SwaggerDocument srcDoc = null;                //只读取一次                if (!_cache.TryGetValue(cacheKey, out srcDoc))                {                    srcDoc = _swaggerProvider.GetSwagger(rootUrl, apiVersion);                    srcDoc.vendorExtensions = new Dictionary<string, object> { { "ControllerDesc", GetControllerDesc() } };                    _cache.TryAdd(cacheKey, srcDoc);                }                return srcDoc;            }            /// <summary>            /// 从API文档中读取控制器描述            /// </summary>            /// <returns>所有控制器描述</returns>            public ConcurrentDictionary<string, string> GetControllerDesc()            {                ConcurrentDictionary<string, string> controllerDescDict = new ConcurrentDictionary<string, string>();                if (_xml != null)                {                    foreach (var item in _xml)                    {                        if (File.Exists(item))                        {                            XmlDocument xmldoc = new XmlDocument();                            xmldoc.Load(item);                            string type = string.Empty, path = string.Empty, controllerName = string.Empty;                            string[] arrPath;                            int length = -1, cCount = "Controller".Length;                            XmlNode summaryNode = null;                            foreach (XmlNode node in xmldoc.SelectNodes("//member"))                            {                                type = node.Attributes["name"].Value;                                if (type.StartsWith("T:"))                                {                                    //控制器                                    arrPath = type.Split('.');                                    length = arrPath.Length;                                    controllerName = arrPath[length - 1];                                    if (controllerName.EndsWith("Controller"))                                    {                                        //获取控制器注释                                        summaryNode = node.SelectSingleNode("summary");                                        string key = controllerName.Remove(controllerName.Length - cCount, cCount);                                        if (summaryNode != null && !string.IsNullOrEmpty(summaryNode.InnerText) && !controllerDescDict.ContainsKey(key))                                        {                                            controllerDescDict.TryAdd(key, summaryNode.InnerText.Trim());                                        }                                    }                                }                            }                        }                    }                                    }                return controllerDescDict;            }        }    }    /// <summary>    /// Swagger文件上传特性标注    /// </summary>    [AttributeUsage(AttributeTargets.Method)]    public sealed class SwaggerFormAttribute : Attribute    {        /// <summary>        ///         /// </summary>        public SwaggerFormAttribute()        {            this.Name = "文件";            this.Description = "选择文件";        }        /// <summary>        /// Swagger特性标注        /// </summary>        /// <param name="name"></param>        /// <param name="description"></param>        public SwaggerFormAttribute(string name, string description)        {            Name = name;            Description = description;        }        /// <summary>        /// 名称        /// </summary>        public string Name { get; private set; }        /// <summary>        /// 描述        /// </summary>        public string Description { get; private set; }    }    /// <summary>    /// autofac属性注入    /// </summary>    [AttributeUsage(AttributeTargets.Property)]    public class AutowiredAttribute : Attribute    {    }    // 属性注入选择器    class AutowiredPropertySelector : IPropertySelector    {        public bool InjectProperty(PropertyInfo propertyInfo, object instance)        {            // 带有 AutowiredAttribute 特性的属性会进行属性注入            return propertyInfo.CustomAttributes.Any(it => it.AttributeType == typeof(AutowiredAttribute));        }    }    class SwaggerEnumFilter : ISchemaFilter    {        public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)        {            UpdateSchemaDescription(schema, type);        }        private void UpdateSchemaDescription(Schema schema, Type type)        {            if (type.IsEnum)//枚举直接应用在controller接口中            {                var items = GetEnumInfo(type);                if (items.Length > 0)                {                    var description = GetEnumInfo(type);                    schema.description = string.IsNullOrEmpty(schema.description) ? description : $"{schema.description}:{description}";                }            }            else if (type.IsClass && type != typeof(string))//枚举在类的属性中            {                if (schema.properties == null) return;                var props = type.GetProperties();                foreach (var prop in props)                {                    if (schema.properties.ContainsKey(prop.Name))                    {                        var propScheama = schema.properties[prop.Name];                        if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string))                        {                            UpdateSchemaDescription(propScheama, prop.PropertyType);                        }                        else                        {                            if (prop.PropertyType.IsEnum)                            {                                var description = GetEnumInfo(prop.PropertyType);                                propScheama.description = string.IsNullOrWhiteSpace(propScheama.description) ? description : $"{propScheama.description}:{description}";                                propScheama.@enum = null;                            }                        }                    }                }            }        }        /// <summary>        /// 获取枚举值+描述          /// </summary>        /// <param name="enumType"></param>        /// <returns></returns>        private string GetEnumInfo(Type enumType)        {            var fields = enumType.GetFields();            List<string> list = new List<string>();            foreach (var field in fields)            {                if (!field.FieldType.IsEnum) continue;                string description = null;                if (description == null)//取DescriptionAttribute的值                {                    var descriptionAttr = field.GetCustomAttribute<DescriptionAttribute>();                    if (descriptionAttr != null && !string.IsNullOrWhiteSpace(descriptionAttr.Description))                    {                        description = descriptionAttr.Description;                    }                }                if (description == null)//取DisplayAttribute的值                {                    var dispalyAttr = field.GetCustomAttribute<DisplayAttribute>();                    if (dispalyAttr != null && !string.IsNullOrWhiteSpace(dispalyAttr.Name))                    {                        description = dispalyAttr.Name;                    }                }                if (description == null)//取DisplayNameAttribute的值                {                    var dispalyNameAttr = field.GetCustomAttribute<DisplayNameAttribute>();                    if (dispalyNameAttr != null && !string.IsNullOrWhiteSpace(dispalyNameAttr.DisplayName))                    {                        description = dispalyNameAttr.DisplayName;                    }                }                if (description == null)//取字段名                {                    description = field.Name;                }                var value = field.GetValue(null);                list.Add($"{description}={(int)value}");            }            if (enumType.GetCustomAttribute<FlagsAttribute>() != null)//支持按位与的枚举            {                list.Add("(多个类型请将对应数字相加)");            }            return string.Join(",", list);        }    }    class CustomModelValidator : ModelValidator    {        public CustomModelValidator(IEnumerable<ModelValidatorProvider> modelValidatorProviders) : base(modelValidatorProviders)        {        }        public override IEnumerable<ModelValidationResult> Validate(ModelMetadata metadata, object container)        {            if (metadata.IsComplexType && metadata.Model == null)            {                return new List<ModelValidationResult> { new ModelValidationResult { MemberName = metadata.GetDisplayName(), Message = "请求参数对象不能为空" } };            }            if (typeof(IValidatableObject).IsAssignableFrom(metadata.ModelType))            {                var validationResult = (metadata.Model as IValidatableObject).Validate(new ValidationContext(metadata.Model));                if (validationResult != null)                {                    var modelValidationResults = new List<ModelValidationResult>();                    foreach (var result in validationResult)                    {                        if (result == null) continue;                        modelValidationResults.Add(new ModelValidationResult                        {                            MemberName = string.Join(",", result.MemberNames),                            Message = result.ErrorMessage                        });                    }                    return modelValidationResults;                }                return null;            }            return GetModelValidator(ValidatorProviders).Validate(metadata, container);        }    }    class SwaggerDefalutValueFilter : ISchemaFilter    {        public SwaggerDefalutValueFilter()        {            var cc = this.GetHashCode();        }        public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)        {            if (schema.properties == null)            {                return;            }            var props = type.GetProperties().Where(p => p.PropertyType == typeof(DateTime) || p.PropertyType == typeof(DateTime?));            var props2 = type.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset) || p.PropertyType == typeof(DateTimeOffset?));            foreach (PropertyInfo propertyInfo in props)            {                foreach (KeyValuePair<string, Schema> property in schema.properties)                {                    if (propertyInfo.Name == property.Key)                    {                        property.Value.example = "2023-05-12 12:00:00";                        if (!string.IsNullOrWhiteSpace(Startup._timeZoneUtc) && propertyInfo.Name.EndsWith("Time"))                        {                            property.Value.description = $"{property.Value.description}({Startup._timeZoneUtc})";                        }                        else                        {                        }                        break;                    }                }            }            foreach (PropertyInfo propertyInfo in props2)            {                foreach (KeyValuePair<string, Schema> property in schema.properties)                {                    if (propertyInfo.Name == property.Key)                    {                        property.Value.example = "2023-05-12 12:00:00 +0800";                        if (!string.IsNullOrWhiteSpace(Startup._timeZoneUtc) && propertyInfo.Name.EndsWith("Time"))                        {                            property.Value.description = $"{property.Value.description}({Startup._timeZoneUtc})";                        }                        else                        {                        }                        break;                    }                }            }        }    }}
 |