Files
Store.Management/Source/Store.Management.Api/Authentication/AuthApi.cs
T
2026-10-28 11:35:48 +08:00

80 lines
3.0 KiB
C#

using System.ComponentModel.DataAnnotations;
using RestSharp;
using Store.Management.Api.Transport;
namespace Store.Management.Api.Authentication;
public sealed class LoginRequest
{
[Required] public string UserName { get; set; } = "";
/// <summary>服务端要求的 UTF-8 密码的 32 位大写 MD5,由调用方计算。</summary>
[Required] public string Password { get; set; } = "";
[StringLength(100)] public string? DeviceId { get; set; }
[StringLength(100)] public string? DeviceName { get; set; }
}
public sealed class LoginResult
{
public string TokenType { get; init; } = "Bearer";
public string Token { get; init; } = "";
public uint ExpiresInMinutes { get; init; }
}
public sealed record CurrentUser(string UserName, string? NickName);
public interface IAuthApi
{
Task<LoginResult> LoginAsync(LoginRequest request, CancellationToken cancellationToken = default);
Task LogoutAsync(CancellationToken cancellationToken = default);
Task<CurrentUser> MeAsync(CancellationToken cancellationToken = default);
}
internal sealed class AuthApi(ApiTransport transport, TokenSession session) : IAuthApi
{
// 登录和退出串行执行,避免迟到的登录响应覆盖退出操作。
private readonly SemaphoreSlim _gate = new(1, 1);
public async Task<LoginResult> LoginAsync(LoginRequest request, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
LoginResult result = await transport.GetRequiredAsync<LoginResult>("Auth/Login", Method.Post, request,
authenticated: false, cancellationToken: cancellationToken).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(result.Token) || result.Token.Contains('\r') || result.Token.Contains('\n') ||
!string.Equals(result.TokenType, "Bearer", StringComparison.OrdinalIgnoreCase))
{
throw new ApiException(ApiErrorKind.InvalidResponse, "登录响应缺少有效的 Bearer Token。");
}
session.Set(result.Token);
return result;
}
finally
{
_gate.Release();
}
}
public async Task LogoutAsync(CancellationToken cancellationToken = default)
{
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
string? token = session.Token;
try
{
if (token is not null)
{
await transport.SendAsync<object>("Auth/Logout", Method.Post, cancellationToken: cancellationToken).ConfigureAwait(false);
}
}
finally
{
session.ClearIfCurrent(token);
_gate.Release();
}
}
public Task<CurrentUser> MeAsync(CancellationToken cancellationToken = default)
=> transport.GetRequiredAsync<CurrentUser>("Auth/Me", Method.Get, cancellationToken: cancellationToken);
}