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);
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, "不能将父级设为当前菜单或其子菜单");
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.OrderNo = input.OrderNo;
existing.Status = input.Status;
existing.Remark = input.Remark;
existing.UpdateTime = DateTime.Now;
var n = await _dbContext.Updateable(existing).ExecuteCommandAsync();
return n > 0 ? (true, "保存成功") : (false, "更新失败");
}
///
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, "删除失败");
}
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;
}
}
}