- 网关管理:GatewayEntity/Service/Controller CRUD + 测试连接;设备 GatewayId 外键关联 - JWT 认证:登录签发 Token(权限编码写入 Claims)、RequirePermission 权限过滤器、CurrentUser 上下文 - RBAC:用户/角色/权限实体与 CRUD,预定义 4 角色 + 6 权限种子(admin/admin123) - 组织架构:sys_org 固定层级树(公司/实验室/部门/班组白夜班),层级校验,用户挂 OrgId/岗位/技能标签 - 数据权限:角色 DataScope(全部/本组织及下级),用户列表按组织子树过滤 - 防锁死保护:禁止删/禁自己,保证至少一名活跃管理员,角色摘除 user:manage 前校验 - 审计日志:AuditRecorder 接入设备增删改/指令下发/登录登出/组织变更,AuditController 查询 - 设备指令下发按网关表取连接参数;设备列表支持 gatewayId/productId 筛选 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
289 lines
13 KiB
C#
289 lines
13 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// 角色权限管理 服务实现
|
|
/// </summary>
|
|
public class RoleService : IRoleService
|
|
{
|
|
public async Task<Result<List<RoleDto>>> GetPagedAsync(int pageIndex, int pageSize, RefAsync<int> total, string? keyword = null)
|
|
{
|
|
try
|
|
{
|
|
var list = await SqlSugarContext.DbContext.Queryable<RoleEntity>()
|
|
.Where(x => x.IsDel == 0)
|
|
.WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.Code.Contains(keyword!) || x.Name.Contains(keyword!))
|
|
.OrderBy(x => x.Sort).OrderBy(x => x.CreateTime, OrderByType.Desc)
|
|
.ToPageListAsync(pageIndex, pageSize, total);
|
|
|
|
var dtos = list.ToDtoList();
|
|
await FillPermissionsAsync(dtos);
|
|
return Result<List<RoleDto>>.Success(dtos);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result<List<RoleDto>>.Error("查询角色列表失败", ex);
|
|
}
|
|
}
|
|
|
|
public async Task<Result<RoleDto>> GetByIdAsync(long id)
|
|
{
|
|
try
|
|
{
|
|
var entity = await SqlSugarContext.DbContext.Queryable<RoleEntity>()
|
|
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
|
|
if (entity == null) return Result<RoleDto>.Error("角色不存在或已被删除");
|
|
var dto = entity.ToDto();
|
|
await FillPermissionsAsync(new List<RoleDto> { dto });
|
|
return Result<RoleDto>.Success(dto);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result<RoleDto>.Error("查询角色详情失败", ex);
|
|
}
|
|
}
|
|
|
|
public async Task<Result<RoleDto>> AddAsync(RoleDto dto)
|
|
{
|
|
if (dto == null || string.IsNullOrWhiteSpace(dto.Code) || string.IsNullOrWhiteSpace(dto.Name))
|
|
return Result<RoleDto>.Error("角色编码和名称不能为空");
|
|
|
|
try
|
|
{
|
|
var exists = await SqlSugarContext.DbContext.Queryable<RoleEntity>()
|
|
.Where(x => x.Code == dto.Code && x.IsDel == 0).AnyAsync();
|
|
if (exists) return Result<RoleDto>.Error($"角色编码「{dto.Code}」已存在");
|
|
|
|
var entity = dto.ToEntity();
|
|
entity.Id = 0;
|
|
entity.IsSystem = 0; // 新建角色一律非系统内置
|
|
entity.CreateTime = DateTime.Now;
|
|
var id = await SqlSugarContext.DbContext.Insertable(entity).ExecuteReturnSnowflakeIdAsync();
|
|
entity.Id = id;
|
|
|
|
if (dto.PermissionIds != null && dto.PermissionIds.Count > 0)
|
|
{
|
|
var permIds = dto.PermissionIds.Select(long.Parse).Where(p => p > 0).ToList();
|
|
await ReplaceRolePermissionsAsync(id, permIds);
|
|
}
|
|
|
|
var result = entity.ToDto();
|
|
await FillPermissionsAsync(new List<RoleDto> { result });
|
|
return Result<RoleDto>.Success(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result<RoleDto>.Error("新增角色失败", ex);
|
|
}
|
|
}
|
|
|
|
public async Task<Result<RoleDto>> UpdateAsync(RoleDto dto)
|
|
{
|
|
if (dto == null || !long.TryParse(dto.Id, out var id) || id <= 0)
|
|
return Result<RoleDto>.Error("角色Id无效");
|
|
|
|
try
|
|
{
|
|
var entity = await SqlSugarContext.DbContext.Queryable<RoleEntity>()
|
|
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
|
|
if (entity == null) return Result<RoleDto>.Error("角色不存在或已被删除");
|
|
|
|
// 系统内置角色不允许改编码
|
|
if (entity.IsSystem == 1 && dto.Code != entity.Code)
|
|
return Result<RoleDto>.Error($"系统内置角色「{entity.Name}」不允许修改编码");
|
|
|
|
var codeExists = await SqlSugarContext.DbContext.Queryable<RoleEntity>()
|
|
.Where(x => x.Code == dto.Code && x.Id != id && x.IsDel == 0).AnyAsync();
|
|
if (codeExists) return Result<RoleDto>.Error($"角色编码「{dto.Code}」已存在");
|
|
|
|
var newEntity = dto.ToEntity();
|
|
newEntity.Id = id;
|
|
newEntity.IsSystem = entity.IsSystem; // 内置标记不可被前端覆盖
|
|
await SqlSugarContext.DbContext.Updateable(newEntity)
|
|
.IgnoreColumns(x => new { x.CreateTime, x.IsDel, x.IsSystem })
|
|
.ExecuteCommandAsync();
|
|
|
|
// 权限变化时同步更新关联(传入 null 表示不改权限)
|
|
if (dto.PermissionIds != null)
|
|
{
|
|
var permIds = dto.PermissionIds.Select(long.Parse).Where(p => p > 0).ToList();
|
|
var guard = await EnsureAdminRemainsAfterRoleChangeAsync(id, permIds);
|
|
if (guard != null) return Result<RoleDto>.Error(guard.Msg);
|
|
await ReplaceRolePermissionsAsync(id, permIds);
|
|
}
|
|
|
|
var result = newEntity.ToDto();
|
|
await FillPermissionsAsync(new List<RoleDto> { result });
|
|
return Result<RoleDto>.Success(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result<RoleDto>.Error("修改角色失败", ex);
|
|
}
|
|
}
|
|
|
|
public async Task<Result> DeleteAsync(long id)
|
|
{
|
|
if (id <= 0) return Result.Error("角色Id无效");
|
|
try
|
|
{
|
|
var entity = await SqlSugarContext.DbContext.Queryable<RoleEntity>()
|
|
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
|
|
if (entity == null) return Result.Error("角色不存在或已被删除");
|
|
if (entity.IsSystem == 1)
|
|
return Result.Error($"系统内置角色「{entity.Name}」不允许删除");
|
|
|
|
var usedByUser = await SqlSugarContext.DbContext.Queryable<UserRoleEntity>()
|
|
.Where(x => x.RoleId == id).AnyAsync();
|
|
if (usedByUser)
|
|
return Result.Error("该角色下存在用户,请先移除角色下的用户再删除");
|
|
|
|
await SqlSugarContext.DbContext.Updateable<RoleEntity>()
|
|
.SetColumns(x => x.IsDel == 1)
|
|
.Where(x => x.Id == id).ExecuteCommandAsync();
|
|
await SqlSugarContext.DbContext.Deleteable<RolePermissionEntity>()
|
|
.Where(x => x.RoleId == id).ExecuteCommandAsync();
|
|
return Result.Success();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Error("删除角色失败", ex);
|
|
}
|
|
}
|
|
|
|
public async Task<Result> AssignPermissionsAsync(long roleId, List<long> permissionIds)
|
|
{
|
|
if (roleId <= 0) return Result.Error("角色Id无效");
|
|
try
|
|
{
|
|
var exists = await SqlSugarContext.DbContext.Queryable<RoleEntity>()
|
|
.Where(x => x.Id == roleId && x.IsDel == 0).AnyAsync();
|
|
if (!exists) return Result.Error("角色不存在或已被删除");
|
|
|
|
// 防锁死:移除角色的用户管理权限后,系统必须仍有其它可管理用户的管理员
|
|
var newIds = permissionIds ?? new List<long>();
|
|
var guard = await EnsureAdminRemainsAfterRoleChangeAsync(roleId, newIds);
|
|
if (guard != null) return guard;
|
|
|
|
await ReplaceRolePermissionsAsync(roleId, newIds);
|
|
return Result.Success();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Error("分配权限失败", ex);
|
|
}
|
|
}
|
|
|
|
public async Task<Result<List<RoleOptionDto>>> GetOptionsAsync()
|
|
{
|
|
try
|
|
{
|
|
var list = await SqlSugarContext.DbContext.Queryable<RoleEntity>()
|
|
.Where(x => x.IsDel == 0)
|
|
.OrderBy(x => x.Sort).OrderBy(x => x.Code)
|
|
.ToListAsync();
|
|
return Result<List<RoleOptionDto>>.Success(list.ToOptionDtoList());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result<List<RoleOptionDto>>.Error("查询角色选项失败", ex);
|
|
}
|
|
}
|
|
|
|
public async Task<Result<List<PermissionDto>>> GetPermissionsAsync()
|
|
{
|
|
try
|
|
{
|
|
var list = await SqlSugarContext.DbContext.Queryable<PermissionEntity>()
|
|
.Where(x => x.IsDel == 0)
|
|
.OrderBy(x => x.Sort).OrderBy(x => x.Code)
|
|
.ToListAsync();
|
|
return Result<List<PermissionDto>>.Success(list.ToDtoList());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result<List<PermissionDto>>.Error("查询权限列表失败", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>全量覆盖角色-权限关联</summary>
|
|
private static async Task ReplaceRolePermissionsAsync(long roleId, List<long> permissionIds)
|
|
{
|
|
var db = SqlSugarContext.DbContext;
|
|
await db.Deleteable<RolePermissionEntity>().Where(x => x.RoleId == roleId).ExecuteCommandAsync();
|
|
if (permissionIds.Count > 0)
|
|
{
|
|
var now = DateTime.Now;
|
|
var links = permissionIds.Distinct().Select(p => new RolePermissionEntity { RoleId = roleId, PermissionId = p, CreateTime = now }).ToList();
|
|
await db.Insertable(links).ExecuteCommandAsync();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 防锁死校验:当 roleId 将失去 user:manage 权限时,系统必须仍存在
|
|
/// 「已启用且通过其它角色拥有 user:manage 权限」的用户,否则返回 Error
|
|
/// </summary>
|
|
private async Task<Result?> EnsureAdminRemainsAfterRoleChangeAsync(long roleId, List<long> newPermissionIds)
|
|
{
|
|
var db = SqlSugarContext.DbContext;
|
|
var managePermId = await db.Queryable<PermissionEntity>()
|
|
.Where(x => x.Code == "user:manage" && x.IsDel == 0)
|
|
.Select(x => x.Id).FirstAsync();
|
|
if (managePermId == 0) return null;
|
|
|
|
bool currentlyHas = await db.Queryable<RolePermissionEntity>()
|
|
.AnyAsync(x => x.RoleId == roleId && x.PermissionId == managePermId);
|
|
bool willHave = newPermissionIds.Contains(managePermId);
|
|
if (currentlyHas && !willHave)
|
|
{
|
|
var otherRoleIds = await db.Queryable<RolePermissionEntity>()
|
|
.Where(x => x.PermissionId == managePermId && x.RoleId != roleId)
|
|
.Select(x => x.RoleId).ToListAsync();
|
|
var otherUserIds = otherRoleIds.Count > 0
|
|
? await db.Queryable<UserRoleEntity>().Where(x => otherRoleIds.Contains(x.RoleId)).Select(x => x.UserId).ToListAsync()
|
|
: new List<long>();
|
|
var exists = otherUserIds.Count > 0 && await db.Queryable<UserEntity>()
|
|
.AnyAsync(x => otherUserIds.Contains(x.Id) && x.IsDel == 0 && x.IsEnabled == 1);
|
|
if (!exists)
|
|
return Result.Error("操作被拒绝:移除该角色的用户管理权限后,系统将没有任何可管理用户的管理员");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>给角色 DTO 填充权限Id列表与权限编码列表</summary>
|
|
private static async Task FillPermissionsAsync(List<RoleDto> dtos)
|
|
{
|
|
if (dtos == null || dtos.Count == 0) return;
|
|
var db = SqlSugarContext.DbContext;
|
|
var roleIds = dtos.Select(d => long.Parse(d.Id)).ToList();
|
|
|
|
var links = await db.Queryable<RolePermissionEntity>()
|
|
.Where(x => roleIds.Contains(x.RoleId)).ToListAsync();
|
|
var permIds = links.Select(l => l.PermissionId).Distinct().ToList();
|
|
var perms = permIds.Count > 0
|
|
? await db.Queryable<PermissionEntity>().Where(x => permIds.Contains(x.Id) && x.IsDel == 0).ToListAsync()
|
|
: new List<PermissionEntity>();
|
|
var permMap = perms.ToDictionary(p => p.Id, p => p.Code);
|
|
|
|
foreach (var d in dtos)
|
|
{
|
|
var rid = long.Parse(d.Id);
|
|
var myPermIds = links.Where(l => l.RoleId == rid).Select(l => l.PermissionId).ToList();
|
|
d.PermissionIds = myPermIds.Select(p => p.ToString()).ToList();
|
|
d.PermissionCodes = myPermIds.Where(p => permMap.ContainsKey(p)).Select(p => permMap[p]).ToList();
|
|
}
|
|
}
|
|
}
|
|
}
|