chore: 优化代码注释
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
namespace TakeoutSaaS.Shared.Abstractions.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// 审计字段接口
|
||||
/// 审计字段接口:提供创建时间和更新时间字段。
|
||||
/// </summary>
|
||||
public interface IAuditableEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建时间(UTC)。
|
||||
/// </summary>
|
||||
DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间(UTC),未更新时为 null。
|
||||
/// </summary>
|
||||
DateTime? UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
|
||||
namespace TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using TakeoutSaaS.Shared.Abstractions.Diagnostics;
|
||||
|
||||
@@ -8,7 +7,7 @@ namespace TakeoutSaaS.Shared.Abstractions.Results;
|
||||
/// 统一的 API 返回结果包装。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据载荷类型</typeparam>
|
||||
public sealed record class ApiResponse<T>
|
||||
public sealed record ApiResponse<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否成功。
|
||||
@@ -97,7 +96,7 @@ public sealed record class ApiResponse<T>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(TraceContext.TraceId))
|
||||
{
|
||||
return TraceContext.TraceId!;
|
||||
return TraceContext.TraceId;
|
||||
}
|
||||
|
||||
return Activity.Current?.Id ?? Guid.NewGuid().ToString("N");
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
namespace TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 租户提供者接口:用于获取当前请求的租户标识。
|
||||
/// </summary>
|
||||
public interface ITenantProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前请求的租户 ID。
|
||||
/// </summary>
|
||||
/// <returns>租户 ID,如果未设置则返回 Guid.Empty</returns>
|
||||
Guid GetCurrentTenantId();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ public static class ServiceCollectionExtensions
|
||||
.AddControllers(options =>
|
||||
{
|
||||
options.Filters.Add<ValidateModelAttribute>();
|
||||
options.Filters.Add<ApiResponseResultFilter>();
|
||||
})
|
||||
.AddNewtonsoftJson();
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
|
||||
namespace TakeoutSaaS.Shared.Web.Filters;
|
||||
|
||||
/// <summary>
|
||||
/// ApiResponse 结果过滤器:自动将 ApiResponse 转换为对应的 HTTP 状态码。
|
||||
/// 使用此过滤器后,控制器可以直接返回 ApiResponse<T>,无需再包一层 Ok() 或 Unauthorized()。
|
||||
/// </summary>
|
||||
public sealed class ApiResponseResultFilter : IAsyncResultFilter
|
||||
{
|
||||
public Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
|
||||
{
|
||||
// 只处理 ObjectResult 类型的结果
|
||||
if (context.Result is not ObjectResult objectResult)
|
||||
{
|
||||
return next();
|
||||
}
|
||||
|
||||
var value = objectResult.Value;
|
||||
if (value == null)
|
||||
{
|
||||
return next();
|
||||
}
|
||||
|
||||
// 检查是否是 ApiResponse<T> 类型
|
||||
var valueType = value.GetType();
|
||||
if (!IsApiResponseType(valueType))
|
||||
{
|
||||
return next();
|
||||
}
|
||||
|
||||
// 使用反射获取 Success 和 Code 属性
|
||||
// 注意:由于已通过 IsApiResponseType 检查,属性名是固定的
|
||||
const string successPropertyName = "Success";
|
||||
const string codePropertyName = "Code";
|
||||
var successProperty = valueType.GetProperty(successPropertyName);
|
||||
var codeProperty = valueType.GetProperty(codePropertyName);
|
||||
|
||||
if (successProperty == null || codeProperty == null)
|
||||
{
|
||||
return next();
|
||||
}
|
||||
|
||||
var success = (bool)(successProperty.GetValue(value) ?? false);
|
||||
var code = (int)(codeProperty.GetValue(value) ?? 200);
|
||||
|
||||
// 根据 Success 和 Code 设置 HTTP 状态码
|
||||
var statusCode = success ? MapSuccessCode(code) : MapErrorCode(code);
|
||||
|
||||
// 更新 ObjectResult 的状态码
|
||||
objectResult.StatusCode = statusCode;
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
private static bool IsApiResponseType(Type type)
|
||||
{
|
||||
// 检查是否是 ApiResponse<T> 类型
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var genericTypeDefinition = type.GetGenericTypeDefinition();
|
||||
return genericTypeDefinition == typeof(ApiResponse<>);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int MapSuccessCode(int code)
|
||||
{
|
||||
// 成功情况下,通常返回 200
|
||||
// 但也可以根据业务码返回其他成功状态码(如 201 Created)
|
||||
return code switch
|
||||
{
|
||||
200 => StatusCodes.Status200OK,
|
||||
201 => StatusCodes.Status201Created,
|
||||
204 => StatusCodes.Status204NoContent,
|
||||
_ => StatusCodes.Status200OK
|
||||
};
|
||||
}
|
||||
|
||||
private static int MapErrorCode(int code)
|
||||
{
|
||||
// 根据业务错误码映射到 HTTP 状态码
|
||||
return code switch
|
||||
{
|
||||
ErrorCodes.BadRequest => StatusCodes.Status400BadRequest,
|
||||
ErrorCodes.Unauthorized => StatusCodes.Status401Unauthorized,
|
||||
ErrorCodes.Forbidden => StatusCodes.Status403Forbidden,
|
||||
ErrorCodes.NotFound => StatusCodes.Status404NotFound,
|
||||
ErrorCodes.Conflict => StatusCodes.Status409Conflict,
|
||||
ErrorCodes.ValidationFailed => StatusCodes.Status422UnprocessableEntity,
|
||||
ErrorCodes.InternalServerError => StatusCodes.Status500InternalServerError,
|
||||
// 业务错误码(10000+)统一返回 422
|
||||
>= 10000 => StatusCodes.Status422UnprocessableEntity,
|
||||
// 默认返回 400
|
||||
_ => StatusCodes.Status400BadRequest
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,8 @@ namespace TakeoutSaaS.Shared.Web.Middleware;
|
||||
/// <summary>
|
||||
/// 安全响应头中间件
|
||||
/// </summary>
|
||||
public sealed class SecurityHeadersMiddleware
|
||||
public sealed class SecurityHeadersMiddleware(RequestDelegate next)
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public SecurityHeadersMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
var headers = context.Response.Headers;
|
||||
@@ -21,7 +14,7 @@ public sealed class SecurityHeadersMiddleware
|
||||
headers["X-Frame-Options"] = "DENY";
|
||||
headers["X-XSS-Protection"] = "1; mode=block";
|
||||
headers["Referrer-Policy"] = "no-referrer";
|
||||
await _next(context);
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -19,7 +18,7 @@ public static class SwaggerExtensions
|
||||
public static IServiceCollection AddSharedSwagger(this IServiceCollection services, Action<SwaggerDocumentSettings>? configure = null)
|
||||
{
|
||||
services.AddSwaggerGen();
|
||||
services.AddSingleton(provider =>
|
||||
services.AddSingleton(_ =>
|
||||
{
|
||||
var settings = new SwaggerDocumentSettings();
|
||||
configure?.Invoke(settings);
|
||||
|
||||
Reference in New Issue
Block a user