using F9WebAiTest.Application.Common; using F9WebAiTest.Application.Dtos; using F9WebAiTest.Application.Dtos.Auth; using F9WebAiTest.Application.Interfaces.Security; using F9WebAiTest.Domain.Common; using F9WebAiTest.Domain.Entities.SYS.Departments; using F9WebAiTest.Domain.Entities.SYS.RBAC; using F9WebAiTest.Domain.Enums.SYS.RBAC; using Microsoft.Extensions.Configuration; using RefreshToken = F9WebAiTest.Domain.Entities.RefreshToken; namespace F9WebAiTest.Application.Services.Auth; public class AuthService : IAuthService { private readonly IRepository _userRepo; private readonly IRepository _userRoleRepo; private readonly IRepository _rolePermissionRepo; private readonly IRepository _permissionRepo; private readonly IRepository _menuRepo; private readonly IRepository _roleRepo; private readonly IRepository _deptRepo; private readonly IRepository _refreshTokenRepo; private readonly IJwtService _jwt; private readonly IConfiguration _config; private readonly IPasswordHasher _passwordHasher; public AuthService( IRepository userRepo, IRepository userRoleRepo, IRepository rolePermissionRepo, IRepository permissionRepo, IRepository menuRepo, IRepository roleRepo, IRepository deptRepo, IRepository refreshTokenRepo, IJwtService jwt, IConfiguration config, IPasswordHasher passwordHasher) { _userRepo = userRepo; _userRoleRepo = userRoleRepo; _rolePermissionRepo = rolePermissionRepo; _permissionRepo = permissionRepo; _menuRepo = menuRepo; _roleRepo = roleRepo; _deptRepo = deptRepo; _refreshTokenRepo = refreshTokenRepo; _jwt = jwt; _config = config; _passwordHasher = passwordHasher; } public async Task LoginAsync(LoginDto dto) { var user = await _userRepo.FirstOrDefaultAsync(u => u.LoginName == dto.Username); if (user == null || !VerifyPassword(dto.Password, user.PasswordHash)) throw new ServiceException("用户名或密码错误", 401); if (!user.IsActive) throw new ServiceException("账号已被禁用,请联系管理员", 403); return await IssueTokensAsync(user); } public async Task RefreshTokenAsync(string refreshToken) { var stored = await _refreshTokenRepo.FirstOrDefaultAsync(rt => rt.Token == refreshToken); if (stored == null || stored.IsRevoked || stored.ExpiresAt < DateTime.UtcNow) throw new ServiceException("刷新令牌无效或已过期", 401); var user = await _userRepo.GetByIdAsync(stored.EmployeeId); if (user == null || !user.IsActive) throw new ServiceException("用户不存在或已禁用", 401); // 撤销旧 token,签发新 token(旋转) stored.IsRevoked = true; await _refreshTokenRepo.UpdateAsync(stored); return await IssueTokensAsync(user); } public async Task RevokeRefreshTokenAsync(string refreshToken) { var stored = await _refreshTokenRepo.FirstOrDefaultAsync(rt => rt.Token == refreshToken); if (stored != null) { stored.IsRevoked = true; await _refreshTokenRepo.UpdateAsync(stored); await _refreshTokenRepo.SaveChangesAsync(); } } public async Task GetCurrentUserAsync(long userId) { var user = await _userRepo.GetByIdAsync(userId); if (user == null) throw new ServiceException("用户不存在", 404); return await BuildUserInfoAsync(user); } public async Task> GetUserMenusAsync(long userId) { var roleIds = (await _userRoleRepo.FindAsync(ur => ur.UserId == userId)) .Select(ur => ur.RoleId).ToList(); // 超级管理员返回全部菜单 var isAdmin = await IsAdminAsync(userId); List menus; if (isAdmin) { menus = await _menuRepo.FindAsync(m => m.IsActive); } else { // 通过 Permission 表关联菜单:RolePermission → Permission(ResourceType=菜单) → ResourceId(=Menu.Id) var permissionIds = (await _rolePermissionRepo.FindAsync(rp => roleIds.Contains(rp.RoleId))) .Select(rp => rp.PermissionId).Distinct().ToList(); var permittedMenuIds = (await _permissionRepo.FindAsync(p => permissionIds.Contains(p.Id) && p.ResourceType == Resource_Type_Enum.菜单)) .Select(p => p.ResourceId).Distinct().ToHashSet(); // 全部活跃菜单 var allMenus = await _menuRepo.FindAsync(m => m.IsActive); var menuDict = allMenus.ToDictionary(m => m.Id); // 有权限的菜单 + 它们的祖先目录(父/祖目录自动包含,保证菜单树完整,非管理员也能看到一级菜单) var resultIds = new HashSet(); foreach (var id in permittedMenuIds) { var currentId = id; while (currentId != 0 && menuDict.ContainsKey(currentId) && resultIds.Add(currentId)) { currentId = menuDict[currentId].ParentId; } } menus = allMenus.Where(m => resultIds.Contains(m.Id)).ToList(); } // 只返回目录和菜单(分组/页面),按排序 menus = menus.Where(m => m.MenuType != MenuType_Enum.默认) .OrderBy(m => m.SortOrder).ToList(); return BuildMenuTree(menus); } public async Task> GetUserPermissionsAsync(long userId) { var isAdmin = await IsAdminAsync(userId); if (isAdmin) { // 超级管理员:返回所有权限单元 Code(含菜单权限 + 资源权限) return (await _permissionRepo.FindAsync(p => !string.IsNullOrEmpty(p.Code))) .Select(p => p.Code).Distinct().ToList(); } // 普通用户:角色 → RolePermission → Permission → Code(统一权限模型) var roleIds = (await _userRoleRepo.FindAsync(ur => ur.UserId == userId)) .Select(ur => ur.RoleId).ToList(); var permissionIds = (await _rolePermissionRepo.FindAsync(rp => roleIds.Contains(rp.RoleId))) .Select(rp => rp.PermissionId).Distinct().ToList(); return (await _permissionRepo.FindAsync(p => permissionIds.Contains(p.Id) && !string.IsNullOrEmpty(p.Code))) .Select(p => p.Code).Distinct().ToList(); } public async Task ChangePasswordAsync(long userId, ChangePasswordDto dto) { var user = await _userRepo.GetByIdAsync(userId); if (user == null) throw new ServiceException("用户不存在", 404); if (!VerifyPassword(dto.OldPassword, user.PasswordHash)) throw new ServiceException("原密码错误"); if ((dto.NewPassword ?? "").Length < 6) throw new ServiceException("新密码至少6位"); // 新密码与创建用户一致:BCrypt(MD5(明文)) user.SetPassword(_passwordHasher.HashPassword(_passwordHasher.GetMd5Hash(dto.NewPassword))); await _userRepo.UpdateAsync(user); await _userRepo.SaveChangesAsync(); } public async Task UpdateProfileAsync(long userId, UpdateProfileDto dto) { var user = await _userRepo.GetByIdAsync(userId); if (user == null) throw new ServiceException("用户不存在", 404); user.UpdateProfile(dto.Nickname, dto.Avatar, dto.Email, dto.Phone, null); await _userRepo.UpdateAsync(user); await _userRepo.SaveChangesAsync(); } // ---- 私有方法 ---- /// 校验密码,兼容旧格式 BCrypt(明文) 与新格式 BCrypt(MD5(明文)) private bool VerifyPassword(string password, string passwordHash) { if (string.IsNullOrEmpty(passwordHash)) return false; if (_passwordHasher.VerifyPassword(password, passwordHash)) return true; return _passwordHasher.VerifyPassword(_passwordHasher.GetMd5Hash(password), passwordHash); } private async Task IssueTokensAsync(User user) { var roles = await GetRoleCodesAsync(user.Id); var accessToken = _jwt.GenerateAccessToken(user, roles); var refreshToken = _jwt.GenerateRefreshToken(); var expireMinutes = int.Parse(_config["Jwt:AccessTokenExpireMinutes"] ?? "120"); // 存储 refresh token var expireDays = int.Parse(_config["Jwt:RefreshTokenExpireDays"] ?? "7"); await _refreshTokenRepo.AddAsync(new RefreshToken { EmployeeId = user.Id, Token = refreshToken, ExpiresAt = DateTime.UtcNow.AddDays(expireDays) }); await _refreshTokenRepo.SaveChangesAsync(); var userInfo = await BuildUserInfoAsync(user); return new LoginResultDto { AccessToken = accessToken, RefreshToken = refreshToken, AccessTokenExpiresAt = DateTime.UtcNow.AddMinutes(expireMinutes), User = userInfo }; } private async Task> GetRoleCodesAsync(long userId) { var roleIds = (await _userRoleRepo.FindAsync(ur => ur.UserId == userId)) .Select(ur => ur.RoleId).ToList(); var roles = await _roleRepo.FindAsync(r => roleIds.Contains(r.Id)); return roles.Select(r => r.Code).ToList(); } private async Task IsAdminAsync(long userId) { var roleIds = (await _userRoleRepo.FindAsync(ur => ur.UserId == userId)) .Select(ur => ur.RoleId).ToList(); var roles = await _roleRepo.FindAsync(r => roleIds.Contains(r.Id)); return roles.Any(r => r.Code == "admin"); } private async Task BuildUserInfoAsync(User user) { var dept = await _deptRepo.GetByIdAsync(user.DepartmentId); var roleIds = (await _userRoleRepo.FindAsync(ur => ur.UserId == user.Id)) .Select(ur => ur.RoleId).ToList(); var roles = await _roleRepo.FindAsync(r => roleIds.Contains(r.Id)); return new EmployeeInfoDto { Id = user.Id, Username = user.LoginName, RealName = user.UserName, Nickname = user.Nickname, Avatar = user.Avatar, Phone = user.PhoneNumber, Email = user.Email, DepartmentId = user.DepartmentId, DepartmentName = dept?.Name ?? "", Status = user.IsActive ? 1 : 0, Roles = roles.Select(r => r.Code).ToList(), Permissions = await GetUserPermissionsAsync(user.Id) }; } private static List BuildMenuTree(List menus) { var dict = menus.ToDictionary(m => m.Id, m => new MenuDto { Id = m.Id, ParentId = m.ParentId == 0 ? null : m.ParentId, Name = m.Name, Type = m.MenuType == MenuType_Enum.分组 ? "directory" : "menu", Path = m.Path, Component = m.Component, Icon = m.IconSvg, Permission = m.Permission, Sort = m.SortOrder, Status = m.IsActive ? 1 : 0 }); var roots = new List(); foreach (var menu in menus) { var dto = dict[menu.Id]; if (menu.ParentId == 0 || !dict.ContainsKey(menu.ParentId)) { roots.Add(dto); } else { dict[menu.ParentId].Children.Add(dto); } } return roots.OrderBy(m => m.Sort).ToList(); } }