fix swagger, add simapihttpclient
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SimApi.Attributes;
|
||||
|
||||
namespace SimApi.SwaggerFilters;
|
||||
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using System.Reflection;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义 Swagger 过滤器:将标注 [AesBody] 的参数显示在 Request Body 中
|
||||
/// </summary>
|
||||
public class AesBodyOperationFilter : IOperationFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
foreach (var parameter in context.ApiDescription.ParameterDescriptions)
|
||||
{
|
||||
var hasAesBodyAttr = parameter.ParameterInfo()
|
||||
.GetCustomAttribute<AesBodyAttribute>() != null;
|
||||
if (!hasAesBodyAttr) continue;
|
||||
// 1. 移除默认的 Query 参数描述(如果存在)
|
||||
var queryParam = operation.Parameters
|
||||
.FirstOrDefault(p => p.Name == parameter.Name);
|
||||
if (queryParam != null)
|
||||
{
|
||||
operation.Parameters.Remove(queryParam);
|
||||
}
|
||||
|
||||
// 2. 添加 Body 参数描述
|
||||
// 获取参数类型的 Schema(Swagger 模型定义)
|
||||
var schema = context.SchemaGenerator.GenerateSchema(
|
||||
parameter.Type,
|
||||
context.SchemaRepository);
|
||||
|
||||
// 将参数添加到 Request Body
|
||||
operation.RequestBody = new OpenApiRequestBody
|
||||
{
|
||||
Content = new Dictionary<string, OpenApiMediaType>
|
||||
{
|
||||
{
|
||||
"application/json", // 假设使用 JSON 格式
|
||||
new OpenApiMediaType { Schema = schema }
|
||||
}
|
||||
},
|
||||
Description = "内容为加密前内容,需要转换为JSON后使用AES加密后提交,提交格式为 {\"data\":\"AES密文\"}",
|
||||
Required = true // 标记为必填
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using SimApi.Attributes;
|
||||
|
||||
namespace SimApi.SwaggerFilters
|
||||
{
|
||||
public class SimApiAuthOperationFilter : IOperationFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
// 检查接口或控制器是否标记了 [SimApiAuth] 特性
|
||||
var requiresAuth =
|
||||
// 方法上有 [SimApiAuth]
|
||||
context.MethodInfo.GetCustomAttributes<SimApiAuthAttribute>(true).Any()
|
||||
||
|
||||
// 控制器上有 [SimApiAuth](继承到所有方法)
|
||||
context.MethodInfo.DeclaringType?.GetCustomAttributes<SimApiAuthAttribute>(true).Any() == true;
|
||||
if (requiresAuth)
|
||||
{
|
||||
// 添加授权要求:关联步骤 2 中定义的 "SimApiAuth" 安全方案
|
||||
operation.Security = new List<OpenApiSecurityRequirement>
|
||||
{
|
||||
new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "SimApiAuth" // 必须与 AddSecurityDefinition 的第一个参数一致
|
||||
}
|
||||
},
|
||||
[] // 无需指定作用域(scope)时留空
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
// 未标记 [SimApiAuth] 的接口:不添加安全要求,Swagger 不显示锁图标
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SimApi.Attributes;
|
||||
using SimApi.Communications;
|
||||
|
||||
namespace SimApi.SwaggerFilters;
|
||||
|
||||
public class SimApiResponseOperationFilter : IOperationFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
// 1. 跳过标记了 [OriginResponseAttribute] 的接口(不包装)
|
||||
if (context.MethodInfo.DeclaringType?.GetCustomAttributes<OriginResponseAttribute>(true).Any() == true
|
||||
|| context.MethodInfo.GetCustomAttributes<OriginResponseAttribute>(true).Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 获取控制器方法声明的“原始返回类型”(如 Task<UserDto> → UserDto)
|
||||
var returnType = GetUnwrappedReturnType(context.MethodInfo.ReturnType, context);
|
||||
if (IsAlreadyWrappedType(returnType))
|
||||
{
|
||||
return; // 保留原始返回类型的文档描述
|
||||
}
|
||||
|
||||
// 3. 定义包装后的目标类型(泛型/非泛型)
|
||||
Type wrappedType;
|
||||
if (returnType == typeof(void) || returnType == typeof(EmptyResult))
|
||||
{
|
||||
// 无数据:用非泛型 SimApiBaseResponse
|
||||
wrappedType = typeof(SimApiBaseResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 有数据:用泛型 SimApiBaseResponse<T>(T 为原始返回类型)
|
||||
wrappedType = typeof(SimApiBaseResponse<>).MakeGenericType(returnType);
|
||||
}
|
||||
|
||||
// 4. 让 Swagger 生成包装类型的 Schema
|
||||
var schema = context.SchemaGenerator.GenerateSchema(wrappedType, context.SchemaRepository);
|
||||
|
||||
// 5. 替换 Swagger 文档中的响应类型(只保留 200 OK 的响应,匹配过滤器逻辑)
|
||||
operation.Responses.Clear(); // 清除默认响应(如 200 返回原始类型)
|
||||
operation.Responses.Add("200", new OpenApiResponse
|
||||
{
|
||||
Description = "请求成功",
|
||||
Content = new Dictionary<string, OpenApiMediaType>
|
||||
{
|
||||
{
|
||||
"application/json", // 只保留 JSON 格式(配合之前的全局配置)
|
||||
new OpenApiMediaType
|
||||
{
|
||||
Schema = schema
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Type GetUnwrappedReturnType(Type returnType, OperationFilterContext context)
|
||||
{
|
||||
// 处理 Task<T>(异步方法)
|
||||
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
{
|
||||
returnType = returnType.GetGenericArguments()[0];
|
||||
}
|
||||
|
||||
// 处理 IActionResult/ObjectResult(如 return Ok(userDto))
|
||||
if (!typeof(IActionResult).IsAssignableFrom(returnType)) return returnType;
|
||||
// 从方法返回语句中提取原始类型(需结合特性,或默认用 object)
|
||||
// 更精准的方式:让控制器方法用 [ProducesResponseType(typeof(UserDto), 200)] 声明原始类型
|
||||
var producesAttr = context.MethodInfo.GetCustomAttributes<ProducesResponseTypeAttribute>()
|
||||
.FirstOrDefault(a => a.StatusCode == 200);
|
||||
if (producesAttr?.Type != null && producesAttr.Type != typeof(void))
|
||||
{
|
||||
return producesAttr.Type;
|
||||
}
|
||||
|
||||
return typeof(object); // 无法识别时默认用 object
|
||||
}
|
||||
|
||||
// 辅助方法:判断类型是否已经是 SimApiBaseResponse 及其泛型
|
||||
private static bool IsAlreadyWrappedType(Type type)
|
||||
{
|
||||
// 情况1:类型本身是 SimApiBaseResponse 或其泛型
|
||||
if (type == typeof(SimApiBaseResponse) ||
|
||||
(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(SimApiBaseResponse<>)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 情况2:类型是 SimApiBaseResponse 的子类(非泛型)
|
||||
if (type.IsSubclassOf(typeof(SimApiBaseResponse)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 情况3:类型是 SimApiBaseResponse<T> 的子类(泛型)
|
||||
// 检查是否继承自泛型父类 SimApiBaseResponse<>(任意 T)
|
||||
if (type.BaseType is { IsGenericType: true })
|
||||
{
|
||||
var genericBaseType = type.BaseType.GetGenericTypeDefinition();
|
||||
if (genericBaseType == typeof(SimApiBaseResponse<>))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 其他情况:不是包装类型或其子类
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using SimApi.Attributes;
|
||||
using SimApi.ModelBinders;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace SimApi.SwaggerFilters;
|
||||
|
||||
public class SimApiSignOperationFilter(IServiceProvider serviceProvider) : IOperationFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
// 1. 检查当前方法或类是否标注了 SimApiSignAttribute 及其子类
|
||||
var signAttribute = context.MethodInfo.GetCustomAttribute<SimApiSignAttribute>(inherit: true)
|
||||
?? context.MethodInfo.DeclaringType?.GetCustomAttribute<SimApiSignAttribute>(inherit: true);
|
||||
|
||||
if (signAttribute == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var keyProviderType = signAttribute.KeyProvider;
|
||||
if (!typeof(SimApiSignProviderBase).IsAssignableFrom(keyProviderType))
|
||||
{
|
||||
throw new InvalidOperationException($"KeyProvider 必须继承自 {nameof(SimApiSignProviderBase)}");
|
||||
}
|
||||
|
||||
SimApiSignProviderBase? keyProvider;
|
||||
try
|
||||
{
|
||||
keyProvider =
|
||||
serviceProvider.CreateScope().ServiceProvider.GetService(keyProviderType) as SimApiSignProviderBase;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"无法从 DI 容器获取 {keyProviderType.Name} 实例:{ex.Message}");
|
||||
}
|
||||
|
||||
if (keyProvider == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{keyProviderType.Name} 未在 DI 容器中注册");
|
||||
}
|
||||
|
||||
var signStr = keyProvider.SignFields.Aggregate(string.Empty, (current, field) => current + $"{field}=xxx&");
|
||||
if (!string.IsNullOrEmpty(keyProvider.AppIdName))
|
||||
{
|
||||
signStr += $"{keyProvider.AppIdName}=xxx&";
|
||||
}
|
||||
|
||||
signStr += $"{keyProvider.TimestampName}=xxx&{keyProvider.NonceName}=xxx&签名密钥";
|
||||
|
||||
var signParameters = new List<(string Name, string Description, bool Required)>
|
||||
{
|
||||
(keyProvider.TimestampName, "时间戳(秒级)", true),
|
||||
(keyProvider.NonceName, "随机字符串", true),
|
||||
(keyProvider.SignName, $"MD5签名结果,签名MD5字符串: {signStr}", true)
|
||||
};
|
||||
if (keyProvider.AppIdName != null)
|
||||
{
|
||||
signParameters.Add((keyProvider.AppIdName, "应用标识", true));
|
||||
}
|
||||
|
||||
signParameters.AddRange(keyProvider.SignFields.Where(x => x != keyProvider.AppIdName)
|
||||
.Select(f => (f, string.Empty, true)));
|
||||
|
||||
foreach (var (name, description, required) in signParameters)
|
||||
{
|
||||
if (operation.Parameters?.Any(p => p.Name == name) == true)
|
||||
{
|
||||
var tmp = operation.Parameters?.FirstOrDefault(p => p.Name == name);
|
||||
if (tmp != null)
|
||||
{
|
||||
tmp.Required = required;
|
||||
tmp.Description = description;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
operation.Parameters ??= new List<OpenApiParameter>();
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = name,
|
||||
In = ParameterLocation.Query, // 指定为 Query 参数
|
||||
Description = description,
|
||||
Required = required,
|
||||
Schema = new OpenApiSchema
|
||||
{
|
||||
Type = "string" // 签名相关参数通常为字符串类型
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user