using System.Text.Json; using FoodLabeling.Application.Contracts.Constants; using FoodLabeling.Application.Helpers; using FoodLabeling.Application.Contracts.Dtos.RbacRole; using FoodLabeling.Application.Contracts.Dtos.Common; using FoodLabeling.Application.Contracts.IServices; using Microsoft.AspNetCore.Mvc; using SqlSugar; using Volo.Abp; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Entities; using Volo.Abp.Uow; using Yi.Framework.Rbac.Domain.Entities; using Yi.Framework.SqlSugarCore.Abstractions; namespace FoodLabeling.Application.Services; /// /// 角色管理(食品标签-美国版对外) /// public class RbacRoleAppService : ApplicationService, IRbacRoleAppService { private readonly ISqlSugarDbContext _dbContext; private readonly ISqlSugarRepository _roleRepository; private readonly ISqlSugarRepository _menuRepository; private readonly ISqlSugarRepository _roleMenuRepository; private readonly ISqlSugarRepository _roleDeptRepository; private readonly ISqlSugarRepository _userRoleRepository; public RbacRoleAppService( ISqlSugarDbContext dbContext, ISqlSugarRepository roleRepository, ISqlSugarRepository menuRepository, ISqlSugarRepository roleMenuRepository, ISqlSugarRepository roleDeptRepository, ISqlSugarRepository userRoleRepository) { _dbContext = dbContext; _roleRepository = roleRepository; _menuRepository = menuRepository; _roleMenuRepository = roleMenuRepository; _roleDeptRepository = roleDeptRepository; _userRoleRepository = userRoleRepository; } /// public async Task> GetListAsync([FromQuery] RbacRoleGetListInputVo input) { RefAsync total = 0; var query = _roleRepository._DbQueryable .Where(x => x.IsDeleted == false) .WhereIF(!string.IsNullOrWhiteSpace(input.RoleCode), x => x.RoleCode.Contains(input.RoleCode!.Trim())) .WhereIF(!string.IsNullOrWhiteSpace(input.RoleName), x => x.RoleName.Contains(input.RoleName!.Trim())) .WhereIF(input.State is not null, x => x.State == input.State); if (!string.IsNullOrWhiteSpace(input.Sorting)) { query = query.OrderBy(input.Sorting); } else { query = query.OrderBy(x => x.OrderNum, OrderByType.Desc); } var entities = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total); var items = entities.Select(x => new RbacRoleGetListOutputDto { Id = x.Id, RoleName = x.RoleName ?? string.Empty, RoleCode = x.RoleCode ?? string.Empty, Remark = x.Remark, DataScope = (int)x.DataScope, State = x.State, OrderNum = x.OrderNum, AccessPermissionCodes = DeserializeAccessPermissionCodes(x.AccessPermissionCodesJson) }).ToList(); await FillAccessPermissionsAsync(items); var pageSize = input.MaxResultCount <= 0 ? items.Count : input.MaxResultCount; var pageIndex = pageSize <= 0 ? 1 : PagedQueryConvention.PageIndexFromSkipCount(input.SkipCount); var totalPages = pageSize <= 0 ? 0 : (int)Math.Ceiling(total / (double)pageSize); return new PagedResultWithPageDto { PageIndex = pageIndex, PageSize = pageSize, TotalCount = total, TotalPages = totalPages, Items = items }; } /// public async Task GetAsync(Guid id) { var entity = await _roleRepository.GetSingleAsync(x => x.Id == id && x.IsDeleted == false); if (entity is null) { throw new UserFriendlyException("角色不存在"); } var menuIds = await _roleMenuRepository._DbQueryable .Where(x => x.RoleId == id) .Select(x => x.MenuId) .ToListAsync(); var dto = new RbacRoleGetOutputDto { Id = entity.Id, RoleName = entity.RoleName ?? string.Empty, RoleCode = entity.RoleCode ?? string.Empty, Remark = entity.Remark, DataScope = (int)entity.DataScope, State = entity.State, OrderNum = entity.OrderNum, AccessPermissionCodes = DeserializeAccessPermissionCodes(entity.AccessPermissionCodesJson), MenuIds = menuIds.Select(x => x.ToString()).ToList() }; await FillAccessPermissionsAsync(new List { dto }); return dto; } /// [UnitOfWork] public async Task CreateAsync([FromBody] RbacRoleCreateInputVo input) { var roleName = input.RoleName?.Trim(); var roleCode = input.RoleCode?.Trim(); if (string.IsNullOrWhiteSpace(roleName)) { throw new UserFriendlyException("角色名称不能为空"); } if (string.IsNullOrWhiteSpace(roleCode)) { throw new UserFriendlyException("角色编码不能为空"); } var isExist = await _roleRepository.IsAnyAsync(x => x.RoleCode == roleCode || x.RoleName == roleName); if (isExist) { throw new UserFriendlyException("角色名称或编码已存在"); } var entity = new RoleAggregateRoot { RoleName = roleName, RoleCode = roleCode, Remark = input.Remark?.Trim(), DataScope = (Yi.Framework.Rbac.Domain.Shared.Enums.DataScopeEnum)input.DataScope, State = input.State, OrderNum = input.OrderNum ?? 0 }; EntityHelper.TrySetId(entity, () => GuidGenerator.Create()); await _roleRepository.InsertAsync(entity); await ApplyRoleMenuBindingsAsync(entity.Id, input); return await GetAsync(entity.Id); } /// [UnitOfWork] public async Task UpdateAsync(Guid id, [FromBody] RbacRoleUpdateInputVo input) { var entity = await _roleRepository.GetSingleAsync(x => x.Id == id && x.IsDeleted == false); if (entity is null) { throw new UserFriendlyException("角色不存在"); } var roleName = input.RoleName?.Trim(); var roleCode = input.RoleCode?.Trim(); if (string.IsNullOrWhiteSpace(roleName)) { throw new UserFriendlyException("角色名称不能为空"); } if (string.IsNullOrWhiteSpace(roleCode)) { throw new UserFriendlyException("角色编码不能为空"); } var isExist = await _roleRepository._DbQueryable .Where(x => x.Id != entity.Id && x.IsDeleted == false) .AnyAsync(x => x.RoleCode == roleCode || x.RoleName == roleName); if (isExist) { throw new UserFriendlyException("角色名称或编码已存在"); } entity.RoleName = roleName; entity.RoleCode = roleCode; entity.Remark = input.Remark?.Trim(); entity.DataScope = (Yi.Framework.Rbac.Domain.Shared.Enums.DataScopeEnum)input.DataScope; entity.State = input.State; if (input.OrderNum is not null) { entity.OrderNum = input.OrderNum.Value; } await _roleRepository.UpdateAsync(entity); await ApplyRoleMenuBindingsAsync(entity.Id, input); return await GetAsync(entity.Id); } /// /// 新增/编辑时按 menuIds 或 accessPermissions 绑定角色菜单(RoleMenu 表)。 /// private async Task ApplyRoleMenuBindingsAsync(Guid roleId, RbacRoleCreateInputVo input) { var hasMenuIds = input.MenuIds is not null; var hasAccessPermissions = input.AccessPermissions is not null; if (hasMenuIds && input.MenuIds!.Count > 0) { await SetRoleMenusAsync(roleId, input.MenuIds); return; } if (hasAccessPermissions) { if (string.IsNullOrWhiteSpace(input.AccessPermissions)) { await SetRoleMenusAsync(roleId, new List()); return; } var menuIds = await ResolveMenuIdsFromAccessPermissionsAsync(input.AccessPermissions); if (menuIds.Count == 0) { throw new UserFriendlyException( "accessPermissions 未匹配到任何菜单,请确认 PermissionCode 与菜单一致,或先执行 menu_backfill_permission_code.sql 回填 Menu.PermissionCode"); } await SetRoleMenusAsync(roleId, menuIds); return; } if (hasMenuIds && input.MenuIds!.Count == 0) { await SetRoleMenusAsync(roleId, new List()); } } private async Task SetRoleMenusAsync(Guid roleId, List menuIds) { var distinct = menuIds?.Distinct().ToList() ?? new List(); await _roleMenuRepository.DeleteAsync(x => x.RoleId == roleId); if (distinct.Count == 0) { return; } var existMenuIds = await _menuRepository._DbQueryable .Where(x => x.IsDeleted == false) .Where(x => distinct.Contains(x.Id)) .Select(x => x.Id) .ToListAsync(); if (existMenuIds.Count == 0) { return; } var entities = existMenuIds.Select(menuId => { var entity = new RoleMenuEntity { RoleId = roleId, MenuId = menuId }; EntityHelper.TrySetId(entity, () => GuidGenerator.Create()); return entity; }).ToList(); await _roleMenuRepository.InsertRangeAsync(entities); } private async Task> ResolveMenuIdsFromAccessPermissionsAsync(string accessPermissions) { var codes = RbacAccessPermissionHelper.ParseAccessPermissionCodes(accessPermissions); if (codes.Count == 0) { return new List(); } var codeSet = new HashSet(codes, StringComparer.OrdinalIgnoreCase); var menus = await _menuRepository._DbQueryable .Where(m => m.IsDeleted == false) .Select(m => new { m.Id, m.PermissionCode, m.Router }) .ToListAsync(); return menus .Where(m => { var effective = RbacAccessPermissionHelper.GetEffectivePermissionCode(m.PermissionCode, m.Router); return effective is not null && codeSet.Contains(effective); }) .Select(m => m.Id) .Distinct() .ToList(); } private async Task FillAccessPermissionsAsync(List items) { if (items.Count == 0) { return; } var map = await GetAccessPermissionsByRoleIdsAsync(items.Select(x => x.Id).ToList()); foreach (var item in items) { item.AccessPermissions = map.GetValueOrDefault(item.Id, string.Empty); } } /// /// Role → RoleMenu → Menu.PermissionCode(空则按 Router 推导)汇总 accessPermissions。 /// private async Task> GetAccessPermissionsByRoleIdsAsync(List roleIds) { var result = roleIds.Distinct().ToDictionary(id => id, _ => string.Empty); if (result.Count == 0) { return result; } var distinctRoleIds = result.Keys.ToList(); var links = await _roleMenuRepository._DbQueryable .Where(rm => distinctRoleIds.Contains(rm.RoleId)) .Select(rm => new { rm.RoleId, rm.MenuId }) .ToListAsync(); if (links.Count == 0) { return result; } var menuIds = links.Select(x => x.MenuId).Distinct().ToList(); var menus = await _menuRepository._DbQueryable .Where(m => menuIds.Contains(m.Id) && m.IsDeleted == false) .Select(m => new { m.Id, m.PermissionCode, m.Router }) .ToListAsync(); var permByMenuId = menus.ToDictionary( x => x.Id, x => RbacAccessPermissionHelper.GetEffectivePermissionCode(x.PermissionCode, x.Router)); var byRole = distinctRoleIds.ToDictionary(id => id, _ => new HashSet(StringComparer.OrdinalIgnoreCase)); foreach (var link in links) { if (!permByMenuId.TryGetValue(link.MenuId, out var code) || string.IsNullOrWhiteSpace(code)) { continue; } if (byRole.TryGetValue(link.RoleId, out var set)) { set.Add(code.Trim()); } } foreach (var kv in byRole) { if (kv.Value.Count == 0) { continue; } result[kv.Key] = string.Join(", ", kv.Value.OrderBy(x => x, StringComparer.Ordinal)); } return result; } /// [UnitOfWork] public async Task DeleteAsync([FromBody] List ids) { var idList = ids?.Distinct().ToList() ?? new List(); if (idList.Count == 0) { return; } await _roleMenuRepository.DeleteAsync(x => idList.Contains(x.RoleId)); await _roleDeptRepository.DeleteAsync(x => idList.Contains(x.RoleId)); await _userRoleRepository.DeleteAsync(x => idList.Contains(x.RoleId)); await _roleRepository.DeleteAsync(x => idList.Contains(x.Id)); } private static List DeserializeAccessPermissionCodes(string? json) { if (string.IsNullOrWhiteSpace(json)) { return new List(); } try { var raw = JsonSerializer.Deserialize>(json); return NormalizeAccessPermissionCodes(raw); } catch { return new List(); } } private static string? SerializeAccessPermissionCodes(List codes) { if (codes == null || codes.Count == 0) { return null; } return JsonSerializer.Serialize(codes); } private static List ResolveAccessPermissions(RbacRoleCreateInputVo input) { var merged = new List(); if (!string.IsNullOrWhiteSpace(input.AccessPermissions)) { merged.AddRange(DeserializeAccessPermissionCodes(input.AccessPermissions)); } if (input.AccessPermissionCodes is { Count: > 0 }) { merged.AddRange(input.AccessPermissionCodes); } return NormalizeAccessPermissionCodes(merged.Count > 0 ? merged : null); } private static List NormalizeAccessPermissionCodes(List? input) { if (input == null || input.Count == 0) { return new List(); } return input .Select(x => x?.Trim() ?? "") .Where(x => x.Length > 0 && RoleAccessPermissionCodes.IsKnown(x)) .Distinct(StringComparer.Ordinal) .ToList(); } }