AvaloniaStack/Avalonia-API/Authentication/ApiAuthEndpointService.cs
luoqian fc6f9f6bc3 docs: 补全全部缺失的 XML 文档注释(中文)
- 为全部 5 个项目(Avalonia-API、Avalonia-Common、Avalonia-EFCore、
  Avalonia-PC、Avalonia-Services)中缺失注释的类、方法、属性、字段、
  接口成员等补全中文 XML 文档注释
- 共修改约 37 个文件,补全约 220+ 处注释
- 修复 ServiceEndpointCollection.cs 中 MapDelete<TService> 语法错误
- 修复 PcAuthService.cs 中 const prefix 位置错乱导致编译失败的问题
- 扫描结果:缺失项 0
- 构建结果:4/4 项目编译通过
2026-05-18 11:35:13 +08:00

161 lines
6.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.AuthService;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
namespace Avalonia_API.Authentication
{
/// <summary>
/// API 鉴权端点服务,实现 <see cref="IApiAuthEndpointService"/>
/// 处理登录、刷新 Token 和登出操作,使用 JWT 与 Refresh Token 机制。
/// </summary>
public sealed class ApiAuthEndpointService(
AppDataContext db,
JwtTokenService jwtTokenService,
RefreshTokenService refreshTokenService) : IApiAuthEndpointService
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
};
/// <summary>
/// 处理用户登录请求。根据账号(邮箱或用户名)查找或创建用户,
/// 生成 JWT Access Token 和 Refresh Token 并返回。
/// </summary>
/// <param name="ctx">服务端点上下文,包含请求体、请求头等信息。</param>
/// <returns>包含 AccessToken、RefreshToken 及过期时间的认证响应。</returns>
public async Task<object?> LoginAsync(ServiceEndpointContext ctx)
{
var request = Deserialize<ApiLoginRequest>(ctx.Body);
if (string.IsNullOrWhiteSpace(request?.Account))
{
ctx.StatusCode = 400;
return ResponseHelper.Failure(400, "账号不能为空");
}
var user = await db.Users.FirstOrDefaultAsync(
x => x.Email == request.Account || x.Name == request.Account);
if (user is null)
{
user = new UserEntity
{
Name = request.Account,
Email = request.Account.Contains('@') ? request.Account : null,
};
db.Users.Add(user);
await db.SaveChangesAsync();
}
var roles = NormalizeRoles(request.Roles);
var accessToken = jwtTokenService.CreateAccessToken(user, roles);
var refreshToken = await refreshTokenService.CreateAsync(
user.Id,
ctx.GetHeader("User-Agent"),
GetRemoteIpAddress(ctx));
return ResponseHelper.Ok(new AuthTokenResponse(
accessToken.Token,
refreshToken.Token,
accessToken.ExpiresAt,
refreshToken.Entity.ExpiresAt,
roles), "登录成功");
}
/// <summary>
/// 使用 Refresh Token 轮换新的 Access Token 和 Refresh Token。
/// 旧的 Refresh Token 会被撤销并替换。
/// </summary>
/// <param name="ctx">服务端点上下文,包含请求体中的 RefreshToken。</param>
/// <returns>新的 Token 对;若 Refresh Token 无效则返回 401 错误。</returns>
public async Task<object?> RefreshAsync(ServiceEndpointContext ctx)
{
var request = Deserialize<ApiRefreshTokenRequest>(ctx.Body);
var rotated = await refreshTokenService.RotateAsync(
request?.RefreshToken,
ctx.GetHeader("User-Agent"),
GetRemoteIpAddress(ctx));
if (rotated is null)
{
ctx.StatusCode = 401;
return ResponseHelper.Failure(401, "刷新 token 无效或已过期");
}
var user = await db.Users.FindAsync(rotated.Value.Entity.UserId);
if (user is null)
{
ctx.StatusCode = 401;
return ResponseHelper.Failure(401, "用户不存在");
}
var roles = new[] { "Admin" };
var accessToken = jwtTokenService.CreateAccessToken(user, roles);
return ResponseHelper.Ok(new AuthTokenResponse(
accessToken.Token,
rotated.Value.Token,
accessToken.ExpiresAt,
rotated.Value.Entity.ExpiresAt,
roles), "刷新成功");
}
/// <summary>
/// 处理用户登出请求,撤销指定的 Refresh Token。
/// </summary>
/// <param name="ctx">服务端点上下文,包含请求体中的 RefreshToken。</param>
/// <returns>登出成功的响应。</returns>
public async Task<object?> LogoutAsync(ServiceEndpointContext ctx)
{
var request = Deserialize<ApiLogoutRequest>(ctx.Body);
await refreshTokenService.RevokeAsync(request?.RefreshToken);
return ResponseHelper.Succeed("退出成功");
}
/// <summary>
/// 将 JSON 请求体反序列化为指定类型。
/// </summary>
/// <typeparam name="T">目标类型。</typeparam>
/// <param name="body">JSON 请求体字符串,可为空。</param>
/// <returns>反序列化后的对象;若 body 为空则返回默认值。</returns>
private static T? Deserialize<T>(string? body)
{
return string.IsNullOrWhiteSpace(body)
? default
: JsonSerializer.Deserialize<T>(body, JsonOptions);
}
/// <summary>
/// 从上下文的 Items 中提取 ASP.NET Core HttpContext并获取客户端远程 IP 地址。
/// </summary>
/// <param name="ctx">服务端点上下文。</param>
/// <returns>客户端 IP 地址字符串;若无法获取则返回 null。</returns>
private static string? GetRemoteIpAddress(ServiceEndpointContext ctx)
{
return ctx.Items.TryGetValue("HttpContext", out var value) && value is HttpContext httpContext
? httpContext.Connection.RemoteIpAddress?.ToString()
: null;
}
/// <summary>
/// 规范化角色数组:去空白、去重(忽略大小写),为空时默认返回 Admin 角色。
/// </summary>
/// <param name="roles">原始角色数组,可为 null。</param>
/// <returns>规范化后的角色数组。</returns>
private static string[] NormalizeRoles(string[]? roles)
{
var normalized = roles?
.Where(role => !string.IsNullOrWhiteSpace(role))
.Select(role => role.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
return normalized is { Length: > 0 } ? normalized : ["Admin"];
}
}
}