Files
qhmes/yy-admin-master/YY.Admin.Services/Service/Menu/SysMenuService.cs

286 lines
11 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 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;
}
/// <summary>
/// 获取登录菜单树
/// </summary>
/// <returns></returns>
public async Task<List<MenuOutput>> GetLoginMenuTree()
{
var currentUser = AppSession.CurrentUser;
if (currentUser == null)
{
return new List<MenuOutput>();
}
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<List<MenuOutput>>();
}
var menuIdList = await GetMenuIdList();
if (menuIdList == null || menuIdList.Count == 0)
{
// Jeecg自动建档用户可能暂未分配本地角色这里回退显示基础菜单
var fallbackMenus = await GetFallbackMenuTreeAsync();
return fallbackMenus.Adapt<List<MenuOutput>>();
}
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<List<MenuOutput>>();
}
/// <summary>
/// 获取当前用户菜单Id集合
/// </summary>
/// <returns></returns>
public async Task<List<long>> GetMenuIdList()
{
var currentUser = AppSession.CurrentUser;
if (currentUser == null)
{
return new List<long>();
}
var roleIdList = await _sysUserRoleService.GetUserRoleIdList(currentUser.Id);
return await _sysRoleMenuService.GetRoleMenuIdList(roleIdList);
}
/// <summary>
/// 根据租户id获取构建菜单联表查询实例
/// </summary>
/// <param name="tenantId"></param>
/// <returns></returns>
public (ISugarQueryable<SysMenu, SysTenantMenu> query, long tenantId) GetSugarQueryableAndTenantId(long tenantId)
{
if (!AppSession.CurrentUser!.IsSuperAdmin) tenantId = AppSession.CurrentUser.TenantId!.Value;
// 超管用户菜单范围:种子菜单 + 租户id菜单
ISugarQueryable<SysMenu, SysTenantMenu> query;
if (AppSession.CurrentUser.IsSuperAdmin)
{
if (tenantId <= 0)
{
query = _dbContext.Queryable<SysMenu>().InnerJoinIF<SysTenantMenu>(false, (u, t) => true);
}
else
{
// 指定租户的菜单
var menuIds = _dbContext.Queryable<SysTenantMenu>().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<SysMenu>().InnerJoinIF<SysTenantMenu>(false, (u, t) => true).Where(u => menuIds.Contains(u.Id));
}
}
else if (AppSession.CurrentUser.IsSysAdmin)
{
// 系统管理员直接读取全量启用菜单,不依赖租户菜单关联表
query = _dbContext.Queryable<SysMenu>().InnerJoinIF<SysTenantMenu>(false, (u, t) => true);
}
else
{
query = _dbContext.Queryable<SysMenu>().InnerJoinIF<SysTenantMenu>(tenantId > 0, (u, t) => t.TenantId == tenantId && u.Id == t.MenuId);
}
return (query, tenantId);
}
/// <summary>
/// 菜单兜底:当角色/租户未完成绑定时返回可用基础菜单,避免界面空白
/// </summary>
private async Task<List<SysMenu>> GetFallbackMenuTreeAsync()
{
return await _dbContext.Queryable<SysMenu>()
.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);
}
/// <inheritdoc />
public async Task<List<SysMenu>> GetAllMenusForManageAsync()
{
return await _dbContext.Queryable<SysMenu>()
.OrderBy(m => m.OrderNo)
.OrderBy(m => m.Id)
.ToListAsync();
}
/// <inheritdoc />
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<SysMenu>();
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);
}
/// <inheritdoc />
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, "保存成功");
}
/// <inheritdoc />
public async Task<(bool ok, string message)> DeleteMenuAsync(long id)
{
if (id <= 0)
return (false, "无效的菜单 Id");
var childCount = await _dbContext.Queryable<SysMenu>().Where(m => m.Pid == id).CountAsync();
if (childCount > 0)
return (false, "存在子菜单,请先删除子节点");
await _dbContext.Deleteable<SysRoleMenu>().Where(r => r.MenuId == id).ExecuteCommandAsync();
await _dbContext.Deleteable<SysTenantMenu>().Where(t => t.MenuId == id).ExecuteCommandAsync();
var n = await _dbContext.Deleteable<SysMenu>().Where(m => m.Id == id).ExecuteCommandAsync();
return n > 0 ? (true, "已删除") : (false, "删除失败");
}
/// <summary>
/// 保证全表仅一条菜单为桌面默认首页(当前保留项除外全部置 false
/// </summary>
private async Task ClearDefaultDesktopHomeExceptAsync(long keepMenuId)
{
await _dbContext.Updateable<SysMenu>()
.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<SysTenantMenu>()
.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();
}
/// <summary>
/// 判断 newPid 是否位于以 menuId 为根的子树内(含自身),用于防止环状父级
/// </summary>
private static bool NewParentIsInsideMenuSubtree(long menuId, long newPid, List<SysMenu> 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;
}
}
}