286 lines
11 KiB
C#
286 lines
11 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|