Compare commits

...
13 Commits
Author SHA1 Message Date
xrain b7775cbe6c fix logout bug 2026-04-26 11:33:39 +08:00
xrain 28c6d53f38 修正了文档,增加了SimApiError 2026-04-26 10:03:38 +08:00
xrain ebceb0a260 Error系列全局可用 2026-04-26 04:09:35 +08:00
xrain 162b92a415 增加了cache.Remove 2026-04-26 01:23:14 +08:00
xrain a859986a93 fix csproj 2026-04-24 09:29:02 +08:00
xrain e1c6694e18 fix ex aggr 2026-04-14 13:38:19 +08:00
xrain 566b1d190b add AiContext 2026-04-07 10:55:46 +08:00
xrain eb8c9732bd 修改了说明书 2026-04-07 10:44:34 +08:00
xrain c8b78ce86d add tags for doc 2026-03-26 00:52:52 +08:00
xrain b2ec94e957 fix ci 2026-03-26 00:41:55 +08:00
xrain a8439c174c SimApiDoc增加了描述 2026-03-26 00:32:20 +08:00
xrain 81e166ad45 修正了swgger生成的类型名,SimApiAuth 如果不加参数,则默认接受所有登录了的用户请求. 2025-12-23 08:18:11 +08:00
xrain d50d32f032 优化了文档中的类型名称,修复了认证接口不显示需要认证 2025-12-23 07:20:34 +08:00
19 changed files with 1571 additions and 206 deletions
+8 -3
View File
@@ -9,16 +9,21 @@ jobs:
publish:
name: Publish Project to Nuget
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Setup .NET Core
uses: actions/setup-dotnet@v5
with:
dotnet-version: "10.x.x"
- name: NuGet login (OIDC → temp API key)
uses: NuGet/login@v1
id: login
with:
user: ${{ secrets.NUGET_USER }}
- name: Publish
run: |
version=`git describe --tags`
dotnet build --configuration release -p:Version=$version
dotnet nuget push bin/release/Simcu.SimApi.$version.nupkg -k ${NUGET_APIKEY} -s https://www.nuget.org/api/v2/package
env:
NUGET_APIKEY: ${{ secrets.NUGET_APIKEY }}
dotnet nuget push bin/release/Simcu.SimApi.$version.nupkg -k ${{steps.login.outputs.NUGET_API_KEY}} -s https://api.nuget.org/v3/index.json
+4 -9
View File
@@ -13,13 +13,13 @@ namespace SimApi.Attributes;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SimApiAuthAttribute : ActionFilterAttribute
{
private string[] Types { get; }
private string[]? Types { get; }
//默认是user登录类型
public SimApiAuthAttribute()
{
Types = new[] { "user" };
Types = null;
}
//只检测一种用户类型的快捷方式
@@ -34,13 +34,6 @@ public class SimApiAuthAttribute : ActionFilterAttribute
Types = types;
}
//只检测一种用户类型的快捷方式
public SimApiAuthAttribute(string type, string url)
{
Types = new[] { type };
new HttpPostAttribute(url);
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var loginInfo = (SimApiLoginItem)context.HttpContext.Items["LoginInfo"]!;
@@ -49,6 +42,8 @@ public class SimApiAuthAttribute : ActionFilterAttribute
{
throw new SimApiException(401);
}
if (Types == null) return;
//检测用户类型
if (!Types.Intersect(loginInfo.Type).Any())
{
+18 -3
View File
@@ -12,13 +12,28 @@ public class SimApiDocAttribute : SwaggerOperationAttribute
/// <summary>
/// 定义接口说明
/// </summary>
/// <param name="tag">接口分组</param>
/// <param name="tags">接口分组列表</param>
/// <param name="name">接口名称</param>
public SimApiDocAttribute(string tag, string name)
/// <param name="description">接口描述</param>
public SimApiDocAttribute(string[] tags, string name, string? description = null)
{
Tags = [tag];
Tags = tags;
Summary = name;
if (description != null)
{
Description = description;
}
// Consumes = new[] {"application/json"};
// Produces = new[] {"application/json"};
}
/// <summary>
/// 定义接口说明
/// </summary>
/// <param name="tag">接口分组</param>
/// <param name="name">接口名称</param>
/// <param name="description">接口描述</param>
public SimApiDocAttribute(string tag, string name, string? description = null) : this([tag], name, description)
{
}
}
+1
View File
@@ -7,6 +7,7 @@ using SimApi.Attributes;
using SimApi.Communications;
using SimApi.Controllers;
using SimApi.Helpers;
using static SimApi.Helpers.SimApiError;
namespace SimApi.CoceSdk;
+2 -1
View File
@@ -29,11 +29,12 @@ public class SimApiJobOptions
/// 设置为null 使用默认redis配置
/// </summary>
public int? Database { get; set; } = null;
public SimApiJobServerConfig[] Servers { get; set; } = [new()];
}
public class SimApiJobServerConfig()
{
public string[] Queues { get; set; } = ["default"];
public int WorkerNum { get; set; } = 50;
public int WorkerNum { get; set; } = 5;
}
+3 -2
View File
@@ -1,11 +1,12 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using SimApi.Attributes;
using SimApi.Communications;
using SimApi.Helpers;
namespace SimApi.Controllers;
using static SimApiError;
public class SimApiAuthController(SimApiAuth auth) : SimApiBaseController
{
/// <summary>
+2 -73
View File
@@ -1,10 +1,9 @@
using System.Diagnostics.CodeAnalysis;
using SimApi.Communications;
using SimApi.Communications;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Linq;
using SimApi.Exceptions;
using static SimApi.Helpers.SimApiError;
namespace SimApi.Controllers;
@@ -38,74 +37,4 @@ public class SimApiBaseController : Controller
break;
}
}
/// <summary>
/// 错误返回
/// </summary>
/// <param name="code">错误代码</param>
/// <param name="message">错误描述(若是常规错误,代码可自动带取描述)</param>
/// <returns></returns>
protected static void Error(int code = 500, string message = "")
{
throw new SimApiException(code, message);
}
/// <summary>
/// 检测条件,根据条件返回报错 如果condition是ture报错
/// </summary>
/// <param name="condition">检测条件</param>
/// <param name="code">错误代码</param>
/// <param name="message">错误描述</param>
protected static void ErrorWhen([DoesNotReturnIf(true)] bool condition, int code = 400, string message = "")
{
if (condition)
{
Error(code, message);
}
}
/// <summary>
/// 检测条件,根据条件返回报错 如果condition是ture报错
/// </summary>
/// <param name="condition">检测条件</param>
/// <param name="code">错误代码</param>
/// <param name="message">错误描述</param>
protected static void ErrorWhenTrue([DoesNotReturnIf(true)] bool condition, int code = 400, string message = "")
{
ErrorWhen(condition, code, message);
}
/// <summary>
/// 如果condition是false 报错
/// </summary>
/// <param name="condition"></param>
/// <param name="code"></param>
/// <param name="message"></param>
protected static void ErrorWhenFalse([DoesNotReturnIf(false)] bool condition, int code = 400, string message = "")
{
ErrorWhen(!condition, code, message);
}
/// <summary>
/// 检测给定的变量是否为NUll
/// </summary>
/// <param name="condition">检测条件</param>
/// <param name="code">错误代码</param>
/// <param name="message">错误描述</param>
protected static void ErrorWhenNull([NotNull] object? condition, int code = 404, string message = "请求的资源不存在")
{
ErrorWhen(condition == null, code, message);
}
/// <summary>
/// 上传文件
/// </summary>
/// <returns></returns>
protected SimApiBaseResponse<string> UploadFile()
{
return new SimApiBaseResponse<string>();
}
}
+26
View File
@@ -7,6 +7,12 @@ public class SimApiCache(IDistributedCache cache)
{
private const string Prefix = "SimApi:Cache:";
/// <summary>
/// 设置缓存
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public void Set(string key, object value, DistributedCacheEntryOptions? options = null)
{
if (options is not null)
@@ -19,11 +25,31 @@ public class SimApiCache(IDistributedCache cache)
}
}
/// <summary>
/// 移除缓存
/// </summary>
/// <param name="key"></param>
public void Remove(string key)
{
cache.Remove(Prefix + key);
}
/// <summary>
/// 获取string类型缓存
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public string? Get(string key)
{
return cache.GetString(Prefix + key);
}
/// <summary>
/// 获取特定类型缓存
/// </summary>
/// <param name="key"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T? Get<T>(string key)
{
var data = cache.GetString(Prefix + key);
+83
View File
@@ -0,0 +1,83 @@
using System.Diagnostics.CodeAnalysis;
using SimApi.Exceptions;
namespace SimApi.Helpers;
public static class SimApiError
{
/// <summary>
/// 错误返回
/// </summary>
/// <param name="code">错误代码</param>
/// <param name="message">错误描述(若是常规错误,代码可自动带取描述)</param>
/// <returns></returns>
public static void Error(int code = 500, string message = "")
{
throw new SimApiException(code, message);
}
/// <summary>
/// 检测条件,根据条件返回报错 如果condition是ture报错
/// </summary>
/// <param name="condition">检测条件</param>
/// <param name="code">错误代码</param>
/// <param name="message">错误描述</param>
public static void ErrorWhen([DoesNotReturnIf(true)] bool condition, int code = 400,
string message = "")
{
if (condition)
{
Error(code, message);
}
}
/// <summary>
/// 检测条件,根据条件返回报错 如果condition是ture报错
/// </summary>
/// <param name="condition">检测条件</param>
/// <param name="code">错误代码</param>
/// <param name="message">错误描述</param>
public static void ErrorWhenTrue([DoesNotReturnIf(true)] bool condition, int code = 400,
string message = "")
{
ErrorWhen(condition, code, message);
}
/// <summary>
/// 如果condition是false 报错
/// </summary>
/// <param name="condition"></param>
/// <param name="code"></param>
/// <param name="message"></param>
public static void ErrorWhenFalse([DoesNotReturnIf(false)] bool condition, int code = 400,
string message = "")
{
ErrorWhen(!condition, code, message);
}
/// <summary>
/// 检测给定的变量是否为NUll
/// </summary>
/// <param name="condition">检测条件</param>
/// <param name="code">错误代码</param>
/// <param name="message">错误描述</param>
public static void ErrorWhenNull([NotNull] object? condition, int code = 404,
string message = "请求的资源不存在")
{
ErrorWhen(condition == null, code, message);
}
/// <summary>
/// 检测给定的泛型变量是否为Null(提供更精确的编译器 null 流分析)
/// </summary>
/// <param name="condition">检测条件</param>
/// <param name="code">错误代码</param>
/// <param name="message">错误描述</param>
public static void ErrorWhenNull<T>([NotNull] T? condition, int code = 404,
string message = "请求的资源不存在") where T : class
{
ErrorWhen(condition == null, code, message);
}
}
+31
View File
@@ -17,6 +17,14 @@ public class SimApiHttpClient(string? appId, string appKey, bool debug = false)
public string? AppIdName { get; init; } = "appId";
public string[] SignFields { get; init; } = [];
/// <summary>
/// 发起签名请求
/// </summary>
/// <param name="url"></param>
/// <param name="body"></param>
/// <param name="queries"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T? SignQuery<T>(string url, object? body = null, Dictionary<string, string>? queries = null)
{
url = Server + url;
@@ -40,6 +48,13 @@ public class SimApiHttpClient(string? appId, string appKey, bool debug = false)
return Query<T>(path, body);
}
/// <summary>
/// 发起AES加密请求
/// </summary>
/// <param name="url"></param>
/// <param name="body"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T? AesQuery<T>(string url, object body)
{
url = Server + url;
@@ -55,6 +70,14 @@ public class SimApiHttpClient(string? appId, string appKey, bool debug = false)
return Query<T>(url, req);
}
/// <summary>
/// 发起AES加密以及签名请求
/// </summary>
/// <param name="url"></param>
/// <param name="body"></param>
/// <param name="queries"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T? AesSignQuery<T>(string url, object body, Dictionary<string, string>? queries = null)
{
var req = new SimApiOneFieldRequest<string>
@@ -64,6 +87,14 @@ public class SimApiHttpClient(string? appId, string appKey, bool debug = false)
return SignQuery<T>(url, req, queries);
}
/// <summary>
/// 发起请求
/// </summary>
/// <param name="url"></param>
/// <param name="req"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="SimApiException"></exception>
private T? Query<T>(string url, object? req)
{
var http = new HttpClient();
+36 -21
View File
@@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using SimApi.Communications;
@@ -20,7 +19,6 @@ public class SimApiExceptionMiddleware(RequestDelegate next, ILogger<SimApiExcep
context.Response.Headers["Query-Id"] = header;
}
SimApiBaseResponse response;
try
{
await next(context);
@@ -39,31 +37,48 @@ public class SimApiExceptionMiddleware(RequestDelegate next, ILogger<SimApiExcep
}
}
}
catch (SimApiException ex)
{
response = string.IsNullOrEmpty(ex.Message)
? new SimApiBaseResponse(ex.Code)
: new SimApiBaseResponse(ex.Code, ex.Message);
ErrorResponse(context, response);
}
catch (Exception ex)
{
log.LogError("{Msg}", ex.Message);
log.LogError("{Msg}", ex.StackTrace);
response = new SimApiBaseResponse(500, ex.Message);
ErrorResponse(context, response);
// 解包异步异常
ex = UnwrapAggregateException(ex);
SimApiBaseResponse response;
if (ex is SimApiException simEx)
{
response = string.IsNullOrEmpty(simEx.Message)
? new SimApiBaseResponse(simEx.Code)
: new SimApiBaseResponse(simEx.Code, simEx.Message);
}
else
{
log.LogError(ex, "服务器异常");
response = new SimApiBaseResponse(500, "服务器错误");
}
await ErrorResponseAsync(context, response);
}
}
/// <summary>
/// 异常抛出错误
/// </summary>
/// <param name="context"></param>
/// <param name="response"></param>
private static void ErrorResponse(HttpContext context, SimApiBaseResponse response)
private static Exception UnwrapAggregateException(Exception ex)
{
while (ex is AggregateException aggEx && aggEx.InnerException != null)
{
ex = aggEx.InnerException;
}
return ex;
}
/// <summary>
/// 异步输出错误响应(修复异步异常捕获核心)
/// </summary>
private static async Task ErrorResponseAsync(HttpContext context, SimApiBaseResponse response)
{
// 响应已开始则直接返回,不修改
if (context.Response.HasStarted)
return;
context.Response.StatusCode = 200;
context.Response.Headers.Append("Content-Type", "application/json");
context.Response.WriteAsync(response.ToString()).Wait();
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(response.ToString());
}
}
+1275 -41
View File
File diff suppressed because it is too large Load Diff
+2 -16
View File
@@ -1,5 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Library</OutputType>
<PackOnBuild>true</PackOnBuild>
@@ -12,10 +11,6 @@
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<Folder Include="Communications\"/>
<Folder Include="Exceptions\"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.22" />
<PackageReference Include="Hangfire.Console" Version="1.4.3"/>
@@ -23,16 +18,7 @@
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.1" />
<PackageReference Include="Minio" Version="7.0.0" />
<PackageReference Include="MQTTnet" Version="5.0.1.1416"/>
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="10.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="10.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.0" />
</ItemGroup>
<ProjectExtensions>
<MonoDevelop>
<Properties>
<Policies>
<DotNetNamingPolicy ResourceNamePolicy="FileFormatDefault" DirectoryNamespaceAssociation="PrefixedHierarchical"/>
</Policies>
</Properties>
</MonoDevelop>
</ProjectExtensions>
</Project>
+70 -15
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
@@ -124,7 +123,75 @@ public static class SimApiExtensions
});
}
x.CustomSchemaIds(type => type.FullName?.Replace("+", "."));
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<SimApiResponseOperationFilter>();
x.OperationFilter<SimApiSignOperationFilter>();
x.OperationFilter<AesBodyOperationFilter>();
@@ -159,19 +226,13 @@ public static class SimApiExtensions
switch (auth)
{
case "SimApiAuth":
x.AddSecurityDefinition("HeaderToken",
x.AddSecurityDefinition("SimApiAuth",
new OpenApiSecurityScheme
{
Name = "Token",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
});
x.AddSecurityRequirement(docs => new OpenApiSecurityRequirement
{
{
new OpenApiSecuritySchemeReference("HeaderToken"), new List<string>()
}
});
break;
case "ClientCredentials":
oauthFlows.ClientCredentials = new OpenApiOAuthFlow
@@ -220,12 +281,6 @@ public static class SimApiExtensions
Description = docOptions.ApiAuth.Description,
In = ParameterLocation.Header
});
x.AddSecurityRequirement(docs => new OpenApiSecurityRequirement
{
{
new OpenApiSecuritySchemeReference("oauth2"), new List<string>()
}
});
}
});
}
+2 -2
View File
@@ -22,11 +22,11 @@ public class AesBodyOperationFilter : IOperationFilter
.GetCustomAttribute<AesBodyAttribute>() != null;
if (!hasAesBodyAttr) continue;
// 1. 移除默认的 Query 参数描述(如果存在)
var queryParam = operation.Parameters
var queryParam = operation.Parameters!
.FirstOrDefault(p => p.Name == parameter.Name);
if (queryParam != null)
{
operation.Parameters.Remove(queryParam);
operation.Parameters!.Remove(queryParam);
}
// 2. 添加 Body 参数描述
@@ -13,7 +13,7 @@ public class GlobalDynamicObjectSchemaFilter : ISchemaFilter
{
if (!IsDynamicObjectType(context.Type)) return;
var oaSchema = schema as OpenApiSchema;
oaSchema.AdditionalPropertiesAllowed = true;
oaSchema!.AdditionalPropertiesAllowed = true;
oaSchema.AdditionalProperties = new OpenApiSchema
{
Type = JsonSchemaType.Object, // 表示 value 可以是任意类型(兼容所有类型)
+4 -4
View File
@@ -10,16 +10,16 @@ public class RemoveEmptyTagsFilter : IDocumentFilter
{
// 步骤1:收集所有有接口的 Tag 名称
var tagsWithOperations = swaggerDoc.Paths.Values
.SelectMany(path => path.Operations.Values)
.SelectMany(op => op.Tags.Select(t => t.Name))
.SelectMany(path => path.Operations!.Values)
.SelectMany(op => op.Tags!.Select(t => t.Name))
.Distinct()
.ToList();
// 步骤2:移除无接口的空 Tag
var emptyTags = swaggerDoc.Tags.Where(t => !tagsWithOperations.Contains(t.Name)).ToList();
var emptyTags = swaggerDoc.Tags!.Where(t => !tagsWithOperations.Contains(t.Name)).ToList();
foreach (var tag in emptyTags)
{
swaggerDoc.Tags.Remove(tag);
swaggerDoc.Tags!.Remove(tag);
}
}
}
+2 -14
View File
@@ -19,22 +19,10 @@ namespace SimApi.SwaggerFilters
// 控制器上有 [SimApiAuth](继承到所有方法)
context.MethodInfo.DeclaringType?.GetCustomAttributes<SimApiAuthAttribute>(true).Any() == true;
if (!requiresAuth) return;
// 关键修复:正确构造 OpenApiSecuritySchemeReference(匹配键类型要求)
var securitySchemeRef = new OpenApiSecuritySchemeReference(
referenceId: "SimApiAuth", // 必须与 AddSecurityDefinition 的 ID 一致
hostDocument: null,
externalResource: null
);
// 给 Security 赋值(确保键类型是 OpenApiSecuritySchemeReference
operation.Security ??= new List<OpenApiSecurityRequirement>();
operation.Security.Add(new OpenApiSecurityRequirement
operation.Security.Add(new OpenApiSecurityRequirement()
{
{
securitySchemeRef,
[]
}
{ new OpenApiSecuritySchemeReference("SimApiAuth", context.Document), [] }
});
}
}
@@ -46,7 +46,7 @@ public class SimApiResponseOperationFilter : IOperationFilter
var schema = context.SchemaGenerator.GenerateSchema(wrappedType, context.SchemaRepository);
// 5. 替换 Swagger 文档中的响应类型(只保留 200 OK 的响应,匹配过滤器逻辑)
operation.Responses.Clear(); // 清除默认响应(如 200 返回原始类型)
operation.Responses!.Clear(); // 清除默认响应(如 200 返回原始类型)
operation.Responses.Add("200", new OpenApiResponse
{
Description = "请求成功",