using System; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text.Json; using Hangfire; using Hangfire.Console; using Hangfire.Redis.StackExchange; using SimApi.Helpers; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi; using SimApi.Middlewares; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using SimApi.Attributes; using SimApi.AuthSDK; using SimApi.Configurations; using SimApi.Interfaces; using SimApi.Logger; using SimApi.SwaggerFilters; using StackExchange.Redis; namespace SimApi; /// /// 加入系统的扩展信息 /// public static class SimApiExtensions { //**********快捷添加************** public static IServiceCollection AddSimApi(this IServiceCollection builder, Action? options = null) { var simApiOptions = new SimApiOptions(); options?.Invoke(simApiOptions); if (simApiOptions.RedisConfiguration != null) { builder.AddStackExchangeRedisCache(x => x.Configuration = simApiOptions.RedisConfiguration); builder.AddSingleton(_ => ConnectionMultiplexer.Connect(simApiOptions.RedisConfiguration)); builder.AddSingleton(); } if (simApiOptions.EnableLogger) { builder.AddLogging(logger => { logger.ClearProviders(); logger.AddProvider(new SimApiLoggerProvider()); }); } // 是否使用 AUTH if (simApiOptions.EnableSimApiAuth) { builder.AddSingleton(); } var simApiAuthChecker = typeof(ISimApiAuthChecker); var stackTrace = new StackTrace(); var callingMethod = stackTrace.GetFrame(stackTrace.FrameCount - 1)?.GetMethod(); var callerAssembly = callingMethod?.DeclaringType?.Assembly; var callerTypes = callerAssembly?.GetTypes() ?? []; foreach (var type in callerTypes) { if (type is { IsClass: true, IsAbstract: false } && simApiAuthChecker.IsAssignableFrom(type)) { builder.AddScoped(simApiAuthChecker, type); } } if (simApiOptions.EnableJob) { builder.AddHangfire(x => { var redisOption = new RedisStorageOptions(); if (simApiOptions.SimApiJobOptions.Database.HasValue) { redisOption.Db = simApiOptions.SimApiJobOptions.Database.Value; } x.UseRedisStorage(simApiOptions.SimApiJobOptions.RedisConfiguration ?? simApiOptions.RedisConfiguration, redisOption); x.UseConsole(); }); foreach (var server in simApiOptions.SimApiJobOptions.Servers) { builder.AddHangfireServer(hfs => { hfs.Queues = server.Queues; hfs.WorkerCount = server.WorkerNum; }); } } if (simApiOptions.EnableCors) { builder.AddCors(cors => cors.AddPolicy("any", policy => { policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin(); })); } if (simApiOptions.EnableSimApiHttpClient) { builder.AddSingleton(); } if (simApiOptions.EnableSynapse) { builder.AddSingleton(); foreach (var type in callerTypes) { var methodsWithSynapse = type.GetMethods() .Where(m => m.GetCustomAttribute() != null || m.GetCustomAttribute() != null); if (!methodsWithSynapse.Any()) continue; builder.AddScoped(type); } } // 使用SimApiDoc if (simApiOptions.EnableSimApiDoc) { var docOptions = simApiOptions.SimApiDocOptions; builder.AddSwaggerGen(x => { foreach (var group in docOptions.ApiGroups) { x.SwaggerDoc(group.Id, new OpenApiInfo { Title = group.Name, Description = group.Description }); } x.CustomSchemaIds(type => { // 递归解析类型名称(处理嵌套泛型/数组/可空 + 保证唯一性) string GetSimpleTypeName(Type t, int depth = 0) { // 防止无限递归 if (depth > 5) return t.Name.Split('`')[0]; // 处理数组类型 if (t.IsArray) { var elementType = t.GetElementType(); return $"{GetSimpleTypeName(elementType!, depth + 1)}[]"; } // 处理可空类型 if (Nullable.GetUnderlyingType(t) != null) { var underlyingType = Nullable.GetUnderlyingType(t); return GetSimpleTypeName(underlyingType!, depth + 1); } // 处理泛型类型(递归解析嵌套泛型) if (t.IsGenericType) { var genericBaseName = t.GetGenericTypeDefinition().Name.Split('`')[0]; var genericArgs = t.GetGenericArguments() .Select(arg => GetSimpleTypeName(arg, depth + 1)) .Where(arg => !string.IsNullOrEmpty(arg)) .ToArray(); return $"{genericBaseName}<{string.Join(",", genericArgs)}>"; } // 处理基础类型(小写) if (t.IsPrimitive || t == typeof(string) || t == typeof(DateTime) || t == typeof(Guid)) { return t.Name switch { "String" => "string", "Int32" => "int", "Int64" => "long", "Boolean" => "boolean", "DateTime" => "datetime", "Guid" => "guid", _ => t.Name.ToLower() }; } // 核心修复:生成唯一名称(处理嵌套类/同名不同类) var typeName = t.Name; // 步骤1:处理嵌套类(如 ApplicationDto+ApplicationEditRequest → ApplicationDto_ApplicationEditRequest) if (t.DeclaringType != null) { typeName = $"{GetSimpleTypeName(t.DeclaringType)}.{typeName}"; } // 步骤2:(可选)处理同命名空间下的同名类(拼接命名空间前缀,避免全局重复) // 如需更严格的唯一性,取消注释下面这行 // typeName = $"{t.Namespace?.Replace(".", "_")}_{typeName}"; return typeName; } // 根调用:解析当前类型 var uniqueSchemaId = GetSimpleTypeName(type); // 可选:移除特殊字符(如 $、+),避免 Swagger 解析问题 return uniqueSchemaId.Replace("$", "").Replace("+", "_"); }); x.OperationFilter(); x.OperationFilter(); x.OperationFilter(); x.SchemaFilter(); x.SchemaFilter(); x.DocumentFilter(); if (simApiOptions.EnableSimApiAuth) { x.OperationFilter(); } x.DocInclusionPredicate((docName, apiDesc) => { // 获取接口标记的 GroupName(未标记则为 null) var actionGroupName = apiDesc.ActionDescriptor.EndpointMetadata .OfType() .FirstOrDefault()?.GroupName; // 情况1:接口未标记任何 GroupName(actionGroupName 为 null) if (actionGroupName == null) { return docName == "api"; // 未分组接口只属于默认分组v1 } return docName == actionGroupName; }); x.EnableAnnotations(); var haveOauth = false; var oauthFlows = new OpenApiOAuthFlows(); foreach (var auth in docOptions.ApiAuth.Type) { switch (auth) { case "SimApiAuth": x.AddSecurityDefinition("SimApiAuth", new OpenApiSecurityScheme { Name = "Token", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey }); break; case "ClientCredentials": oauthFlows.ClientCredentials = new OpenApiOAuthFlow { TokenUrl = new Uri(docOptions.ApiAuth.TokenUrl, UriKind.RelativeOrAbsolute), Scopes = docOptions.ApiAuth.Scopes }; haveOauth = true; break; case "Implicit": oauthFlows.Implicit = new OpenApiOAuthFlow { AuthorizationUrl = new Uri(docOptions.ApiAuth.AuthorizationUrl, UriKind.RelativeOrAbsolute), Scopes = docOptions.ApiAuth.Scopes }; haveOauth = true; break; case "AuthorizationCode": oauthFlows.AuthorizationCode = new OpenApiOAuthFlow { TokenUrl = new Uri(docOptions.ApiAuth.TokenUrl, UriKind.RelativeOrAbsolute), AuthorizationUrl = new Uri(docOptions.ApiAuth.AuthorizationUrl, UriKind.RelativeOrAbsolute), Scopes = docOptions.ApiAuth.Scopes }; haveOauth = true; break; case "Password": oauthFlows.Password = new OpenApiOAuthFlow { TokenUrl = new Uri(docOptions.ApiAuth.TokenUrl, UriKind.RelativeOrAbsolute), Scopes = docOptions.ApiAuth.Scopes }; haveOauth = true; break; } } if (haveOauth) { x.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, Flows = oauthFlows, Description = docOptions.ApiAuth.Description, In = ParameterLocation.Header }); } }); } // 使用Header转发,应对代理后获取真实ip if (simApiOptions.EnableForwardHeaders) { builder.Configure(fwOptions => { fwOptions.ForwardedHeaders = ForwardedHeaders.All; fwOptions.KnownNetworks.Clear(); fwOptions.KnownProxies.Clear(); }); } if (simApiOptions.EnableLowerUrl) { builder.AddRouting(rOptions => rOptions.LowercaseUrls = true); } if (simApiOptions.EnableSimApiStorage) { builder.AddHttpContextAccessor(); builder.AddSingleton(); } if (simApiOptions.EnableSimApiResponseFilter) { builder.AddControllers(opt => opt.Filters.Add()) .AddJsonOptions(opt => { opt.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; }); } if (simApiOptions.EnableSimApiAuthGate) { builder.AddSingleton(); builder.AddSingleton(); if (simApiOptions.SimApiAuthCenterOptions.UseIam) { builder.AddSingleton(); } } builder.AddSingleton(simApiOptions.SimApiRequestLogOptions); builder.AddSingleton(simApiOptions); return builder; } public static IHost UseSimApi(this IHost builder) { var options = builder.Services.GetRequiredService(); var logger = builder.Services.GetRequiredService>(); logger.LogInformation("当前时区: {LocalId}", TimeZoneInfo.Local.Id); logger.LogInformation("主应用版本: {AppVersion}\nSimApi版本: {SimApiVersion}", SimApiUtil.AppVersion, SimApiUtil.SimApiVersion); if (options.RedisConfiguration != null) { logger.LogInformation("开始配置 RedisCache ..."); } //请求一下检测存储错误 if (options.EnableSimApiStorage) { logger.LogInformation("开始配置SimApiStorage..."); builder.Services.GetService(); } if (options.EnableSimApiHttpClient) { logger.LogInformation("开始配置SimApiHttpClient...\n服务器地址: {ApiUrl}\nAppId:{AuthUrl}n\nAppkey: {AppId}", options.SimApiHttpClientOptions.Server, options.SimApiHttpClientOptions.AppId, !string.IsNullOrEmpty(options.SimApiHttpClientOptions.AppKey)); } if (options.EnableSynapse) { var synapse = builder.Services.GetRequiredService(); synapse.Init(); } if (options.EnableJob) { logger.LogInformation("开始配置 SimApiJob ..."); } return builder; } /// /// 使用所有SimApi自定义中间件 /// /// /// public static WebApplication UseSimApi(this WebApplication builder) { var options = builder.Services.GetRequiredService(); var logger = builder.Services.GetRequiredService>(); UseSimApi((IHost)builder); if (options.EnableForwardHeaders) { logger.LogInformation("开始配置ForwardedHeaders..."); builder.UseForwardedHeaders(); } if (options.EnableCors) { logger.LogInformation("开始配置Cors全部允许..."); builder.UseCors("any"); } if (options.EnableSimApiResponseFilter) { logger.LogInformation("开始配置SimApiResponseFilter..."); builder.MapControllers(); } var checkers = builder.Services.CreateScope().ServiceProvider.GetServices().ToArray(); if (checkers.Length != 0) { var msg = checkers.Aggregate("开始配置SimApiAuthChecker...", (current, checker) => current + $"\n|- {checker.GetType().FullName}"); logger.LogInformation(msg); } if (options.EnableSimApiAuthGate) { logger.LogInformation("开始配置SimApiAuthGate..."); if (string.IsNullOrEmpty(options.SimApiAuthCenterOptions.AppId) || string.IsNullOrEmpty(options.SimApiAuthCenterOptions.AppKey)) { logger.LogCritical("必须配置AuthGate的AppId和AppKey才能启用SimApiAuthGate"); } else { if (options.SimApiAuthCenterOptions.UseMiddleware) { builder.UseMiddleware(); } } } if (options.EnableSimApiAuth) { logger.LogInformation("开始配置SimApiAuth..."); builder.UseMiddleware(); } if (options.SimApiRouteOptions.VersionRoute != null) { logger.LogInformation("注册内置Route: Versions => {}", options.SimApiRouteOptions.LogoutRoute); builder.MapControllerRoute(name: "Versions", pattern: options.SimApiRouteOptions.VersionRoute, defaults: new { controller = "SimApiCommon", action = "Versions" }); } if (options.SimApiRouteOptions.UserInfoRoute != null) { logger.LogInformation("注册内置Route: UserInfo => {}", options.SimApiRouteOptions.UserInfoRoute); builder.MapControllerRoute(name: "UserInfo", pattern: options.SimApiRouteOptions.UserInfoRoute, defaults: new { controller = "SimApiCommon", action = "UserInfo" }); } if (options.SimApiRouteOptions.LogoutRoute != null) { logger.LogInformation("注册内置Route: Logout => {}", options.SimApiRouteOptions.LogoutRoute); builder.MapControllerRoute(name: "Logout", pattern: options.SimApiRouteOptions.LogoutRoute, defaults: new { controller = "SimApiAuth", action = "Logout" }); } if (options.EnableSimApiDoc) { logger.LogInformation("开始配置SimApiDoc..."); var docOptions = options.SimApiDocOptions; builder.UseSwagger(x => x.RouteTemplate = "/swagger/{documentName}.json") .UseSwaggerUI(x => { x.DocumentTitle = docOptions.DocumentTitle; foreach (var group in docOptions.ApiGroups) { x.SwaggerEndpoint($"{group.Id}.json", name: group.Name); } x.SupportedSubmitMethods(docOptions.SupportedMethod); x.DisplayRequestDuration(); }); } if (options.EnableRequestLog) { logger.LogInformation("开始配置SimApiRequestLog..."); builder.UseMiddleware(); } if (options.EnableSimApiException) { logger.LogInformation("开始配置SimApiException..."); builder.UseMiddleware(); } if (options.EnableLowerUrl) { logger.LogInformation("开始配置使用URL小写..."); } if (options is { EnableJob: true, SimApiJobOptions.DashboardUrl: not null }) { logger.LogInformation("开始配置 SimApiJob Web控制台..."); builder.UseHangfireDashboard(options.SimApiJobOptions.DashboardUrl, new DashboardOptions { Authorization = [ new SimApiJobWebAuth(options.SimApiJobOptions.DashboardAuthUser, options.SimApiJobOptions.DashboardAuthPass) ] }); } return builder; } }