docs: 完善身份模块文档注释与字段说明

This commit is contained in:
2025-12-12 11:08:39 +08:00
parent 715cbb3d36
commit 641598de86
36 changed files with 409 additions and 37 deletions

View File

@@ -12,20 +12,29 @@ namespace TakeoutSaaS.Shared.Web.Filters;
/// </summary>
public sealed class ApiResponseResultFilter : IAsyncResultFilter
{
/// <summary>
/// 执行结果过滤,将 ApiResponse 映射为对应 HTTP 状态码。
/// </summary>
/// <param name="context">结果执行上下文。</param>
/// <param name="next">后续委托。</param>
/// <returns>异步任务。</returns>
public Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
// 1. 仅处理 ObjectResult
// 只处理 ObjectResult 类型的结果
if (context.Result is not ObjectResult objectResult)
{
return next();
}
// 2. 结果为空直接跳过
var value = objectResult.Value;
if (value == null)
{
return next();
}
// 3. 确认类型为 ApiResponse<T>
// 检查是否是 ApiResponse<T> 类型
var valueType = value.GetType();
if (!IsApiResponseType(valueType))
@@ -33,6 +42,7 @@ public sealed class ApiResponseResultFilter : IAsyncResultFilter
return next();
}
// 4. 读取 Success 与 Code
// 使用反射获取 Success 和 Code 属性
// 注意:由于已通过 IsApiResponseType 检查,属性名是固定的
const string successPropertyName = "Success";
@@ -48,9 +58,11 @@ public sealed class ApiResponseResultFilter : IAsyncResultFilter
var success = (bool)(successProperty.GetValue(value) ?? false);
var code = (int)(codeProperty.GetValue(value) ?? 200);
// 5. 映射 HTTP 状态码
// 根据 Success 和 Code 设置 HTTP 状态码
var statusCode = success ? MapSuccessCode(code) : MapErrorCode(code);
// 6. 回写状态码
// 更新 ObjectResult 的状态码
objectResult.StatusCode = statusCode;
@@ -101,4 +113,3 @@ public sealed class ApiResponseResultFilter : IAsyncResultFilter
};
}
}

View File

@@ -10,8 +10,13 @@ namespace TakeoutSaaS.Shared.Web.Filters;
/// </summary>
public sealed class ValidateModelAttribute : ActionFilterAttribute
{
/// <summary>
/// 在 Action 执行前拦截模型验证错误。
/// </summary>
/// <param name="context">执行上下文。</param>
public override void OnActionExecuting(ActionExecutingContext context)
{
// 1. 模型验证未通过则返回 422
if (!context.ModelState.IsValid)
{
var errors = context.ModelState

View File

@@ -15,8 +15,13 @@ public sealed class CorrelationIdMiddleware(RequestDelegate next, ILogger<Correl
private const string SpanHeader = "X-Span-Id";
private const string RequestHeader = "X-Request-Id";
/// <summary>
/// 管道入口,确保 TraceId/SpanId 贯穿请求。
/// </summary>
/// <param name="context">HTTP 上下文。</param>
public async Task InvokeAsync(HttpContext context)
{
// 1. 确保活动存在并启动
var ownsActivity = Activity.Current is null;
var activity = Activity.Current ?? new Activity("TakeoutSaaS.Request");
@@ -26,6 +31,7 @@ public sealed class CorrelationIdMiddleware(RequestDelegate next, ILogger<Correl
activity.Start();
}
// 2. 生成/解析 TraceId、SpanId
var traceId = activity.TraceId.ToString();
var spanId = activity.SpanId.ToString();
@@ -34,6 +40,7 @@ public sealed class CorrelationIdMiddleware(RequestDelegate next, ILogger<Correl
traceId = ResolveTraceId(context);
}
// 3. 写入上下文与响应头
context.TraceIdentifier = traceId;
TraceContext.TraceId = traceId;
TraceContext.SpanId = spanId;
@@ -45,6 +52,7 @@ public sealed class CorrelationIdMiddleware(RequestDelegate next, ILogger<Correl
return Task.CompletedTask;
});
// 4. 带 Scope 调用后续中间件
using (logger.BeginScope(new Dictionary<string, object>
{
["TraceId"] = traceId,
@@ -57,6 +65,7 @@ public sealed class CorrelationIdMiddleware(RequestDelegate next, ILogger<Correl
}
finally
{
// 5. 清理上下文与活动
TraceContext.Clear();
if (ownsActivity)
{

View File

@@ -31,6 +31,10 @@ public sealed class ExceptionHandlingMiddleware(RequestDelegate next, ILogger<Ex
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
/// <summary>
/// 中间件入口,捕获并统一处理异常。
/// </summary>
/// <param name="context">HTTP 上下文。</param>
public async Task InvokeAsync(HttpContext context)
{
try
@@ -39,17 +43,21 @@ public sealed class ExceptionHandlingMiddleware(RequestDelegate next, ILogger<Ex
}
catch (Exception ex)
{
// 1. 记录异常
logger.LogError(ex, "未处理异常:{Message}", ex.Message);
// 2. 返回统一错误响应
await HandleExceptionAsync(context, ex);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
// 1. 构建错误响应与状态码
var (statusCode, response) = BuildErrorResponse(exception);
if (environment.IsDevelopment())
{
// 2. 开发环境附加细节
response = response with
{
Message = exception.Message,
@@ -61,6 +69,7 @@ public sealed class ExceptionHandlingMiddleware(RequestDelegate next, ILogger<Ex
};
}
// 3. 写入响应
context.Response.StatusCode = statusCode;
context.Response.ContentType = "application/json";
return context.Response.WriteAsJsonAsync(response, SerializerOptions);

View File

@@ -10,8 +10,13 @@ namespace TakeoutSaaS.Shared.Web.Middleware;
/// </summary>
public sealed class RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
/// <summary>
/// 记录请求日志并调用后续中间件。
/// </summary>
/// <param name="context">HTTP 上下文。</param>
public async Task InvokeAsync(HttpContext context)
{
// 1. 启动计时
var stopwatch = Stopwatch.StartNew();
try
{
@@ -19,6 +24,7 @@ public sealed class RequestLoggingMiddleware(RequestDelegate next, ILogger<Reque
}
finally
{
// 2. 结束计时并输出日志
stopwatch.Stop();
var traceId = TraceContext.TraceId ?? context.TraceIdentifier;
var spanId = TraceContext.SpanId ?? Activity.Current?.SpanId.ToString() ?? string.Empty;

View File

@@ -7,14 +7,19 @@ namespace TakeoutSaaS.Shared.Web.Middleware;
/// </summary>
public sealed class SecurityHeadersMiddleware(RequestDelegate next)
{
/// <summary>
/// 设置基础安全响应头。
/// </summary>
/// <param name="context">HTTP 上下文。</param>
public async Task InvokeAsync(HttpContext context)
{
// 1. 写入安全响应头
var headers = context.Response.Headers;
headers["X-Content-Type-Options"] = "nosniff";
headers["X-Frame-Options"] = "DENY";
headers["X-XSS-Protection"] = "1; mode=block";
headers["Referrer-Policy"] = "no-referrer";
// 2. 继续后续管道
await next(context);
}
}