AvaloniaStack/Avalonia-Services/Core/ServiceEndpointCollection.cs
luoqian a9abd90874 feat(auth): 添加统一 API 和 PC 认证端点
- 新增 API 端 JWT 登录、refresh token 轮换和退出登录流程
- 新增 refresh token 实体、DbSet 配置和 EF Core 迁移
- 新增 PC 端授权码登录、本地全局 token 刷新、登出和鉴权服务
- 扩展统一端点模型,支持宿主过滤、角色鉴权、OpenAPI 元数据和 DI 服务处理器
- API 启用 JwtBearer 认证、Swagger UI 和认证端点注册
- PC 端注册认证服务,并按宿主过滤桌面拦截端点
2026-05-15 17:35:07 +08:00

299 lines
9.5 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;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace Avalonia_Services.Core
{
[Flags]
public enum EndpointHostTarget
{
Api = 1,
Pc = 2,
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;
}
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;
}
public bool SupportsHost(EndpointHostTarget host)
{
return (HostTarget & host) != 0;
}
}
/// <summary>
/// 端点集合 —— 所有端点的注册中心。在 Avalonia-Services 中统一配置。
/// </summary>
public class ServiceEndpointCollection
{
/// <summary>所有已注册的端点</summary>
public List<ServiceEndpoint> Endpoints { get; } = new();
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);
}
public ServiceEndpoint MapGet<TService>(
string pattern,
Func<TService, ServiceEndpointContext, Task<object?>> handler)
where TService : notnull
{
return MapGet(pattern, CreateServiceHandler(handler));
}
/// <summary>
/// 注册一个 POST 端点。
/// </summary>
public ServiceEndpoint MapPost(string pattern, Func<ServiceEndpointContext, Task<object?>> handler)
{
return AddEndpoint(pattern, "POST", handler);
}
public ServiceEndpoint MapPost<TService>(
string pattern,
Func<TService, ServiceEndpointContext, Task<object?>> handler)
where TService : notnull
{
return MapPost(pattern, CreateServiceHandler(handler));
}
/// <summary>
/// 注册一个 PUT 端点。
/// </summary>
public ServiceEndpoint MapPut(string pattern, Func<ServiceEndpointContext, Task<object?>> handler)
{
return AddEndpoint(pattern, "PUT", handler);
}
public ServiceEndpoint MapPut<TService>(
string pattern,
Func<TService, ServiceEndpointContext, Task<object?>> handler)
where TService : notnull
{
return MapPut(pattern, CreateServiceHandler(handler));
}
/// <summary>
/// 注册一个 DELETE 端点。
/// </summary>
public ServiceEndpoint MapDelete(string pattern, Func<ServiceEndpointContext, Task<object?>> handler)
{
return AddEndpoint(pattern, "DELETE", handler);
}
public ServiceEndpoint MapDelete<TService>(
string pattern,
Func<TService, ServiceEndpointContext, Task<object?>> handler)
where TService : notnull
{
return MapDelete(pattern, CreateServiceHandler(handler));
}
/// <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;
}
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;
}
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>
/// 构建器 —— 提供 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;
}
}
}