using System; using System.Collections.Generic; 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.CoceSdk; using SimApi.Configurations; using SimApi.Logger; using SimApi.SwaggerFilters; 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(); } if (simApiOptions.EnableLogger) { builder.AddLogging(logger => { logger.ClearProviders(); logger.AddProvider(new SimApiLoggerProvider()); }); } // 是否使用 AUTH if (simApiOptions.EnableSimApiAuth) { builder.AddSingleton(); } if (simApiOptions.EnableCoceSdk) { builder.AddSingleton(); } 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.EnableSynapse) { builder.AddSingleton(); //自动依赖注入 var stackTrace = new StackTrace(); var callingMethod = stackTrace.GetFrame(stackTrace.FrameCount - 1)?.GetMethod(); var assembly = callingMethod?.DeclaringType?.Assembly; var types = assembly?.GetTypes() ?? []; foreach (var type in types) { 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 => type.FullName?.Replace("+", ".")); x.OperationFilter(); x.OperationFilter(); x.OperationFilter(); 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("HeaderToken", new OpenApiSecurityScheme { Name = "Token", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey }); x.AddSecurityRequirement(docs => new OpenApiSecurityRequirement { { new OpenApiSecuritySchemeReference("HeaderToken"), new List() } }); 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 }); x.AddSecurityRequirement(docs => new OpenApiSecurityRequirement { { new OpenApiSecuritySchemeReference("oauth2"), new List() } }); } }); } // 使用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; }); } 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.EnableCoceSdk) { logger.LogInformation("开始配置CoceAppSdk...\nApi入口: {ApiUrl}\nAuth入口:{AuthUrl}n\nAppId: {AppId}", options.CoceSdkOptions.ApiEndpoint, options.CoceSdkOptions.AuthEndpoint, options.CoceSdkOptions.AppId); } 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(); } if (options.EnableSimApiAuth) { logger.LogInformation("开始配置SimApiAuth..."); builder.UseMiddleware(); builder.MapControllerRoute(name: "GetUserInfo", pattern: "/user/info", defaults: new { controller = "SimApiAuth", action = "UserInfo" }); builder.MapControllerRoute(name: "CheckLogin", pattern: "/auth/check", defaults: new { controller = "SimApiAuth", action = "CheckLogin" }); builder.MapControllerRoute(name: "Logout", pattern: "/auth/logout", defaults: new { controller = "SimApiAuth", action = "Logout" }); if (options.EnableCoceSdk) { logger.LogInformation("开始配置CoceAppSdk...\nApi入口: {ApiUrl}\nAuth入口:{AuthUrl}n\nAppId: {AppId}", options.CoceSdkOptions.ApiEndpoint, options.CoceSdkOptions.AuthEndpoint, options.CoceSdkOptions.AppId); builder.MapControllerRoute(name: "LoginUseCoce", pattern: "/auth/login", defaults: new { controller = "Coce", action = "Login" }); builder.MapControllerRoute(name: "LoginUseCoce", pattern: "/user/groups", defaults: new { controller = "Coce", action = "ListGroups" }); builder.MapControllerRoute(name: "LoginUseCoce", pattern: "/auth/config", defaults: new { controller = "Coce", action = "GetConfig" }); } } if (options.EnableVersionUrl) { builder.MapControllerRoute(name: "Versions", pattern: "/versions", defaults: new { controller = "SimApiCommon", action = "Versions" }); } 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($"/swagger/{group.Id}.json", name: group.Name); } x.SupportedSubmitMethods(docOptions.SupportedMethod); x.DisplayRequestDuration(); }); } 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; } }