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

138 lines
5.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 Avalonia_Common.Core;
using Avalonia_EFCore.Database;
using Avalonia_EFCore.Models;
using Avalonia_Services.Core;
using Avalonia_Services.Services;
using Microsoft.EntityFrameworkCore;
namespace Avalonia_Services.Endpoints
{
/// <summary>
/// 统一端点配置 —— 所有业务端点在此定义一次。
/// 这是 Avalonia-API 和 Avalonia-PC 的唯一入口。
/// </summary>
public static class AppEndpoints
{
/// <summary>
/// 配置所有业务端点。调用方传入 builder按需叠加鉴权、过滤器等。
/// </summary>
/// <param name="builder">端点构建器</param>
/// <param name="includeDetails">是否在错误响应中包含异常详情(开发环境 true</param>
public static ServiceEndpointBuilder Configure(ServiceEndpointBuilder builder, bool includeDetails = false)
{
// ---- 全局异常拦截(自动捕获所有端点中未处理的异常) ----
builder.Endpoints.AddGlobalFilter(new GlobalExceptionFilter(includeDetails));
builder.ConfigureEndpoints(endpoints =>
{
// ---- 全局日志过滤器(记录每个请求) ----
endpoints.AddGlobalFilter(async (ctx, next) =>
{
Serilog.Log.Debug("→ {Method} {Path}", ctx.Method, ctx.Path);
await next(ctx);
Serilog.Log.Debug("← {Method} {Path} | {StatusCode}", ctx.Method, ctx.Path, ctx.StatusCode);
});
// ---- 业务端点注册 ----
// 天气预报(从数据库读取)
endpoints.MapGet("api/wData", GetWeatherForecastsAsync)
.WithOpenApi("Weather", "获取天气预报信息。")
.WithName("GetWeatherForecast");
// 获取用户(演示从数据库查询)
endpoints.MapGet("api/getUser", GetUserFromDatabaseAsync)
.WithName("GetUser");
// 处理数据POST — 演示参数处理)
endpoints.MapPost("api/processData", ProcessDataAsync)
.WithName("ProcessData");
// ---- 需要鉴权的端点示例 ----
// endpoints.MapGet("api/admin/dashboard", AdminDashboardAsync)
// .WithName("AdminDashboard")
// .RequireAuthorization = true
// .Policy = "AdminOnly";
});
return builder;
}
#region
/// <summary>
/// 从数据库查询天气预报(优先数据库,回退到内存生成)。
/// </summary>
private static async Task<object?> GetWeatherForecastsAsync(ServiceEndpointContext ctx)
{
var sp = ctx.Items["ServiceProvider"] as IServiceProvider;
// 尝试从数据库读取
if (sp?.GetService(typeof(AppDataContext)) is AppDataContext db)
{
var dbForecasts = await db.WeatherForecasts
.OrderByDescending(f => f.Date)
.Take(5)
.ToListAsync();
if (dbForecasts.Count > 0)
{
return ResponseHelper.Ok(dbForecasts, "获取天气预报成功(来自数据库)");
}
}
// 回退:内存生成(数据库为空时)
var service = sp?.GetService(typeof(WeatherForecastService)) as WeatherForecastService
?? new WeatherForecastService();
var forecasts = service.GetWeatherForecasts();
return ResponseHelper.Ok(forecasts, "获取天气预报成功(内存生成)");
}
private static async Task<object?> GetUserFromDatabaseAsync(ServiceEndpointContext ctx)
{
var sp = ctx.Items["ServiceProvider"] as IServiceProvider;
// 尝试从数据库读取用户
if (sp?.GetService(typeof(AppDataContext)) is AppDataContext db)
{
var users = await db.Set<UserEntity>().Take(1).ToListAsync();
if (users.Count > 0)
{
return ResponseHelper.Ok(users[0], "获取用户成功(来自数据库)");
}
}
// 回退:演示数据
await Task.Delay(100);
var user = new { id = 1, name = "张三", email = "zhangsan@example.com" };
return ResponseHelper.Ok(user);
}
private static async Task<object?> ProcessDataAsync(ServiceEndpointContext ctx)
{
var sp = ctx.Items["ServiceProvider"] as IServiceProvider;
// 演示:将收到的数据存入数据库
var input = ctx.Body ?? string.Empty;
if (sp?.GetService(typeof(AppDataContext)) is AppDataContext db && !string.IsNullOrWhiteSpace(input))
{
var forecast = new WeatherForecastEntity
{
Date = DateOnly.FromDateTime(DateTime.Now),
TemperatureC = 20,
Summary = input,
};
db.WeatherForecasts.Add(forecast);
await db.SaveChangesAsync();
return ResponseHelper.Ok(forecast, "数据已存入数据库");
}
await Task.Delay(200);
return ResponseHelper.Ok(new { input, processed = input.ToUpperInvariant() });
}
#endregion
}
}