using YD_Event.Application.Auth; using YD_Event.Application.Menu.Dtos; using Microsoft.OpenApi; namespace YD_Event.Application.Menu; /// /// 系统菜单管理 /// public class SysMenuService : BaseService, ITransient { private readonly ISqlSugarRepository _sysMenuRepository; private readonly IEasyCachingProvider _easyCachingProvider; private readonly AuthManager _authManager; public SysMenuService(ISqlSugarRepository sysMenuRepository, IEasyCachingProvider easyCachingProvider, AuthManager authManager) : base(sysMenuRepository) { _sysMenuRepository = sysMenuRepository; _easyCachingProvider = easyCachingProvider; _authManager = authManager; } /// /// 菜单列表查询 /// /// /// [DisplayName("菜单列表查询")] [HttpGet] public async Task> 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>(); } var menus = await q1.ToTreeAsync(x => x.Children, x => x.ParentId, null); return menus.Adapt>(); } else { long userId = _authManager.UserId; var q2 = _sysMenuRepository.AsQueryable().InnerJoin((menu, roleMenu) => menu.Id == roleMenu.MenuId) .InnerJoin((menu, roleMenu, role) => roleMenu.RoleId == role.Id) .InnerJoin((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>(); } 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>(); } } /// /// 添加菜单/按钮 /// /// [DisplayName("添加菜单/按钮")] [HttpPost("add")] public async Task AddMenu(AddSysMenuInput dto) { SysMenu sysMenu = dto.Adapt(); 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); } /// /// 修改菜单/按钮 /// /// /// [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 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); } /// /// 根据菜单Id获取系统菜单详情 /// /// /// [DisplayName("系统菜单详情")] [HttpGet] public async Task 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(); } /// /// 菜单下拉树 /// /// [DisplayName("菜单下拉树")] [HttpGet] public async Task> TreeSelect() { // 按当前用户已分配的菜单返回,避免越权看到未授权菜单(如客户端门户菜单) var list = await QueryGrantedMenusAsync(includeDisabled: true); return list.Adapt>(); } /// /// 删除菜单/按钮 /// /// /// [DisplayName("删除菜单/按钮"), HttpDelete("delete")] public override async Task Delete(KeyDto dto) { await base.Delete(dto); await _easyCachingProvider.RemoveByPrefixAsync(CacheConst.PermissionKey); } /// /// 修改菜单/按钮状态 /// /// /// [DisplayName("修改菜单/按钮状态"), HttpPut("setStatus")] public override async Task SetStatus(AvailabilityDto dto) { await base.SetStatus(dto); await _easyCachingProvider.RemoveByPrefixAsync(CacheConst.PermissionKey); } /// /// 获取当前登录用户可用菜单 /// /// [DisplayName("获取当前登录用户可用菜单")] [HttpGet] public async Task> 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 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 menuIdList = await _sysMenuRepository.AsSugarClient().Queryable().InnerJoin((role, userRole) => userRole.RoleId == role.Id) .InnerJoin((role, userRole, roleMenu) => role.Id == roleMenu.RoleId) .InnerJoin((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>(); }, TimeSpan.FromDays(1)); return value.Value ?? new List(); } /// /// 检查当前登录用户是否拥有菜单管理的全量数据权限 /// /// private async Task IsCurrentUserAdminAsync() { return await HasPermissionAsync(PermissionCodeConst.MenuManage); } /// /// 查询当前用户已被授权的菜单树(超级管理员返回全部) /// /// 是否包含已禁用的菜单,供菜单/角色配置页使用 private async Task> QueryGrantedMenusAsync(bool includeDisabled) { if (_authManager.IsSuperAdmin) { return await _sysMenuRepository.AsQueryable() .OrderBy(x => x.Sort) .OrderBy(x => x.Id) .ToTreeAsync(x => x.Children, x => x.ParentId, null); } long userId = _authManager.UserId; return await _sysMenuRepository.AsQueryable() .InnerJoin((menu, roleMenu) => menu.Id == roleMenu.MenuId) .InnerJoin((menu, roleMenu, role) => roleMenu.RoleId == role.Id) .InnerJoin((menu, roleMenu, role, userRole) => role.Id == userRole.RoleId) .Where((menu, roleMenu, role, userRole) => role.Status == AvailabilityStatus.Enable && userRole.UserId == userId) .WhereIF(!includeDisabled, (menu, roleMenu, role, userRole) => menu.Status == AvailabilityStatus.Enable) .OrderBy(menu => menu.Sort) .OrderBy(menu => menu.Id) .Select(menu => menu) .ToTreeAsync(x => x.Children, x => x.ParentId, null); } /// /// 菜单按钮树 - 角色分配菜单时使用 /// /// [DisplayName("菜单按钮树")] [HttpGet] public async Task> TreeMenuButton() { var menus = await QueryGrantedMenusAsync(includeDisabled: false); return menus.Adapt>(); } /// /// 严格权限校验 /// /// /// 与 的区别:权限码未配置时返回 false 而不是放行。 /// 用于数据范围/管理员判断,避免迁移脚本未执行时把全量数据权限误放给所有用户。 /// /// 权限标识 /// [NonAction] public async Task HasPermissionAsync(string code) { if (_authManager.IsSuperAdmin) return true; var authCodes = await GetAuthPermissionCodeList(_authManager.UserId); return authCodes.Any(x => string.Equals(x, code, StringComparison.OrdinalIgnoreCase)); } /// /// 校验权限 /// /// 权限标识 /// [NonAction] public async Task 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)); } /// /// 获取指定用户的访问权限集合 /// /// 系统用户id /// [NonAction] public async Task> GetAuthButtonCodeList(long userId) { var cacheKey = $"{CacheConst.PermissionButtonCodeKey}v2.{userId}"; var cache = await _easyCachingProvider.GetAsync(cacheKey, async () => { var list = await _sysMenuRepository.AsSugarClient() .Queryable() .InnerJoin((userRole, role) => userRole.RoleId == role.Id) .InnerJoin((userRole, role, roleMenu) => role.Id == roleMenu.RoleId) .InnerJoin((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> GetAuthPermissionCodeList(long userId) { var cacheKey = $"{CacheConst.PermissionKey}code.v2.{userId}"; var cache = await _easyCachingProvider.GetAsync(cacheKey, async () => { var list = await _sysMenuRepository.AsSugarClient() .Queryable() .InnerJoin((userRole, role) => userRole.RoleId == role.Id) .InnerJoin((userRole, role, roleMenu) => role.Id == roleMenu.RoleId) .InnerJoin((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(); } /// /// 移除菜单中的按钮 /// /// void RemoveButton(List 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); } } } }