feat: 管理后台登录增加手机号校验
This commit is contained in:
Submodule TakeoutSaaS.Docs updated: e962798f46...2f508d7b52
@@ -23,7 +23,7 @@ namespace TakeoutSaaS.AdminApi.Controllers;
|
||||
public sealed class AuthController(IAdminAuthService authService, IMediator mediator) : BaseApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录获取 Token
|
||||
/// 账号名+手机号登录获取 Token
|
||||
/// </summary>
|
||||
/// <param name="request">登录请求。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
@@ -37,22 +37,6 @@ public sealed class AuthController(IAdminAuthService authService, IMediator medi
|
||||
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>
|
||||
/// 刷新 Token
|
||||
/// </summary>
|
||||
|
||||
@@ -12,11 +12,6 @@ public interface IAdminAuthService
|
||||
/// </summary>
|
||||
Task<TokenResponse> LoginAsync(AdminLoginRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 简化登录:与标准登录一致(Admin Portal)。
|
||||
/// </summary>
|
||||
Task<TokenResponse> LoginSimpleAsync(AdminLoginRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 刷新 Token。
|
||||
/// </summary>
|
||||
|
||||
@@ -5,19 +5,26 @@ namespace TakeoutSaaS.Application.Identity.Contracts;
|
||||
/// <summary>
|
||||
/// 管理后台登录请求。
|
||||
/// </summary>
|
||||
public sealed class AdminLoginRequest
|
||||
public sealed record AdminLoginRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 账号。
|
||||
/// 账号名。
|
||||
/// </summary>
|
||||
[Required]
|
||||
[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>
|
||||
[Required]
|
||||
[MaxLength(128)]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
public string Password { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -32,11 +32,28 @@ public sealed class AdminAuthService(
|
||||
/// <exception cref="BusinessException">账号或密码错误时抛出</exception>
|
||||
public async Task<TokenResponse> LoginAsync(AdminLoginRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 根据账号查找用户
|
||||
var user = await userRepository.FindByAccountAsync(PortalType.Admin, null, request.Account, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.Unauthorized, "账号或密码错误");
|
||||
// 1. 标准化输入
|
||||
var accountName = request.AccountName?.Trim() ?? string.Empty;
|
||||
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;
|
||||
if (user.Status == IdentityUserStatus.Disabled)
|
||||
{
|
||||
@@ -58,43 +75,22 @@ public sealed class AdminAuthService(
|
||||
throw new BusinessException(ErrorCodes.Forbidden, "账号需要重置密码,请通过重置链接设置新密码");
|
||||
}
|
||||
|
||||
// 3. 验证密码(使用 ASP.NET Core Identity 的密码哈希器)
|
||||
var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, request.Password);
|
||||
// 6. (空行后) 验证密码(使用 ASP.NET Core Identity 的密码哈希器)
|
||||
var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password);
|
||||
if (result == PasswordVerificationResult.Failed)
|
||||
{
|
||||
await IncreaseFailedLoginAsync(user.Id, now, cancellationToken);
|
||||
throw new BusinessException(ErrorCodes.Unauthorized, "账号或密码错误");
|
||||
throw new BusinessException(ErrorCodes.Unauthorized, "账号名、手机号或密码错误");
|
||||
}
|
||||
|
||||
// 4. 更新登录成功状态
|
||||
// 7. (空行后) 更新登录成功状态
|
||||
await UpdateLoginSuccessAsync(user.Id, now, cancellationToken);
|
||||
|
||||
// 5. 构建用户档案并生成令牌
|
||||
// 8. (空行后) 构建用户档案并生成令牌
|
||||
var profile = await BuildProfileAsync(user, 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>
|
||||
|
||||
Reference in New Issue
Block a user