Files
simapi-net/SimApiExtensions.cs
T

508 lines
19 KiB
C#
Raw Normal View History

2019-12-23 16:32:06 +08:00
using System;
2024-09-08 14:42:51 +08:00
using System.Diagnostics;
using System.Linq;
using System.Reflection;
2024-12-23 20:26:21 +08:00
using System.Text.Json;
2025-01-16 22:05:59 +08:00
using Hangfire;
using Hangfire.Console;
using Hangfire.Redis.StackExchange;
2020-08-05 15:29:56 +08:00
using SimApi.Helpers;
2019-12-23 16:32:06 +08:00
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
2025-12-18 09:08:35 +08:00
using Microsoft.OpenApi;
2020-08-05 15:29:56 +08:00
using SimApi.Middlewares;
2020-12-29 17:18:02 +08:00
using Microsoft.AspNetCore.HttpOverrides;
2025-11-01 18:40:51 +08:00
using Microsoft.AspNetCore.Mvc;
2024-07-26 05:03:28 +08:00
using Microsoft.Extensions.Hosting;
2021-06-28 05:36:54 +08:00
using Microsoft.Extensions.Logging;
2024-09-08 14:42:51 +08:00
using SimApi.Attributes;
2026-05-12 09:39:23 +08:00
using SimApi.AuthGate;
2024-04-16 06:57:56 +08:00
using SimApi.Configurations;
using SimApi.Interfaces;
2021-06-28 05:36:54 +08:00
using SimApi.Logger;
2025-11-02 22:14:34 +08:00
using SimApi.SwaggerFilters;
using StackExchange.Redis;
2019-12-23 16:32:06 +08:00
2024-03-23 07:29:43 +08:00
namespace SimApi;
/// <summary>
/// 加入系统的扩展信息
/// </summary>
public static class SimApiExtensions
2019-12-23 16:32:06 +08:00
{
2024-03-23 07:29:43 +08:00
//**********快捷添加**************
public static IServiceCollection AddSimApi(this IServiceCollection builder,
2024-08-03 19:19:42 +08:00
Action<SimApiOptions>? options = null)
2019-12-23 16:32:06 +08:00
{
2024-03-23 07:29:43 +08:00
var simApiOptions = new SimApiOptions();
options?.Invoke(simApiOptions);
2025-01-16 22:05:59 +08:00
if (simApiOptions.RedisConfiguration != null)
{
builder.AddStackExchangeRedisCache(x => x.Configuration = simApiOptions.RedisConfiguration);
builder.AddSingleton<IConnectionMultiplexer>(_ =>
ConnectionMultiplexer.Connect(simApiOptions.RedisConfiguration));
2025-01-16 22:05:59 +08:00
builder.AddSingleton<SimApiCache>();
}
2024-03-23 07:29:43 +08:00
if (simApiOptions.EnableLogger)
2019-12-23 16:32:06 +08:00
{
2024-03-23 07:29:43 +08:00
builder.AddLogging(logger =>
2022-08-05 02:20:27 +08:00
{
2024-03-23 07:29:43 +08:00
logger.ClearProviders();
logger.AddProvider(new SimApiLoggerProvider());
});
}
2024-04-16 06:57:56 +08:00
2024-03-23 07:29:43 +08:00
// 是否使用 AUTH
if (simApiOptions.EnableSimApiAuth)
{
2024-03-24 17:40:48 +08:00
builder.AddSingleton<SimApiAuth>();
2024-03-23 07:29:43 +08:00
}
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);
}
}
2025-01-16 22:05:59 +08:00
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;
});
}
}
2024-03-23 07:29:43 +08:00
if (simApiOptions.EnableCors)
{
builder.AddCors(cors => cors.AddPolicy("any",
policy => { policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin(); }));
}
if (simApiOptions.EnableSimApiHttpClient)
{
builder.AddSingleton<SimApiHttpClient>();
}
2024-03-23 07:29:43 +08:00
if (simApiOptions.EnableSynapse)
{
builder.AddSingleton<Synapse>();
foreach (var type in callerTypes)
2024-09-08 14:42:51 +08:00
{
var methodsWithSynapse = type.GetMethods()
.Where(m => m.GetCustomAttribute<SynapseRpcAttribute>() != null ||
m.GetCustomAttribute<SynapseEventAttribute>() != null);
if (!methodsWithSynapse.Any()) continue;
builder.AddScoped(type);
}
2024-03-23 07:29:43 +08:00
}
// 使用SimApiDoc
if (simApiOptions.EnableSimApiDoc)
{
var docOptions = simApiOptions.SimApiDocOptions;
builder.AddSwaggerGen(x =>
{
foreach (var group in docOptions.ApiGroups)
2022-08-05 02:20:27 +08:00
{
2024-03-23 07:29:43 +08:00
x.SwaggerDoc(group.Id, new OpenApiInfo
{
Title = group.Name,
Description = group.Description
});
}
2025-12-23 06:00:33 +08:00
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();
2026-04-26 04:09:35 +08:00
return $"{GetSimpleTypeName(elementType!, depth + 1)}[]";
}
// 处理可空类型
if (Nullable.GetUnderlyingType(t) != null)
{
var underlyingType = Nullable.GetUnderlyingType(t);
2026-04-26 04:09:35 +08:00
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("+", "_");
});
2025-11-02 22:14:34 +08:00
x.OperationFilter<SimApiResponseOperationFilter>();
x.OperationFilter<SimApiSignOperationFilter>();
x.OperationFilter<AesBodyOperationFilter>();
x.SchemaFilter<GlobalDynamicObjectSchemaFilter>();
x.DocumentFilter<RemoveEmptyTagsFilter>();
2025-11-01 15:40:55 +08:00
if (simApiOptions.EnableSimApiAuth)
{
x.OperationFilter<SimApiAuthOperationFilter>();
}
2025-11-01 18:40:51 +08:00
x.DocInclusionPredicate((docName, apiDesc) =>
{
// 获取接口标记的 GroupName(未标记则为 null
var actionGroupName = apiDesc.ActionDescriptor.EndpointMetadata
.OfType<ApiExplorerSettingsAttribute>()
.FirstOrDefault()?.GroupName;
// 情况1:接口未标记任何 GroupNameactionGroupName 为 null
if (actionGroupName == null)
{
return docName == "api"; // 未分组接口只属于默认分组v1
}
2025-11-01 21:38:55 +08:00
2025-11-01 18:40:51 +08:00
return docName == actionGroupName;
});
2024-03-23 07:29:43 +08:00
x.EnableAnnotations();
var haveOauth = false;
var oauthFlows = new OpenApiOAuthFlows();
foreach (var auth in docOptions.ApiAuth.Type)
{
2024-03-23 07:29:43 +08:00
switch (auth)
{
2024-03-23 07:29:43 +08:00
case "SimApiAuth":
x.AddSecurityDefinition("SimApiAuth",
2025-11-01 15:40:55 +08:00
new OpenApiSecurityScheme
{
2025-11-01 15:40:55 +08:00
Name = "Token",
2025-12-23 06:00:33 +08:00
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
2025-11-01 15:40:55 +08:00
});
2024-03-23 07:29:43 +08:00
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;
}
2024-03-23 07:29:43 +08:00
}
2024-03-23 07:29:43 +08:00
if (haveOauth)
{
x.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
2024-03-23 07:29:43 +08:00
Type = SecuritySchemeType.OAuth2,
Flows = oauthFlows,
Description = docOptions.ApiAuth.Description,
In = ParameterLocation.Header
});
}
});
2020-02-04 16:05:09 +08:00
}
2024-03-23 07:29:43 +08:00
// 使用Header转发,应对代理后获取真实ip
if (simApiOptions.EnableForwardHeaders)
2020-02-04 16:05:09 +08:00
{
2024-03-23 07:29:43 +08:00
builder.Configure<ForwardedHeadersOptions>(fwOptions =>
2021-06-03 17:16:55 +08:00
{
2024-03-23 07:29:43 +08:00
fwOptions.ForwardedHeaders = ForwardedHeaders.All;
fwOptions.KnownNetworks.Clear();
fwOptions.KnownProxies.Clear();
});
2019-12-23 16:32:06 +08:00
}
2024-03-23 07:29:43 +08:00
if (simApiOptions.EnableLowerUrl)
{
builder.AddRouting(rOptions => rOptions.LowercaseUrls = true);
}
if (simApiOptions.EnableSimApiStorage)
{
builder.AddHttpContextAccessor();
builder.AddSingleton<SimApiStorage>();
}
2024-12-23 22:11:00 +08:00
if (simApiOptions.EnableSimApiResponseFilter)
2024-12-23 20:26:21 +08:00
{
builder.AddControllers(opt => opt.Filters.Add<SimApiResponseFilter>())
2025-05-14 06:26:10 +08:00
.AddJsonOptions(opt =>
{
opt.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});
2024-12-23 20:26:21 +08:00
}
2026-05-12 09:39:23 +08:00
if (simApiOptions.EnableSimApiAuthGate)
{
builder.AddSingleton<SimApiAuthGateClient>();
builder.AddSingleton<SimApiAuthGate>();
2026-05-12 10:08:00 +08:00
if (simApiOptions.SimApiAuthGateOptions.UseIam)
{
builder.AddSingleton<SimApiIam>();
}
2026-05-12 09:39:23 +08:00
}
2024-03-23 07:29:43 +08:00
builder.AddSingleton(simApiOptions);
return builder;
}
2024-07-26 05:03:28 +08:00
public static IHost UseSimApi(this IHost builder)
{
2024-08-19 03:39:35 +08:00
var options = builder.Services.GetRequiredService<SimApiOptions>();
2024-07-26 05:03:28 +08:00
var logger = builder.Services.GetRequiredService<ILogger<SimApiOptions>>();
logger.LogInformation("当前时区: {LocalId}", TimeZoneInfo.Local.Id);
2025-11-01 15:40:55 +08:00
logger.LogInformation("主应用版本: {AppVersion}\nSimApi版本: {SimApiVersion}", SimApiUtil.AppVersion,
SimApiUtil.SimApiVersion);
2024-08-19 03:39:35 +08:00
2025-01-16 22:05:59 +08:00
if (options.RedisConfiguration != null)
{
logger.LogInformation("开始配置 RedisCache ...");
}
2024-07-26 05:03:28 +08:00
//请求一下检测存储错误
if (options.EnableSimApiStorage)
{
logger.LogInformation("开始配置SimApiStorage...");
builder.Services.GetService<SimApiStorage>();
}
if (options.EnableSimApiHttpClient)
2024-08-19 03:39:35 +08:00
{
logger.LogInformation("开始配置SimApiHttpClient...\n服务器地址: {ApiUrl}\nAppId:{AuthUrl}n\nAppkey: {AppId}",
options.SimApiHttpClientOptions.Server, options.SimApiHttpClientOptions.AppId,
!string.IsNullOrEmpty(options.SimApiHttpClientOptions.AppKey));
2024-08-19 03:39:35 +08:00
}
2024-07-26 05:03:28 +08:00
if (options.EnableSynapse)
{
var synapse = builder.Services.GetRequiredService<Synapse>();
synapse.Init();
}
2025-01-16 22:05:59 +08:00
if (options.EnableJob)
{
logger.LogInformation("开始配置 SimApiJob ...");
}
2024-07-26 05:03:28 +08:00
return builder;
}
2024-03-23 07:29:43 +08:00
/// <summary>
/// 使用所有SimApi自定义中间件
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
2024-07-26 19:18:59 +08:00
public static WebApplication UseSimApi(this WebApplication builder)
2024-03-23 07:29:43 +08:00
{
2024-07-26 19:18:59 +08:00
var options = builder.Services.GetRequiredService<SimApiOptions>();
var logger = builder.Services.GetRequiredService<ILogger<SimApiOptions>>();
2025-01-16 22:05:59 +08:00
UseSimApi((IHost)builder);
2024-03-23 07:29:43 +08:00
if (options.EnableForwardHeaders)
{
logger.LogInformation("开始配置ForwardedHeaders...");
builder.UseForwardedHeaders();
}
if (options.EnableCors)
{
logger.LogInformation("开始配置Cors全部允许...");
builder.UseCors("any");
}
2025-01-16 22:05:59 +08:00
if (options.EnableSimApiResponseFilter)
{
logger.LogInformation("开始配置SimApiResponseFilter...");
2025-01-17 01:30:13 +08:00
builder.MapControllers();
2025-01-16 22:05:59 +08:00
}
var checkers = builder.Services.CreateScope().ServiceProvider.GetServices<ISimApiAuthChecker>().ToArray();
if (checkers.Length != 0)
{
var msg = checkers.Aggregate("开始配置SimApiAuthChecker...",
(current, checker) => current + $"\n|- {checker.GetType().FullName}");
logger.LogInformation(msg);
}
2026-05-12 09:39:23 +08:00
if (options.EnableSimApiAuthGate)
{
2026-05-12 09:39:23 +08:00
logger.LogInformation("开始配置SimApiAuthGate...");
if (string.IsNullOrEmpty(options.SimApiAuthGateOptions.AppId) ||
string.IsNullOrEmpty(options.SimApiAuthGateOptions.AppKey))
{
2026-05-12 09:39:23 +08:00
logger.LogCritical("必须配置AuthGate的AppId和AppKey才能启用SimApiAuthGate");
}
else
{
2026-05-12 09:39:23 +08:00
if (options.SimApiAuthGateOptions.UseMiddleware)
{
builder.UseMiddleware<SimApiAuthGateMiddleware>();
}
}
}
2024-03-23 07:29:43 +08:00
if (options.EnableSimApiAuth)
{
logger.LogInformation("开始配置SimApiAuth...");
builder.UseMiddleware<SimApiAuthMiddleware>();
2024-08-19 03:39:35 +08:00
builder.MapControllerRoute(name: "Logout", pattern: "/auth/logout",
2025-05-14 06:26:10 +08:00
defaults: new
{
2025-11-01 21:38:55 +08:00
controller = "SimApiAuth",
2025-05-14 06:26:10 +08:00
action = "Logout"
});
2024-03-23 07:29:43 +08:00
}
2025-11-01 15:40:55 +08:00
if (options.EnableVersionUrl)
{
builder.MapControllerRoute(name: "Versions", pattern: "/versions",
defaults: new
{
controller = "SimApiCommon",
action = "Versions"
});
}
2024-03-23 07:29:43 +08:00
if (options.EnableSimApiDoc)
{
logger.LogInformation("开始配置SimApiDoc...");
var docOptions = options.SimApiDocOptions;
2025-11-01 15:40:55 +08:00
builder.UseSwagger(x => x.RouteTemplate = "/swagger/{documentName}.json")
.UseSwaggerUI(x =>
2024-03-23 07:29:43 +08:00
{
2025-11-01 15:40:55 +08:00
x.DocumentTitle = docOptions.DocumentTitle;
foreach (var group in docOptions.ApiGroups)
{
x.SwaggerEndpoint($"{group.Id}.json", name: group.Name);
2025-11-01 15:40:55 +08:00
}
2024-03-23 07:29:43 +08:00
2025-11-01 15:40:55 +08:00
x.SupportedSubmitMethods(docOptions.SupportedMethod);
x.DisplayRequestDuration();
});
2024-03-23 07:29:43 +08:00
}
if (options.EnableSimApiException)
{
logger.LogInformation("开始配置SimApiException...");
builder.UseMiddleware<SimApiExceptionMiddleware>();
}
if (options.EnableLowerUrl)
{
logger.LogInformation("开始配置使用URL小写...");
}
2025-01-16 22:05:59 +08:00
if (options is { EnableJob: true, SimApiJobOptions.DashboardUrl: not null })
2024-03-23 07:29:43 +08:00
{
2025-01-16 22:05:59 +08:00
logger.LogInformation("开始配置 SimApiJob Web控制台...");
builder.UseHangfireDashboard(options.SimApiJobOptions.DashboardUrl, new DashboardOptions
{
Authorization =
[
new SimApiJobWebAuth(options.SimApiJobOptions.DashboardAuthUser,
options.SimApiJobOptions.DashboardAuthPass)
]
});
2024-03-23 07:29:43 +08:00
}
return builder;
2019-12-23 16:32:06 +08:00
}
}