luoqian 5cdc7052e0 feat: 完善统一端点响应与请求绑定框架
- 新增 IApiResponse 统一响应契约,覆盖普通响应和分页响应
- 扩展端点映射,支持 IApiResponse 和带请求 DTO 的 MapXxx 重载
- 增加 Body、Query、Route Values 到请求 DTO 的自动绑定
- 增加 PC 端路由模式匹配,支持 {id} 和 {id:int}
- API 与 PC 端点上下文补充路由参数传递
- 调整 OpenAPI 请求类型处理,避免 GET/DELETE 被标记为 JSON Body
- 鉴权端点迁移到强类型请求绑定和 IApiResponse 返回
- 增加统一端点文件流响应支持
2026-05-22 11:42:38 +08:00

218 lines
7.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text.Json.Serialization;
namespace Avalonia_Common.Core
{
/// <summary>
/// 统一端点响应契约。
/// </summary>
public interface IApiResponse
{
/// <summary>是否成功。</summary>
bool Success { get; }
/// <summary>业务状态码。</summary>
int Code { get; }
}
/// <summary>
/// 统一 API 返回格式。
/// 所有接口的返回都包装为此格式,确保前端收到一致的数据结构。
/// </summary>
/// <typeparam name="T">业务数据类型</typeparam>
public class ApiResponse<T> : IApiResponse
{
/// <summary>是否成功</summary>
[JsonPropertyName("success")]
public bool Success { get; set; }
/// <summary>HTTP 状态码</summary>
[JsonPropertyName("code")]
public int Code { get; set; }
/// <summary>消息(成功时可为 null失败时包含错误描述</summary>
[JsonPropertyName("message")]
public string? Message { get; set; }
/// <summary>业务数据</summary>
[JsonPropertyName("data")]
public T? Data { get; set; }
/// <summary>时间戳</summary>
[JsonPropertyName("timestamp")]
public DateTime Timestamp { get; set; } = DateTime.Now;
/// <summary>请求追踪 ID用于排查问题</summary>
[JsonPropertyName("traceId")]
public string? TraceId { get; set; }
// ---- 快捷工厂方法 ----
/// <summary>成功返回(有数据)</summary>
public static ApiResponse<T> Ok(T data, string? message = null)
{
return new ApiResponse<T>
{
Success = true,
Code = 200,
Message = message,
Data = data,
};
}
/// <summary>失败返回</summary>
public static ApiResponse<T> Fail(int code, string message, T? data = default)
{
return new ApiResponse<T>
{
Success = false,
Code = code,
Message = message,
Data = data,
};
}
/// <summary>400 参数错误</summary>
public static ApiResponse<T> BadRequest(string message = "参数错误")
=> Fail(400, message);
/// <summary>401 未授权</summary>
public static ApiResponse<T> Unauthorized(string message = "未授权")
=> Fail(401, message);
/// <summary>403 无权限</summary>
public static ApiResponse<T> Forbidden(string message = "无权限")
=> Fail(403, message);
/// <summary>404 未找到</summary>
public static ApiResponse<T> NotFound(string message = "资源不存在")
=> Fail(404, message);
/// <summary>500 服务器内部错误</summary>
public static ApiResponse<T> ServerError(string message = "服务器内部错误")
=> Fail(500, message);
}
/// <summary>
/// 无数据的统一返回格式object? 版本)。
/// </summary>
public class ApiResponse : ApiResponse<object?>
{
/// <summary>成功返回(无数据)</summary>
public static ApiResponse Succeed(string? message = null)
{
return new ApiResponse
{
Success = true,
Code = 200,
Message = message,
Data = null,
};
}
/// <summary>失败返回</summary>
public static ApiResponse Failure(int code, string message)
{
return new ApiResponse
{
Success = false,
Code = code,
Message = message,
Data = null,
};
}
}
/// <summary>
/// 分页返回格式
/// </summary>
public class PagedResponse<T> : IApiResponse
{
/// <summary>
/// 获取或设置操作是否成功。
/// </summary>
[JsonPropertyName("success")]
public bool Success { get; set; } = true;
/// <summary>
/// 获取或设置业务状态码,默认 200。
/// </summary>
[JsonPropertyName("code")]
public int Code { get; set; } = 200;
/// <summary>
/// 获取或设置分页数据项列表。
/// </summary>
[JsonPropertyName("items")]
public List<T> Items { get; set; } = new();
/// <summary>
/// 获取或设置数据总条数。
/// </summary>
[JsonPropertyName("total")]
public int Total { get; set; }
/// <summary>
/// 获取或设置当前页码,从 1 开始。
/// </summary>
[JsonPropertyName("page")]
public int Page { get; set; } = 1;
/// <summary>
/// 获取或设置每页条数,默认 20。
/// </summary>
[JsonPropertyName("pageSize")]
public int PageSize { get; set; } = 20;
/// <summary>
/// 获取总页数(根据 Total 和 PageSize 自动计算)。
/// </summary>
[JsonPropertyName("totalPages")]
public int TotalPages => PageSize > 0 ? (int)Math.Ceiling((double)Total / PageSize) : 0;
/// <summary>
/// 从数据列表和分页参数创建分页响应。
/// </summary>
/// <param name="items">当前页数据项。</param>
/// <param name="total">数据总条数。</param>
/// <param name="page">当前页码。</param>
/// <param name="pageSize">每页条数。</param>
/// <returns>分页响应实例。</returns>
public static PagedResponse<T> From(List<T> items, int total, int page, int pageSize)
{
return new PagedResponse<T>
{
Items = items,
Total = total,
Page = page,
PageSize = pageSize,
};
}
}
/// <summary>
/// 端点返回辅助方法 —— 在 AppEndpoints 中快捷构建统一响应。
/// </summary>
public static class ResponseHelper
{
/// <summary>成功返回</summary>
public static ApiResponse<T> Ok<T>(T data, string? message = null)
=> ApiResponse<T>.Ok(data, message);
/// <summary>成功返回(无数据)</summary>
public static ApiResponse Succeed(string? message = null)
=> ApiResponse.Succeed(message);
/// <summary>失败返回</summary>
public static ApiResponse<T> Fail<T>(int code, string message, T? data = default)
=> ApiResponse<T>.Fail(code, message, data);
/// <summary>失败返回(无数据)</summary>
public static ApiResponse Failure(int code, string message)
=> ApiResponse.Failure(code, message);
/// <summary>分页返回</summary>
public static PagedResponse<T> Paged<T>(List<T> items, int total, int page, int pageSize)
=> PagedResponse<T>.From(items, total, page, pageSize);
}
}