2026-06-23 22:09:15 +08:00

427 lines
16 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 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 cache = await GetAuthButtonCodeList(_authManager.UserId);
var output = cache.FirstOrDefault(x => string.Equals(x.Code, code, StringComparison.CurrentCultureIgnoreCase));
return output?.Access ?? false;
}
/// <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;
}
/// <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);
}
}
}
}