feat: 完善统一端点响应与请求绑定框架

- 新增 IApiResponse 统一响应契约,覆盖普通响应和分页响应
- 扩展端点映射,支持 IApiResponse 和带请求 DTO 的 MapXxx 重载
- 增加 Body、Query、Route Values 到请求 DTO 的自动绑定
- 增加 PC 端路由模式匹配,支持 {id} 和 {id:int}
- API 与 PC 端点上下文补充路由参数传递
- 调整 OpenAPI 请求类型处理,避免 GET/DELETE 被标记为 JSON Body
- 鉴权端点迁移到强类型请求绑定和 IApiResponse 返回
- 增加统一端点文件流响应支持
This commit is contained in:
2026-05-22 11:42:38 +08:00
parent e72bff954b
commit 5cdc7052e0
13 changed files with 414 additions and 82 deletions
+3 -3
View File
@@ -63,7 +63,7 @@ namespace Avalonia_Services.Endpoints
/// <summary>
/// 从数据库查询天气预报(优先数据库,回退到内存生成)。
/// </summary>
private static async Task<object?> GetWeatherForecastsAsync(ServiceEndpointContext ctx)
private static async Task<IApiResponse> GetWeatherForecastsAsync(ServiceEndpointContext ctx)
{
var sp = ctx.Items["ServiceProvider"] as IServiceProvider;
@@ -94,7 +94,7 @@ namespace Avalonia_Services.Endpoints
/// </summary>
/// <param name="ctx">服务端点上下文。</param>
/// <returns>用户信息。</returns>
private static async Task<object?> GetUserFromDatabaseAsync(ServiceEndpointContext ctx)
private static async Task<IApiResponse> GetUserFromDatabaseAsync(ServiceEndpointContext ctx)
{
var sp = ctx.Items["ServiceProvider"] as IServiceProvider;
@@ -119,7 +119,7 @@ namespace Avalonia_Services.Endpoints
/// </summary>
/// <param name="ctx">服务端点上下文。</param>
/// <returns>处理结果。</returns>
private static async Task<object?> ProcessDataAsync(ServiceEndpointContext ctx)
private static async Task<IApiResponse> ProcessDataAsync(ServiceEndpointContext ctx)
{
var sp = ctx.Items["ServiceProvider"] as IServiceProvider;
+18 -6
View File
@@ -16,17 +16,23 @@ namespace Avalonia_Services.Endpoints
{
builder.ConfigureEndpoints(endpoints =>
{
endpoints.MapPost<IApiAuthEndpointService>("api/auth/login", (service, ctx) => service.LoginAsync(ctx))
endpoints.MapPost<IApiAuthEndpointService, ApiLoginRequest>(
"api/auth/login",
(service, request, ctx) => service.LoginAsync(request, ctx))
.WithName("ApiLogin")
.WithOpenApi("Auth", "API 登录,返回 access token 和 refresh token。", "", typeof(ApiLoginRequest), typeof(AuthTokenResponse))
.ApiOnly();
endpoints.MapPost<IApiAuthEndpointService>("api/auth/refresh", (service, ctx) => service.RefreshAsync(ctx))
endpoints.MapPost<IApiAuthEndpointService, ApiRefreshTokenRequest>(
"api/auth/refresh",
(service, request, ctx) => service.RefreshAsync(request, ctx))
.WithName("ApiRefresh")
.WithOpenApi("Auth", "API refresh token 轮换。", "", typeof(ApiRefreshTokenRequest), typeof(AuthTokenResponse))
.ApiOnly();
endpoints.MapPost<IApiAuthEndpointService>("api/auth/logout", (service, ctx) => service.LogoutAsync(ctx))
endpoints.MapPost<IApiAuthEndpointService, ApiLogoutRequest>(
"api/auth/logout",
(service, request, ctx) => service.LogoutAsync(request, ctx))
.WithName("ApiLogout")
.WithOpenApi("Auth", "API 退出登录并吊销 refresh token。", "", typeof(ApiLogoutRequest))
.ApiOnly();
@@ -41,17 +47,23 @@ namespace Avalonia_Services.Endpoints
{
builder.ConfigureEndpoints(endpoints =>
{
endpoints.MapPost<IPcAuthEndpointService>("api/pc/auth/authorize", (service, ctx) => service.AuthorizeAsync(ctx))
endpoints.MapPost<IPcAuthEndpointService, PcAuthorizeRequest>(
"api/pc/auth/authorize",
(service, request, ctx) => service.AuthorizeAsync(request, ctx))
.WithName("PcAuthorize")
.WithOpenApi("Auth", "PC 授权码登录,生成本地全局 token。", "", typeof(PcAuthorizeRequest), typeof(PcTokenResponse))
.PcOnly();
endpoints.MapPost<IPcAuthEndpointService>("api/pc/auth/refresh", (service, ctx) => service.RefreshAsync(ctx))
endpoints.MapPost<IPcAuthEndpointService, PcRefreshRequest>(
"api/pc/auth/refresh",
(service, request, ctx) => service.RefreshAsync(request, ctx))
.WithName("PcRefresh")
.WithOpenApi("Auth", "PC 全局 token 刷新。", "", typeof(PcRefreshRequest), typeof(PcTokenResponse))
.PcOnly();
endpoints.MapPost<IPcAuthEndpointService>("api/pc/auth/logout", (service, ctx) => service.LogoutAsync(ctx))
endpoints.MapPost<IPcAuthEndpointService, PcLogoutRequest>(
"api/pc/auth/logout",
(service, request, ctx) => service.LogoutAsync(request, ctx))
.WithName("PcLogout")
.WithOpenApi("Auth", "PC 退出登录。", "", typeof(PcLogoutRequest))
.PcOnly();