using Model; using Model.Dto.System; using Model.Entity.System; using Model.Mapper; using ORM; using Service.Interface; using SqlSugar; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Service.Implement { /// /// 用户管理 服务实现 /// 数据权限:当前用户角色 DataScope=1 看全部;=2 只能看自己组织及下级组织下的用户 /// public class UserService : IUserService { private readonly IOrgService _orgService; private readonly ICurrentUser _currentUser; public UserService(IOrgService orgService, ICurrentUser currentUser) { _orgService = orgService; _currentUser = currentUser; } public async Task>> GetPagedAsync(int pageIndex, int pageSize, RefAsync total, string? keyword = null, long? roleId = null, long? orgId = null) { try { // 按角色筛选时先查出该角色下的用户Id List? userIdsInRole = null; if (roleId.HasValue && roleId.Value > 0) { userIdsInRole = await SqlSugarContext.DbContext.Queryable() .Where(x => x.RoleId == roleId!.Value) .Select(x => x.UserId) .ToListAsync(); if (userIdsInRole.Count == 0) return Result>.Success(new List()); } // 数据范围过滤:DataScope=2 时只允许看本组织及下级的用户 List? allowedOrgIds = null; if (_currentUser.IsAuthenticated && _currentUser.DataScope != 1) { var me = await SqlSugarContext.DbContext.Queryable() .Where(x => x.Id == _currentUser.UserId).FirstAsync(); if (me != null && me.OrgId > 0) allowedOrgIds = await _orgService.GetSubtreeIdsAsync(me.OrgId); else return Result>.Success(new List()); // 无组织归属则看不到任何用户 } // 前端按组织节点筛选时叠加(同时受数据范围约束) List? orgFilterIds = null; if (orgId.HasValue && orgId.Value > 0) { orgFilterIds = await _orgService.GetSubtreeIdsAsync(orgId.Value); if (allowedOrgIds != null) orgFilterIds = orgFilterIds.Where(allowedOrgIds.Contains).ToList(); } var list = await SqlSugarContext.DbContext.Queryable() .Where(x => x.IsDel == 0) .WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.UserName.Contains(keyword!) || (x.RealName != null && x.RealName.Contains(keyword!))) .WhereIF(userIdsInRole != null, x => userIdsInRole!.Contains(x.Id)) .WhereIF(allowedOrgIds != null, x => allowedOrgIds!.Contains(x.OrgId)) .WhereIF(orgFilterIds != null, x => orgFilterIds!.Contains(x.OrgId)) .OrderBy(x => x.CreateTime, OrderByType.Desc) .ToPageListAsync(pageIndex, pageSize, total); var dtos = list.ToDtoList(); await FillRolesAsync(dtos); await FillOrgNamesAsync(dtos); return Result>.Success(dtos); } catch (Exception ex) { return Result>.Error("查询用户列表失败", ex); } } public async Task> GetByIdAsync(long id) { try { var entity = await SqlSugarContext.DbContext.Queryable() .Where(x => x.Id == id && x.IsDel == 0).FirstAsync(); if (entity == null) return Result.Error("用户不存在或已被删除"); var dto = entity.ToDto(); await FillRolesAsync(new List { dto }); await FillOrgNamesAsync(new List { dto }); return Result.Success(dto); } catch (Exception ex) { return Result.Error("查询用户详情失败", ex); } } public async Task> AddAsync(UserDto dto) { if (dto == null || string.IsNullOrWhiteSpace(dto.UserName)) return Result.Error("用户名不能为空"); try { var exists = await SqlSugarContext.DbContext.Queryable() .Where(x => x.UserName == dto.UserName && x.IsDel == 0).AnyAsync(); if (exists) return Result.Error($"用户名「{dto.UserName}」已存在"); var entity = dto.ToEntity(); entity.Id = 0; entity.CreateTime = DateTime.Now; // 新增用户初始密码必填(前端通过 InitialPassword 明文传入,落库前 PBKDF2 哈希) var initialPassword = dto.InitialPassword; if (string.IsNullOrWhiteSpace(initialPassword) || initialPassword.Length < 6) return Result.Error("初始密码不能为空且长度不少于 6 位"); entity.PasswordHash = PasswordHelper.Hash(initialPassword); // 角色先解析校验再落库,避免"用户已插入但角色解析失败"留下无角色用户 var newRoleIds = new List(); if (dto.RoleIds != null && dto.RoleIds.Count > 0) { foreach (var r in dto.RoleIds) { if (!long.TryParse(r, out var rid) || rid <= 0) return Result.Error($"角色Id无效:{r}"); newRoleIds.Add(rid); } } var id = await SqlSugarContext.DbContext.Insertable(entity).ExecuteReturnSnowflakeIdAsync(); entity.Id = id; if (newRoleIds.Count > 0) await ReplaceUserRolesAsync(id, newRoleIds); var result = entity.ToDto(); await FillRolesAsync(new List { result }); return Result.Success(result); } catch (Exception ex) { return Result.Error("新增用户失败", ex); } } public async Task> UpdateAsync(UserDto dto) { if (dto == null || !long.TryParse(dto.Id, out var id) || id <= 0) return Result.Error("用户Id无效"); try { var entity = await SqlSugarContext.DbContext.Queryable() .Where(x => x.Id == id && x.IsDel == 0).FirstAsync(); if (entity == null) return Result.Error("用户不存在或已被删除"); var nameExists = await SqlSugarContext.DbContext.Queryable() .Where(x => x.UserName == dto.UserName && x.Id != id && x.IsDel == 0).AnyAsync(); if (nameExists) return Result.Error($"用户名「{dto.UserName}」已存在"); // 防锁死:把自己禁用不允许;禁用或改角色后必须仍存在可管理用户的管理员 if (dto.IsEnabled == 0 && id == _currentUser.UserId) return Result.Error("不能禁用当前登录的账号"); if (dto.IsEnabled == 0 || dto.RoleIds != null) { var guard = await EnsureAnotherActiveAdminAsync(id); if (guard != null) return Result.Error(guard.Msg); } var newEntity = dto.ToEntity(); newEntity.Id = id; // 密码、登录时间不允许在此接口覆盖 await SqlSugarContext.DbContext.Updateable(newEntity) .IgnoreColumns(x => new { x.CreateTime, x.IsDel, x.PasswordHash, x.LastLoginTime }) .ExecuteCommandAsync(); // 角色变化时同步更新关联(传入 null 表示不改角色) if (dto.RoleIds != null) { var roleIds = dto.RoleIds.Select(long.Parse).Where(r => r > 0).ToList(); await ReplaceUserRolesAsync(id, roleIds); } var result = newEntity.ToDto(); await FillRolesAsync(new List { result }); return Result.Success(result); } catch (Exception ex) { return Result.Error("修改用户失败", ex); } } public async Task DeleteAsync(long id) { if (id <= 0) return Result.Error("用户Id无效"); if (id == _currentUser.UserId) return Result.Error("不能删除当前登录的账号"); try { var exists = await SqlSugarContext.DbContext.Queryable() .Where(x => x.Id == id && x.IsDel == 0).AnyAsync(); if (!exists) return Result.Error("用户不存在或已被删除"); // 防锁死:删除后必须仍存在可管理用户的管理员 var guard = await EnsureAnotherActiveAdminAsync(id); if (guard != null) return guard; await SqlSugarContext.DbContext.Updateable() .SetColumns(x => x.IsDel == 1) .Where(x => x.Id == id).ExecuteCommandAsync(); await SqlSugarContext.DbContext.Deleteable() .Where(x => x.UserId == id).ExecuteCommandAsync(); return Result.Success(); } catch (Exception ex) { return Result.Error("删除用户失败", ex); } } public async Task AssignRolesAsync(long userId, List roleIds) { if (userId <= 0) return Result.Error("用户Id无效"); try { var exists = await SqlSugarContext.DbContext.Queryable() .Where(x => x.Id == userId && x.IsDel == 0).AnyAsync(); if (!exists) return Result.Error("用户不存在或已被删除"); // 防锁死:改角色后必须仍存在可管理用户的管理员 var guard = await EnsureAnotherActiveAdminAsync(userId); if (guard != null) return guard; await ReplaceUserRolesAsync(userId, roleIds ?? new List()); return Result.Success(); } catch (Exception ex) { return Result.Error("分配角色失败", ex); } } public async Task ResetPasswordAsync(long userId, string newPassword) { if (userId <= 0) return Result.Error("用户Id无效"); if (string.IsNullOrWhiteSpace(newPassword) || newPassword.Length < 6) return Result.Error("新密码不能为空且长度不少于 6 位"); try { var exists = await SqlSugarContext.DbContext.Queryable() .Where(x => x.Id == userId && x.IsDel == 0).AnyAsync(); if (!exists) return Result.Error("用户不存在或已被删除"); await SqlSugarContext.DbContext.Updateable() .SetColumns(x => x.PasswordHash == PasswordHelper.Hash(newPassword)) .Where(x => x.Id == userId).ExecuteCommandAsync(); return Result.Success(); } catch (Exception ex) { return Result.Error("重置密码失败", ex); } } public async Task SetEnabledAsync(long userId, bool enabled) { if (userId <= 0) return Result.Error("用户Id无效"); if (!enabled && userId == _currentUser.UserId) return Result.Error("不能禁用当前登录的账号"); try { // 防锁死:禁用后必须仍存在可管理用户的管理员 if (!enabled) { var guard = await EnsureAnotherActiveAdminAsync(userId); if (guard != null) return guard; } await SqlSugarContext.DbContext.Updateable() .SetColumns(x => x.IsEnabled == (byte)(enabled ? 1 : 0)) .Where(x => x.Id == userId).ExecuteCommandAsync(); return Result.Success(); } catch (Exception ex) { return Result.Error("设置用户状态失败", ex); } } public async Task>> GetOptionsAsync() { try { var list = await SqlSugarContext.DbContext.Queryable() .Where(x => x.IsDel == 0 && x.IsEnabled == 1) .OrderBy(x => x.UserName) .ToListAsync(); return Result>.Success(list.ToOptionDtoList()); } catch (Exception ex) { return Result>.Error("查询用户选项失败", ex); } } /// 全量覆盖用户-角色关联 private static async Task ReplaceUserRolesAsync(long userId, List roleIds) { var db = SqlSugarContext.DbContext; await db.Deleteable().Where(x => x.UserId == userId).ExecuteCommandAsync(); if (roleIds.Count > 0) { var now = DateTime.Now; var links = roleIds.Distinct().Select(r => new UserRoleEntity { UserId = userId, RoleId = r, CreateTime = now }).ToList(); await db.Insertable(links).ExecuteCommandAsync(); } } /// /// 防锁死校验:排除 excludeUserId 后,系统必须仍存在「已启用且拥有 user:manage 权限」的用户, /// 否则返回 Error(保证管理员不会把所有管理员删光/禁用光导致无人可登录管理) /// private async Task EnsureAnotherActiveAdminAsync(long excludeUserId) { var db = SqlSugarContext.DbContext; var permId = await db.Queryable() .Where(x => x.Code == "user:manage" && x.IsDel == 0) .Select(x => x.Id).FirstAsync(); if (permId == 0) return null; // 权限码不存在时不拦,避免权限体系变更后管理功能彻底锁死 var roleIds = await db.Queryable() .Where(x => x.PermissionId == permId) .Select(x => x.RoleId).ToListAsync(); if (roleIds.Count == 0) return null; var userIds = await db.Queryable() .Where(x => roleIds.Contains(x.RoleId)) .Select(x => x.UserId).ToListAsync(); if (userIds.Count == 0) return Result.Error("操作被拒绝:系统将没有任何可管理用户的管理员"); var exists = await db.Queryable() .AnyAsync(x => userIds.Contains(x.Id) && x.IsDel == 0 && x.IsEnabled == 1 && x.Id != excludeUserId); return exists ? null : Result.Error("操作被拒绝:系统至少需要保留一名已启用且拥有用户管理权限的账号"); } /// 给用户 DTO 填充所属组织名称(组织链路径,如 公司/实验室/部门) private async Task FillOrgNamesAsync(List dtos) { if (dtos == null || dtos.Count == 0) return; var orgIds = dtos.Where(d => long.TryParse(d.OrgId, out var oid) && oid > 0) .Select(d => long.Parse(d.OrgId)).Distinct().ToList(); if (orgIds.Count == 0) return; var allOrgs = await SqlSugarContext.DbContext.Queryable() .Where(x => x.IsDel == 0).ToListAsync(); var orgMap = allOrgs.ToDictionary(o => o.Id, o => o); foreach (var d in dtos) { if (!long.TryParse(d.OrgId, out var oid) || !orgMap.TryGetValue(oid, out var org)) continue; // 沿父级向上拼组织路径 var path = new List(); var cursor = org; var guard = 0; while (cursor != null && guard++ < 10) { path.Insert(0, cursor.Name); cursor = cursor.ParentId > 0 && orgMap.TryGetValue(cursor.ParentId, out var parent) ? parent : null; } d.OrgName = string.Join(" / ", path); } } /// 给用户 DTO 填充角色Id列表与角色名称 private static async Task FillRolesAsync(List dtos) { if (dtos == null || dtos.Count == 0) return; var db = SqlSugarContext.DbContext; var userIds = dtos.Select(d => long.Parse(d.Id)).ToList(); var links = await db.Queryable() .Where(x => userIds.Contains(x.UserId)).ToListAsync(); var roleIds = links.Select(l => l.RoleId).Distinct().ToList(); var roles = roleIds.Count > 0 ? await db.Queryable().Where(x => roleIds.Contains(x.Id) && x.IsDel == 0).ToListAsync() : new List(); var roleMap = roles.ToDictionary(r => r.Id, r => r.Name); foreach (var d in dtos) { var uid = long.Parse(d.Id); var myRoleIds = links.Where(l => l.UserId == uid).Select(l => l.RoleId).ToList(); d.RoleIds = myRoleIds.Select(r => r.ToString()).ToList(); d.RoleNames = string.Join(",", myRoleIds.Where(r => roleMap.ContainsKey(r)).Select(r => roleMap[r])); } } } }