init
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Avalonia_Services.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 端点列表打印工具 —— 在应用启动时输出所有已注册的拦截接口。
|
||||
/// 类似 Swagger 的接口清单效果。
|
||||
/// </summary>
|
||||
public static class EndpointPrinter
|
||||
{
|
||||
/// <summary>
|
||||
/// 打印所有已注册端点到控制台。
|
||||
/// </summary>
|
||||
public static void PrintEndpoints(
|
||||
ServiceEndpointCollection collection,
|
||||
string? title = null,
|
||||
EndpointHostTarget host = EndpointHostTarget.All)
|
||||
{
|
||||
title ??= "API Endpoints";
|
||||
var endpoints = collection.ForHost(host).ToList();
|
||||
|
||||
var maxMethodLen = endpoints.Count > 0
|
||||
? endpoints.Max(e => e.HttpMethod.Length)
|
||||
: 4;
|
||||
var maxPathLen = endpoints.Count > 0
|
||||
? endpoints.Max(e => e.Pattern.Length)
|
||||
: 8;
|
||||
|
||||
var totalWidth = maxMethodLen + maxPathLen + 5;
|
||||
var separator = new string('─', Math.Max(totalWidth, 50));
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"╔═ {title} ═{new string('═', Math.Max(0, totalWidth - title.Length - 3))}╗");
|
||||
Console.WriteLine($"║ {"Method".PadRight(maxMethodLen)} │ {"Path".PadRight(maxPathLen)} │ Auth ║");
|
||||
Console.WriteLine($"╟{separator}╢");
|
||||
|
||||
foreach (var ep in endpoints.OrderBy(e => e.Pattern))
|
||||
{
|
||||
var auth = ep.RequireAuthorization
|
||||
? (ep.Roles.Count > 0 ? string.Join(",", ep.Roles) : ep.Policy ?? "✓")
|
||||
: "—";
|
||||
var methodColor = ep.HttpMethod switch
|
||||
{
|
||||
"GET" => ConsoleColor.Green,
|
||||
"POST" => ConsoleColor.Blue,
|
||||
"PUT" => ConsoleColor.Yellow,
|
||||
"DELETE" => ConsoleColor.Red,
|
||||
_ => ConsoleColor.Gray,
|
||||
};
|
||||
|
||||
var savedColor = Console.ForegroundColor;
|
||||
|
||||
Console.Write("║ ");
|
||||
Console.ForegroundColor = methodColor;
|
||||
Console.Write(ep.HttpMethod.PadRight(maxMethodLen));
|
||||
Console.ForegroundColor = savedColor;
|
||||
Console.Write(" │ ");
|
||||
Console.Write(ep.Pattern.PadRight(maxPathLen));
|
||||
Console.Write(" │ ");
|
||||
Console.Write(auth.PadRight(4));
|
||||
Console.WriteLine(" ║");
|
||||
}
|
||||
|
||||
Console.WriteLine($"╚{separator}╝");
|
||||
Console.WriteLine($" Total: {endpoints.Count} endpoint(s)");
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using Avalonia_Common.Core;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Avalonia_Services.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 全局异常拦截过滤器 —— 自动包裹所有端点处理器,无需在每个方法中写 try-catch。
|
||||
/// 所有未捕获异常会被转为统一的 ApiResponse 错误格式。
|
||||
/// </summary>
|
||||
public sealed class GlobalExceptionFilter : IEndpointFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否在错误响应中包含异常详情。
|
||||
/// </summary>
|
||||
private readonly bool _includeDetails;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化全局异常过滤器。
|
||||
/// </summary>
|
||||
/// <param name="includeDetails">是否在响应中包含异常详情(开发环境建议 true,生产环境 false)</param>
|
||||
public GlobalExceptionFilter(bool includeDetails = false)
|
||||
{
|
||||
_includeDetails = includeDetails;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行过滤器逻辑:包裹下一个委托,捕获所有未处理异常并转换为统一错误响应。
|
||||
/// </summary>
|
||||
/// <param name="context">请求上下文。</param>
|
||||
/// <param name="next">管道中的下一个委托。</param>
|
||||
public async Task InvokeAsync(ServiceEndpointContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
try
|
||||
{
|
||||
await next(context);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 取消操作不视为错误
|
||||
context.StatusCode = 499;
|
||||
context.StatusMessage = "Client Closed Request";
|
||||
context.ResponseBody = ApiResponse<object>.Fail(499, "请求已取消");
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
context.StatusCode = 401;
|
||||
context.StatusMessage = "Unauthorized";
|
||||
context.ResponseBody = ApiResponse<object>.Unauthorized(
|
||||
_includeDetails ? ex.Message : "未授权访问");
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.Message.Contains("not found", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
context.StatusCode = 404;
|
||||
context.StatusMessage = "Not Found";
|
||||
context.ResponseBody = ApiResponse<object>.NotFound(
|
||||
_includeDetails ? ex.Message : "资源不存在");
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
context.StatusCode = 400;
|
||||
context.StatusMessage = "Bad Request";
|
||||
context.ResponseBody = ApiResponse<object>.BadRequest(
|
||||
_includeDetails ? ex.Message : "参数错误");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 记录完整日志(无论是否返回详情)
|
||||
LogException(context, ex);
|
||||
|
||||
context.StatusCode = 500;
|
||||
context.StatusMessage = "Internal Server Error";
|
||||
context.ResponseBody = ApiResponse<object>.ServerError(
|
||||
_includeDetails ? ex.Message : "服务器内部错误,请联系管理员");
|
||||
|
||||
// 可选:在开发环境附加堆栈信息
|
||||
if (_includeDetails)
|
||||
{
|
||||
// 通过 Items 传递额外调试信息
|
||||
context.Items["ExceptionDetail"] = ex.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录异常日志,优先使用 Serilog,不可用时回退到 Console。
|
||||
/// </summary>
|
||||
/// <param name="context">请求上下文。</param>
|
||||
/// <param name="ex">异常对象。</param>
|
||||
private static void LogException(ServiceEndpointContext context, Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用 Serilog(如果已配置)
|
||||
Serilog.Log.Error(ex,
|
||||
"全局异常拦截 | {Method} {Path} | {ExceptionType}: {Message}",
|
||||
context.Method, context.Path, ex.GetType().Name, ex.Message);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Serilog 不可用时回退到 Console
|
||||
Console.Error.WriteLine(
|
||||
$"[ERROR] {context.Method} {context.Path} | {ex.GetType().Name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Avalonia_Services.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 鉴权服务抽象 —— 各宿主按自己的方式实现(JWT / Cookie / Token 等)。
|
||||
/// </summary>
|
||||
public interface IAuthService
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证请求并返回用户主体;返回 null 表示未授权。
|
||||
/// </summary>
|
||||
Task<ClaimsPrincipal?> AuthenticateAsync(ServiceEndpointContext context);
|
||||
|
||||
/// <summary>
|
||||
/// 检查当前用户是否有指定权限。
|
||||
/// </summary>
|
||||
Task<bool> AuthorizeAsync(ClaimsPrincipal user, string policy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 无需鉴权的默认实现(开发/公开 API 场景)。
|
||||
/// </summary>
|
||||
public sealed class AnonymousAuthService : IAuthService
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task<ClaimsPrincipal?> AuthenticateAsync(ServiceEndpointContext context)
|
||||
{
|
||||
// 匿名用户,始终通过
|
||||
var identity = new ClaimsIdentity("anonymous");
|
||||
return Task.FromResult<ClaimsPrincipal?>(new ClaimsPrincipal(identity));
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public Task<bool> AuthorizeAsync(ClaimsPrincipal user, string policy)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Avalonia_Services.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 端点过滤器抽象 —— 在请求处理前后执行逻辑。
|
||||
/// 类似于 ASP.NET Core 的 IEndpointFilter,但可在任何宿主中使用。
|
||||
/// </summary>
|
||||
public interface IEndpointFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 过滤器执行方法。
|
||||
/// 调用 next(ctx) 继续管道;不调用则短路。
|
||||
/// </summary>
|
||||
Task InvokeAsync(ServiceEndpointContext context, EndpointFilterDelegate next);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 过滤器管道中的下一个委托。
|
||||
/// </summary>
|
||||
public delegate Task EndpointFilterDelegate(ServiceEndpointContext context);
|
||||
|
||||
/// <summary>
|
||||
/// 用于包装匿名过滤器的简单实现。
|
||||
/// </summary>
|
||||
internal sealed class AnonymousEndpointFilter : IEndpointFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 匿名过滤器的委托实现。
|
||||
/// </summary>
|
||||
private readonly Func<ServiceEndpointContext, EndpointFilterDelegate, Task> _filter;
|
||||
|
||||
/// <summary>
|
||||
/// 使用匿名函数创建过滤器。
|
||||
/// </summary>
|
||||
/// <param name="filter">过滤器委托。</param>
|
||||
public AnonymousEndpointFilter(Func<ServiceEndpointContext, EndpointFilterDelegate, Task> filter)
|
||||
{
|
||||
_filter = filter;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task InvokeAsync(ServiceEndpointContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
return _filter(context, next);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
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-API(ASP.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>
|
||||
/// <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>
|
||||
/// 注册一个 POST 端点。
|
||||
/// </summary>
|
||||
public ServiceEndpoint MapPost(string pattern, Func<ServiceEndpointContext, Task<object?>> handler)
|
||||
{
|
||||
return AddEndpoint(pattern, "POST", 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>
|
||||
/// 注册一个 PUT 端点。
|
||||
/// </summary>
|
||||
public ServiceEndpoint MapPut(string pattern, Func<ServiceEndpointContext, Task<object?>> handler)
|
||||
{
|
||||
return AddEndpoint(pattern, "PUT", 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>
|
||||
/// 注册一个 DELETE 端点。
|
||||
/// </summary>
|
||||
public ServiceEndpoint MapDelete(string pattern, Func<ServiceEndpointContext, Task<object?>> handler)
|
||||
{
|
||||
return AddEndpoint(pattern, "DELETE", 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>
|
||||
/// 添加全局过滤器(作用于所有端点)。
|
||||
/// </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>
|
||||
/// 构建器 —— 提供 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Avalonia_Services.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 抽象的请求上下文,屏蔽不同宿主(ASP.NET Core / Desktop WebView)的差异。
|
||||
/// </summary>
|
||||
public class ServiceEndpointContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求路径,例如 "api/wData"
|
||||
/// </summary>
|
||||
public string Path { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 方法(GET, POST, PUT, DELETE 等)
|
||||
/// </summary>
|
||||
public string Method { get; init; } = "GET";
|
||||
|
||||
/// <summary>
|
||||
/// 请求头
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Headers { get; init; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// 请求体(原始字符串)
|
||||
/// </summary>
|
||||
public string? Body { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 查询参数
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Query { get; init; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// 响应状态码
|
||||
/// </summary>
|
||||
public int StatusCode { get; set; } = 200;
|
||||
|
||||
/// <summary>
|
||||
/// 响应状态描述
|
||||
/// </summary>
|
||||
public string StatusMessage { get; set; } = "OK";
|
||||
|
||||
/// <summary>
|
||||
/// 响应头
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ResponseHeaders { get; set; } = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["Content-Type"] = "application/json; charset=utf-8"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 响应体
|
||||
/// </summary>
|
||||
public object? ResponseBody { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 存储在请求生命周期中的任意数据(由中间件/过滤器使用)
|
||||
/// </summary>
|
||||
public Dictionary<string, object?> Items { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取请求头值
|
||||
/// </summary>
|
||||
public string? GetHeader(string key)
|
||||
{
|
||||
return Headers.TryGetValue(key, out var value) ? value : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置响应头
|
||||
/// </summary>
|
||||
public void SetResponseHeader(string key, string value)
|
||||
{
|
||||
ResponseHeaders[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user