Files
QYZH.InteractiveMagazine/QYZH.InteractiveMagazine.Service/AdminPermissionService.cs
glz a8b19cbacc feat(admin): add admin role type management
1. 新增后台角色类型枚举AdminRoleTypeEnum
2. 为AdminRole实体添加角色类型字段并设置默认值
3. 新增/更新管理员角色相关DTO的角色类型属性
4. 完善权限服务中的角色增删改查逻辑,增加系统角色保护校验
5. 修复任务消费中角色类型存储为枚举字符串的问题,改为存储枚举值
6. 新增初始化角色类型的SQL脚本
2026-07-08 09:55:17 +08:00

507 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// 后台权限管理服务实现
/// </summary>
public class AdminPermissionService(
BaseRepository<AdminRole> adminRoleRepository,
BaseRepository<AdminMenu> adminMenuRepository,
BaseRepository<AdminRoleMenu> adminRoleMenuRepository,
BaseRepository<AdminUserRole> adminUserRoleRepository,
BaseRepository<AdminUser> adminUserRepository,
ILogger<AdminPermissionService> logger) : BaseRepository<AdminRole>, IAdminPermissionService
{
/// <summary>
/// 创建菜单
/// </summary>
public async Task<AdminMenuOutput> 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);
}
/// <summary>
/// 更新菜单
/// </summary>
public async Task<AdminMenuOutput> 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);
}
/// <summary>
/// 删除菜单
/// </summary>
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);
}
/// <summary>
/// 获取菜单树
/// </summary>
public async Task<List<AdminMenuOutput>> 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);
}
/// <summary>
/// 创建角色
/// </summary>
public async Task<AdminRoleOutput> 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);
}
/// <summary>
/// 更新角色
/// </summary>
public async Task<AdminRoleOutput> 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);
}
/// <summary>
/// 删除角色
/// </summary>
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);
});
}
/// <summary>
/// 获取角色详情
/// </summary>
public async Task<AdminRoleOutput> 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);
}
/// <summary>
/// 获取角色分页列表
/// </summary>
public async Task<PageListModel<AdminRoleOutput>> GetRoleListAsync(AdminRoleQueryInput input)
{
RefAsync<int> 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<AdminRoleOutput>(outputs, input.PageIndex, input.PageSize, totalNumber);
}
/// <summary>
/// 分配角色菜单
/// </summary>
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));
}
/// <summary>
/// 分配管理员角色
/// </summary>
public async Task AssignAdminUserRolesAsync(long adminUserId, AssignAdminUserRolesInput input)
{
await ReplaceAdminUserRolesAsync(adminUserId, input.RoleIds);
}
/// <summary>
/// 获取管理员菜单树
/// </summary>
public async Task<List<AdminMenuOutput>> GetAdminUserMenuTreeAsync(long adminUserId)
{
var menus = await GetAdminUserMenusAsync(adminUserId, true);
return BuildMenuTree(menus);
}
/// <summary>
/// 获取管理员角色
/// </summary>
public async Task<List<AdminRoleSimpleOutput>> GetAdminUserRolesAsync(long adminUserId)
{
var adminUser = await adminUserRepository.GetByIdAsync(adminUserId);
BusinessException.ThrowIf(adminUser == null, "管理员不存在", ResultCode.NOT_FOUND);
return await adminRoleRepository.Queryable()
.InnerJoin<AdminUserRole>((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();
}
/// <summary>
/// 获取管理员权限编码
/// </summary>
public async Task<List<string>> 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<long> 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<long> 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<long> 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<long> 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<List<AdminMenu>> 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<AdminRoleMenu>((menu, roleMenu) => menu.Id == roleMenu.MenuId)
.InnerJoin<AdminRole>((menu, roleMenu, role) => roleMenu.RoleId == role.Id)
.InnerJoin<AdminUserRole>((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<AdminMenuOutput> BuildMenuTree(List<AdminMenu> 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<long> 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
};
}
}