FileShare/Avalonia-Services/Core/ServiceEndpointCollection.cs
luoqian d84bbb3a18 feat: 二维码访问功能,统一端点管道增强,端点迁移至 Services 层
- 新增二维码生成端点,自动检测局域网 IP,前端扫一扫即可打开网站
  - 提取 IApiResponse 接口,ServiceRequestBinder 支持强类型请求 DTO 绑定
  - FileStream 端点迁移至 AppEndpoints 统一注册,管道支持 FileStreamResponse 原始文件返回
  - 文件库端点全面使用 MapGet<TService, TRequest> 泛型注册
  - 移除 Avalonia-API/Extensions 中的业务端点文件,统一由 Services 层管理
2026-05-22 11:18:47 +08:00

533 lines
20 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 Avalonia_Common.Core;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace Avalonia_Services.Core
{
/// <summary>
/// 端点挂载的宿主目标。
/// </summary>
[Flags]
public enum EndpointHostTarget
{
/// <summary>挂载到 Avalonia-APIASP.NET Core Web API。</summary>
Api = 1,
/// <summary>挂载到 Avalonia-PC桌面 WebView。</summary>
Pc = 2,
/// <summary>同时挂载到 API 和 PC。</summary>
All = Api | Pc,
}
/// <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; }
/// <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; }
/// <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; }
/// <summary>允许访问该端点的角色。多个角色满足任意一个即可。</summary>
public List<string> Roles { get; } = new();
/// <summary>端点挂载的宿主。默认 API 和 PC 都挂载。</summary>
public EndpointHostTarget HostTarget { get; set; } = EndpointHostTarget.All;
/// <summary>
/// 设置端点名称Fluent API
/// </summary>
public ServiceEndpoint WithName(string name)
{
Name = name;
return this;
}
/// <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>
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;
}
/// <summary>
/// 判断端点是否支持指定的宿主目标。
/// </summary>
/// <param name="host">要检查的宿主目标。</param>
/// <returns>是否支持。</returns>
public bool SupportsHost(EndpointHostTarget host)
{
return (HostTarget & host) != 0;
}
}
/// <summary>
/// 端点集合 —— 所有端点的注册中心。在 Avalonia-Services 中统一配置。
/// </summary>
public class ServiceEndpointCollection
{
/// <summary>所有已注册的端点</summary>
public List<ServiceEndpoint> Endpoints { get; } = new();
/// <summary>
/// 获取指定宿主目标的所有端点。
/// </summary>
/// <param name="host">宿主目标。</param>
/// <returns>匹配的端点集合。</returns>
public IEnumerable<ServiceEndpoint> ForHost(EndpointHostTarget host)
{
return Endpoints.Where(endpoint => endpoint.SupportsHost(host));
}
/// <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);
}
/// <summary>
/// 注册一个返回统一响应契约的 GET 端点。
/// </summary>
public ServiceEndpoint MapGet(string pattern, Func<ServiceEndpointContext, Task<IApiResponse>> handler)
{
return MapGet(pattern, CreateApiResponseHandler(handler));
}
/// <summary>
/// 注册一个带服务依赖注入的 GET 端点。
/// </summary>
/// <typeparam name="TService">服务类型。</typeparam>
/// <param name="pattern">路由路径。</param>
/// <param name="handler">接受服务实例和上下文的处理器。</param>
/// <returns>已注册的端点实例。</returns>
public ServiceEndpoint MapGet<TService>(
string pattern,
Func<TService, ServiceEndpointContext, Task<object?>> handler)
where TService : notnull
{
return MapGet(pattern, CreateServiceHandler(handler));
}
/// <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;
}
/// <summary>
/// 注册一个 POST 端点。
/// </summary>
public ServiceEndpoint MapPost(string pattern, Func<ServiceEndpointContext, Task<object?>> handler)
{
return AddEndpoint(pattern, "POST", handler);
}
/// <summary>
/// 注册一个返回统一响应契约的 POST 端点。
/// </summary>
public ServiceEndpoint MapPost(string pattern, Func<ServiceEndpointContext, Task<IApiResponse>> handler)
{
return MapPost(pattern, CreateApiResponseHandler(handler));
}
/// <summary>
/// 注册一个带服务依赖注入的 POST 端点。
/// </summary>
/// <typeparam name="TService">服务类型。</typeparam>
/// <param name="pattern">路由路径。</param>
/// <param name="handler">接受服务实例和上下文的处理器。</param>
/// <returns>已注册的端点实例。</returns>
public ServiceEndpoint MapPost<TService>(
string pattern,
Func<TService, ServiceEndpointContext, Task<object?>> handler)
where TService : notnull
{
return MapPost(pattern, CreateServiceHandler(handler));
}
/// <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;
}
/// <summary>
/// 注册一个 PUT 端点。
/// </summary>
public ServiceEndpoint MapPut(string pattern, Func<ServiceEndpointContext, Task<object?>> handler)
{
return AddEndpoint(pattern, "PUT", handler);
}
/// <summary>
/// 注册一个返回统一响应契约的 PUT 端点。
/// </summary>
public ServiceEndpoint MapPut(string pattern, Func<ServiceEndpointContext, Task<IApiResponse>> handler)
{
return MapPut(pattern, CreateApiResponseHandler(handler));
}
/// <summary>
/// 注册一个带服务依赖注入的 PUT 端点。
/// </summary>
/// <typeparam name="TService">服务类型。</typeparam>
/// <param name="pattern">路由路径。</param>
/// <param name="handler">接受服务实例和上下文的处理器。</param>
/// <returns>已注册的端点实例。</returns>
public ServiceEndpoint MapPut<TService>(
string pattern,
Func<TService, ServiceEndpointContext, Task<object?>> handler)
where TService : notnull
{
return MapPut(pattern, CreateServiceHandler(handler));
}
/// <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;
}
/// <summary>
/// 注册一个 DELETE 端点。
/// </summary>
public ServiceEndpoint MapDelete(string pattern, Func<ServiceEndpointContext, Task<object?>> handler)
{
return AddEndpoint(pattern, "DELETE", handler);
}
/// <summary>
/// 注册一个返回统一响应契约的 DELETE 端点。
/// </summary>
public ServiceEndpoint MapDelete(string pattern, Func<ServiceEndpointContext, Task<IApiResponse>> handler)
{
return MapDelete(pattern, CreateApiResponseHandler(handler));
}
/// <summary>
/// 注册一个带服务依赖注入的 DELETE 端点。
/// </summary>
/// <typeparam name="TService">服务类型。</typeparam>
/// <param name="pattern">路由路径。</param>
/// <param name="handler">接受服务实例和上下文的处理器。</param>
/// <returns>已注册的端点实例。</returns>
public ServiceEndpoint MapDelete<TService>(
string pattern,
Func<TService, ServiceEndpointContext, Task<object?>> handler)
where TService : notnull
{
return MapDelete(pattern, CreateServiceHandler(handler));
}
/// <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;
}
/// <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;
}
/// <summary>
/// 内部方法,创建端点并添加到集合。
/// </summary>
/// <param name="pattern">路由路径。</param>
/// <param name="method">HTTP 方法。</param>
/// <param name="handler">端点处理器。</param>
/// <returns>已创建的端点实例。</returns>
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;
}
/// <summary>
/// 创建自动从 DI 解析服务实例并调用处理器的委托包装。
/// </summary>
/// <typeparam name="TService">服务类型。</typeparam>
/// <param name="handler">接受服务实例和上下文的处理器。</param>
/// <returns>包装后的处理器委托。</returns>
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);
};
}
/// <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);
};
}
}
/// <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;
}
}
}