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();
}
}