using Microsoft.AspNetCore.Identity; using TakeoutSaaS.Application.Identity.Abstractions; using TakeoutSaaS.Application.Identity.Contracts; using TakeoutSaaS.Domain.Identity.Entities; using TakeoutSaaS.Domain.Identity.Repositories; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Exceptions; using TakeoutSaaS.Shared.Abstractions.Results; using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.Identity.Services; /// /// 管理后台认证服务实现。 /// public sealed class AdminAuthService( IIdentityUserRepository userRepository, IPasswordHasher passwordHasher, IJwtTokenService jwtTokenService, IRefreshTokenStore refreshTokenStore, ITenantProvider tenantProvider) : IAdminAuthService { private readonly ITenantProvider _tenantProvider = tenantProvider; /// /// 管理后台登录:验证账号密码并生成令牌。 /// /// 登录请求 /// 取消令牌 /// 令牌响应 /// 账号或密码错误时抛出 public async Task LoginAsync(AdminLoginRequest request, CancellationToken cancellationToken = default) { // 1. 根据账号查找用户 var user = await userRepository.FindByAccountAsync(request.Account, cancellationToken) ?? throw new BusinessException(ErrorCodes.Unauthorized, "账号或密码错误"); // 2. 验证密码(使用 ASP.NET Core Identity 的密码哈希器) var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, request.Password); if (result == PasswordVerificationResult.Failed) { throw new BusinessException(ErrorCodes.Unauthorized, "账号或密码错误"); } // 3. 构建用户档案并生成令牌 var profile = BuildProfile(user); return await jwtTokenService.CreateTokensAsync(profile, false, cancellationToken); } /// /// 刷新访问令牌:使用刷新令牌获取新的访问令牌和刷新令牌。 /// /// 刷新令牌请求 /// 取消令牌 /// 新的令牌响应 /// 刷新令牌无效、已过期或用户不存在时抛出 public async Task RefreshTokenAsync(RefreshTokenRequest request, CancellationToken cancellationToken = default) { // 1. 验证刷新令牌(检查是否存在、是否过期、是否已撤销) var descriptor = await refreshTokenStore.GetAsync(request.RefreshToken, cancellationToken); if (descriptor == null || descriptor.ExpiresAt <= DateTime.UtcNow || descriptor.Revoked) { throw new BusinessException(ErrorCodes.Unauthorized, "RefreshToken 无效或已过期"); } // 2. 根据用户 ID 查找用户 var user = await userRepository.FindByIdAsync(descriptor.UserId, cancellationToken) ?? throw new BusinessException(ErrorCodes.Unauthorized, "用户不存在"); // 3. 撤销旧刷新令牌(防止重复使用) await refreshTokenStore.RevokeAsync(descriptor.Token, cancellationToken); // 4. 生成新的令牌对 var profile = BuildProfile(user); return await jwtTokenService.CreateTokensAsync(profile, false, cancellationToken); } /// /// 获取用户档案。 /// /// 用户 ID /// 取消令牌 /// 用户档案 /// 用户不存在时抛出 public async Task GetProfileAsync(long userId, CancellationToken cancellationToken = default) { var user = await userRepository.FindByIdAsync(userId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "用户不存在"); return BuildProfile(user); } /// /// 获取指定用户的权限概览(校验当前租户)。 /// public async Task GetUserPermissionsAsync(long userId, CancellationToken cancellationToken = default) { var tenantId = _tenantProvider.GetCurrentTenantId(); var user = await userRepository.FindByIdAsync(userId, cancellationToken); if (user == null || user.TenantId != tenantId) { return null; } return new UserPermissionDto { UserId = user.Id, TenantId = user.TenantId, MerchantId = user.MerchantId, Account = user.Account, DisplayName = user.DisplayName, Roles = user.Roles, Permissions = user.Permissions, CreatedAt = user.CreatedAt }; } /// /// 按租户分页查询用户权限概览。 /// public async Task> SearchUserPermissionsAsync( string? keyword, int page, int pageSize, string? sortBy, bool sortDescending, CancellationToken cancellationToken = default) { var tenantId = _tenantProvider.GetCurrentTenantId(); var users = await userRepository.SearchAsync(tenantId, keyword, cancellationToken); var sorted = sortBy?.ToLowerInvariant() switch { "account" => sortDescending ? users.OrderByDescending(x => x.Account) : users.OrderBy(x => x.Account), "displayname" => sortDescending ? users.OrderByDescending(x => x.DisplayName) : users.OrderBy(x => x.DisplayName), _ => sortDescending ? users.OrderByDescending(x => x.CreatedAt) : users.OrderBy(x => x.CreatedAt) }; var paged = sorted .Skip((page - 1) * pageSize) .Take(pageSize) .ToList(); var items = paged.Select(user => new UserPermissionDto { UserId = user.Id, TenantId = user.TenantId, MerchantId = user.MerchantId, Account = user.Account, DisplayName = user.DisplayName, Roles = user.Roles, Permissions = user.Permissions, CreatedAt = user.CreatedAt }).ToList(); return new PagedResult(items, page, pageSize, users.Count); } private static CurrentUserProfile BuildProfile(IdentityUser user) => new() { UserId = user.Id, Account = user.Account, DisplayName = user.DisplayName, TenantId = user.TenantId, MerchantId = user.MerchantId, Roles = user.Roles, Permissions = user.Permissions, Avatar = user.Avatar }; }