using System; using System.Collections.Generic; using System.Linq; 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, IUserRoleRepository userRoleRepository, IRoleRepository roleRepository, IPermissionRepository permissionRepository, IRolePermissionRepository rolePermissionRepository, IPasswordHasher passwordHasher, IJwtTokenService jwtTokenService, IRefreshTokenStore refreshTokenStore, ITenantProvider tenantProvider) : IAdminAuthService { private readonly ITenantProvider _tenantProvider = tenantProvider; private readonly IUserRoleRepository _userRoleRepository = userRoleRepository; private readonly IRoleRepository _roleRepository = roleRepository; private readonly IPermissionRepository _permissionRepository = permissionRepository; private readonly IRolePermissionRepository _rolePermissionRepository = rolePermissionRepository; /// /// 管理后台登录:验证账号密码并生成令牌。 /// /// 登录请求 /// 取消令牌 /// 令牌响应 /// 账号或密码错误时抛出 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 = await BuildProfileAsync(user, cancellationToken); 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 = await BuildProfileAsync(user, cancellationToken); 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 await BuildProfileAsync(user, cancellationToken); } /// /// 获取指定用户的权限概览(校验当前租户)。 /// 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; } var roleCodes = await ResolveUserRolesAsync(tenantId, user.Id, cancellationToken); var permissionCodes = await ResolveUserPermissionsAsync(tenantId, user.Id, cancellationToken); return new UserPermissionDto { UserId = user.Id, TenantId = user.TenantId, MerchantId = user.MerchantId, Account = user.Account, DisplayName = user.DisplayName, Roles = roleCodes, Permissions = permissionCodes, 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 resolved = await ResolveRolesAndPermissionsAsync(tenantId, paged, cancellationToken); var items = paged.Select(user => new UserPermissionDto { UserId = user.Id, TenantId = user.TenantId, MerchantId = user.MerchantId, Account = user.Account, DisplayName = user.DisplayName, Roles = resolved[user.Id].roles, Permissions = resolved[user.Id].permissions, CreatedAt = user.CreatedAt }).ToList(); return new PagedResult(items, page, pageSize, users.Count); } private async Task BuildProfileAsync(IdentityUser user, CancellationToken cancellationToken) { var tenantId = user.TenantId; var roles = await ResolveUserRolesAsync(tenantId, user.Id, cancellationToken); var permissions = await ResolveUserPermissionsAsync(tenantId, user.Id, cancellationToken); return new CurrentUserProfile { UserId = user.Id, Account = user.Account, DisplayName = user.DisplayName, TenantId = user.TenantId, MerchantId = user.MerchantId, Roles = roles, Permissions = permissions, Avatar = user.Avatar }; } private async Task ResolveUserRolesAsync(long tenantId, long userId, CancellationToken cancellationToken) { var relations = await _userRoleRepository.GetByUserIdAsync(tenantId, userId, cancellationToken); var roleIds = relations.Select(x => x.RoleId).Distinct().ToArray(); if (roleIds.Length == 0) { return Array.Empty(); } var roles = await _roleRepository.GetByIdsAsync(tenantId, roleIds, cancellationToken); return roles.Select(x => x.Code).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); } private async Task ResolveUserPermissionsAsync(long tenantId, long userId, CancellationToken cancellationToken) { var relations = await _userRoleRepository.GetByUserIdAsync(tenantId, userId, cancellationToken); var roleIds = relations.Select(x => x.RoleId).Distinct().ToArray(); if (roleIds.Length == 0) { return Array.Empty(); } var rolePermissions = await _rolePermissionRepository.GetByRoleIdsAsync(tenantId, roleIds, cancellationToken); var permissionIds = rolePermissions.Select(x => x.PermissionId).Distinct().ToArray(); if (permissionIds.Length == 0) { return Array.Empty(); } var permissions = await _permissionRepository.GetByIdsAsync(tenantId, permissionIds, cancellationToken); return permissions.Select(x => x.Code).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); } private async Task> ResolveRolesAndPermissionsAsync( long tenantId, IReadOnlyCollection users, CancellationToken cancellationToken) { var userIds = users.Select(x => x.Id).ToArray(); var userRoleRelations = await _userRoleRepository.GetByUserIdsAsync(tenantId, userIds, cancellationToken); var roleIds = userRoleRelations.Select(x => x.RoleId).Distinct().ToArray(); var roles = roleIds.Length == 0 ? Array.Empty() : await _roleRepository.GetByIdsAsync(tenantId, roleIds, cancellationToken); var roleCodeMap = roles.ToDictionary(r => r.Id, r => r.Code, comparer: EqualityComparer.Default); var rolePermissions = roleIds.Length == 0 ? Array.Empty() : await _rolePermissionRepository.GetByRoleIdsAsync(tenantId, roleIds, cancellationToken); var permissionIds = rolePermissions.Select(x => x.PermissionId).Distinct().ToArray(); var permissions = permissionIds.Length == 0 ? Array.Empty() : await _permissionRepository.GetByIdsAsync(tenantId, permissionIds, cancellationToken); var permissionCodeMap = permissions.ToDictionary(p => p.Id, p => p.Code, comparer: EqualityComparer.Default); var rolePermissionsLookup = rolePermissions .GroupBy(rp => rp.RoleId) .ToDictionary(g => g.Key, g => g.Select(rp => rp.PermissionId).ToArray(), comparer: EqualityComparer.Default); var result = new Dictionary(); foreach (var userId in userIds) { var rolesForUser = userRoleRelations.Where(ur => ur.UserId == userId).Select(ur => ur.RoleId).Distinct().ToArray(); var roleCodes = rolesForUser .Select(rid => roleCodeMap.GetValueOrDefault(rid)) .Where(c => !string.IsNullOrWhiteSpace(c)) .Select(c => c!) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); var permissionCodes = rolesForUser .SelectMany(rid => rolePermissionsLookup.GetValueOrDefault(rid) ?? Array.Empty()) .Select(pid => permissionCodeMap.GetValueOrDefault(pid)) .Where(code => !string.IsNullOrWhiteSpace(code)) .Select(code => code!) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); result[userId] = (roleCodes, permissionCodes); } return result; } }