462 lines
18 KiB
C#
462 lines
18 KiB
C#
using YD_Event.Application.Auth;
|
||
using YD_Event.Application.Menu.Dtos;
|
||
using YD_Event.Core.Options;
|
||
using Microsoft.Extensions.Options;
|
||
using Microsoft.OpenApi;
|
||
|
||
namespace YD_Event.Application.Menu;
|
||
/// <summary>
|
||
/// 系统菜单管理
|
||
/// </summary>
|
||
public class SysMenuService : BaseService<SysMenu>, ITransient
|
||
{
|
||
private readonly ISqlSugarRepository<SysMenu> _sysMenuRepository;
|
||
private readonly IEasyCachingProvider _easyCachingProvider;
|
||
private readonly AuthManager _authManager;
|
||
private readonly ISqlSugarRepository<SysUserRole> _sysUserRoleRepository;
|
||
private readonly IOptionsMonitor<RoleOptions> _roleOptionsMonitor;
|
||
|
||
public SysMenuService(ISqlSugarRepository<SysMenu> sysMenuRepository,
|
||
IEasyCachingProvider easyCachingProvider,
|
||
AuthManager authManager,
|
||
ISqlSugarRepository<SysUserRole> sysUserRoleRepository,
|
||
IOptionsMonitor<RoleOptions> roleOptionsMonitor) : base(sysMenuRepository)
|
||
{
|
||
_sysMenuRepository = sysMenuRepository;
|
||
_easyCachingProvider = easyCachingProvider;
|
||
_authManager = authManager;
|
||
_sysUserRoleRepository = sysUserRoleRepository;
|
||
_roleOptionsMonitor = roleOptionsMonitor;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 菜单列表查询
|
||
/// </summary>
|
||
/// <param name="name"></param>
|
||
/// <returns></returns>
|
||
[DisplayName("菜单列表查询")]
|
||
[HttpGet]
|
||
public async Task<List<SysMenuPageOutput>> Page([FromQuery] string name)
|
||
{
|
||
if (_authManager.IsSuperAdmin)
|
||
{
|
||
var q1 = _sysMenuRepository.AsQueryable()
|
||
.OrderBy(x => x.Sort)
|
||
.OrderBy(x => x.Id);
|
||
if (!string.IsNullOrWhiteSpace(name))
|
||
{
|
||
var list = await q1.Where(x => x.Name.Contains(name)).ToListAsync();
|
||
return list.Adapt<List<SysMenuPageOutput>>();
|
||
}
|
||
|
||
var menus = await q1.ToTreeAsync(x => x.Children, x => x.ParentId, null);
|
||
return menus.Adapt<List<SysMenuPageOutput>>();
|
||
}
|
||
else
|
||
{
|
||
long userId = _authManager.UserId;
|
||
var q2 = _sysMenuRepository.AsQueryable().InnerJoin<SysRoleMenu>((menu, roleMenu) => menu.Id == roleMenu.MenuId)
|
||
.InnerJoin<SysRole>((menu, roleMenu, role) => roleMenu.RoleId == role.Id)
|
||
.InnerJoin<SysUserRole>((menu, roleMenu, role, userRole) => role.Id == userRole.RoleId)
|
||
.Where((menu, roleMenu, role, userRole) => role.Status == AvailabilityStatus.Enable && userRole.UserId == userId);
|
||
if (!string.IsNullOrWhiteSpace(name))
|
||
{
|
||
var list = await q2.Where(menu => menu.Name.Contains(name)).Distinct().OrderBy(menu => menu.Sort).OrderBy(menu => menu.Id).ToListAsync();
|
||
return list.Adapt<List<SysMenuPageOutput>>();
|
||
}
|
||
|
||
var menuIdList = await q2.Select(menu => menu.Id).ToListAsync();
|
||
var array = menuIdList.Select(x => x as object).ToArray();
|
||
var menus = await _sysMenuRepository.AsQueryable()
|
||
.Where(x => x.Status == AvailabilityStatus.Enable)
|
||
.OrderBy(x => x.Sort)
|
||
.OrderBy(x => x.Id)
|
||
.ToTreeAsync(x => x.Children, x => x.ParentId, null, array);
|
||
return menus.Adapt<List<SysMenuPageOutput>>();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加菜单/按钮
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[DisplayName("添加菜单/按钮")]
|
||
[HttpPost("add")]
|
||
public async Task AddMenu(AddSysMenuInput dto)
|
||
{
|
||
SysMenu sysMenu = dto.Adapt<SysMenu>();
|
||
if (sysMenu.Type == MenuType.Button || sysMenu.Type == MenuType.DataScope)
|
||
{
|
||
sysMenu.Link = sysMenu.Icon = sysMenu.Component = sysMenu.Path = sysMenu.Redirect = sysMenu.RouteName = null;
|
||
}
|
||
else
|
||
{
|
||
if (await _sysMenuRepository.IsAnyAsync(x => x.RouteName.ToLower() == dto.RouteName.ToLower()))
|
||
{
|
||
throw Oops.Bah("路由名称已存在");
|
||
}
|
||
sysMenu.Code = null;
|
||
}
|
||
await _sysMenuRepository.InsertAsync(sysMenu);
|
||
await _easyCachingProvider.RemoveByPrefixAsync(CacheConst.PermissionKey);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 修改菜单/按钮
|
||
/// </summary>
|
||
/// <param name="dto"></param>
|
||
/// <returns></returns>
|
||
[DisplayName("更新菜单/按钮")]
|
||
[HttpPut("edit")]
|
||
public async Task UpdateMenu(UpdateSysMenuInput dto)
|
||
{
|
||
SysMenu sysMenu = await _sysMenuRepository.GetByIdAsync(dto.Id);
|
||
if (sysMenu == null)
|
||
{
|
||
throw Oops.Bah("无线参数");
|
||
}
|
||
|
||
if (dto.Type != MenuType.Button && await _sysMenuRepository.IsAnyAsync(x => x.RouteName.ToLower() == dto.RouteName.ToLower() && x.Id != dto.Id))
|
||
{
|
||
throw Oops.Bah("路由名称已存在");
|
||
}
|
||
//检查菜单父子关系是否存在循环引用
|
||
if (dto.ParentId.HasValue && dto.ParentId != sysMenu.ParentId)
|
||
{
|
||
List<SysMenu> list = await _sysMenuRepository.AsQueryable().ToChildListAsync(x => x.Children, dto.Id);
|
||
if (list.Any(x => x.Id == dto.ParentId))
|
||
{
|
||
throw Oops.Bah($"请勿将当前{dto.Type.Description()}的父级菜单设置为它的子级");
|
||
}
|
||
}
|
||
|
||
dto.Adapt(sysMenu);
|
||
if (sysMenu.Type == MenuType.Button || sysMenu.Type == MenuType.DataScope)
|
||
{
|
||
sysMenu.Link = sysMenu.Icon = sysMenu.Component = sysMenu.Path = sysMenu.Redirect = sysMenu.RouteName = null;
|
||
}
|
||
else
|
||
{
|
||
sysMenu.Code = null;
|
||
}
|
||
|
||
await _sysMenuRepository.UpdateAsync(sysMenu);
|
||
await _easyCachingProvider.RemoveByPrefixAsync(CacheConst.PermissionKey);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据菜单Id获取系统菜单详情
|
||
/// </summary>
|
||
/// <param name="id"></param>
|
||
/// <returns></returns>
|
||
[DisplayName("系统菜单详情")]
|
||
[HttpGet]
|
||
public async Task<SysMenuDetailOutput> Detail([FromQuery] long id)
|
||
{
|
||
return await _sysMenuRepository.AsQueryable().Where(x => x.Id == id)
|
||
.Select(x => new SysMenuDetailOutput
|
||
{
|
||
Id = x.Id,
|
||
Name = x.Name,
|
||
ParentId = x.ParentId,
|
||
Status = x.Status,
|
||
Code = x.Code,
|
||
Sort = x.Sort,
|
||
Component = x.Component,
|
||
Icon = x.Icon,
|
||
IsFixed = x.IsFixed,
|
||
IsIframe = x.IsIframe,
|
||
IsKeepAlive = x.IsKeepAlive,
|
||
IsVisible = x.IsVisible,
|
||
Link = x.Link,
|
||
Remark = x.Remark,
|
||
Path = x.Path,
|
||
Redirect = x.Redirect,
|
||
RouteName = x.RouteName,
|
||
Type = x.Type
|
||
}).FirstAsync();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 菜单下拉树
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[DisplayName("菜单下拉树")]
|
||
[HttpGet]
|
||
public async Task<List<TreeSelectOutput>> TreeSelect()
|
||
{
|
||
var list = await _sysMenuRepository.AsQueryable()
|
||
.OrderBy(x => x.Sort)
|
||
.OrderBy(x => x.Id)
|
||
.WithCache()
|
||
.ToTreeAsync(x => x.Children, x => x.ParentId, null);
|
||
|
||
// 检查当前用户是否是管理员,如果不是管理员则过滤指定菜单
|
||
var isAdmin = await IsCurrentUserAdminAsync();
|
||
if (!isAdmin)
|
||
{
|
||
var hiddenMenuIds = _roleOptionsMonitor.CurrentValue.HiddenMenuIds ?? new List<long>();
|
||
if (hiddenMenuIds.Any())
|
||
{
|
||
FilterMenus(list, hiddenMenuIds);
|
||
}
|
||
}
|
||
|
||
return list.Adapt<List<TreeSelectOutput>>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除菜单/按钮
|
||
/// </summary>
|
||
/// <param name="dto"></param>
|
||
/// <returns></returns>
|
||
[DisplayName("删除菜单/按钮"), HttpDelete("delete")]
|
||
public override async Task Delete(KeyDto dto)
|
||
{
|
||
await base.Delete(dto);
|
||
await _easyCachingProvider.RemoveByPrefixAsync(CacheConst.PermissionKey);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 修改菜单/按钮状态
|
||
/// </summary>
|
||
/// <param name="dto"></param>
|
||
/// <returns></returns>
|
||
[DisplayName("修改菜单/按钮状态"), HttpPut("setStatus")]
|
||
public override async Task SetStatus(AvailabilityDto dto)
|
||
{
|
||
await base.SetStatus(dto);
|
||
await _easyCachingProvider.RemoveByPrefixAsync(CacheConst.PermissionKey);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前登录用户可用菜单
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[DisplayName("获取当前登录用户可用菜单")]
|
||
[HttpGet]
|
||
public async Task<List<RouterOutput>> PermissionMenus()
|
||
{
|
||
long userId = _authManager.UserId;
|
||
var value = await _easyCachingProvider.GetAsync($"{CacheConst.PermissionMenuKey}{userId}", async () =>
|
||
{
|
||
var queryable = _sysMenuRepository.AsQueryable()
|
||
.Where(x => x.Status == AvailabilityStatus.Enable)
|
||
.OrderBy(x => x.Sort)
|
||
.OrderBy(x => x.Id);
|
||
List<SysMenu> list;
|
||
if (_authManager.IsSuperAdmin)
|
||
{
|
||
list = await queryable
|
||
.Where(x => x.Type != MenuType.Button && x.Type != MenuType.DataScope)
|
||
.ToTreeAsync(x => x.Children, x => x.ParentId, null);
|
||
}
|
||
else
|
||
{
|
||
List<long> menuIdList = await _sysMenuRepository.AsSugarClient().Queryable<SysRole>().InnerJoin<SysUserRole>((role, userRole) => userRole.RoleId == role.Id)
|
||
.InnerJoin<SysRoleMenu>((role, userRole, roleMenu) => role.Id == roleMenu.RoleId)
|
||
.InnerJoin<SysMenu>((role, userRole, roleMenu, menu) => roleMenu.MenuId == menu.Id)
|
||
.Where((role, userRole, roleMenu, menu) => role.Status == AvailabilityStatus.Enable && userRole.UserId == userId && menu.Status == AvailabilityStatus.Enable)
|
||
.Select((role, userRole, roleMenu) => roleMenu.MenuId).ToListAsync();
|
||
var array = menuIdList.Select(x => x as object).ToArray();
|
||
list = await queryable
|
||
.ToTreeAsync(x => x.Children, x => x.ParentId, null, array);
|
||
RemoveButton(list);
|
||
}
|
||
return list.Adapt<List<RouterOutput>>();
|
||
}, TimeSpan.FromDays(1));
|
||
return value.Value ?? new List<RouterOutput>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查当前登录用户是否是管理员
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private async Task<bool> IsCurrentUserAdminAsync()
|
||
{
|
||
var userId = _authManager.UserId;
|
||
// 首先检查是否是超级管理员
|
||
if (_authManager.IsSuperAdmin)
|
||
{
|
||
return true;
|
||
}
|
||
// 检查用户是否拥有管理员角色
|
||
return await _sysUserRoleRepository.IsAnyAsync(x => x.UserId == userId && x.RoleId == _roleOptionsMonitor.CurrentValue.AdminRoleId);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 过滤菜单,移除配置中指定的菜单ID及其子菜单
|
||
/// </summary>
|
||
/// <param name="menus">菜单列表</param>
|
||
/// <param name="hiddenMenuIds">需要隐藏的菜单ID列表</param>
|
||
private void FilterMenus(List<SysMenu> menus, List<long> hiddenMenuIds)
|
||
{
|
||
for (int i = menus.Count - 1; i >= 0; i--)
|
||
{
|
||
var menu = menus[i];
|
||
// 如果当前菜单在隐藏列表中,移除它
|
||
if (hiddenMenuIds.Contains(menu.Id))
|
||
{
|
||
menus.RemoveAt(i);
|
||
continue;
|
||
}
|
||
// 递归处理子菜单
|
||
if (menu.Children != null && menu.Children.Any())
|
||
{
|
||
FilterMenus(menu.Children, hiddenMenuIds);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 菜单按钮树 - 仅在角色管理页面使用此接口时应用隐藏规则
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[DisplayName("菜单按钮树")]
|
||
[HttpGet]
|
||
public async Task<List<TreeSelectOutput>> TreeMenuButton()
|
||
{
|
||
long userId = _authManager.UserId;
|
||
List<SysMenu> menus;
|
||
if (_authManager.IsSuperAdmin)//超级管理员
|
||
{
|
||
menus = await _sysMenuRepository.AsQueryable().ToTreeAsync(x => x.Children, x => x.ParentId, null);
|
||
}
|
||
else
|
||
{
|
||
menus = await _sysMenuRepository.AsQueryable()
|
||
.InnerJoin<SysRoleMenu>((menu, roleMenu) => menu.Id == roleMenu.MenuId)
|
||
.InnerJoin<SysRole>((menu, roleMenu, role) => roleMenu.RoleId == role.Id)
|
||
.InnerJoin<SysUserRole>((menu, roleMenu, role, userRole) => role.Id == userRole.RoleId)
|
||
.Where((menu, roleMenu, role, userRole) => menu.Status == AvailabilityStatus.Enable &&
|
||
role.Status == AvailabilityStatus.Enable &&
|
||
userRole.UserId == userId)
|
||
.Select(menu => menu)
|
||
.ToTreeAsync(x => x.Children, x => x.ParentId, null);
|
||
}
|
||
|
||
// 检查当前用户是否是管理员,如果不是管理员则过滤指定菜单
|
||
var isAdmin = await IsCurrentUserAdminAsync();
|
||
if (!isAdmin)
|
||
{
|
||
var hiddenMenuIds = _roleOptionsMonitor.CurrentValue.HiddenMenuIds ?? new List<long>();
|
||
if (hiddenMenuIds.Any())
|
||
{
|
||
FilterMenus(menus, hiddenMenuIds);
|
||
}
|
||
}
|
||
|
||
return menus.Adapt<List<TreeSelectOutput>>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 校验权限
|
||
/// </summary>
|
||
/// <param name="code">权限标识</param>
|
||
/// <returns></returns>
|
||
[NonAction]
|
||
public async Task<bool> CheckPermission(string code)
|
||
{
|
||
if (_authManager.IsSuperAdmin) return true;
|
||
|
||
// 兼容历史接口:未配置权限码的接口仅要求已登录,不做菜单权限拦截
|
||
var hasConfiguredPermission = await _sysMenuRepository.IsAnyAsync(x => x.Code == code);
|
||
if (!hasConfiguredPermission)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
var authCodes = await GetAuthPermissionCodeList(_authManager.UserId);
|
||
return authCodes.Any(x => string.Equals(x, code, StringComparison.CurrentCultureIgnoreCase));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定用户的访问权限集合
|
||
/// </summary>
|
||
/// <param name="userId">系统用户id</param>
|
||
/// <returns></returns>
|
||
[NonAction]
|
||
public async Task<List<CheckPermissionOutput>> GetAuthButtonCodeList(long userId)
|
||
{
|
||
var cacheKey = $"{CacheConst.PermissionButtonCodeKey}v2.{userId}";
|
||
var cache = await _easyCachingProvider.GetAsync(cacheKey, async () =>
|
||
{
|
||
var list = await _sysMenuRepository.AsSugarClient()
|
||
.Queryable<SysUserRole>()
|
||
.InnerJoin<SysRole>((userRole, role) => userRole.RoleId == role.Id)
|
||
.InnerJoin<SysRoleMenu>((userRole, role, roleMenu) => role.Id == roleMenu.RoleId)
|
||
.InnerJoin<SysMenu>((userRole, role, roleMenu, menu) => roleMenu.MenuId == menu.Id)
|
||
.Where((userRole, role, roleMenu, menu) =>
|
||
userRole.UserId == userId &&
|
||
role.Status == AvailabilityStatus.Enable &&
|
||
menu.Status == AvailabilityStatus.Enable &&
|
||
(menu.Type == MenuType.Button || menu.Type == MenuType.DataScope))
|
||
.Select((userRole, role, roleMenu, menu) => new CheckPermissionOutput
|
||
{
|
||
Code = menu.Code,
|
||
Access = true
|
||
})
|
||
.ToListAsync();
|
||
|
||
return list
|
||
.Where(x => !string.IsNullOrWhiteSpace(x.Code))
|
||
.GroupBy(x => x.Code)
|
||
.Select(g => new CheckPermissionOutput
|
||
{
|
||
Code = g.Key,
|
||
Access = true
|
||
})
|
||
.ToList();
|
||
}, TimeSpan.FromDays(1));
|
||
return cache.Value;
|
||
}
|
||
|
||
[NonAction]
|
||
private async Task<List<string>> GetAuthPermissionCodeList(long userId)
|
||
{
|
||
var cacheKey = $"{CacheConst.PermissionKey}code.v2.{userId}";
|
||
var cache = await _easyCachingProvider.GetAsync(cacheKey, async () =>
|
||
{
|
||
var list = await _sysMenuRepository.AsSugarClient()
|
||
.Queryable<SysUserRole>()
|
||
.InnerJoin<SysRole>((userRole, role) => userRole.RoleId == role.Id)
|
||
.InnerJoin<SysRoleMenu>((userRole, role, roleMenu) => role.Id == roleMenu.RoleId)
|
||
.InnerJoin<SysMenu>((userRole, role, roleMenu, menu) => roleMenu.MenuId == menu.Id)
|
||
.Where((userRole, role, roleMenu, menu) =>
|
||
userRole.UserId == userId &&
|
||
role.Status == AvailabilityStatus.Enable &&
|
||
menu.Status == AvailabilityStatus.Enable &&
|
||
!string.IsNullOrEmpty(menu.Code))
|
||
.Select((userRole, role, roleMenu, menu) => menu.Code)
|
||
.ToListAsync();
|
||
|
||
return list
|
||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||
.Distinct()
|
||
.ToList();
|
||
}, TimeSpan.FromDays(1));
|
||
|
||
return cache.Value ?? new List<string>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除菜单中的按钮
|
||
/// </summary>
|
||
/// <param name="menus"></param>
|
||
void RemoveButton(List<SysMenu> menus)
|
||
{
|
||
for (int i = menus.Count - 1; i >= 0; i--)
|
||
{
|
||
if (menus[i].Type == MenuType.Button || menus[i].Type == MenuType.DataScope)
|
||
{
|
||
menus.Remove(menus[i]);
|
||
continue;
|
||
}
|
||
if (menus[i].Children.Any())
|
||
{
|
||
RemoveButton(menus[i].Children);
|
||
}
|
||
}
|
||
}
|
||
}
|