246 lines
7.5 KiB
TypeScript
246 lines
7.5 KiB
TypeScript
import { InjectRedis } from '@liaoliaots/nestjs-redis';
|
||
import { Injectable } from '@nestjs/common';
|
||
import { InjectRepository } from '@nestjs/typeorm';
|
||
import Redis from 'ioredis';
|
||
import { concat, isEmpty, isNumber, uniq } from 'lodash';
|
||
|
||
import { In, IsNull, Like, Not, Repository } from 'typeorm';
|
||
|
||
import { BusinessException } from '~/common/exceptions/biz.exception';
|
||
import { RedisKeys } from '~/constants/cache.constant';
|
||
import { ErrorEnum } from '~/constants/error-code.constant';
|
||
import { genAuthPermKey, genAuthTokenKey } from '~/helper/genRedisKey';
|
||
import { SseService } from '~/modules/sse/sse.service';
|
||
import { MenuEntity } from '~/modules/system/menu/menu.entity';
|
||
|
||
import { deleteEmptyChildren, generatorMenu, generatorRouters } from '~/utils';
|
||
|
||
import { RoleService } from '../role/role.service';
|
||
|
||
import { MenuDto, MenuQueryDto, MenuUpdateDto } from './menu.dto';
|
||
|
||
@Injectable()
|
||
export class MenuService {
|
||
constructor(
|
||
@InjectRedis() private redis: Redis,
|
||
@InjectRepository(MenuEntity)
|
||
private menuRepository: Repository<MenuEntity>,
|
||
private roleService: RoleService,
|
||
private sseService: SseService
|
||
) {}
|
||
|
||
/**
|
||
* 获取所有菜单以及权限
|
||
*/
|
||
async list({ name, path, permission, component, status }: MenuQueryDto): Promise<MenuEntity[]> {
|
||
const menus = await this.menuRepository.find({
|
||
where: {
|
||
...(name && { name: Like(`%${name}%`) }),
|
||
...(path && { path: Like(`%${path}%`) }),
|
||
...(permission && { permission: Like(`%${permission}%`) }),
|
||
...(component && { component: Like(`%${component}%`) }),
|
||
...(isNumber(status) ? { status } : null)
|
||
},
|
||
order: { orderNo: 'ASC' }
|
||
});
|
||
const menuList = generatorMenu(menus);
|
||
|
||
if (!isEmpty(menuList)) {
|
||
deleteEmptyChildren(menuList);
|
||
return menuList;
|
||
}
|
||
// 如果生产树形结构为空,则返回原始菜单列表
|
||
return menus;
|
||
}
|
||
|
||
async create(menu: MenuDto): Promise<void> {
|
||
const result = await this.menuRepository.save(menu);
|
||
this.sseService.noticeClientToUpdateMenusByMenuIds([result.id]);
|
||
}
|
||
|
||
async update(id: number, menu: MenuUpdateDto): Promise<void> {
|
||
await this.menuRepository.update(id, menu);
|
||
this.sseService.noticeClientToUpdateMenusByMenuIds([id]);
|
||
}
|
||
|
||
/**
|
||
* 根据角色获取所有菜单
|
||
*/
|
||
async getMenus(uid: number): Promise<string[]> {
|
||
const roleIds = await this.roleService.getRoleIdsByUser(uid);
|
||
let menus: MenuEntity[] = [];
|
||
|
||
if (isEmpty(roleIds)) return generatorRouters([]);
|
||
|
||
if (this.roleService.hasAdminRole(roleIds)) {
|
||
menus = await this.menuRepository.find({ order: { orderNo: 'ASC' } });
|
||
} else {
|
||
menus = await this.menuRepository
|
||
.createQueryBuilder('menu')
|
||
.innerJoinAndSelect('menu.roles', 'role')
|
||
.andWhere('role.id IN (:...roleIds)', { roleIds })
|
||
.orderBy('menu.order_no', 'ASC')
|
||
.getMany();
|
||
}
|
||
|
||
const menuList = generatorRouters(menus);
|
||
return menuList;
|
||
}
|
||
|
||
/**
|
||
* 检查菜单创建规则是否符合
|
||
*/
|
||
async check(dto: Partial<MenuDto>): Promise<void | never> {
|
||
if (dto.type === 2 && !dto.parentId) {
|
||
// 无法直接创建权限,必须有parent
|
||
throw new BusinessException(ErrorEnum.PERMISSION_REQUIRES_PARENT);
|
||
}
|
||
if (dto.type === 1 && dto.parentId) {
|
||
const parent = await this.getMenuItemInfo(dto.parentId);
|
||
if (isEmpty(parent)) throw new BusinessException(ErrorEnum.PARENT_MENU_NOT_FOUND);
|
||
|
||
if (parent && parent.type === 1) {
|
||
// 当前新增为菜单但父节点也为菜单时为非法操作
|
||
throw new BusinessException(ErrorEnum.ILLEGAL_OPERATION_DIRECTORY_PARENT);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 查找当前菜单下的子菜单,目录以及菜单
|
||
*/
|
||
async findChildMenus(mid: number): Promise<any> {
|
||
const allMenus: any = [];
|
||
const menus = await this.menuRepository.findBy({ parentId: mid });
|
||
// if (_.isEmpty(menus)) {
|
||
// return allMenus;
|
||
// }
|
||
// const childMenus: any = [];
|
||
for (const menu of menus) {
|
||
if (menu.type !== 2) {
|
||
// 子目录下是菜单或目录,继续往下级查找
|
||
const c = await this.findChildMenus(menu.id);
|
||
allMenus.push(c);
|
||
}
|
||
allMenus.push(menu.id);
|
||
}
|
||
return allMenus;
|
||
}
|
||
|
||
/**
|
||
* 获取某个菜单的信息
|
||
* @param mid menu id
|
||
*/
|
||
async getMenuItemInfo(mid: number): Promise<MenuEntity> {
|
||
const menu = await this.menuRepository.findOneBy({ id: mid });
|
||
return menu;
|
||
}
|
||
|
||
/**
|
||
* 获取某个菜单以及关联的父菜单的信息
|
||
*/
|
||
async getMenuItemAndParentInfo(mid: number) {
|
||
const menu = await this.menuRepository.findOneBy({ id: mid });
|
||
let parentMenu: MenuEntity | undefined;
|
||
if (menu && menu.parentId)
|
||
parentMenu = await this.menuRepository.findOneBy({ id: menu.parentId });
|
||
|
||
return { menu, parentMenu };
|
||
}
|
||
|
||
/**
|
||
* 查找节点路由是否存在
|
||
*/
|
||
async findRouterExist(path: string): Promise<boolean> {
|
||
const menus = await this.menuRepository.findOneBy({ path });
|
||
return !isEmpty(menus);
|
||
}
|
||
|
||
/**
|
||
* 获取当前用户的所有权限
|
||
*/
|
||
async getPermissions(uid: number): Promise<string[]> {
|
||
const roleIds = await this.roleService.getRoleIdsByUser(uid);
|
||
let permission: any[] = [];
|
||
let result: any = null;
|
||
if (this.roleService.hasAdminRole(roleIds)) {
|
||
result = await this.menuRepository.findBy({
|
||
permission: Not(IsNull()),
|
||
type: In([1, 2])
|
||
});
|
||
} else {
|
||
if (isEmpty(roleIds)) return permission;
|
||
|
||
result = await this.menuRepository
|
||
.createQueryBuilder('menu')
|
||
.innerJoinAndSelect('menu.roles', 'role')
|
||
.andWhere('role.id IN (:...roleIds)', { roleIds })
|
||
.andWhere('menu.type IN (1,2)')
|
||
.andWhere('menu.permission IS NOT NULL')
|
||
.getMany();
|
||
}
|
||
if (!isEmpty(result)) {
|
||
result.forEach(e => {
|
||
if (e.permission) permission = concat(permission, e.permission.split(','));
|
||
});
|
||
permission = uniq(permission);
|
||
}
|
||
return permission;
|
||
}
|
||
|
||
/**
|
||
* 删除多项菜单
|
||
*/
|
||
async deleteMenuItem(mids: number[]): Promise<void> {
|
||
await this.menuRepository.delete(mids);
|
||
}
|
||
|
||
/**
|
||
* 刷新指定用户ID的权限
|
||
*/
|
||
async refreshPerms(uid: number): Promise<void> {
|
||
const perms = await this.getPermissions(uid);
|
||
const online = await this.redis.get(genAuthTokenKey(uid));
|
||
if (online) {
|
||
// 判断是否在线
|
||
await this.redis.set(genAuthPermKey(uid), JSON.stringify(perms));
|
||
console.log('refreshPerms');
|
||
|
||
this.sseService.noticeClientToUpdateMenusByUserIds([uid]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 刷新所有在线用户的权限
|
||
*/
|
||
async refreshOnlineUserPerms(): Promise<void> {
|
||
const onlineUserIds: string[] = await this.redis.keys(genAuthTokenKey('*'));
|
||
if (onlineUserIds && onlineUserIds.length > 0) {
|
||
const promiseArr = onlineUserIds
|
||
.map(i => Number.parseInt(i.split(RedisKeys.AUTH_TOKEN_PREFIX)[1]))
|
||
.filter(i => i)
|
||
.map(async uid => {
|
||
const perms = await this.getPermissions(uid);
|
||
await this.redis.set(genAuthPermKey(uid), JSON.stringify(perms));
|
||
return uid;
|
||
});
|
||
const uids = await Promise.all(promiseArr);
|
||
console.log('refreshOnlineUserPerms');
|
||
this.sseService.noticeClientToUpdateMenusByUserIds(uids);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 根据菜单ID查找是否有关联角色
|
||
*/
|
||
async checkRoleByMenuId(id: number): Promise<boolean> {
|
||
return !!(await this.menuRepository.findOne({
|
||
where: {
|
||
roles: {
|
||
id
|
||
}
|
||
}
|
||
}));
|
||
}
|
||
}
|