using Microsoft.Extensions.Logging; using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto; using QYZH.InteractiveMagazine.Models.Entity; using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; using SqlSugar; namespace QYZH.InteractiveMagazine.Service; /// /// 后台权限管理服务实现 /// public class AdminPermissionService( BaseRepository adminRoleRepository, BaseRepository adminMenuRepository, BaseRepository adminRoleMenuRepository, BaseRepository adminUserRoleRepository, BaseRepository adminUserRepository, ILogger logger) : BaseRepository, IAdminPermissionService { /// /// 创建菜单 /// public async Task CreateMenuAsync(AdminMenuInput input) { await ValidateMenuInputAsync(input); var menu = new AdminMenu { ParentId = input.ParentId, Name = input.Name.Trim(), Code = input.Code.Trim(), Path = input.Path?.Trim(), Component = input.Component?.Trim(), Icon = input.Icon?.Trim(), Sort = input.Sort, IsVisible = input.IsVisible, Status = input.Status, CreatedBy = "System", CreatedAt = DateTime.Now, UpdatedBy = "System", UpdatedAt = DateTime.Now, IsDeleted = false }; var result = await adminMenuRepository.InsertAsync(menu); BusinessException.ThrowIf(!result, "创建菜单失败", ResultCode.GLOBAL_ERROR); logger.LogInformation("创建后台菜单成功:{Code},ID:{Id}", menu.Code, menu.Id); return ToMenuOutput(menu); } /// /// 更新菜单 /// public async Task UpdateMenuAsync(long id, AdminMenuInput input) { var menu = await adminMenuRepository.GetByIdAsync(id); BusinessException.ThrowIf(menu == null, "菜单不存在", ResultCode.NOT_FOUND); BusinessException.ThrowIf(input.ParentId == id, "父级菜单不能选择自身", ResultCode.BAD_REQUEST); await ValidateMenuInputAsync(input, id); menu!.ParentId = input.ParentId; menu.Name = input.Name.Trim(); menu.Code = input.Code.Trim(); menu.Path = input.Path?.Trim(); menu.Component = input.Component?.Trim(); menu.Icon = input.Icon?.Trim(); menu.Sort = input.Sort; menu.IsVisible = input.IsVisible; menu.Status = input.Status; menu.UpdatedBy = "System"; menu.UpdatedAt = DateTime.Now; var result = await adminMenuRepository.UpdateAsync(menu); BusinessException.ThrowIf(!result, "更新菜单失败", ResultCode.GLOBAL_ERROR); logger.LogInformation("更新后台菜单成功:{Code},ID:{Id}", menu.Code, menu.Id); return ToMenuOutput(menu); } /// /// 删除菜单 /// public async Task DeleteMenuAsync(long id) { var menu = await adminMenuRepository.GetByIdAsync(id); BusinessException.ThrowIf(menu == null, "菜单不存在", ResultCode.NOT_FOUND); var hasChildren = await adminMenuRepository.Queryable().AnyAsync(x => x.ParentId == id && !x.IsDeleted); BusinessException.ThrowIf(hasChildren, "请先删除子菜单", ResultCode.CONFLICT); var usedByRole = await adminRoleMenuRepository.Queryable().AnyAsync(x => x.MenuId == id && !x.IsDeleted); BusinessException.ThrowIf(usedByRole, "菜单已被角色使用,不能删除", ResultCode.CONFLICT); var result = await adminMenuRepository.DeleteByIdAsync(id); BusinessException.ThrowIf(!result, "删除菜单失败", ResultCode.GLOBAL_ERROR); } /// /// 获取菜单树 /// public async Task> GetMenuTreeAsync(AdminMenuQueryInput input) { var menus = await adminMenuRepository.Queryable() .Where(x => !x.IsDeleted) .WhereIF(!string.IsNullOrWhiteSpace(input.Name), x => x.Name.Contains(input.Name!)) .WhereIF(!string.IsNullOrWhiteSpace(input.Code), x => x.Code.Contains(input.Code!)) .WhereIF(input.Status.HasValue, x => x.Status == input.Status!.Value) .OrderBy(x => x.Sort) .OrderBy(x => x.Id) .ToListAsync(); return BuildMenuTree(menus); } /// /// 创建角色 /// public async Task CreateRoleAsync(AdminRoleInput input) { await ValidateRoleInputAsync(input); var role = new AdminRole { Name = input.Name.Trim(), Code = input.Code.Trim(), Type = input.Type, Remark = input.Remark?.Trim(), Status = input.Status, CreatedBy = "System", CreatedAt = DateTime.Now, UpdatedBy = "System", UpdatedAt = DateTime.Now, IsDeleted = false }; await UseTranAsync(async () => { var inserted = await adminRoleRepository.InsertAsync(role); BusinessException.ThrowIf(!inserted, "创建角色失败", ResultCode.GLOBAL_ERROR); await ReplaceRoleMenusAsync(role.Id, input.MenuIds); }); logger.LogInformation("创建后台角色成功:{Code},ID:{Id}", role.Code, role.Id); return await GetRoleByIdAsync(role.Id); } /// /// 更新角色 /// public async Task UpdateRoleAsync(long id, AdminRoleInput input) { var role = await adminRoleRepository.GetByIdAsync(id); BusinessException.ThrowIf(role == null, "角色不存在", ResultCode.NOT_FOUND); await ValidateRoleInputAsync(input, id); BusinessException.ThrowIf(role!.Type == AdminRoleTypeEnum.System && input.Type != AdminRoleTypeEnum.System, "系统角色不能修改为普通角色", ResultCode.FORBIDDEN); role.Name = input.Name.Trim(); role.Code = input.Code.Trim(); role.Type = input.Type; role.Remark = input.Remark?.Trim(); role.Status = input.Status; role.UpdatedBy = "System"; role.UpdatedAt = DateTime.Now; await UseTranAsync(async () => { var updated = await adminRoleRepository.UpdateAsync(role); BusinessException.ThrowIf(!updated, "更新角色失败", ResultCode.GLOBAL_ERROR); await ReplaceRoleMenusAsync(role.Id, input.MenuIds); }); logger.LogInformation("更新后台角色成功:{Code},ID:{Id}", role.Code, role.Id); return await GetRoleByIdAsync(role.Id); } /// /// 删除角色 /// public async Task DeleteRoleAsync(long id) { var role = await adminRoleRepository.GetByIdAsync(id); BusinessException.ThrowIf(role?.Type == AdminRoleTypeEnum.System, "系统角色不允许删除", ResultCode.FORBIDDEN); BusinessException.ThrowIf(role == null, "角色不存在", ResultCode.NOT_FOUND); var usedByUser = await adminUserRoleRepository.Queryable().AnyAsync(x => x.RoleId == id && !x.IsDeleted); BusinessException.ThrowIf(usedByUser, "角色已分配给管理员,不能删除", ResultCode.CONFLICT); await UseTranAsync(async () => { await adminRoleMenuRepository.Deleteable().Where(x => x.RoleId == id).ExecuteCommandAsync(); var deleted = await adminRoleRepository.DeleteByIdAsync(id); BusinessException.ThrowIf(!deleted, "删除角色失败", ResultCode.GLOBAL_ERROR); }); } /// /// 获取角色详情 /// public async Task GetRoleByIdAsync(long id) { var role = await adminRoleRepository.GetByIdAsync(id); BusinessException.ThrowIf(role == null, "角色不存在", ResultCode.NOT_FOUND); var menuIds = await adminRoleMenuRepository.Queryable() .Where(x => x.RoleId == id && !x.IsDeleted) .Select(x => x.MenuId) .ToListAsync(); return ToRoleOutput(role!, menuIds); } /// /// 获取角色分页列表 /// public async Task> GetRoleListAsync(AdminRoleQueryInput input) { RefAsync totalNumber = 0; var roles = await adminRoleRepository.Queryable() .Where(x => !x.IsDeleted) .WhereIF(!string.IsNullOrWhiteSpace(input.Name), x => x.Name.Contains(input.Name!)) .WhereIF(!string.IsNullOrWhiteSpace(input.Code), x => x.Code.Contains(input.Code!)) .WhereIF(input.Type.HasValue, x => x.Type == input.Type!.Value) .WhereIF(input.Status.HasValue, x => x.Status == input.Status!.Value) .OrderByDescending(x => x.CreatedAt) .ToPageListAsync(input.PageIndex, input.PageSize, totalNumber); var roleIds = roles.Select(x => x.Id).ToList(); var roleMenus = roleIds.Count == 0 ? [] : await adminRoleMenuRepository.Queryable() .Where(x => roleIds.Contains(x.RoleId) && !x.IsDeleted) .ToListAsync(); var outputs = roles .Select(x => ToRoleOutput(x, roleMenus.Where(rm => rm.RoleId == x.Id).Select(rm => rm.MenuId).ToList())) .ToList(); return new PageListModel(outputs, input.PageIndex, input.PageSize, totalNumber); } /// /// 分配角色菜单 /// public async Task AssignRoleMenusAsync(long roleId, AssignRoleMenusInput input) { var role = await adminRoleRepository.GetByIdAsync(roleId); BusinessException.ThrowIf(role == null, "角色不存在", ResultCode.NOT_FOUND); await ValidateMenuIdsAsync(input.MenuIds); await UseTranAsync(async () => await ReplaceRoleMenusAsync(roleId, input.MenuIds)); } /// /// 分配管理员角色 /// public async Task AssignAdminUserRolesAsync(long adminUserId, AssignAdminUserRolesInput input) { await ReplaceAdminUserRolesAsync(adminUserId, input.RoleIds); } /// /// 获取管理员菜单树 /// public async Task> GetAdminUserMenuTreeAsync(long adminUserId) { var menus = await GetAdminUserMenusAsync(adminUserId, true); return BuildMenuTree(menus); } /// /// 获取管理员角色 /// public async Task> GetAdminUserRolesAsync(long adminUserId) { var adminUser = await adminUserRepository.GetByIdAsync(adminUserId); BusinessException.ThrowIf(adminUser == null, "管理员不存在", ResultCode.NOT_FOUND); return await adminRoleRepository.Queryable() .InnerJoin((role, userRole) => role.Id == userRole.RoleId) .Where((role, userRole) => userRole.AdminUserId == adminUserId && !role.IsDeleted && !userRole.IsDeleted) .Select((role, userRole) => new AdminRoleSimpleOutput { Id = role.Id, Name = role.Name, Code = role.Code, Type = role.Type }) .ToListAsync(); } /// /// 获取管理员权限编码 /// public async Task> GetAdminUserPermissionCodesAsync(long adminUserId) { var menus = await GetAdminUserMenusAsync(adminUserId, false); return menus.Select(x => x.Code).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList(); } private async Task ValidateMenuInputAsync(AdminMenuInput input, long? id = null) { BusinessException.ThrowIf(string.IsNullOrWhiteSpace(input.Name), "菜单名称不能为空", ResultCode.BAD_REQUEST); BusinessException.ThrowIf(string.IsNullOrWhiteSpace(input.Code), "权限编码不能为空", ResultCode.BAD_REQUEST); if (input.ParentId > 0) { var parentExists = await adminMenuRepository.Queryable().AnyAsync(x => x.Id == input.ParentId && !x.IsDeleted); BusinessException.ThrowIf(!parentExists, "父级菜单不存在", ResultCode.NOT_FOUND); } var code = input.Code.Trim(); var codeExists = await adminMenuRepository.Queryable() .Where(x => x.Code == code && !x.IsDeleted) .WhereIF(id.HasValue, x => x.Id != id!.Value) .AnyAsync(); BusinessException.ThrowIf(codeExists, "权限编码已存在", ResultCode.CONFLICT); } private async Task ValidateRoleInputAsync(AdminRoleInput input, long? id = null) { BusinessException.ThrowIf(string.IsNullOrWhiteSpace(input.Name), "角色名称不能为空", ResultCode.BAD_REQUEST); BusinessException.ThrowIf(string.IsNullOrWhiteSpace(input.Code), "角色编码不能为空", ResultCode.BAD_REQUEST); var code = input.Code.Trim(); var codeExists = await adminRoleRepository.Queryable() .Where(x => x.Code == code && !x.IsDeleted) .WhereIF(id.HasValue, x => x.Id != id!.Value) .AnyAsync(); BusinessException.ThrowIf(codeExists, "角色编码已存在", ResultCode.CONFLICT); await ValidateMenuIdsAsync(input.MenuIds); } private async Task ValidateMenuIdsAsync(List menuIds) { var ids = menuIds.Distinct().ToList(); if (ids.Count == 0) { return; } var existsCount = await adminMenuRepository.Queryable() .Where(x => ids.Contains(x.Id) && !x.IsDeleted) .CountAsync(); BusinessException.ThrowIf(existsCount != ids.Count, "包含不存在的菜单", ResultCode.BAD_REQUEST); } private async Task ValidateRoleIdsAsync(List roleIds) { var ids = roleIds.Distinct().ToList(); if (ids.Count == 0) { return; } var existsCount = await adminRoleRepository.Queryable() .Where(x => ids.Contains(x.Id) && !x.IsDeleted) .CountAsync(); BusinessException.ThrowIf(existsCount != ids.Count, "包含不存在的角色", ResultCode.BAD_REQUEST); } private async Task ReplaceRoleMenusAsync(long roleId, List menuIds) { await adminRoleMenuRepository.Deleteable().Where(x => x.RoleId == roleId).ExecuteCommandAsync(); var now = DateTime.Now; var items = menuIds.Distinct().Select(menuId => new AdminRoleMenu { RoleId = roleId, MenuId = menuId, Status = (int)DefaultStatusEnum.Active, CreatedBy = "System", CreatedAt = now, UpdatedBy = "System", UpdatedAt = now, IsDeleted = false }).ToList(); if (items.Count > 0) { await adminRoleMenuRepository.Context.Insertable(items).ExecuteCommandAsync(); } } private async Task ReplaceAdminUserRolesAsync(long adminUserId, List roleIds) { var adminUser = await adminUserRepository.GetByIdAsync(adminUserId); BusinessException.ThrowIf(adminUser == null, "管理员不存在", ResultCode.NOT_FOUND); await ValidateRoleIdsAsync(roleIds); await UseTranAsync(async () => { await adminUserRoleRepository.Deleteable().Where(x => x.AdminUserId == adminUserId).ExecuteCommandAsync(); var now = DateTime.Now; var items = roleIds.Distinct().Select(roleId => new AdminUserRole { AdminUserId = adminUserId, RoleId = roleId, Status = (int)DefaultStatusEnum.Active, CreatedBy = "System", CreatedAt = now, UpdatedBy = "System", UpdatedAt = now, IsDeleted = false }).ToList(); if (items.Count > 0) { await adminUserRoleRepository.Context.Insertable(items).ExecuteCommandAsync(); } }); } private async Task> GetAdminUserMenusAsync(long adminUserId, bool visibleOnly) { var adminUser = await adminUserRepository.GetByIdAsync(adminUserId); BusinessException.ThrowIf(adminUser == null, "管理员不存在", ResultCode.NOT_FOUND); if (adminUser!.Type == AdminUserTypeEnum.SuperAdmin) { return await adminMenuRepository.Queryable() .Where(x => !x.IsDeleted && x.Status == (int)DefaultStatusEnum.Active) .WhereIF(visibleOnly, x => x.IsVisible) .OrderBy(x => x.Sort) .OrderBy(x => x.Id) .ToListAsync(); } var menus = await adminMenuRepository.Queryable() .InnerJoin((menu, roleMenu) => menu.Id == roleMenu.MenuId) .InnerJoin((menu, roleMenu, role) => roleMenu.RoleId == role.Id) .InnerJoin((menu, roleMenu, role, userRole) => role.Id == userRole.RoleId) .Where((menu, roleMenu, role, userRole) => userRole.AdminUserId == adminUserId && !menu.IsDeleted && !roleMenu.IsDeleted && !role.IsDeleted && !userRole.IsDeleted && menu.Status == (int)DefaultStatusEnum.Active && role.Status == (int)DefaultStatusEnum.Active) .WhereIF(visibleOnly, (menu, roleMenu, role, userRole) => menu.IsVisible) .OrderBy((menu, roleMenu, role, userRole) => menu.Sort) .OrderBy((menu, roleMenu, role, userRole) => menu.Id) .Select((menu, roleMenu, role, userRole) => menu) .ToListAsync(); return menus.DistinctBy(x => x.Id).OrderBy(x => x.Sort).ThenBy(x => x.Id).ToList(); } private static List BuildMenuTree(List menus) { var outputs = menus.Select(ToMenuOutput).ToList(); var lookup = outputs.ToLookup(x => x.ParentId); foreach (var item in outputs) { item.Children = lookup[item.Id].OrderBy(x => x.Sort).ThenBy(x => x.Id).ToList(); } return outputs .Where(x => x.ParentId == 0 || outputs.All(item => item.Id != x.ParentId)) .OrderBy(x => x.Sort) .ThenBy(x => x.Id) .ToList(); } private static AdminMenuOutput ToMenuOutput(AdminMenu menu) { return new AdminMenuOutput { Id = menu.Id, ParentId = menu.ParentId, Name = menu.Name, Code = menu.Code, Path = menu.Path, Component = menu.Component, Icon = menu.Icon, Sort = menu.Sort, IsVisible = menu.IsVisible, Status = menu.Status }; } private static AdminRoleOutput ToRoleOutput(AdminRole role, List menuIds) { return new AdminRoleOutput { Id = role.Id, Name = role.Name, Code = role.Code, Type = role.Type, Remark = role.Remark, Status = role.Status, MenuIds = menuIds, CreatedAt = role.CreatedAt }; } }