import { App, IMidwayApplication, Scope, ScopeEnum } from '@midwayjs/core';
|
import { ALL, Config, Inject, Provide } from '@midwayjs/core';
|
import { BaseService, CoolCommException } from '@cool-midway/core';
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
import { In, Repository } from 'typeorm';
|
import { BaseSysMenuEntity } from '../../entity/sys/menu';
|
import * as _ from 'lodash';
|
import { BaseSysPermsService } from './perms';
|
import { Context } from '@midwayjs/koa';
|
import { TempDataSource } from './data';
|
// eslint-disable-next-line node/no-unpublished-import
|
import * as ts from 'typescript';
|
import * as fs from 'fs';
|
import * as pathUtil from 'path';
|
import { BaseSysRoleMenuEntity } from '../../entity/sys/role_menu';
|
import { BaseSysUserRoleEntity } from '../../entity/sys/user_role';
|
|
/**
|
* 菜单
|
*/
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Provide()
|
export class BaseSysMenuService extends BaseService {
|
@Inject()
|
ctx: Context;
|
|
@InjectEntityModel(BaseSysMenuEntity)
|
baseSysMenuEntity: Repository<BaseSysMenuEntity>;
|
|
@InjectEntityModel(BaseSysRoleMenuEntity)
|
baseSysRoleMenuEntity: Repository<BaseSysRoleMenuEntity>;
|
|
@Inject()
|
baseSysPermsService: BaseSysPermsService;
|
|
@Config(ALL)
|
config;
|
|
@App()
|
app: IMidwayApplication;
|
|
/**
|
* 获得所有菜单
|
*/
|
async list() {
|
const menus = await this.getMenus(
|
this.ctx.admin.roleIds,
|
this.ctx.admin.username === 'admin'
|
);
|
if (!_.isEmpty(menus)) {
|
menus.forEach((e: any) => {
|
const parentMenu = menus.filter(m => {
|
e.parentId = parseInt(e.parentId);
|
if (e.parentId == m.id) {
|
return m.name;
|
}
|
});
|
if (!_.isEmpty(parentMenu)) {
|
e.parentName = parentMenu[0].name;
|
}
|
});
|
}
|
return menus;
|
}
|
|
/**
|
* 修改之后
|
* @param param
|
*/
|
async modifyAfter(param) {
|
if (param.id) {
|
await this.refreshPerms(param.id);
|
}
|
}
|
|
/**
|
* 根据角色获得权限信息
|
* @param {[]} roleIds 数组
|
*/
|
async getPerms(roleIds) {
|
let perms = [];
|
if (!_.isEmpty(roleIds)) {
|
const find = await this.baseSysMenuEntity.createQueryBuilder('a');
|
if (!roleIds.includes(1)) {
|
find.innerJoinAndSelect(
|
BaseSysRoleMenuEntity,
|
'b',
|
'a.id = b.menuId AND b.roleId in (:...roleIds)',
|
{ roleIds }
|
);
|
}
|
find.where('a.perms is not NULL');
|
const result = await find.getMany();
|
if (result) {
|
result.forEach(d => {
|
if (d.perms) {
|
perms = perms.concat(d.perms.split(','));
|
}
|
});
|
}
|
perms = _.uniq(perms);
|
perms = _.remove(perms, n => {
|
return !_.isEmpty(n);
|
});
|
}
|
return _.uniq(perms);
|
}
|
|
/**
|
* 获得用户菜单信息
|
* @param roleIds
|
* @param isAdmin 是否是超管
|
*/
|
async getMenus(roleIds, isAdmin) {
|
const find = this.baseSysMenuEntity.createQueryBuilder('a');
|
if (!isAdmin) {
|
find.innerJoinAndSelect(
|
BaseSysRoleMenuEntity,
|
'b',
|
'a.id = b.menuId AND b.roleId in (:...roleIds)',
|
{ roleIds }
|
);
|
}
|
find.orderBy('a.orderNum', 'ASC');
|
const list = await find.getMany();
|
return _.uniqBy(list, 'id');
|
}
|
|
/**
|
* 删除
|
* @param ids
|
*/
|
async delete(ids) {
|
let idArr;
|
if (ids instanceof Array) {
|
idArr = ids;
|
} else {
|
idArr = ids.split(',');
|
}
|
for (const id of idArr) {
|
await this.baseSysMenuEntity.delete({ id });
|
await this.delChildMenu(id);
|
}
|
}
|
|
/**
|
* 删除子菜单
|
* @param id
|
*/
|
private async delChildMenu(id) {
|
await this.refreshPerms(id);
|
const delMenu = await this.baseSysMenuEntity.findBy({ parentId: id });
|
if (_.isEmpty(delMenu)) {
|
return;
|
}
|
const delMenuIds = delMenu.map(e => {
|
return e.id;
|
});
|
await this.baseSysMenuEntity.delete(delMenuIds);
|
for (const menuId of delMenuIds) {
|
await this.delChildMenu(menuId);
|
}
|
}
|
|
/**
|
* 更新权限
|
* @param menuId
|
*/
|
async refreshPerms(menuId) {
|
const find = this.baseSysRoleMenuEntity.createQueryBuilder('a');
|
find.leftJoinAndSelect(BaseSysUserRoleEntity, 'b', 'a.roleId = b.roleId');
|
find.where('a.menuId = :menuId', { menuId: menuId });
|
find.select('b.userId', 'userId');
|
const users = await find.getRawMany();
|
// 刷新admin权限
|
await this.baseSysPermsService.refreshPerms(1);
|
if (!_.isEmpty(users)) {
|
// 刷新其他权限
|
for (const user of _.uniqBy(users, 'userId')) {
|
await this.baseSysPermsService.refreshPerms(user.userId);
|
}
|
}
|
}
|
|
/**
|
* 解析实体和Controller
|
* @param entityString
|
* @param controller
|
* @param module
|
*/
|
async parse(entityString: string, controller: string, module: string) {
|
const tempDataSource = new TempDataSource({
|
...this.config.typeorm.dataSource.default,
|
entities: [],
|
});
|
// 连接数据库
|
await tempDataSource.initialize();
|
const { newCode, className, oldTableName } = this.parseCode(entityString);
|
const code = ts.transpile(
|
`${newCode}
|
tempDataSource.options.entities.push(${className})
|
`,
|
{
|
emitDecoratorMetadata: true,
|
module: ts.ModuleKind.CommonJS,
|
target: ts.ScriptTarget.ES2018,
|
removeComments: true,
|
experimentalDecorators: true,
|
noImplicitThis: true,
|
noUnusedLocals: true,
|
stripInternal: true,
|
skipLibCheck: true,
|
pretty: true,
|
declaration: true,
|
noImplicitAny: false,
|
}
|
);
|
eval(code);
|
await tempDataSource.buildMetadatas();
|
const meta = tempDataSource.getMetadata(className);
|
const columnArr = meta.columns;
|
await tempDataSource.destroy();
|
|
const commColums = [];
|
const columns = _.filter(
|
columnArr.map(e => {
|
return {
|
propertyName: e.propertyName,
|
type: typeof e.type == 'string' ? e.type : e.type.name.toLowerCase(),
|
length: e.length,
|
comment: e.comment,
|
nullable: e.isNullable,
|
};
|
}),
|
o => {
|
if (['createTime', 'updateTime'].includes(o.propertyName)) {
|
commColums.push(o);
|
}
|
return o && !['createTime', 'updateTime'].includes(o.propertyName);
|
}
|
).concat(commColums);
|
if (!controller) {
|
const tableNames = oldTableName.split('_');
|
const fileName = tableNames[tableNames.length - 1];
|
return {
|
columns,
|
className: className.replace('TEMP', ''),
|
tableName: oldTableName,
|
fileName,
|
path: `/admin/${module}/${fileName}`,
|
};
|
}
|
const fileName = await this.fileName(controller);
|
return {
|
columns,
|
path: `/admin/${module}/${fileName}`,
|
};
|
}
|
|
/**
|
* 解析Entity类名
|
* @param code
|
* @returns
|
*/
|
parseCode(code: string) {
|
try {
|
const oldClassName = code
|
.match('class(.*)extends')[1]
|
.replace(/\s*/g, '');
|
const oldTableStart = code.indexOf('@Entity(');
|
const oldTableEnd = code.indexOf(')');
|
|
const oldTableName = code
|
.substring(oldTableStart + 9, oldTableEnd - 1)
|
.replace(/\s*/g, '')
|
// eslint-disable-next-line no-useless-escape
|
.replace(/\"/g, '')
|
// eslint-disable-next-line no-useless-escape
|
.replace(/\'/g, '');
|
const className = `${oldClassName}TEMP`;
|
return {
|
newCode: code
|
.replace(oldClassName, className)
|
.replace(oldTableName, `func_${oldTableName}`),
|
className,
|
tableName: `func_${oldTableName}`,
|
oldTableName,
|
};
|
} catch (err) {
|
throw new CoolCommException('代码结构不正确,请检查');
|
}
|
}
|
|
/**
|
* 创建代码
|
* @param body body
|
*/
|
async create(body) {
|
const { module, entity, controller, service, fileName } = body;
|
const basePath = this.app.getBaseDir();
|
const modulePath = pathUtil.join(basePath, '..', 'src', 'modules', module);
|
// 生成Entity
|
const entityPath = pathUtil.join(modulePath, 'entity', `${fileName}.ts`);
|
// 生成Controller
|
const controllerPath = pathUtil.join(
|
modulePath,
|
'controller',
|
'admin',
|
`${fileName}.ts`
|
);
|
// 生成Service
|
const servicePath = pathUtil.join(modulePath, 'service', `${fileName}.ts`);
|
this.createConfigFile(module);
|
this.createFile(entityPath, entity);
|
this.createFile(controllerPath, controller);
|
this.createFile(servicePath, service);
|
}
|
|
/**
|
* 创建配置文件
|
* @param module
|
*/
|
async createConfigFile(module: string) {
|
const basePath = this.app.getBaseDir();
|
const configFilePath = pathUtil.join(
|
basePath,
|
'..',
|
'src',
|
'modules',
|
module,
|
'config.ts'
|
);
|
if (!fs.existsSync(configFilePath)) {
|
const data = `import { ModuleConfig } from '@cool-midway/core';
|
|
/**
|
* 模块配置
|
*/
|
export default () => {
|
return {
|
// 模块名称
|
name: 'xxx',
|
// 模块描述
|
description: 'xxx',
|
// 中间件,只对本模块有效
|
middlewares: [],
|
// 中间件,全局有效
|
globalMiddlewares: [],
|
// 模块加载顺序,默认为0,值越大越优先加载
|
order: 0,
|
} as ModuleConfig;
|
};
|
`;
|
await this.createFile(configFilePath, data);
|
}
|
}
|
|
/**
|
* 找到文件名
|
* @param controller
|
* @returns
|
*/
|
async fileName(controller: string) {
|
const regex = /import\s*{\s*\w+\s*}\s*from\s*'[^']*\/([\w-]+)';/;
|
const match = regex.exec(controller);
|
|
if (match && match.length > 1) {
|
return match[1];
|
}
|
|
return null;
|
}
|
|
/**
|
* 创建文件
|
* @param filePath
|
* @param content
|
*/
|
async createFile(filePath: string, content: string) {
|
const folderPath = pathUtil.dirname(filePath);
|
if (!fs.existsSync(folderPath)) {
|
fs.mkdirSync(folderPath, { recursive: true });
|
}
|
fs.writeFileSync(filePath, content);
|
}
|
|
/**
|
* 导出菜单
|
* @param ids
|
* @returns
|
*/
|
async export(ids: number[]) {
|
const result: any[] = [];
|
const menus = await this.baseSysMenuEntity.findBy({ id: In(ids) });
|
|
// 递归取出子菜单
|
const getChildMenus = (parentId: number): any[] => {
|
const children = _.remove(menus, e => e.parentId == parentId);
|
children.forEach(child => {
|
child.childMenus = getChildMenus(child.id);
|
// 删除不需要的字段
|
delete child.id;
|
delete child.createTime;
|
delete child.updateTime;
|
delete child.parentId;
|
});
|
return children;
|
};
|
|
// lodash取出父级菜单(parentId为 null), 并从menus 删除
|
const parentMenus = _.remove(menus, e => {
|
return e.parentId == null;
|
});
|
|
// 对于每个父级菜单,获取它的子菜单
|
parentMenus.forEach(parent => {
|
parent.childMenus = getChildMenus(parent.id);
|
// 删除不需要的字段
|
delete parent.id;
|
delete parent.createTime;
|
delete parent.updateTime;
|
delete parent.parentId;
|
|
result.push(parent);
|
});
|
|
return result;
|
}
|
|
/**
|
* 导入
|
* @param menus
|
*/
|
async import(menus: any[]) {
|
// 递归保存子菜单
|
const saveChildMenus = async (parentMenu: any, parentId: number | null) => {
|
const children = parentMenu.childMenus || [];
|
for (let child of children) {
|
const childData = { ...child, parentId: parentId }; // 保持与数据库的parentId字段的一致性
|
delete childData.childMenus; // 删除childMenus属性,因为我们不想将它保存到数据库中
|
|
// 保存子菜单并获取其ID,以便为其子菜单设置parentId
|
const savedChild = await this.baseSysMenuEntity.save(childData);
|
|
if (!_.isEmpty(child.childMenus)) {
|
await saveChildMenus(child, savedChild.id);
|
}
|
}
|
};
|
|
for (let menu of menus) {
|
const menuData = { ...menu };
|
delete menuData.childMenus; // 删除childMenus属性,因为我们不想将它保存到数据库中
|
|
// 保存主菜单并获取其ID
|
const savedMenu = await this.baseSysMenuEntity.save(menuData);
|
|
if (menu.childMenus && menu.childMenus.length > 0) {
|
await saveChildMenus(menu, savedMenu.id);
|
}
|
}
|
}
|
}
|