chore: 优化代码注释

This commit is contained in:
2025-11-23 09:52:54 +08:00
parent 1169e1f220
commit ccadacaa9d
33 changed files with 457 additions and 221 deletions

View File

@@ -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; }
}

View File

@@ -1,5 +1,3 @@
using System;
namespace TakeoutSaaS.Shared.Abstractions.Exceptions;
/// <summary>

View File

@@ -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");

View File

@@ -1,7 +1,14 @@
namespace TakeoutSaaS.Shared.Abstractions.Tenancy;
/// <summary>
/// 租户提供者接口:用于获取当前请求的租户标识。
/// </summary>
public interface ITenantProvider
{
/// <summary>
/// 获取当前请求的租户 ID。
/// </summary>
/// <returns>租户 ID如果未设置则返回 Guid.Empty</returns>
Guid GetCurrentTenantId();
}

View File

@@ -22,6 +22,7 @@ public static class ServiceCollectionExtensions
.AddControllers(options =>
{
options.Filters.Add<ValidateModelAttribute>();
options.Filters.Add<ApiResponseResultFilter>();
})
.AddNewtonsoftJson();

View File

@@ -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&lt;T&gt;,无需再包一层 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
};
}
}

View File

@@ -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);
}
}

View File

@@ -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);