using NCC.Dependency; using NCC.DynamicApiController; using NCC.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerUI; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; namespace NCC.SpecificationDocument { /// /// 规范化文档构建器 /// [SuppressSniffer] public static class SpecificationDocumentBuilder { /// /// 规范化文档配置 /// private static readonly SpecificationDocumentSettingsOptions _specificationDocumentSettings; /// /// 应用全局配置 /// private static readonly AppSettingsOptions _appSettings; /// /// 分组信息 /// private static readonly IEnumerable DocumentGroupExtras; /// /// 带排序的分组名 /// private static readonly Regex _groupOrderRegex; /// /// 文档分组列表 /// public static readonly IEnumerable DocumentGroups; /// /// 构造函数 /// static SpecificationDocumentBuilder() { // 载入配置 _specificationDocumentSettings = App.GetOptions(); _appSettings = App.Settings; // 初始化常量 _groupOrderRegex = new Regex(@"@(?[0-9]+$)"); GetActionGroupsCached = new ConcurrentDictionary>(); GetControllerGroupsCached = new ConcurrentDictionary>(); GetGroupOpenApiInfoCached = new ConcurrentDictionary(); GetControllerTagCached = new ConcurrentDictionary(); GetActionTagCached = new ConcurrentDictionary(); // 默认分组,支持多个逗号分割 DocumentGroupExtras = new List { ResolveGroupExtraInfo(_specificationDocumentSettings.DefaultGroupName) }; // 加载所有分组 DocumentGroups = ReadGroups(); } /// /// 检查方法是否在分组中 /// /// /// /// public static bool CheckApiDescriptionInCurrentGroup(string currentGroup, ApiDescription apiDescription) { if (!apiDescription.TryGetMethodInfo(out var method) || typeof(Controller).IsAssignableFrom(method.ReflectedType)) return false; return GetActionGroups(method).Any(u => u.Group == currentGroup); } /// /// 构建Swagger全局配置 /// /// Swagger 全局配置 /// internal static void Build(SwaggerOptions swaggerOptions, Action configure = null) { // 生成V2版本 swaggerOptions.SerializeAsV2 = _specificationDocumentSettings.FormatAsV2 == true; // 判断是否启用 Server if (_specificationDocumentSettings.HideServers != true) { // 启动服务器 Servers swaggerOptions.PreSerializeFilters.Add((swagger, request) => { // 默认 Server var servers = new List { new OpenApiServer { Url = $"{request.Scheme}://{request.Host.Value}{_appSettings.VirtualPath}",Description="Default" } }; servers.AddRange(_specificationDocumentSettings.Servers); swagger.Servers = servers; }); } // 配置路由模板 swaggerOptions.RouteTemplate = _specificationDocumentSettings.RouteTemplate; // 自定义配置 configure?.Invoke(swaggerOptions); } /// /// Swagger 生成器构建 /// /// Swagger 生成器配置 /// 自定义配置 internal static void BuildGen(SwaggerGenOptions swaggerGenOptions, Action configure = null) { // 创建分组文档 CreateSwaggerDocs(swaggerGenOptions); // 加载分组控制器和动作方法列表 LoadGroupControllerWithActions(swaggerGenOptions); // 配置 Swagger SchemaId ConfigureSchemaId(swaggerGenOptions); // 配置标签 ConfigureTagsAction(swaggerGenOptions); // 配置 Action 排序 ConfigureActionSequence(swaggerGenOptions); // 加载注释描述文件 LoadXmlComments(swaggerGenOptions); // 配置授权 ConfigureSecurities(swaggerGenOptions); //使得 Swagger 能够正确地显示 Enum 的对应关系 if (_specificationDocumentSettings.EnableEnumSchemaFilter == true) swaggerGenOptions.SchemaFilter(); // 支持控制器排序操作 if (_specificationDocumentSettings.EnableTagsOrderDocumentFilter == true) swaggerGenOptions.DocumentFilter(); // 自定义配置 configure?.Invoke(swaggerGenOptions); } /// /// Swagger UI 构建 /// /// /// /// internal static void BuildUI(SwaggerUIOptions swaggerUIOptions, string routePrefix = default, Action configure = null) { // 配置分组终点路由 CreateGroupEndpoint(swaggerUIOptions); // 配置文档标题 swaggerUIOptions.DocumentTitle = _specificationDocumentSettings.DocumentTitle; // 配置UI地址(处理二级虚拟目录) swaggerUIOptions.RoutePrefix = _specificationDocumentSettings.RoutePrefix ?? routePrefix ?? "api"; // 文档展开设置 swaggerUIOptions.DocExpansion(_specificationDocumentSettings.DocExpansionState.Value); // 注入 MiniProfiler 组件 InjectMiniProfilerPlugin(swaggerUIOptions); // 配置多语言和自动登录token AddDefaultInterceptor(swaggerUIOptions); // 自定义配置 configure?.Invoke(swaggerUIOptions); } /// /// 创建分组文档 /// /// Swagger生成器对象 private static void CreateSwaggerDocs(SwaggerGenOptions swaggerGenOptions) { foreach (var group in DocumentGroups) { var groupOpenApiInfo = GetGroupOpenApiInfo(group) as OpenApiInfo; swaggerGenOptions.SwaggerDoc(group, groupOpenApiInfo); } } /// /// 加载分组控制器和动作方法列表 /// /// Swagger 生成器配置 private static void LoadGroupControllerWithActions(SwaggerGenOptions swaggerGenOptions) { swaggerGenOptions.DocInclusionPredicate(CheckApiDescriptionInCurrentGroup); } /// /// 配置标签 /// /// private static void ConfigureTagsAction(SwaggerGenOptions swaggerGenOptions) { swaggerGenOptions.TagActionsBy(apiDescription => { return new[] { GetActionTag(apiDescription) }; }); } /// /// 配置 Action 排序 /// /// private static void ConfigureActionSequence(SwaggerGenOptions swaggerGenOptions) { swaggerGenOptions.OrderActionsBy(apiDesc => { var apiDescriptionSettings = apiDesc.CustomAttributes() .FirstOrDefault(u => u.GetType() == typeof(ApiDescriptionSettingsAttribute)) as ApiDescriptionSettingsAttribute ?? new ApiDescriptionSettingsAttribute(); return (int.MaxValue - apiDescriptionSettings.Order).ToString() .PadLeft(int.MaxValue.ToString().Length, '0'); }); } /// /// 配置 Swagger SchemaId /// /// Swagger 生成器配置 private static void ConfigureSchemaId(SwaggerGenOptions swaggerGenOptions) { // 本地函数 static string DefaultSchemaIdSelector(Type modelType) { if (!modelType.IsConstructedGenericType) return modelType.Name; var prefix = modelType.GetGenericArguments() .Select(genericArg => DefaultSchemaIdSelector(genericArg)) .Aggregate((previous, current) => previous + current); // 通过 _ 拼接多个泛型 return modelType.Name.Split('`').First() + "_" + prefix; } // 调用本地函数 swaggerGenOptions.CustomSchemaIds(modelType => DefaultSchemaIdSelector(modelType)); } /// /// 加载注释描述文件 /// /// Swagger 生成器配置 private static void LoadXmlComments(SwaggerGenOptions swaggerGenOptions) { var xmlComments = _specificationDocumentSettings.XmlComments; foreach (var xmlComment in xmlComments) { var assemblyXmlName = xmlComment.EndsWith(".xml") ? xmlComment : $"{xmlComment}.xml"; var assemblyXmlPath = Path.Combine(AppContext.BaseDirectory, assemblyXmlName); if (File.Exists(assemblyXmlPath)) { swaggerGenOptions.IncludeXmlComments(assemblyXmlPath, true); } } } /// /// 配置授权 /// /// Swagger 生成器配置 private static void ConfigureSecurities(SwaggerGenOptions swaggerGenOptions) { // 判断是否启用了授权 if (_specificationDocumentSettings.EnableAuthorized != true || _specificationDocumentSettings.SecurityDefinitions.Length == 0) return; var openApiSecurityRequirement = new OpenApiSecurityRequirement(); // 生成安全定义 foreach (var securityDefinition in _specificationDocumentSettings.SecurityDefinitions) { // Id 必须定义 if (string.IsNullOrWhiteSpace(securityDefinition.Id)) continue; // 添加安全定义 var openApiSecurityScheme = securityDefinition as OpenApiSecurityScheme; swaggerGenOptions.AddSecurityDefinition(securityDefinition.Id, openApiSecurityScheme); // 添加安全需求 var securityRequirement = securityDefinition.Requirement; // C# 9.0 模式匹配新语法 if (securityRequirement is { Scheme: { Reference: not null } }) { securityRequirement.Scheme.Reference.Id ??= securityDefinition.Id; openApiSecurityRequirement.Add(securityRequirement.Scheme, securityRequirement.Accesses); } } // 添加安全需求 if (openApiSecurityRequirement.Count > 0) { swaggerGenOptions.AddSecurityRequirement(openApiSecurityRequirement); } } /// /// 配置分组终点路由 /// /// private static void CreateGroupEndpoint(SwaggerUIOptions swaggerUIOptions) { foreach (var group in DocumentGroups) { var groupOpenApiInfo = GetGroupOpenApiInfo(group); // 替换路由模板 var routeTemplate = _specificationDocumentSettings.RouteTemplate.Replace("{documentName}", Uri.EscapeDataString(group)); swaggerUIOptions.SwaggerEndpoint($"{_appSettings.VirtualPath}/{routeTemplate}", groupOpenApiInfo?.Title ?? group); } } /// /// 注入 MiniProfiler 插件 /// /// private static void InjectMiniProfilerPlugin(SwaggerUIOptions swaggerUIOptions) { // 启用 MiniProfiler 组件 var thisType = typeof(SpecificationDocumentBuilder); var thisAssembly = thisType.Assembly; // 自定义 Swagger 首页 var customIndex = $"{Reflect.GetAssemblyName(thisAssembly)}{thisType.Namespace.Replace(nameof(NCC), string.Empty)}.Assets.{(App.Settings.InjectMiniProfiler != true ? "index" : "index-mini-profiler")}.html"; swaggerUIOptions.IndexStream = () => thisAssembly.GetManifestResourceStream(customIndex); } /// /// 添加默认请求/响应拦截器 /// /// private static void AddDefaultInterceptor(SwaggerUIOptions swaggerUIOptions) { // 配置多语言和自动登录token swaggerUIOptions.UseRequestInterceptor("(request) => { return defaultRequestInterceptor(request); }"); swaggerUIOptions.UseResponseInterceptor("(response) => { return defaultResponseInterceptor(response); }"); } /// /// 获取分组信息缓存集合 /// private static readonly ConcurrentDictionary GetGroupOpenApiInfoCached; /// /// 获取分组配置信息 /// /// /// private static SpecificationOpenApiInfo GetGroupOpenApiInfo(string group) { return GetGroupOpenApiInfoCached.GetOrAdd(group, Function); // 本地函数 static SpecificationOpenApiInfo Function(string group) { return _specificationDocumentSettings.GroupOpenApiInfos.FirstOrDefault(u => u.Group == group) ?? new SpecificationOpenApiInfo { Group = group }; } } /// /// 读取所有分组信息 /// /// private static IEnumerable ReadGroups() { // 获取所有的控制器和动作方法 var controllers = App.EffectiveTypes.Where(u => Penetrates.IsApiController(u)); if (!controllers.Any()) return new[] { _specificationDocumentSettings.DefaultGroupName }; var actions = controllers.SelectMany(c => c.GetMethods().Where(u => IsApiAction(u, c))); // 合并所有分组 var groupOrders = controllers.SelectMany(u => GetControllerGroups(u)) .Union( actions.SelectMany(u => GetActionGroups(u)) ) .Where(u => u != null && u.Visible) // 分组后取最大排序 .GroupBy(u => u.Group) .Select(u => new GroupExtraInfo { Group = u.Key, Order = u.Max(x => x.Order), Visible = true }); // 分组排序 return groupOrders .OrderByDescending(u => u.Order) .ThenBy(u => u.Group) .Select(u => u.Group) .Union(_specificationDocumentSettings.PackagesGroups); } /// /// 获取控制器组缓存集合 /// private static readonly ConcurrentDictionary> GetControllerGroupsCached; /// /// 获取控制器分组列表 /// /// /// private static IEnumerable GetControllerGroups(Type type) { return GetControllerGroupsCached.GetOrAdd(type, Function); // 本地函数 static IEnumerable Function(Type type) { // 如果控制器没有定义 [ApiDescriptionSettings] 特性,则返回默认分组 if (!type.IsDefined(typeof(ApiDescriptionSettingsAttribute), true)) return DocumentGroupExtras; // 读取分组 var apiDescriptionSettings = type.GetCustomAttribute(true); if (apiDescriptionSettings.Groups == null || apiDescriptionSettings.Groups.Length == 0) return DocumentGroupExtras; // 处理分组额外信息 var groupExtras = new List(); foreach (var group in apiDescriptionSettings.Groups) { groupExtras.Add(ResolveGroupExtraInfo(group)); } return groupExtras; } } /// /// 缓存集合 /// private static readonly ConcurrentDictionary> GetActionGroupsCached; /// /// 获取动作方法分组列表 /// /// 方法 /// private static IEnumerable GetActionGroups(MethodInfo method) { return GetActionGroupsCached.GetOrAdd(method, Function); // 本地函数 static IEnumerable Function(MethodInfo method) { // 如果动作方法没有定义 [ApiDescriptionSettings] 特性,则返回所在控制器分组 if (!method.IsDefined(typeof(ApiDescriptionSettingsAttribute), true)) return GetControllerGroups(method.ReflectedType); // 读取分组 var apiDescriptionSettings = method.GetCustomAttribute(true); if (apiDescriptionSettings.Groups == null || apiDescriptionSettings.Groups.Length == 0) return GetControllerGroups(method.ReflectedType); // 处理排序 var groupExtras = new List(); foreach (var group in apiDescriptionSettings.Groups) { groupExtras.Add(ResolveGroupExtraInfo(group)); } return groupExtras; } } /// /// 缓存集合 /// private static readonly ConcurrentDictionary GetControllerTagCached; /// /// 获取控制器标签 /// /// 控制器接口描述器 /// private static string GetControllerTag(ControllerActionDescriptor controllerActionDescriptor) { return GetControllerTagCached.GetOrAdd(controllerActionDescriptor, Function); // 本地函数 static string Function(ControllerActionDescriptor controllerActionDescriptor) { var type = controllerActionDescriptor.ControllerTypeInfo; // 如果动作方法没有定义 [ApiDescriptionSettings] 特性,则返回所在控制器名 if (!type.IsDefined(typeof(ApiDescriptionSettingsAttribute), true)) return controllerActionDescriptor.ControllerName; // 读取标签 var apiDescriptionSettings = type.GetCustomAttribute(true); return string.IsNullOrWhiteSpace(apiDescriptionSettings.Tag) ? controllerActionDescriptor.ControllerName : apiDescriptionSettings.Tag; } } /// /// 缓存集合 /// private static readonly ConcurrentDictionary GetActionTagCached; /// /// 获取动作方法标签 /// /// 接口描述器 /// private static string GetActionTag(ApiDescription apiDescription) { return GetActionTagCached.GetOrAdd(apiDescription, Function); // 本地函数 static string Function(ApiDescription apiDescription) { if (!apiDescription.TryGetMethodInfo(out var method)) return "unknown"; // 获取控制器描述器 var controllerActionDescriptor = apiDescription.ActionDescriptor as ControllerActionDescriptor; // 如果动作方法没有定义 [ApiDescriptionSettings] 特性,则返回所在控制器名 if (!method.IsDefined(typeof(ApiDescriptionSettingsAttribute), true)) return GetControllerTag(controllerActionDescriptor); // 读取标签 var apiDescriptionSettings = method.GetCustomAttribute(true); return string.IsNullOrWhiteSpace(apiDescriptionSettings.Tag) ? GetControllerTag(controllerActionDescriptor) : apiDescriptionSettings.Tag; } } /// /// 是否是动作方法 /// /// 方法 /// 声明类型 /// private static bool IsApiAction(MethodInfo method, Type ReflectedType) { // 不是非公开、抽象、静态、泛型方法 if (!method.IsPublic || method.IsAbstract || method.IsStatic || method.IsGenericMethod) return false; // 如果所在类型不是控制器,则该行为也被忽略 if (method.ReflectedType != ReflectedType || method.DeclaringType == typeof(object)) return false; // 不是能被导出忽略的接方法 if (method.IsDefined(typeof(ApiExplorerSettingsAttribute), true) && method.GetCustomAttribute(true).IgnoreApi) return false; return true; } /// /// 解析分组附加信息 /// /// 分组名 /// private static GroupExtraInfo ResolveGroupExtraInfo(string group) { string realGroup; var order = 0; if (!_groupOrderRegex.IsMatch(group)) realGroup = group; else { realGroup = _groupOrderRegex.Replace(group, ""); order = int.Parse(_groupOrderRegex.Match(group).Groups["order"].Value); } var groupOpenApiInfo = GetGroupOpenApiInfo(realGroup); return new GroupExtraInfo { Group = realGroup, Order = groupOpenApiInfo.Order ?? order, Visible = groupOpenApiInfo.Visible ?? true }; } } }