feat: 管理后台登录增加手机号校验

This commit is contained in:
2026-01-30 07:09:35 +00:00
parent f4ac933716
commit 8dff802248
5 changed files with 39 additions and 57 deletions

View File

@@ -23,7 +23,7 @@ namespace TakeoutSaaS.AdminApi.Controllers;
public sealed class AuthController(IAdminAuthService authService, IMediator mediator) : BaseApiController public sealed class AuthController(IAdminAuthService authService, IMediator mediator) : BaseApiController
{ {
/// <summary> /// <summary>
/// 登录获取 Token /// 账号名+手机号登录获取 Token
/// </summary> /// </summary>
/// <param name="request">登录请求。</param> /// <param name="request">登录请求。</param>
/// <param name="cancellationToken">取消标记。</param> /// <param name="cancellationToken">取消标记。</param>
@@ -37,22 +37,6 @@ public sealed class AuthController(IAdminAuthService authService, IMediator medi
return ApiResponse<TokenResponse>.Ok(response); return ApiResponse<TokenResponse>.Ok(response);
} }
/// <summary>
/// 免租户号登录(仅账号+密码)。
/// </summary>
/// <param name="request">登录请求。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>包含访问令牌与刷新令牌的响应。</returns>
/// <remarks>用于前端简化登录,无需额外传递租户号。</remarks>
[HttpPost("login/simple")]
[AllowAnonymous]
[ProducesResponseType(typeof(ApiResponse<TokenResponse>), StatusCodes.Status200OK)]
public async Task<ApiResponse<TokenResponse>> LoginSimple([FromBody] AdminLoginRequest request, CancellationToken cancellationToken)
{
var response = await authService.LoginSimpleAsync(request, cancellationToken);
return ApiResponse<TokenResponse>.Ok(response);
}
/// <summary> /// <summary>
/// 刷新 Token /// 刷新 Token
/// </summary> /// </summary>

View File

@@ -12,11 +12,6 @@ public interface IAdminAuthService
/// </summary> /// </summary>
Task<TokenResponse> LoginAsync(AdminLoginRequest request, CancellationToken cancellationToken = default); Task<TokenResponse> LoginAsync(AdminLoginRequest request, CancellationToken cancellationToken = default);
/// <summary>
/// 简化登录与标准登录一致Admin Portal
/// </summary>
Task<TokenResponse> LoginSimpleAsync(AdminLoginRequest request, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// 刷新 Token。 /// 刷新 Token。
/// </summary> /// </summary>

View File

@@ -5,19 +5,26 @@ namespace TakeoutSaaS.Application.Identity.Contracts;
/// <summary> /// <summary>
/// 管理后台登录请求。 /// 管理后台登录请求。
/// </summary> /// </summary>
public sealed class AdminLoginRequest public sealed record AdminLoginRequest
{ {
/// <summary> /// <summary>
/// 账号。 /// 账号
/// </summary> /// </summary>
[Required] [Required]
[MaxLength(64)] [MaxLength(64)]
public string Account { get; set; } = string.Empty; public string AccountName { get; init; } = string.Empty;
/// <summary>
/// 手机号。
/// </summary>
[Required]
[MaxLength(32)]
public string Phone { get; init; } = string.Empty;
/// <summary> /// <summary>
/// 密码。 /// 密码。
/// </summary> /// </summary>
[Required] [Required]
[MaxLength(128)] [MaxLength(128)]
public string Password { get; set; } = string.Empty; public string Password { get; init; } = string.Empty;
} }

View File

@@ -32,11 +32,28 @@ public sealed class AdminAuthService(
/// <exception cref="BusinessException">账号或密码错误时抛出</exception> /// <exception cref="BusinessException">账号或密码错误时抛出</exception>
public async Task<TokenResponse> LoginAsync(AdminLoginRequest request, CancellationToken cancellationToken = default) public async Task<TokenResponse> LoginAsync(AdminLoginRequest request, CancellationToken cancellationToken = default)
{ {
// 1. 根据账号查找用户 // 1. 标准化输入
var user = await userRepository.FindByAccountAsync(PortalType.Admin, null, request.Account, cancellationToken) var accountName = request.AccountName?.Trim() ?? string.Empty;
?? throw new BusinessException(ErrorCodes.Unauthorized, "账号或密码错误"); var phone = request.Phone?.Trim() ?? string.Empty;
var password = request.Password;
// 2. 校验账号状态 // 2. (空行后) 参数校验
if (string.IsNullOrWhiteSpace(accountName) || string.IsNullOrWhiteSpace(phone) || string.IsNullOrWhiteSpace(password))
{
throw new BusinessException(ErrorCodes.BadRequest, "账号名、手机号与密码不能为空");
}
// 3. (空行后) 根据账号查找用户
var user = await userRepository.FindByAccountAsync(PortalType.Admin, null, accountName, cancellationToken)
?? throw new BusinessException(ErrorCodes.Unauthorized, "账号名、手机号或密码错误");
// 4. (空行后) 校验手机号匹配
if (!string.Equals(user.Phone?.Trim(), phone, StringComparison.Ordinal))
{
throw new BusinessException(ErrorCodes.Unauthorized, "账号名、手机号或密码错误");
}
// 5. (空行后) 校验账号状态
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
if (user.Status == IdentityUserStatus.Disabled) if (user.Status == IdentityUserStatus.Disabled)
{ {
@@ -58,43 +75,22 @@ public sealed class AdminAuthService(
throw new BusinessException(ErrorCodes.Forbidden, "账号需要重置密码,请通过重置链接设置新密码"); throw new BusinessException(ErrorCodes.Forbidden, "账号需要重置密码,请通过重置链接设置新密码");
} }
// 3. 验证密码(使用 ASP.NET Core Identity 的密码哈希器) // 6. (空行后) 验证密码(使用 ASP.NET Core Identity 的密码哈希器)
var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, request.Password); var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password);
if (result == PasswordVerificationResult.Failed) if (result == PasswordVerificationResult.Failed)
{ {
await IncreaseFailedLoginAsync(user.Id, now, cancellationToken); await IncreaseFailedLoginAsync(user.Id, now, cancellationToken);
throw new BusinessException(ErrorCodes.Unauthorized, "账号或密码错误"); throw new BusinessException(ErrorCodes.Unauthorized, "账号名、手机号或密码错误");
} }
// 4. 更新登录成功状态 // 7. (空行后) 更新登录成功状态
await UpdateLoginSuccessAsync(user.Id, now, cancellationToken); await UpdateLoginSuccessAsync(user.Id, now, cancellationToken);
// 5. 构建用户档案并生成令牌 // 8. (空行后) 构建用户档案并生成令牌
var profile = await BuildProfileAsync(user, cancellationToken); var profile = await BuildProfileAsync(user, cancellationToken);
return await jwtTokenService.CreateTokensAsync(profile, false, cancellationToken); return await jwtTokenService.CreateTokensAsync(profile, false, cancellationToken);
} }
/// <summary>
/// 简化登录与标准登录一致Admin Portal
/// </summary>
/// <param name="request">登录请求</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>令牌响应</returns>
public async Task<TokenResponse> LoginSimpleAsync(AdminLoginRequest request, CancellationToken cancellationToken = default)
{
// 1. 参数校验
if (string.IsNullOrWhiteSpace(request.Account))
{
throw new BusinessException(ErrorCodes.BadRequest, "账号不能为空");
}
// 2. (空行后) 标准化账号
request.Account = request.Account.Trim();
// 3. (空行后) 走标准登录逻辑Admin Portal
return await LoginAsync(request, cancellationToken);
}
/// <summary> /// <summary>
/// 刷新访问令牌:使用刷新令牌获取新的访问令牌和刷新令牌。 /// 刷新访问令牌:使用刷新令牌获取新的访问令牌和刷新令牌。
/// </summary> /// </summary>