feat: admin simple 登录支持 账号@手机号

This commit is contained in:
2025-12-12 22:06:37 +08:00
parent 624a7bc04d
commit 8b18d0cb96
5 changed files with 115 additions and 2 deletions

View File

@@ -3,6 +3,7 @@ using TakeoutSaaS.Application.Identity.Abstractions;
using TakeoutSaaS.Application.Identity.Contracts;
using TakeoutSaaS.Domain.Identity.Entities;
using TakeoutSaaS.Domain.Identity.Repositories;
using TakeoutSaaS.Domain.Tenants.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Results;
@@ -23,9 +24,13 @@ public sealed class AdminAuthService(
IPasswordHasher<IdentityUser> passwordHasher,
IJwtTokenService jwtTokenService,
IRefreshTokenStore refreshTokenStore,
ITenantProvider tenantProvider) : IAdminAuthService
ITenantProvider tenantProvider,
ITenantContextAccessor tenantContextAccessor,
ITenantRepository tenantRepository) : IAdminAuthService
{
private readonly ITenantProvider _tenantProvider = tenantProvider;
private readonly ITenantContextAccessor _tenantContextAccessor = tenantContextAccessor;
private readonly ITenantRepository _tenantRepository = tenantRepository;
private readonly IUserRoleRepository _userRoleRepository = userRoleRepository;
private readonly IRoleRepository _roleRepository = roleRepository;
private readonly IPermissionRepository _permissionRepository = permissionRepository;
@@ -57,6 +62,60 @@ public sealed class AdminAuthService(
return await jwtTokenService.CreateTokensAsync(profile, false, cancellationToken);
}
/// <summary>
/// 简化登录:支持使用“账号@手机号”解析租户后登录。
/// </summary>
/// <param name="request">登录请求</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>令牌响应</returns>
public async Task<TokenResponse> LoginSimpleAsync(AdminLoginRequest request, CancellationToken cancellationToken = default)
{
// 1. 标准化输入
var rawAccount = request.Account?.Trim() ?? string.Empty;
// 2. 尝试解析 “账号@手机号”
var atIndex = rawAccount.LastIndexOf('@');
if (atIndex > 0 && atIndex < rawAccount.Length - 1)
{
var accountPart = rawAccount[..atIndex].Trim();
var phonePart = rawAccount[(atIndex + 1)..].Trim();
if (IsLikelyPhone(phonePart))
{
if (string.IsNullOrWhiteSpace(accountPart))
{
throw new BusinessException(ErrorCodes.BadRequest, "账号格式错误,应为 账号@手机号");
}
var tenantId = await _tenantRepository.FindTenantIdByContactPhoneAsync(phonePart, cancellationToken);
if (!tenantId.HasValue || tenantId.Value == 0)
{
throw new BusinessException(ErrorCodes.Unauthorized, "账号或密码错误");
}
var originalTenant = _tenantContextAccessor.Current;
_tenantContextAccessor.Current = new TenantContext(tenantId.Value, null, "login:simple:contact_phone");
try
{
return await LoginAsync(new AdminLoginRequest { Account = accountPart, Password = request.Password }, cancellationToken);
}
finally
{
_tenantContextAccessor.Current = originalTenant;
}
}
}
// 3. 未携带手机号时要求外部已解析租户Header/Host 等)
if (_tenantProvider.GetCurrentTenantId() == 0)
{
throw new BusinessException(ErrorCodes.BadRequest, "缺少租户标识,请使用 账号@手机号 登录");
}
// 4. 走原有登录逻辑
return await LoginAsync(new AdminLoginRequest { Account = rawAccount, Password = request.Password }, cancellationToken);
}
/// <summary>
/// 刷新访问令牌:使用刷新令牌获取新的访问令牌和刷新令牌。
/// </summary>
@@ -214,6 +273,35 @@ public sealed class AdminAuthService(
};
}
private static bool IsLikelyPhone(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
var span = value.AsSpan();
if (span[0] == '+')
{
span = span[1..];
}
if (span.Length < 6 || span.Length > 32)
{
return false;
}
foreach (var ch in span)
{
if (!char.IsDigit(ch))
{
return false;
}
}
return true;
}
private static IReadOnlyList<MenuNodeDto> BuildMenuTree(
IReadOnlyList<Domain.Identity.Entities.MenuDefinition> definitions,
IReadOnlyList<string> permissions)