2026-05-22 11:42:38 +08:00
|
|
|
|
using Avalonia_Common.Core;
|
2026-05-11 14:35:34 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Threading.Tasks;
|
2026-05-15 17:35:07 +08:00
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
2026-05-11 14:35:34 +08:00
|
|
|
|
|
|
|
|
|
|
namespace Avalonia_Services.Core
|
|
|
|
|
|
{
|
2026-05-18 11:35:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 端点挂载的宿主目标。
|
|
|
|
|
|
/// </summary>
|
2026-05-15 17:35:07 +08:00
|
|
|
|
[Flags]
|
|
|
|
|
|
public enum EndpointHostTarget
|
|
|
|
|
|
{
|
2026-05-18 11:35:13 +08:00
|
|
|
|
/// <summary>挂载到 Avalonia-API(ASP.NET Core Web API)。</summary>
|
2026-05-15 17:35:07 +08:00
|
|
|
|
Api = 1,
|
2026-05-18 11:35:13 +08:00
|
|
|
|
/// <summary>挂载到 Avalonia-PC(桌面 WebView)。</summary>
|
2026-05-15 17:35:07 +08:00
|
|
|
|
Pc = 2,
|
2026-05-18 11:35:13 +08:00
|
|
|
|
/// <summary>同时挂载到 API 和 PC。</summary>
|
2026-05-15 17:35:07 +08:00
|
|
|
|
All = Api | Pc,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 14:35:34 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 单个端点定义。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class ServiceEndpoint
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>路由路径,如 "api/wData"</summary>
|
|
|
|
|
|
public string Pattern { get; init; } = string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>HTTP 方法(GET/POST/PUT/DELETE)</summary>
|
|
|
|
|
|
public string HttpMethod { get; init; } = "GET";
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>端点名称(用于 OpenAPI / 日志)</summary>
|
|
|
|
|
|
public string? Name { get; set; }
|
|
|
|
|
|
|
2026-05-15 17:35:07 +08:00
|
|
|
|
/// <summary>OpenAPI 分组标签。</summary>
|
|
|
|
|
|
public string? OpenApiTag { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>OpenAPI 摘要。</summary>
|
|
|
|
|
|
public string? OpenApiSummary { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>OpenAPI 描述。</summary>
|
|
|
|
|
|
public string? OpenApiDescription { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>OpenAPI 请求体类型。</summary>
|
|
|
|
|
|
public Type? OpenApiRequestType { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>OpenAPI 200 响应数据类型。</summary>
|
|
|
|
|
|
public Type? OpenApiResponseType { get; set; }
|
|
|
|
|
|
|
2026-05-11 14:35:34 +08:00
|
|
|
|
/// <summary>端点处理器</summary>
|
|
|
|
|
|
public Func<ServiceEndpointContext, Task<object?>> Handler { get; init; } = _ => Task.FromResult<object?>(null);
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>该端点专属的过滤器(按顺序执行)</summary>
|
|
|
|
|
|
public List<IEndpointFilter> Filters { get; init; } = new();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>是否需要鉴权</summary>
|
|
|
|
|
|
public bool RequireAuthorization { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>鉴权策略名</summary>
|
|
|
|
|
|
public string? Policy { get; set; }
|
|
|
|
|
|
|
2026-05-15 17:35:07 +08:00
|
|
|
|
/// <summary>允许访问该端点的角色。多个角色满足任意一个即可。</summary>
|
|
|
|
|
|
public List<string> Roles { get; } = new();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>端点挂载的宿主。默认 API 和 PC 都挂载。</summary>
|
|
|
|
|
|
public EndpointHostTarget HostTarget { get; set; } = EndpointHostTarget.All;
|
|
|
|
|
|
|
2026-05-11 14:35:34 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置端点名称(Fluent API)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint WithName(string name)
|
|
|
|
|
|
{
|
|
|
|
|
|
Name = name;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
2026-05-15 17:35:07 +08:00
|
|
|
|
|
2026-05-18 11:35:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置端点的 OpenAPI 元数据(标签、摘要、描述、请求/响应类型)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="tag">OpenAPI 分组标签。</param>
|
|
|
|
|
|
/// <param name="summary">简要摘要。</param>
|
|
|
|
|
|
/// <param name="description">详细描述。</param>
|
|
|
|
|
|
/// <param name="requestType">请求体类型。</param>
|
|
|
|
|
|
/// <param name="responseType">成功响应类型。</param>
|
|
|
|
|
|
/// <returns>当前端点实例(Fluent API)。</returns>
|
2026-05-15 17:35:07 +08:00
|
|
|
|
public ServiceEndpoint WithOpenApi(
|
|
|
|
|
|
string tag,
|
|
|
|
|
|
string summary,
|
|
|
|
|
|
string? description = null,
|
|
|
|
|
|
Type? requestType = null,
|
|
|
|
|
|
Type? responseType = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
OpenApiTag = tag;
|
|
|
|
|
|
OpenApiSummary = summary;
|
|
|
|
|
|
OpenApiDescription = description;
|
|
|
|
|
|
OpenApiRequestType = requestType;
|
|
|
|
|
|
OpenApiResponseType = responseType;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 标记端点需要登录。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint RequireAuth()
|
|
|
|
|
|
{
|
|
|
|
|
|
RequireAuthorization = true;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 标记端点需要指定角色。多个角色满足任意一个即可。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint RequireRoles(params string[] roles)
|
|
|
|
|
|
{
|
|
|
|
|
|
RequireAuthorization = true;
|
|
|
|
|
|
Roles.Clear();
|
|
|
|
|
|
Roles.AddRange(roles.Where(role => !string.IsNullOrWhiteSpace(role)).Select(role => role.Trim()));
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 只挂载到 Avalonia-API。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint ApiOnly()
|
|
|
|
|
|
{
|
|
|
|
|
|
HostTarget = EndpointHostTarget.Api;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 只挂载到 Avalonia-PC。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint PcOnly()
|
|
|
|
|
|
{
|
|
|
|
|
|
HostTarget = EndpointHostTarget.Pc;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 11:35:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 判断端点是否支持指定的宿主目标。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="host">要检查的宿主目标。</param>
|
|
|
|
|
|
/// <returns>是否支持。</returns>
|
2026-05-15 17:35:07 +08:00
|
|
|
|
public bool SupportsHost(EndpointHostTarget host)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (HostTarget & host) != 0;
|
|
|
|
|
|
}
|
2026-05-11 14:35:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 端点集合 —— 所有端点的注册中心。在 Avalonia-Services 中统一配置。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class ServiceEndpointCollection
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>所有已注册的端点</summary>
|
|
|
|
|
|
public List<ServiceEndpoint> Endpoints { get; } = new();
|
|
|
|
|
|
|
2026-05-18 11:35:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取指定宿主目标的所有端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="host">宿主目标。</param>
|
|
|
|
|
|
/// <returns>匹配的端点集合。</returns>
|
2026-05-15 17:35:07 +08:00
|
|
|
|
public IEnumerable<ServiceEndpoint> ForHost(EndpointHostTarget host)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Endpoints.Where(endpoint => endpoint.SupportsHost(host));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 14:35:34 +08:00
|
|
|
|
/// <summary>作用于所有端点的全局过滤器</summary>
|
|
|
|
|
|
public List<IEndpointFilter> GlobalFilters { get; } = new();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapGet(string pattern, Func<ServiceEndpointContext, Task<object?>> handler)
|
|
|
|
|
|
{
|
|
|
|
|
|
return AddEndpoint(pattern, "GET", handler);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 11:42:38 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个返回统一响应契约的 GET 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapGet(string pattern, Func<ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MapGet(pattern, CreateApiResponseHandler(handler));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 11:35:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个带服务依赖注入的 GET 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="TService">服务类型。</typeparam>
|
|
|
|
|
|
/// <param name="pattern">路由路径。</param>
|
|
|
|
|
|
/// <param name="handler">接受服务实例和上下文的处理器。</param>
|
|
|
|
|
|
/// <returns>已注册的端点实例。</returns>
|
2026-05-15 17:35:07 +08:00
|
|
|
|
public ServiceEndpoint MapGet<TService>(
|
|
|
|
|
|
string pattern,
|
|
|
|
|
|
Func<TService, ServiceEndpointContext, Task<object?>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
return MapGet(pattern, CreateServiceHandler(handler));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 11:42:38 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个带服务依赖注入且返回统一响应契约的 GET 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapGet<TService>(
|
|
|
|
|
|
string pattern,
|
|
|
|
|
|
Func<TService, ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
return MapGet(pattern, CreateServiceHandler(handler));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个带查询请求 DTO 和服务依赖注入的 GET 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapGet<TService, TRequest>(
|
|
|
|
|
|
string pattern,
|
|
|
|
|
|
Func<TService, TRequest, ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
var endpoint = MapGet(
|
|
|
|
|
|
pattern,
|
|
|
|
|
|
CreateServiceHandler<TService>((service, ctx) =>
|
|
|
|
|
|
handler(service, ServiceRequestBinder.BindQuery<TRequest>(ctx), ctx)));
|
|
|
|
|
|
endpoint.OpenApiRequestType ??= typeof(TRequest);
|
|
|
|
|
|
return endpoint;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 14:35:34 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个 POST 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapPost(string pattern, Func<ServiceEndpointContext, Task<object?>> handler)
|
|
|
|
|
|
{
|
|
|
|
|
|
return AddEndpoint(pattern, "POST", handler);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 11:42:38 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个返回统一响应契约的 POST 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapPost(string pattern, Func<ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MapPost(pattern, CreateApiResponseHandler(handler));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 11:35:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个带服务依赖注入的 POST 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="TService">服务类型。</typeparam>
|
|
|
|
|
|
/// <param name="pattern">路由路径。</param>
|
|
|
|
|
|
/// <param name="handler">接受服务实例和上下文的处理器。</param>
|
|
|
|
|
|
/// <returns>已注册的端点实例。</returns>
|
2026-05-15 17:35:07 +08:00
|
|
|
|
public ServiceEndpoint MapPost<TService>(
|
|
|
|
|
|
string pattern,
|
|
|
|
|
|
Func<TService, ServiceEndpointContext, Task<object?>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
return MapPost(pattern, CreateServiceHandler(handler));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 11:42:38 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个带服务依赖注入且返回统一响应契约的 POST 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapPost<TService>(
|
|
|
|
|
|
string pattern,
|
|
|
|
|
|
Func<TService, ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
return MapPost(pattern, CreateServiceHandler(handler));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个带 JSON 请求 DTO 和服务依赖注入的 POST 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapPost<TService, TRequest>(
|
|
|
|
|
|
string pattern,
|
|
|
|
|
|
Func<TService, TRequest, ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
var endpoint = MapPost(
|
|
|
|
|
|
pattern,
|
|
|
|
|
|
CreateServiceHandler<TService>((service, ctx) =>
|
|
|
|
|
|
handler(service, ServiceRequestBinder.BindBody<TRequest>(ctx), ctx)));
|
|
|
|
|
|
endpoint.OpenApiRequestType ??= typeof(TRequest);
|
|
|
|
|
|
return endpoint;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 14:35:34 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个 PUT 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapPut(string pattern, Func<ServiceEndpointContext, Task<object?>> handler)
|
|
|
|
|
|
{
|
|
|
|
|
|
return AddEndpoint(pattern, "PUT", handler);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 11:42:38 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个返回统一响应契约的 PUT 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapPut(string pattern, Func<ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MapPut(pattern, CreateApiResponseHandler(handler));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 11:35:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个带服务依赖注入的 PUT 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="TService">服务类型。</typeparam>
|
|
|
|
|
|
/// <param name="pattern">路由路径。</param>
|
|
|
|
|
|
/// <param name="handler">接受服务实例和上下文的处理器。</param>
|
|
|
|
|
|
/// <returns>已注册的端点实例。</returns>
|
2026-05-15 17:35:07 +08:00
|
|
|
|
public ServiceEndpoint MapPut<TService>(
|
|
|
|
|
|
string pattern,
|
|
|
|
|
|
Func<TService, ServiceEndpointContext, Task<object?>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
return MapPut(pattern, CreateServiceHandler(handler));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 11:42:38 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个带服务依赖注入且返回统一响应契约的 PUT 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapPut<TService>(
|
|
|
|
|
|
string pattern,
|
|
|
|
|
|
Func<TService, ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
return MapPut(pattern, CreateServiceHandler(handler));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个带 JSON 请求 DTO 和服务依赖注入的 PUT 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapPut<TService, TRequest>(
|
|
|
|
|
|
string pattern,
|
|
|
|
|
|
Func<TService, TRequest, ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
var endpoint = MapPut(
|
|
|
|
|
|
pattern,
|
|
|
|
|
|
CreateServiceHandler<TService>((service, ctx) =>
|
|
|
|
|
|
handler(service, ServiceRequestBinder.BindBody<TRequest>(ctx), ctx)));
|
|
|
|
|
|
endpoint.OpenApiRequestType ??= typeof(TRequest);
|
|
|
|
|
|
return endpoint;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 14:35:34 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个 DELETE 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapDelete(string pattern, Func<ServiceEndpointContext, Task<object?>> handler)
|
|
|
|
|
|
{
|
|
|
|
|
|
return AddEndpoint(pattern, "DELETE", handler);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 11:42:38 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个返回统一响应契约的 DELETE 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapDelete(string pattern, Func<ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
{
|
|
|
|
|
|
return MapDelete(pattern, CreateApiResponseHandler(handler));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 11:35:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个带服务依赖注入的 DELETE 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="TService">服务类型。</typeparam>
|
|
|
|
|
|
/// <param name="pattern">路由路径。</param>
|
|
|
|
|
|
/// <param name="handler">接受服务实例和上下文的处理器。</param>
|
|
|
|
|
|
/// <returns>已注册的端点实例。</returns>
|
2026-05-15 17:35:07 +08:00
|
|
|
|
public ServiceEndpoint MapDelete<TService>(
|
|
|
|
|
|
string pattern,
|
|
|
|
|
|
Func<TService, ServiceEndpointContext, Task<object?>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
return MapDelete(pattern, CreateServiceHandler(handler));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 11:42:38 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个带服务依赖注入且返回统一响应契约的 DELETE 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapDelete<TService>(
|
|
|
|
|
|
string pattern,
|
|
|
|
|
|
Func<TService, ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
return MapDelete(pattern, CreateServiceHandler(handler));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个带查询请求 DTO 和服务依赖注入的 DELETE 端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpoint MapDelete<TService, TRequest>(
|
|
|
|
|
|
string pattern,
|
|
|
|
|
|
Func<TService, TRequest, ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
var endpoint = MapDelete(
|
|
|
|
|
|
pattern,
|
|
|
|
|
|
CreateServiceHandler<TService>((service, ctx) =>
|
|
|
|
|
|
handler(service, ServiceRequestBinder.BindQuery<TRequest>(ctx), ctx)));
|
|
|
|
|
|
endpoint.OpenApiRequestType ??= typeof(TRequest);
|
|
|
|
|
|
return endpoint;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 14:35:34 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 添加全局过滤器(作用于所有端点)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpointCollection AddGlobalFilter(IEndpointFilter filter)
|
|
|
|
|
|
{
|
|
|
|
|
|
GlobalFilters.Add(filter);
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 通过匿名函数添加全局过滤器。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpointCollection AddGlobalFilter(Func<ServiceEndpointContext, EndpointFilterDelegate, Task> filter)
|
|
|
|
|
|
{
|
|
|
|
|
|
GlobalFilters.Add(new AnonymousEndpointFilter(filter));
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 11:35:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 内部方法,创建端点并添加到集合。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="pattern">路由路径。</param>
|
|
|
|
|
|
/// <param name="method">HTTP 方法。</param>
|
|
|
|
|
|
/// <param name="handler">端点处理器。</param>
|
|
|
|
|
|
/// <returns>已创建的端点实例。</returns>
|
2026-05-11 14:35:34 +08:00
|
|
|
|
private ServiceEndpoint AddEndpoint(string pattern, string method, Func<ServiceEndpointContext, Task<object?>> handler)
|
|
|
|
|
|
{
|
|
|
|
|
|
var endpoint = new ServiceEndpoint
|
|
|
|
|
|
{
|
|
|
|
|
|
Pattern = pattern,
|
|
|
|
|
|
HttpMethod = method,
|
|
|
|
|
|
Handler = handler,
|
|
|
|
|
|
};
|
|
|
|
|
|
Endpoints.Add(endpoint);
|
|
|
|
|
|
return endpoint;
|
|
|
|
|
|
}
|
2026-05-15 17:35:07 +08:00
|
|
|
|
|
2026-05-18 11:35:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 创建自动从 DI 解析服务实例并调用处理器的委托包装。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="TService">服务类型。</typeparam>
|
|
|
|
|
|
/// <param name="handler">接受服务实例和上下文的处理器。</param>
|
|
|
|
|
|
/// <returns>包装后的处理器委托。</returns>
|
2026-05-15 17:35:07 +08:00
|
|
|
|
private static Func<ServiceEndpointContext, Task<object?>> CreateServiceHandler<TService>(
|
|
|
|
|
|
Func<TService, ServiceEndpointContext, Task<object?>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
return async ctx =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var serviceProvider = ctx.Items["ServiceProvider"] as IServiceProvider
|
|
|
|
|
|
?? throw new InvalidOperationException("ServiceProvider 未注入。");
|
|
|
|
|
|
|
|
|
|
|
|
await using var scope = serviceProvider.CreateAsyncScope();
|
|
|
|
|
|
var service = scope.ServiceProvider.GetRequiredService<TService>();
|
|
|
|
|
|
return await handler(service, ctx);
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2026-05-22 11:42:38 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将统一响应契约适配为端点集合内部使用的异构响应类型。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static Func<ServiceEndpointContext, Task<object?>> CreateApiResponseHandler(
|
|
|
|
|
|
Func<ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
{
|
|
|
|
|
|
return async ctx => await handler(ctx);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 为服务端点创建统一响应契约的 DI 包装。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static Func<ServiceEndpointContext, Task<IApiResponse>> CreateServiceHandler<TService>(
|
|
|
|
|
|
Func<TService, ServiceEndpointContext, Task<IApiResponse>> handler)
|
|
|
|
|
|
where TService : notnull
|
|
|
|
|
|
{
|
|
|
|
|
|
return async ctx =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var serviceProvider = ctx.Items["ServiceProvider"] as IServiceProvider
|
|
|
|
|
|
?? throw new InvalidOperationException("ServiceProvider 未注入。");
|
|
|
|
|
|
|
|
|
|
|
|
await using var scope = serviceProvider.CreateAsyncScope();
|
|
|
|
|
|
var service = scope.ServiceProvider.GetRequiredService<TService>();
|
|
|
|
|
|
return await handler(service, ctx);
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2026-05-11 14:35:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 构建器 —— 提供 Fluent API 来配置所有端点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class ServiceEndpointBuilder
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 端点集合
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpointCollection Endpoints { get; } = new();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 鉴权服务(默认匿名)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public IAuthService AuthService { get; set; } = new AnonymousAuthService();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 配置端点(在此方法中调用 endpoints.MapGet 等)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpointBuilder ConfigureEndpoints(Action<ServiceEndpointCollection> configure)
|
|
|
|
|
|
{
|
|
|
|
|
|
configure(Endpoints);
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置鉴权服务。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpointBuilder UseAuthService(IAuthService authService)
|
|
|
|
|
|
{
|
|
|
|
|
|
AuthService = authService;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 构建最终的端点集合。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ServiceEndpointCollection Build()
|
|
|
|
|
|
{
|
|
|
|
|
|
return Endpoints;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|