using Mapster; using SqlSugar; using YY.Admin.Core; using YY.Admin.Core.Session; using YY.Admin.Services.Service.Role; using YY.Admin.Services.Service.User; namespace YY.Admin.Services.Service.Menu { public class SysMenuService : ISysMenuService, ISingletonDependency { private readonly ISqlSugarClient _dbContext; private readonly SysUserRoleService _sysUserRoleService; private readonly SysRoleMenuService _sysRoleMenuService; public SysMenuService( ISqlSugarClient context, SysRoleMenuService sysRoleMenuService, SysUserRoleService sysUserRoleService) { _dbContext=context; _sysUserRoleService=sysUserRoleService; _sysRoleMenuService=sysRoleMenuService; } /// /// 获取登录菜单树 /// /// public async Task> GetLoginMenuTree() { var currentUser = AppSession.CurrentUser; if (currentUser == null) { return new List(); } var tenantId = currentUser.TenantId ?? 0; var (query, _) = GetSugarQueryableAndTenantId(tenantId); if (currentUser.IsSuperAdmin || currentUser.IsSysAdmin) { var menuList = await query.Where(u => u.Type != MenuTypeEnum.Btn && u.Status == StatusEnum.Enable) .OrderBy(u => new { u.OrderNo, u.Id }) .ToTreeAsync(u => u.Children, u => u.Pid, 0); return menuList.Adapt>(); } var menuIdList = await GetMenuIdList(); if (menuIdList == null || menuIdList.Count == 0) { // Jeecg自动建档用户可能暂未分配本地角色,这里回退显示基础菜单 var fallbackMenus = await GetFallbackMenuTreeAsync(); return fallbackMenus.Adapt>(); } var menuTree = await query.Where(u => u.Type != MenuTypeEnum.Btn && u.Status == StatusEnum.Enable) .OrderBy(u => new { u.OrderNo, u.Id }).ToTreeAsync(u => u.Children, u => u.Pid, 0, menuIdList.Select(d => (object)d).ToArray()); // 角色或租户菜单未配置时,避免左侧功能列表全空白 if (menuTree == null || menuTree.Count == 0) { menuTree = await GetFallbackMenuTreeAsync(); } return menuTree.Adapt>(); } /// /// 获取当前用户菜单Id集合 /// /// public async Task> GetMenuIdList() { var currentUser = AppSession.CurrentUser; if (currentUser == null) { return new List(); } var roleIdList = await _sysUserRoleService.GetUserRoleIdList(currentUser.Id); return await _sysRoleMenuService.GetRoleMenuIdList(roleIdList); } /// /// 根据租户id获取构建菜单联表查询实例 /// /// /// public (ISugarQueryable query, long tenantId) GetSugarQueryableAndTenantId(long tenantId) { if (!AppSession.CurrentUser!.IsSuperAdmin) tenantId = AppSession.CurrentUser.TenantId!.Value; // 超管用户菜单范围:种子菜单 + 租户id菜单 ISugarQueryable query; if (AppSession.CurrentUser.IsSuperAdmin) { if (tenantId <= 0) { query = _dbContext.Queryable().InnerJoinIF(false, (u, t) => true); } else { // 指定租户的菜单 var menuIds = _dbContext.Queryable().Where(u => u.TenantId == tenantId).ToList(u => u.MenuId) ?? new(); // 种子菜单 //menuIds.AddRange(new SysMenuSeedData().HasData().Select(u => u.Id).ToList()); menuIds = menuIds.Distinct().ToList(); query = _dbContext.Queryable().InnerJoinIF(false, (u, t) => true).Where(u => menuIds.Contains(u.Id)); } } else if (AppSession.CurrentUser.IsSysAdmin) { // 系统管理员直接读取全量启用菜单,不依赖租户菜单关联表 query = _dbContext.Queryable().InnerJoinIF(false, (u, t) => true); } else { query = _dbContext.Queryable().InnerJoinIF(tenantId > 0, (u, t) => t.TenantId == tenantId && u.Id == t.MenuId); } return (query, tenantId); } /// /// 菜单兜底:当角色/租户未完成绑定时返回可用基础菜单,避免界面空白 /// private async Task> GetFallbackMenuTreeAsync() { return await _dbContext.Queryable() .Where(u => u.Type != MenuTypeEnum.Btn && u.Status == StatusEnum.Enable) .OrderBy(u => new { u.OrderNo, u.Id }) .ToTreeAsync(u => u.Children, u => u.Pid, 0); } /// public async Task> GetAllMenusForManageAsync() { return await _dbContext.Queryable() .OrderBy(m => m.OrderNo) .OrderBy(m => m.Id) .ToListAsync(); } /// public async Task<(bool ok, string message, long id)> CreateMenuAsync(SysMenu input) { if (string.IsNullOrWhiteSpace(input.Title)) return (false, "菜单名称不能为空", 0); var all = await GetAllMenusForManageAsync(); if (input.Pid != 0 && all.All(m => m.Id != input.Pid)) return (false, "父级菜单不存在", 0); var id = SnowflakeId.Next(); input.Id = id; input.CreateTime = DateTime.Now; input.UpdateTime = null; input.Children = new List(); var n = await _dbContext.Insertable(input).ExecuteCommandAsync(); if (n <= 0) return (false, "保存失败", 0); if (input.IsDefaultDesktopHome) await ClearDefaultDesktopHomeExceptAsync(input.Id); await TryLinkCurrentTenantMenuAsync(id); return (true, "保存成功", id); } /// public async Task<(bool ok, string message)> UpdateMenuAsync(SysMenu input) { if (input.Id <= 0) return (false, "无效的菜单 Id"); if (string.IsNullOrWhiteSpace(input.Title)) return (false, "菜单名称不能为空"); var all = await GetAllMenusForManageAsync(); var existing = all.FirstOrDefault(m => m.Id == input.Id); if (existing == null) return (false, "菜单不存在"); if (input.Pid != 0 && all.All(m => m.Id != input.Pid)) return (false, "父级菜单不存在"); if (NewParentIsInsideMenuSubtree(input.Id, input.Pid, all)) return (false, "不能将父级设为当前菜单或其子菜单"); if (input.IsDefaultDesktopHome) await ClearDefaultDesktopHomeExceptAsync(input.Id); existing.Pid = input.Pid; existing.Type = input.Type; existing.Name = input.Name; existing.Path = input.Path; existing.Component = input.Component; existing.Redirect = input.Redirect; existing.Permission = input.Permission; existing.Title = input.Title.Trim(); existing.Icon = input.Icon; existing.IsIframe = input.IsIframe; existing.OutLink = input.OutLink; existing.IsHide = input.IsHide; existing.IsKeepAlive = input.IsKeepAlive; existing.IsAffix = input.IsAffix; existing.IsDefaultDesktopHome = input.IsDefaultDesktopHome; existing.OrderNo = input.OrderNo; existing.Status = input.Status; existing.Remark = input.Remark; existing.UpdateTime = DateTime.Now; var n = await _dbContext.Updateable(existing).ExecuteCommandAsync(); if (n <= 0) return (false, "更新失败"); await TryLinkCurrentTenantMenuAsync(input.Id); return (true, "保存成功"); } /// public async Task<(bool ok, string message)> DeleteMenuAsync(long id) { if (id <= 0) return (false, "无效的菜单 Id"); var childCount = await _dbContext.Queryable().Where(m => m.Pid == id).CountAsync(); if (childCount > 0) return (false, "存在子菜单,请先删除子节点"); await _dbContext.Deleteable().Where(r => r.MenuId == id).ExecuteCommandAsync(); await _dbContext.Deleteable().Where(t => t.MenuId == id).ExecuteCommandAsync(); var n = await _dbContext.Deleteable().Where(m => m.Id == id).ExecuteCommandAsync(); return n > 0 ? (true, "已删除") : (false, "删除失败"); } /// /// 保证全表仅一条菜单为桌面默认首页(当前保留项除外全部置 false) /// private async Task ClearDefaultDesktopHomeExceptAsync(long keepMenuId) { await _dbContext.Updateable() .SetColumns(m => new SysMenu { IsDefaultDesktopHome = false }) .Where(m => m.Id != keepMenuId && m.IsDefaultDesktopHome) .ExecuteCommandAsync(); } private async Task TryLinkCurrentTenantMenuAsync(long menuId) { var tenantId = AppSession.CurrentUser?.TenantId; if (tenantId == null || tenantId <= 0) return; var exists = await _dbContext.Queryable() .AnyAsync(t => t.TenantId == tenantId && t.MenuId == menuId); if (exists) return; await _dbContext.Insertable(new SysTenantMenu { Id = SnowflakeId.Next(), TenantId = tenantId.Value, MenuId = menuId }).ExecuteCommandAsync(); } /// /// 判断 newPid 是否位于以 menuId 为根的子树内(含自身),用于防止环状父级 /// private static bool NewParentIsInsideMenuSubtree(long menuId, long newPid, List all) { if (newPid == 0) return false; if (newPid == menuId) return true; var cur = all.FirstOrDefault(x => x.Id == newPid); for (var i = 0; i < 5000 && cur != null; i++) { if (cur.Pid == menuId) return true; if (cur.Pid == 0) return false; cur = all.FirstOrDefault(x => x.Id == cur.Pid); } return false; } } }