mirror of
https://github.com/cool-team-official/cool-admin-midway.git
synced 2025-12-11 16:52:49 +00:00
兼容打包成二进制
This commit is contained in:
parent
fd8aaad60a
commit
fcef095b2a
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "cool-admin-midway",
|
"name": "cool-admin",
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"description": "一个很酷的Ai快速开发框架",
|
"description": "一个很酷的Ai快速开发框架",
|
||||||
"private": true,
|
"private": true,
|
||||||
@ -49,8 +49,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "NODE_ENV=production node ./bootstrap.js",
|
"start": "NODE_ENV=production node ./bootstrap.js",
|
||||||
"entity": "cool entity",
|
"dev": "rimraf src/index.ts && cool check entity && cross-env NODE_ENV=local mwtsc --cleanOutDir --watch --run @midwayjs/mock/app.js",
|
||||||
"dev": "cool check entity && cross-env NODE_ENV=local mwtsc --cleanOutDir --watch --run @midwayjs/mock/app.js",
|
|
||||||
"test": "cross-env NODE_ENV=unittest jest",
|
"test": "cross-env NODE_ENV=unittest jest",
|
||||||
"cov": "jest --coverage",
|
"cov": "jest --coverage",
|
||||||
"lint": "mwts check",
|
"lint": "mwts check",
|
||||||
@ -66,7 +65,9 @@
|
|||||||
"pkg": {
|
"pkg": {
|
||||||
"scripts": "dist/**/*",
|
"scripts": "dist/**/*",
|
||||||
"assets": [
|
"assets": [
|
||||||
"public/**/*"
|
"public/**/*",
|
||||||
|
"typings/**/*",
|
||||||
|
"cool/**/*"
|
||||||
],
|
],
|
||||||
"targets": [
|
"targets": [
|
||||||
"node18-macos-x64",
|
"node18-macos-x64",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
import { Context } from '@midwayjs/koa';
|
import { Context } from '@midwayjs/koa';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { LocationUtil } from '@cool-midway/core';
|
import * as path from 'path';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 帮助类
|
* 帮助类
|
||||||
@ -12,8 +12,20 @@ export class Utils {
|
|||||||
@Inject()
|
@Inject()
|
||||||
baseDir;
|
baseDir;
|
||||||
|
|
||||||
@Inject()
|
/**
|
||||||
locationUtil: LocationUtil;
|
* 获得dist路径
|
||||||
|
*/
|
||||||
|
getDistPath() {
|
||||||
|
const runPath = __dirname;
|
||||||
|
const distIndex =
|
||||||
|
runPath.lastIndexOf('/dist/') !== -1
|
||||||
|
? runPath.lastIndexOf('/dist/')
|
||||||
|
: runPath.lastIndexOf('\\dist\\');
|
||||||
|
if (distIndex !== -1) {
|
||||||
|
return path.join(runPath.substring(0, distIndex), 'dist');
|
||||||
|
}
|
||||||
|
return path.join(runPath, 'dist');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得请求IP
|
* 获得请求IP
|
||||||
|
|||||||
@ -2,12 +2,14 @@ import { CoolConfig } from '@cool-midway/core';
|
|||||||
import { MidwayConfig } from '@midwayjs/core';
|
import { MidwayConfig } from '@midwayjs/core';
|
||||||
import { CoolCacheStore } from '@cool-midway/core';
|
import { CoolCacheStore } from '@cool-midway/core';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { getUploadDir } from '../modules/plugin/hooks/upload';
|
||||||
|
|
||||||
// redis缓存
|
// redis缓存
|
||||||
// import { redisStore } from 'cache-manager-ioredis-yet';
|
// import { redisStore } from 'cache-manager-ioredis-yet';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// use for cookie sign key, should change to your own and keep security
|
// use for cookie sign key, should change to your own and keep security
|
||||||
keys: '673dcd50-f95d-4109-b69d-aa80df64098e',
|
keys: '576848ea-bb0c-4c0c-ac95-c8602ef908b5',
|
||||||
koa: {
|
koa: {
|
||||||
port: 8001,
|
port: 8001,
|
||||||
},
|
},
|
||||||
@ -19,6 +21,10 @@ export default {
|
|||||||
prefix: '/public',
|
prefix: '/public',
|
||||||
dir: path.join(__dirname, '..', '..', 'public'),
|
dir: path.join(__dirname, '..', '..', 'public'),
|
||||||
},
|
},
|
||||||
|
static: {
|
||||||
|
prefix: '/upload',
|
||||||
|
dir: getUploadDir(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 文件上传
|
// 文件上传
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import * as LocalConfig from './config/config.local';
|
|||||||
import * as ProdConfig from './config/config.prod';
|
import * as ProdConfig from './config/config.prod';
|
||||||
import * as cool from '@cool-midway/core';
|
import * as cool from '@cool-midway/core';
|
||||||
import * as upload from '@midwayjs/upload';
|
import * as upload from '@midwayjs/upload';
|
||||||
|
import * as os from 'os';
|
||||||
|
|
||||||
@Configuration({
|
@Configuration({
|
||||||
imports: [
|
imports: [
|
||||||
@ -60,15 +61,5 @@ export class MainConfiguration {
|
|||||||
@Inject()
|
@Inject()
|
||||||
logger: ILogger;
|
logger: ILogger;
|
||||||
|
|
||||||
async onReady() {
|
async onReady() {}
|
||||||
this.webRouterService.addRouter(
|
|
||||||
async ctx => {
|
|
||||||
ctx.redirect('/public/index.html');
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/',
|
|
||||||
requestMethod: 'GET',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
// 自动生成的文件,请勿手动修改
|
// 自动生成的文件,请勿手动修改
|
||||||
import * as entity0 from './modules/base/entity/sys/user_role';
|
import * as entity0 from './modules/plugin/entity/info';
|
||||||
import * as entity1 from './modules/base/entity/sys/user';
|
import * as entity1 from './modules/base/entity/sys/user_role';
|
||||||
import * as entity2 from './modules/base/entity/sys/role_menu';
|
import * as entity2 from './modules/base/entity/sys/user';
|
||||||
import * as entity3 from './modules/base/entity/sys/role_department';
|
import * as entity3 from './modules/base/entity/sys/role_menu';
|
||||||
import * as entity4 from './modules/base/entity/sys/role';
|
import * as entity4 from './modules/base/entity/sys/role_department';
|
||||||
import * as entity5 from './modules/base/entity/sys/param';
|
import * as entity5 from './modules/base/entity/sys/role';
|
||||||
import * as entity6 from './modules/base/entity/sys/menu';
|
import * as entity6 from './modules/base/entity/sys/param';
|
||||||
import * as entity7 from './modules/base/entity/sys/log';
|
import * as entity7 from './modules/base/entity/sys/menu';
|
||||||
import * as entity8 from './modules/base/entity/sys/department';
|
import * as entity8 from './modules/base/entity/sys/log';
|
||||||
import * as entity9 from './modules/base/entity/sys/conf';
|
import * as entity9 from './modules/base/entity/sys/department';
|
||||||
|
import * as entity10 from './modules/base/entity/sys/conf';
|
||||||
export const entities = [
|
export const entities = [
|
||||||
...Object.values(entity0),
|
...Object.values(entity0),
|
||||||
...Object.values(entity1),
|
...Object.values(entity1),
|
||||||
@ -20,4 +21,5 @@ export const entities = [
|
|||||||
...Object.values(entity7),
|
...Object.values(entity7),
|
||||||
...Object.values(entity8),
|
...Object.values(entity8),
|
||||||
...Object.values(entity9),
|
...Object.values(entity9),
|
||||||
|
...Object.values(entity10),
|
||||||
];
|
];
|
||||||
|
|||||||
13
src/index.ts
13
src/index.ts
@ -3,6 +3,7 @@
|
|||||||
export * from './comm/utils';
|
export * from './comm/utils';
|
||||||
export * from './config/config.default';
|
export * from './config/config.default';
|
||||||
export * from './config/config.local';
|
export * from './config/config.local';
|
||||||
|
export * from './modules/plugin/entity/info';
|
||||||
export * from './modules/base/entity/sys/user_role';
|
export * from './modules/base/entity/sys/user_role';
|
||||||
export * from './modules/base/entity/sys/user';
|
export * from './modules/base/entity/sys/user';
|
||||||
export * from './modules/base/entity/sys/role_menu';
|
export * from './modules/base/entity/sys/role_menu';
|
||||||
@ -21,6 +22,11 @@ export * from './modules/base/service/sys/log';
|
|||||||
export * from './modules/base/middleware/log';
|
export * from './modules/base/middleware/log';
|
||||||
export * from './modules/base/middleware/authority';
|
export * from './modules/base/middleware/authority';
|
||||||
export * from './modules/base/config';
|
export * from './modules/base/config';
|
||||||
|
export * from './modules/plugin/interface';
|
||||||
|
export * from './modules/plugin/service/center';
|
||||||
|
export * from './modules/plugin/event/init';
|
||||||
|
export * from './modules/plugin/service/types';
|
||||||
|
export * from './modules/plugin/service/info';
|
||||||
export * from './modules/base/dto/login';
|
export * from './modules/base/dto/login';
|
||||||
export * from './modules/base/service/sys/data';
|
export * from './modules/base/service/sys/data';
|
||||||
export * from './modules/base/service/sys/menu';
|
export * from './modules/base/service/sys/menu';
|
||||||
@ -39,6 +45,11 @@ export * from './modules/base/controller/admin/sys/param';
|
|||||||
export * from './modules/base/controller/admin/sys/role';
|
export * from './modules/base/controller/admin/sys/role';
|
||||||
export * from './modules/base/controller/admin/sys/user';
|
export * from './modules/base/controller/admin/sys/user';
|
||||||
export * from './modules/base/controller/app/comm';
|
export * from './modules/base/controller/app/comm';
|
||||||
export * from './modules/base/event/app';
|
|
||||||
export * from './modules/base/event/menu';
|
export * from './modules/base/event/menu';
|
||||||
export * from './modules/base/job/log';
|
export * from './modules/base/job/log';
|
||||||
|
export * from './modules/plugin/config';
|
||||||
|
export * from './modules/plugin/controller/admin/info';
|
||||||
|
export * from './modules/plugin/event/app';
|
||||||
|
export * from './modules/plugin/hooks/base';
|
||||||
|
export * from './modules/plugin/hooks/upload/interface';
|
||||||
|
export * from './modules/plugin/hooks/upload/index';
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
} from '@cool-midway/core';
|
} from '@cool-midway/core';
|
||||||
import { ALL, Body, Get, Inject, Post, Provide } from '@midwayjs/core';
|
import { ALL, Body, Get, Inject, Post, Provide } from '@midwayjs/core';
|
||||||
import { Context } from '@midwayjs/koa';
|
import { Context } from '@midwayjs/koa';
|
||||||
// import { PluginService } from '../../../plugin/service/info';
|
import { PluginService } from '../../../plugin/service/info';
|
||||||
import { BaseSysUserEntity } from '../../entity/sys/user';
|
import { BaseSysUserEntity } from '../../entity/sys/user';
|
||||||
import { BaseSysLoginService } from '../../service/sys/login';
|
import { BaseSysLoginService } from '../../service/sys/login';
|
||||||
import { BaseSysPermsService } from '../../service/sys/perms';
|
import { BaseSysPermsService } from '../../service/sys/perms';
|
||||||
@ -32,8 +32,8 @@ export class BaseCommController extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
ctx: Context;
|
ctx: Context;
|
||||||
|
|
||||||
// @Inject()
|
@Inject()
|
||||||
// pluginService: PluginService;
|
pluginService: PluginService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得个人信息
|
* 获得个人信息
|
||||||
@ -69,8 +69,8 @@ export class BaseCommController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@Post('/upload', { summary: '文件上传' })
|
@Post('/upload', { summary: '文件上传' })
|
||||||
async upload() {
|
async upload() {
|
||||||
// const file = await this.pluginService.getInstance('upload');
|
const file = await this.pluginService.getInstance('upload');
|
||||||
// return this.ok(await file.upload(this.ctx));
|
return this.ok(await file.upload(this.ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,8 +78,8 @@ export class BaseCommController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@Get('/uploadMode', { summary: '文件上传模式' })
|
@Get('/uploadMode', { summary: '文件上传模式' })
|
||||||
async uploadMode() {
|
async uploadMode() {
|
||||||
// const file = await this.pluginService.getInstance('upload');
|
const file = await this.pluginService.getInstance('upload');
|
||||||
// return this.ok(await file.getMode());
|
return this.ok(await file.getMode());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
} from '@cool-midway/core';
|
} from '@cool-midway/core';
|
||||||
import { Context } from '@midwayjs/koa';
|
import { Context } from '@midwayjs/koa';
|
||||||
import { BaseSysParamService } from '../../service/sys/param';
|
import { BaseSysParamService } from '../../service/sys/param';
|
||||||
// import { PluginService } from '../../../plugin/service/info';
|
import { PluginService } from '../../../plugin/service/info';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 不需要登录的后台接口
|
* 不需要登录的后台接口
|
||||||
@ -18,8 +18,8 @@ import { BaseSysParamService } from '../../service/sys/param';
|
|||||||
@Provide()
|
@Provide()
|
||||||
@CoolController()
|
@CoolController()
|
||||||
export class BaseAppCommController extends BaseController {
|
export class BaseAppCommController extends BaseController {
|
||||||
// @Inject()
|
@Inject()
|
||||||
// pluginService: PluginService;
|
pluginService: PluginService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
ctx: Context;
|
ctx: Context;
|
||||||
@ -57,8 +57,8 @@ export class BaseAppCommController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@Post('/upload', { summary: '文件上传' })
|
@Post('/upload', { summary: '文件上传' })
|
||||||
async upload() {
|
async upload() {
|
||||||
// const file = await this.pluginService.getInstance('upload');
|
const file = await this.pluginService.getInstance('upload');
|
||||||
// return this.ok(await file.upload(this.ctx));
|
return this.ok(await file.upload(this.ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,7 +66,7 @@ export class BaseAppCommController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@Get('/uploadMode', { summary: '文件上传模式' })
|
@Get('/uploadMode', { summary: '文件上传模式' })
|
||||||
async uploadMode() {
|
async uploadMode() {
|
||||||
// const file = await this.pluginService.getInstance('upload');
|
const file = await this.pluginService.getInstance('upload');
|
||||||
// return this.ok(await file.getMode());
|
return this.ok(await file.getMode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,102 +0,0 @@
|
|||||||
import { CoolEvent, Event } from '@cool-midway/core';
|
|
||||||
import { App, Config, ILogger, Logger } from '@midwayjs/core';
|
|
||||||
import { IMidwayKoaApplication } from '@midwayjs/koa';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
import { v1 as uuid } from 'uuid';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改jwt.secret
|
|
||||||
*/
|
|
||||||
@CoolEvent()
|
|
||||||
export class BaseAppEvent {
|
|
||||||
@Logger()
|
|
||||||
coreLogger: ILogger;
|
|
||||||
|
|
||||||
@Config('module')
|
|
||||||
config;
|
|
||||||
|
|
||||||
@Config('keys')
|
|
||||||
configKeys;
|
|
||||||
|
|
||||||
@Config('koa.port')
|
|
||||||
port;
|
|
||||||
|
|
||||||
@App()
|
|
||||||
app: IMidwayKoaApplication;
|
|
||||||
|
|
||||||
@Event('onMenuInit')
|
|
||||||
async onMenuInit() {
|
|
||||||
if (this.app.getEnv() != 'local') return;
|
|
||||||
this.checkConfig();
|
|
||||||
this.checkKeys();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Event('onServerReady')
|
|
||||||
async onServerReady() {
|
|
||||||
this.coreLogger.info(`服务启动成功,端口:${this.port}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查配置
|
|
||||||
*/
|
|
||||||
async checkConfig() {
|
|
||||||
if (this.config.base.jwt.secret == 'cool-admin-xxxxxx') {
|
|
||||||
this.coreLogger.warn(
|
|
||||||
'\x1B[36m 检测到模块[base] jwt.secret 配置是默认值,请不要关闭!即将自动修改... \x1B[0m'
|
|
||||||
);
|
|
||||||
setTimeout(() => {
|
|
||||||
const filePath = path.join(
|
|
||||||
this.app.getBaseDir(),
|
|
||||||
'..',
|
|
||||||
'src',
|
|
||||||
'modules',
|
|
||||||
'base',
|
|
||||||
'config.ts'
|
|
||||||
);
|
|
||||||
// 替换文件内容
|
|
||||||
let fileData = fs.readFileSync(filePath, 'utf8');
|
|
||||||
const secret = uuid().replace(/-/g, '');
|
|
||||||
this.config.base.jwt.secret = secret;
|
|
||||||
fs.writeFileSync(
|
|
||||||
filePath,
|
|
||||||
fileData.replace('cool-admin-xxxxxx', secret)
|
|
||||||
);
|
|
||||||
this.coreLogger.info(
|
|
||||||
'\x1B[36m [cool:module:base] midwayjs cool module base auto modify jwt.secret\x1B[0m'
|
|
||||||
);
|
|
||||||
}, 6000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查keys
|
|
||||||
*/
|
|
||||||
async checkKeys() {
|
|
||||||
if (this.configKeys == 'cool-admin-keys-xxxxxx') {
|
|
||||||
this.coreLogger.warn(
|
|
||||||
'\x1B[36m 检测到基础配置[Keys] 是默认值,请不要关闭!即将自动修改... \x1B[0m'
|
|
||||||
);
|
|
||||||
setTimeout(() => {
|
|
||||||
const filePath = path.join(
|
|
||||||
this.app.getBaseDir(),
|
|
||||||
'..',
|
|
||||||
'src',
|
|
||||||
'config',
|
|
||||||
'config.default.ts'
|
|
||||||
);
|
|
||||||
// 替换文件内容
|
|
||||||
let fileData = fs.readFileSync(filePath, 'utf8');
|
|
||||||
const secret = uuid().replace(/-/g, '');
|
|
||||||
this.config.base.jwt.secret = secret;
|
|
||||||
fs.writeFileSync(
|
|
||||||
filePath,
|
|
||||||
fileData.replace('cool-admin-keys-xxxxxx', secret)
|
|
||||||
);
|
|
||||||
this.coreLogger.info(
|
|
||||||
'\x1B[36m [cool:module:base] midwayjs cool keys auto modify \x1B[0m'
|
|
||||||
);
|
|
||||||
}, 6000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
27
src/modules/plugin/config.ts
Normal file
27
src/modules/plugin/config.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { ModuleConfig } from '@cool-midway/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模块配置
|
||||||
|
*/
|
||||||
|
export default options => {
|
||||||
|
return {
|
||||||
|
// 模块名称
|
||||||
|
name: '插件模块',
|
||||||
|
// 模块描述
|
||||||
|
description: '插件查看、安装、卸载、配置等',
|
||||||
|
// 中间件,只对本模块有效
|
||||||
|
middlewares: [],
|
||||||
|
// 中间件,全局有效
|
||||||
|
globalMiddlewares: [],
|
||||||
|
// 模块加载顺序,默认为0,值越大越优先加载
|
||||||
|
order: 0,
|
||||||
|
// 基础插件配置
|
||||||
|
hooks: {
|
||||||
|
// 文件上传
|
||||||
|
upload: {
|
||||||
|
// 地址前缀
|
||||||
|
domain: `http://127.0.0.1:${options?.app?.getConfig('koa.port')}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ModuleConfig;
|
||||||
|
};
|
||||||
56
src/modules/plugin/controller/admin/info.ts
Normal file
56
src/modules/plugin/controller/admin/info.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import {
|
||||||
|
CoolController,
|
||||||
|
BaseController,
|
||||||
|
CoolTag,
|
||||||
|
CoolUrlTag,
|
||||||
|
TagTypes,
|
||||||
|
} from '@cool-midway/core';
|
||||||
|
import { PluginInfoEntity } from '../../entity/info';
|
||||||
|
import { Body, Fields, Files, Inject, Post } from '@midwayjs/core';
|
||||||
|
import { PluginService } from '../../service/info';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件信息
|
||||||
|
*/
|
||||||
|
@CoolUrlTag({
|
||||||
|
key: TagTypes.IGNORE_TOKEN,
|
||||||
|
value: [],
|
||||||
|
})
|
||||||
|
@CoolController({
|
||||||
|
api: ['add', 'delete', 'update', 'info', 'list', 'page'],
|
||||||
|
entity: PluginInfoEntity,
|
||||||
|
service: PluginService,
|
||||||
|
pageQueryOp: {
|
||||||
|
select: [
|
||||||
|
'a.id',
|
||||||
|
'a.name',
|
||||||
|
'a.keyName',
|
||||||
|
'a.hook',
|
||||||
|
'a.version',
|
||||||
|
'a.status',
|
||||||
|
'a.readme',
|
||||||
|
'a.author',
|
||||||
|
'a.logo',
|
||||||
|
'a.description',
|
||||||
|
'a.pluginJson',
|
||||||
|
'a.config',
|
||||||
|
'a.createTime',
|
||||||
|
'a.updateTime',
|
||||||
|
],
|
||||||
|
addOrderBy: {
|
||||||
|
id: 'DESC',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class AdminPluginInfoController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
pluginService: PluginService;
|
||||||
|
|
||||||
|
@CoolTag(TagTypes.IGNORE_TOKEN)
|
||||||
|
@Post('/install', { summary: '安装插件' })
|
||||||
|
async install(@Files() files, @Fields() fields) {
|
||||||
|
return this.ok(
|
||||||
|
await this.pluginService.install(files[0].data, fields.force)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/modules/plugin/entity/info.ts
Normal file
44
src/modules/plugin/entity/info.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { BaseEntity } from '@cool-midway/core';
|
||||||
|
import { Column, Entity, DataSource, Index } from 'typeorm';
|
||||||
|
|
||||||
|
console.log(DataSource);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件信息
|
||||||
|
*/
|
||||||
|
@Entity('plugin_info')
|
||||||
|
export class PluginInfoEntity extends BaseEntity {
|
||||||
|
@Column({ comment: '名称' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ comment: '简介' })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ comment: 'Key名' })
|
||||||
|
keyName: string;
|
||||||
|
|
||||||
|
@Column({ comment: 'Hook' })
|
||||||
|
hook: string;
|
||||||
|
|
||||||
|
@Column({ comment: '描述', type: 'text' })
|
||||||
|
readme: string;
|
||||||
|
|
||||||
|
@Column({ comment: '版本' })
|
||||||
|
version: string;
|
||||||
|
|
||||||
|
@Column({ comment: 'Logo(base64)', type: 'text', nullable: true })
|
||||||
|
logo: string;
|
||||||
|
|
||||||
|
@Column({ comment: '作者' })
|
||||||
|
author: string;
|
||||||
|
|
||||||
|
@Column({ comment: '状态 0-禁用 1-启用', default: 0 })
|
||||||
|
status: number;
|
||||||
|
|
||||||
|
@Column({ comment: '插件的plugin.json', type: 'json', nullable: true })
|
||||||
|
pluginJson: any;
|
||||||
|
|
||||||
|
@Column({ comment: '配置', type: 'json', nullable: true })
|
||||||
|
config: any;
|
||||||
|
}
|
||||||
44
src/modules/plugin/event/app.ts
Normal file
44
src/modules/plugin/event/app.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { CoolEvent, Event } from '@cool-midway/core';
|
||||||
|
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
|
||||||
|
import {
|
||||||
|
App,
|
||||||
|
Config,
|
||||||
|
ILogger,
|
||||||
|
Inject,
|
||||||
|
InjectClient,
|
||||||
|
Logger,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
import { IMidwayKoaApplication } from '@midwayjs/koa';
|
||||||
|
import { PLUGIN_CACHE_KEY, PluginCenterService } from '../service/center';
|
||||||
|
import { PluginTypesService } from '../service/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件事件
|
||||||
|
*/
|
||||||
|
@CoolEvent()
|
||||||
|
export class PluginAppEvent {
|
||||||
|
@Logger()
|
||||||
|
coreLogger: ILogger;
|
||||||
|
|
||||||
|
@Config('module')
|
||||||
|
config;
|
||||||
|
|
||||||
|
@App()
|
||||||
|
app: IMidwayKoaApplication;
|
||||||
|
|
||||||
|
@InjectClient(CachingFactory, 'default')
|
||||||
|
midwayCache: MidwayCache;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
pluginCenterService: PluginCenterService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
pluginTypesService: PluginTypesService;
|
||||||
|
|
||||||
|
@Event('onServerReady')
|
||||||
|
async onServerReady() {
|
||||||
|
await this.midwayCache.set(PLUGIN_CACHE_KEY, []);
|
||||||
|
this.pluginCenterService.init();
|
||||||
|
// this.pluginTypesService.reGenerate();
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/modules/plugin/event/init.ts
Normal file
36
src/modules/plugin/event/init.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { CoolEvent, Event } from '@cool-midway/core';
|
||||||
|
import { Inject } from '@midwayjs/core';
|
||||||
|
import { PluginCenterService } from '../service/center';
|
||||||
|
|
||||||
|
// 插件初始化全局事件
|
||||||
|
export const GLOBAL_EVENT_PLUGIN_INIT = 'globalPluginInit';
|
||||||
|
// 插件移除全局事件
|
||||||
|
export const GLOBAL_EVENT_PLUGIN_REMOVE = 'globalPluginRemove';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收事件
|
||||||
|
*/
|
||||||
|
@CoolEvent()
|
||||||
|
export class PluginInitEvent {
|
||||||
|
@Inject()
|
||||||
|
pluginCenterService: PluginCenterService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件初始化事件,某个插件重新初始化
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
@Event(GLOBAL_EVENT_PLUGIN_INIT)
|
||||||
|
async globalPluginInit(key: string) {
|
||||||
|
await this.pluginCenterService.initOne(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件移除或者关闭事件
|
||||||
|
* @param key
|
||||||
|
* @param isHook
|
||||||
|
*/
|
||||||
|
@Event(GLOBAL_EVENT_PLUGIN_REMOVE)
|
||||||
|
async globalPluginRemove(key: string, isHook: boolean) {
|
||||||
|
await this.pluginCenterService.remove(key, isHook);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/modules/plugin/hooks/base.ts
Normal file
26
src/modules/plugin/hooks/base.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { IMidwayContext, IMidwayApplication } from '@midwayjs/core';
|
||||||
|
import { PluginInfo } from '../interface';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hook基类
|
||||||
|
*/
|
||||||
|
export class BasePluginHook {
|
||||||
|
/** 请求上下文,用到此项无法本地调试,需安装到cool-admin中才能调试 */
|
||||||
|
ctx: IMidwayContext;
|
||||||
|
/** 应用实例,用到此项无法本地调试,需安装到cool-admin中才能调试 */
|
||||||
|
app: IMidwayApplication;
|
||||||
|
/** 插件信息 */
|
||||||
|
pluginInfo: PluginInfo;
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
async init(
|
||||||
|
pluginInfo: PluginInfo,
|
||||||
|
ctx?: IMidwayContext,
|
||||||
|
app?: IMidwayApplication
|
||||||
|
) {
|
||||||
|
this.pluginInfo = pluginInfo;
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/modules/plugin/hooks/upload/index.ts
Normal file
120
src/modules/plugin/hooks/upload/index.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { BaseUpload, MODETYPE } from './interface';
|
||||||
|
import { BasePluginHook } from '../base';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { v1 as uuid } from 'uuid';
|
||||||
|
import { CoolCommException } from '@cool-midway/core';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import * as os from 'os';
|
||||||
|
import * as pkg from '../../../../../package.json';
|
||||||
|
|
||||||
|
// 获得上传目录
|
||||||
|
export const getUploadDir = () => {
|
||||||
|
const uploadDir = path.join(os.homedir(), `.${pkg.name}`, 'upload');
|
||||||
|
if (!fs.existsSync(uploadDir)) {
|
||||||
|
fs.mkdirSync(uploadDir, { recursive: true });
|
||||||
|
}
|
||||||
|
return uploadDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件上传
|
||||||
|
*/
|
||||||
|
export class CoolPlugin extends BasePluginHook implements BaseUpload {
|
||||||
|
/**
|
||||||
|
* 获得上传模式
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getMode() {
|
||||||
|
return {
|
||||||
|
mode: MODETYPE.LOCAL,
|
||||||
|
type: MODETYPE.LOCAL,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得原始操作对象
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getMetaFileObj() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载并上传
|
||||||
|
* @param url
|
||||||
|
* @param fileName
|
||||||
|
*/
|
||||||
|
async downAndUpload(url: string, fileName?: string) {
|
||||||
|
const { domain } = this.pluginInfo.config;
|
||||||
|
// 从url获取扩展名
|
||||||
|
const extend = path.extname(url);
|
||||||
|
const download = require('download');
|
||||||
|
// 数据
|
||||||
|
const data = url.includes('http')
|
||||||
|
? await download(url)
|
||||||
|
: fs.readFileSync(url);
|
||||||
|
// 创建文件夹
|
||||||
|
const dirPath = path.join(getUploadDir(), `${moment().format('YYYYMMDD')}`);
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
const uuidStr = uuid();
|
||||||
|
const name = `${moment().format('YYYYMMDD')}/${
|
||||||
|
fileName ? fileName : uuidStr + extend
|
||||||
|
}`;
|
||||||
|
fs.writeFileSync(
|
||||||
|
`${dirPath}/${fileName ? fileName : uuid() + extend}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return `${domain}/upload/${name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定Key(路径)上传,本地文件上传到存储服务
|
||||||
|
* @param filePath 文件路径
|
||||||
|
* @param key 路径一致会覆盖源文件
|
||||||
|
*/
|
||||||
|
async uploadWithKey(filePath: any, key: any) {
|
||||||
|
const { domain } = this.pluginInfo.config;
|
||||||
|
const data = fs.readFileSync(filePath);
|
||||||
|
fs.writeFileSync(path.join(this.app.getBaseDir(), '..', key), data);
|
||||||
|
return domain + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件
|
||||||
|
* @param ctx
|
||||||
|
* @param key 文件路径
|
||||||
|
*/
|
||||||
|
async upload(ctx: any) {
|
||||||
|
const { domain } = this.pluginInfo.config;
|
||||||
|
try {
|
||||||
|
const { key } = ctx.fields;
|
||||||
|
if (_.isEmpty(ctx.files)) {
|
||||||
|
throw new CoolCommException('上传文件为空');
|
||||||
|
}
|
||||||
|
const basePath = getUploadDir();
|
||||||
|
|
||||||
|
const file = ctx.files[0];
|
||||||
|
const extension = file.filename.split('.').pop();
|
||||||
|
const name =
|
||||||
|
moment().format('YYYYMMDD') + '/' + (key || `${uuid()}.${extension}`);
|
||||||
|
const target = path.join(basePath, name);
|
||||||
|
const dirPath = path.join(basePath, moment().format('YYYYMMDD'));
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
fs.mkdirSync(dirPath);
|
||||||
|
}
|
||||||
|
const data = fs.readFileSync(file.data);
|
||||||
|
fs.writeFileSync(target, data);
|
||||||
|
return domain + '/upload/' + name;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
throw new CoolCommException('上传失败' + err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出插件实例, Plugin名称不可修改
|
||||||
|
export const Plugin = CoolPlugin;
|
||||||
56
src/modules/plugin/hooks/upload/interface.ts
Normal file
56
src/modules/plugin/hooks/upload/interface.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// 模式
|
||||||
|
export enum MODETYPE {
|
||||||
|
// 本地
|
||||||
|
LOCAL = 'local',
|
||||||
|
// 云存储
|
||||||
|
CLOUD = 'cloud',
|
||||||
|
// 其他
|
||||||
|
OTHER = 'other',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传模式
|
||||||
|
*/
|
||||||
|
export interface Mode {
|
||||||
|
// 模式
|
||||||
|
mode: MODETYPE;
|
||||||
|
// 类型
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件上传
|
||||||
|
*/
|
||||||
|
export interface BaseUpload {
|
||||||
|
/**
|
||||||
|
* 获得上传模式
|
||||||
|
*/
|
||||||
|
getMode(): Promise<Mode>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得原始操作对象
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getMetaFileObj(): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载并上传
|
||||||
|
* @param url
|
||||||
|
* @param fileName 文件名
|
||||||
|
*/
|
||||||
|
downAndUpload(url: string, fileName?: string): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定Key(路径)上传,本地文件上传到存储服务
|
||||||
|
* @param filePath 文件路径
|
||||||
|
* @param key 路径一致会覆盖源文件
|
||||||
|
*/
|
||||||
|
uploadWithKey(filePath, key): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件
|
||||||
|
* @param ctx
|
||||||
|
* @param key 文件路径
|
||||||
|
*/
|
||||||
|
upload(ctx): Promise<string>;
|
||||||
|
}
|
||||||
25
src/modules/plugin/interface.ts
Normal file
25
src/modules/plugin/interface.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 插件信息
|
||||||
|
*/
|
||||||
|
export interface PluginInfo {
|
||||||
|
/** 名称 */
|
||||||
|
name?: string;
|
||||||
|
/** 唯一标识 */
|
||||||
|
key?: string;
|
||||||
|
/** 钩子 */
|
||||||
|
hook?: string;
|
||||||
|
/** 是否单例 */
|
||||||
|
singleton?: boolean;
|
||||||
|
/** 版本 */
|
||||||
|
version?: string;
|
||||||
|
/** 描述 */
|
||||||
|
description?: string;
|
||||||
|
/** 作者 */
|
||||||
|
author?: string;
|
||||||
|
/** logo */
|
||||||
|
logo?: string;
|
||||||
|
/** README 使用说明 */
|
||||||
|
readme?: string;
|
||||||
|
/** 配置 */
|
||||||
|
config?: any;
|
||||||
|
}
|
||||||
200
src/modules/plugin/service/center.ts
Normal file
200
src/modules/plugin/service/center.ts
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import {
|
||||||
|
App,
|
||||||
|
IMidwayApplication,
|
||||||
|
Inject,
|
||||||
|
InjectClient,
|
||||||
|
Scope,
|
||||||
|
Provide,
|
||||||
|
ScopeEnum,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { PluginInfoEntity } from '../entity/info';
|
||||||
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { PluginInfo } from '../interface';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
|
||||||
|
import { CoolEventManager } from '@cool-midway/core';
|
||||||
|
import { PluginService } from './info';
|
||||||
|
|
||||||
|
export const PLUGIN_CACHE_KEY = 'plugin:init';
|
||||||
|
|
||||||
|
export const EVENT_PLUGIN_READY = 'EVENT_PLUGIN_READY';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件中心
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Singleton)
|
||||||
|
export class PluginCenterService {
|
||||||
|
// 插件列表
|
||||||
|
plugins: Map<string, any> = new Map();
|
||||||
|
|
||||||
|
// 插件配置
|
||||||
|
pluginInfos: Map<string, PluginInfo> = new Map();
|
||||||
|
|
||||||
|
@App()
|
||||||
|
app: IMidwayApplication;
|
||||||
|
|
||||||
|
@InjectEntityModel(PluginInfoEntity)
|
||||||
|
pluginInfoEntity: Repository<PluginInfoEntity>;
|
||||||
|
|
||||||
|
@InjectClient(CachingFactory, 'default')
|
||||||
|
midwayCache: MidwayCache;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
coolEventManager: CoolEventManager;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
pluginService: PluginService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async init() {
|
||||||
|
this.plugins.clear();
|
||||||
|
await this.initHooks();
|
||||||
|
await this.initPlugin();
|
||||||
|
this.coolEventManager.emit(EVENT_PLUGIN_READY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化一个
|
||||||
|
* @param keyName key名
|
||||||
|
*/
|
||||||
|
async initOne(keyName: string) {
|
||||||
|
await this.initPlugin({
|
||||||
|
keyName,
|
||||||
|
});
|
||||||
|
this.coolEventManager.emit(EVENT_PLUGIN_READY, keyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除插件
|
||||||
|
* @param keyName
|
||||||
|
* @param isHook
|
||||||
|
*/
|
||||||
|
async remove(keyName: string, isHook = false) {
|
||||||
|
this.plugins.delete(keyName);
|
||||||
|
this.pluginInfos.delete(keyName);
|
||||||
|
if (isHook) {
|
||||||
|
await this.initHooks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册插件
|
||||||
|
* @param key 唯一标识
|
||||||
|
* @param cls 类
|
||||||
|
* @param pluginInfo 插件信息
|
||||||
|
*/
|
||||||
|
async register(key: string, cls: any, pluginInfo?: PluginInfo) {
|
||||||
|
// 单例插件
|
||||||
|
if (pluginInfo?.singleton) {
|
||||||
|
const instance = new cls();
|
||||||
|
await instance.init(this.pluginInfos.get(key), null, this.app, {
|
||||||
|
cache: this.midwayCache,
|
||||||
|
pluginService: this.pluginService,
|
||||||
|
});
|
||||||
|
this.plugins.set(key, instance);
|
||||||
|
} else {
|
||||||
|
// 普通插件
|
||||||
|
this.plugins.set(key, cls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化钩子
|
||||||
|
*/
|
||||||
|
async initHooks() {
|
||||||
|
const hooksPath = path.join(
|
||||||
|
this.app.getBaseDir(),
|
||||||
|
'modules',
|
||||||
|
'plugin',
|
||||||
|
'hooks'
|
||||||
|
);
|
||||||
|
for (const key of fs.readdirSync(hooksPath)) {
|
||||||
|
const stat = fs.statSync(path.join(hooksPath, key));
|
||||||
|
if (!stat.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const { Plugin } = await import(path.join(hooksPath, key, 'index'));
|
||||||
|
await this.register(key, Plugin);
|
||||||
|
this.pluginInfos.set(key, {
|
||||||
|
name: key,
|
||||||
|
config: this.app.getConfig('module.plugin.hooks.' + key),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化插件
|
||||||
|
* @param condition 插件条件
|
||||||
|
*/
|
||||||
|
async initPlugin(condition?: {
|
||||||
|
hook?: string;
|
||||||
|
id?: number;
|
||||||
|
keyName?: string;
|
||||||
|
}) {
|
||||||
|
let find: any = { status: 1 };
|
||||||
|
if (condition) {
|
||||||
|
find = {
|
||||||
|
...find,
|
||||||
|
...condition,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const plugins = await this.pluginInfoEntity.findBy(find);
|
||||||
|
for (const plugin of plugins) {
|
||||||
|
const data = await this.pluginService.getData(plugin.keyName);
|
||||||
|
if (!data) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const instance = await this.getInstance(data.content.data);
|
||||||
|
const pluginInfo = {
|
||||||
|
...plugin.pluginJson,
|
||||||
|
config: this.getConfig(plugin.config),
|
||||||
|
};
|
||||||
|
if (plugin.hook) {
|
||||||
|
this.pluginInfos.set(plugin.hook, pluginInfo);
|
||||||
|
await this.register(plugin.hook, instance, pluginInfo);
|
||||||
|
} else {
|
||||||
|
this.pluginInfos.set(plugin.keyName, pluginInfo);
|
||||||
|
await this.register(plugin.keyName, instance, pluginInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得配置
|
||||||
|
* @param config
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getConfig(config: any) {
|
||||||
|
const env = this.app.getEnv();
|
||||||
|
let isMulti = false;
|
||||||
|
for (const key in config) {
|
||||||
|
if (key.includes('@')) {
|
||||||
|
isMulti = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isMulti ? config[`@${env}`] : config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得实例
|
||||||
|
* @param content
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getInstance(content: string) {
|
||||||
|
let _instance;
|
||||||
|
const script = `
|
||||||
|
${content}
|
||||||
|
_instance = Plugin;
|
||||||
|
`;
|
||||||
|
eval(script);
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
447
src/modules/plugin/service/info.ts
Normal file
447
src/modules/plugin/service/info.ts
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
import {
|
||||||
|
BaseService,
|
||||||
|
CoolCommException,
|
||||||
|
CoolEventManager,
|
||||||
|
} from '@cool-midway/core';
|
||||||
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
|
import { Equal, In, Not, Repository } from 'typeorm';
|
||||||
|
import { PluginInfoEntity } from '../entity/info';
|
||||||
|
import {
|
||||||
|
App,
|
||||||
|
Config,
|
||||||
|
ILogger,
|
||||||
|
IMidwayApplication,
|
||||||
|
IMidwayContext,
|
||||||
|
Inject,
|
||||||
|
InjectClient,
|
||||||
|
Logger,
|
||||||
|
Provide,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import { PluginInfo } from '../interface';
|
||||||
|
import { PluginCenterService } from './center';
|
||||||
|
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
|
||||||
|
import {
|
||||||
|
GLOBAL_EVENT_PLUGIN_INIT,
|
||||||
|
GLOBAL_EVENT_PLUGIN_REMOVE,
|
||||||
|
} from '../event/init';
|
||||||
|
import { PluginMap, AnyString } from '../../../../typings/plugin';
|
||||||
|
import { PluginTypesService } from './types';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
/**
|
||||||
|
* 插件信息
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
export class PluginService extends BaseService {
|
||||||
|
@InjectEntityModel(PluginInfoEntity)
|
||||||
|
pluginInfoEntity: Repository<PluginInfoEntity>;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
ctx: IMidwayContext;
|
||||||
|
|
||||||
|
@App()
|
||||||
|
app: IMidwayApplication;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
pluginCenterService: PluginCenterService;
|
||||||
|
|
||||||
|
@Config('module.plugin.hooks')
|
||||||
|
hooksConfig;
|
||||||
|
|
||||||
|
@InjectClient(CachingFactory, 'default')
|
||||||
|
midwayCache: MidwayCache;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
coolEventManager: CoolEventManager;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
pluginTypesService: PluginTypesService;
|
||||||
|
|
||||||
|
@Logger()
|
||||||
|
logger: ILogger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增或更新
|
||||||
|
* @param param
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
async addOrUpdate(param: any, type?: 'add' | 'update') {
|
||||||
|
await super.addOrUpdate(param, type);
|
||||||
|
const info = await this.pluginInfoEntity
|
||||||
|
.createQueryBuilder('a')
|
||||||
|
.select(['a.id', 'a.keyName', 'a.status', 'a.hook'])
|
||||||
|
.where({
|
||||||
|
id: Equal(param.id),
|
||||||
|
})
|
||||||
|
.getOne();
|
||||||
|
if (info.status == 1) {
|
||||||
|
await this.reInit(info.keyName);
|
||||||
|
} else {
|
||||||
|
await this.remove(info.keyName, !!info.hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新初始化插件
|
||||||
|
*/
|
||||||
|
async reInit(keyName: string) {
|
||||||
|
// 多进程发送全局事件,pm2下生效,本地开发则通过普通事件
|
||||||
|
this.coolEventManager.globalEmit(GLOBAL_EVENT_PLUGIN_INIT, false, keyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除插件
|
||||||
|
* @param keyName
|
||||||
|
* @param isHook
|
||||||
|
*/
|
||||||
|
async remove(keyName: string, isHook = false) {
|
||||||
|
// 多进程发送全局事件,pm2下生效
|
||||||
|
this.coolEventManager.globalEmit(
|
||||||
|
GLOBAL_EVENT_PLUGIN_REMOVE,
|
||||||
|
false,
|
||||||
|
keyName,
|
||||||
|
isHook
|
||||||
|
);
|
||||||
|
this.pluginTypesService.deleteDtsFile(keyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除不经过回收站
|
||||||
|
* @param ids
|
||||||
|
*/
|
||||||
|
async delete(ids: any) {
|
||||||
|
const list = await this.pluginInfoEntity.findBy({ id: In(ids) });
|
||||||
|
for (const item of list) {
|
||||||
|
await this.remove(item.keyName, !!item.hook);
|
||||||
|
// 删除文件
|
||||||
|
await this.deleteData(item.keyName);
|
||||||
|
}
|
||||||
|
await this.pluginInfoEntity.delete(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
async update(param: any) {
|
||||||
|
const old = await this.pluginInfoEntity.findOne({
|
||||||
|
where: { id: param.id },
|
||||||
|
select: ['id', 'status', 'hook'],
|
||||||
|
});
|
||||||
|
// 启用插件,禁用同名插件
|
||||||
|
if (old.hook && param.status == 1 && old.status != param.status) {
|
||||||
|
await this.pluginInfoEntity.update(
|
||||||
|
{ hook: old.hook, status: 1, id: Not(old.id) },
|
||||||
|
{ status: 0 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await super.update(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得插件配置
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
async getConfig(key: string) {
|
||||||
|
return this.pluginCenterService.pluginInfos.get(key)?.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用插件
|
||||||
|
* @param key 插件key
|
||||||
|
* @param method 方法
|
||||||
|
* @param params 参数
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async invoke<K extends keyof PluginMap>(
|
||||||
|
key: K | AnyString,
|
||||||
|
method: string,
|
||||||
|
...params
|
||||||
|
) {
|
||||||
|
// 实例
|
||||||
|
const instance: any = await this.getInstance(key);
|
||||||
|
return await instance[method](...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得插件实例
|
||||||
|
* @param key
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getInstance<K extends keyof PluginMap>(
|
||||||
|
key: K | AnyString
|
||||||
|
): Promise<K extends keyof PluginMap ? PluginMap[K] : any> {
|
||||||
|
const check = await this.checkStatus(key);
|
||||||
|
if (!check) throw new CoolCommException(`插件[${key}]不存在或已禁用`);
|
||||||
|
let instance;
|
||||||
|
const pluginInfo = this.pluginCenterService.pluginInfos.get(key);
|
||||||
|
if (pluginInfo.singleton) {
|
||||||
|
instance = this.pluginCenterService.plugins.get(key);
|
||||||
|
} else {
|
||||||
|
instance = new (await this.pluginCenterService.plugins.get(key))();
|
||||||
|
await instance.init(pluginInfo, this.ctx, this.app, {
|
||||||
|
cache: this.midwayCache,
|
||||||
|
pluginService: this,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查状态
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
async checkStatus(key: string) {
|
||||||
|
if (Object.keys(this.hooksConfig).includes(key)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const info = await this.pluginInfoEntity
|
||||||
|
.createQueryBuilder('a')
|
||||||
|
.select(['a.id', 'a.status'])
|
||||||
|
.where({ status: 1, keyName: Equal(key) })
|
||||||
|
.getOne();
|
||||||
|
|
||||||
|
return !!info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查
|
||||||
|
* @param filePath
|
||||||
|
*/
|
||||||
|
async check(filePath: string) {
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = await this.data(filePath);
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
type: 0,
|
||||||
|
message: `插件信息不完整,请检查${data.errorData}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const check = await this.pluginInfoEntity.findOne({
|
||||||
|
where: { keyName: Equal(data.pluginJson.key) },
|
||||||
|
select: ['id', 'hook', 'status'],
|
||||||
|
});
|
||||||
|
if (check && !check.hook) {
|
||||||
|
return {
|
||||||
|
type: 1,
|
||||||
|
message: '插件已存在,继续安装将覆盖',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (check && check.hook && check.status == 1) {
|
||||||
|
return {
|
||||||
|
type: 2,
|
||||||
|
message:
|
||||||
|
'已存在同名Hook插件,你可以继续安装,但是多个相同的Hook插件只能同时开启一个',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 3,
|
||||||
|
message: '检查通过',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得插件数据
|
||||||
|
* @param filePath
|
||||||
|
*/
|
||||||
|
async data(filePath: string): Promise<{
|
||||||
|
pluginJson: any;
|
||||||
|
readme: string;
|
||||||
|
logo: string;
|
||||||
|
content: string;
|
||||||
|
tsContent: string;
|
||||||
|
errorData: string;
|
||||||
|
}> {
|
||||||
|
const decompress = require('decompress');
|
||||||
|
const files = await decompress(filePath);
|
||||||
|
let errorData;
|
||||||
|
let pluginJson: PluginInfo,
|
||||||
|
readme: string,
|
||||||
|
logo: string,
|
||||||
|
content: string,
|
||||||
|
tsContent: string;
|
||||||
|
try {
|
||||||
|
errorData = 'plugin.json';
|
||||||
|
pluginJson = JSON.parse(
|
||||||
|
_.find(files, { path: 'plugin.json', type: 'file' }).data.toString()
|
||||||
|
);
|
||||||
|
errorData = 'readme';
|
||||||
|
readme = _.find(files, {
|
||||||
|
path: pluginJson.readme,
|
||||||
|
type: 'file',
|
||||||
|
}).data.toString();
|
||||||
|
errorData = 'logo';
|
||||||
|
logo = _.find(files, {
|
||||||
|
path: pluginJson.logo,
|
||||||
|
type: 'file',
|
||||||
|
}).data.toString('base64');
|
||||||
|
content = _.find(files, {
|
||||||
|
path: 'src/index.js',
|
||||||
|
type: 'file',
|
||||||
|
}).data.toString();
|
||||||
|
tsContent =
|
||||||
|
_.find(files, {
|
||||||
|
path: 'source/index.ts',
|
||||||
|
type: 'file',
|
||||||
|
})?.data?.toString() || '';
|
||||||
|
} catch (e) {
|
||||||
|
throw new CoolCommException('插件信息不完整');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
pluginJson,
|
||||||
|
readme,
|
||||||
|
logo,
|
||||||
|
content,
|
||||||
|
tsContent,
|
||||||
|
errorData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安装插件
|
||||||
|
* @param file 文件
|
||||||
|
* @param force 是否强制安装
|
||||||
|
*/
|
||||||
|
async install(filePath: string, force = false) {
|
||||||
|
const forceBool = typeof force === 'string' ? force === 'true' : force;
|
||||||
|
const checkResult = await this.check(filePath);
|
||||||
|
if (checkResult.type != 3 && !forceBool) {
|
||||||
|
return checkResult;
|
||||||
|
}
|
||||||
|
const { pluginJson, readme, logo, content, tsContent } = await this.data(
|
||||||
|
filePath
|
||||||
|
);
|
||||||
|
if (pluginJson.key == 'plugin') {
|
||||||
|
throw new CoolCommException('插件key不能为plugin,请更换其他key');
|
||||||
|
}
|
||||||
|
const check = await this.pluginInfoEntity.findOne({
|
||||||
|
where: { keyName: Equal(pluginJson.key) },
|
||||||
|
select: ['id', 'status', 'config'],
|
||||||
|
});
|
||||||
|
const data = {
|
||||||
|
name: pluginJson.name,
|
||||||
|
keyName: pluginJson.key,
|
||||||
|
version: pluginJson.version,
|
||||||
|
author: pluginJson.author,
|
||||||
|
hook: pluginJson.hook,
|
||||||
|
readme,
|
||||||
|
logo,
|
||||||
|
description: pluginJson.description,
|
||||||
|
pluginJson,
|
||||||
|
config: pluginJson.config,
|
||||||
|
status: 1,
|
||||||
|
} as PluginInfoEntity;
|
||||||
|
// 存在同名插件,更新,保留配置
|
||||||
|
if (check) {
|
||||||
|
await this.pluginInfoEntity.update(check.id, {
|
||||||
|
...data,
|
||||||
|
status: check.status,
|
||||||
|
config: {
|
||||||
|
...pluginJson.config,
|
||||||
|
...check.config,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 全新安装
|
||||||
|
await this.pluginInfoEntity.insert(data);
|
||||||
|
}
|
||||||
|
// 保存插件内容
|
||||||
|
await this.saveData(
|
||||||
|
{
|
||||||
|
content: {
|
||||||
|
type: 'comm',
|
||||||
|
data: content,
|
||||||
|
},
|
||||||
|
tsContent: {
|
||||||
|
type: 'ts',
|
||||||
|
data: tsContent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pluginJson.key
|
||||||
|
);
|
||||||
|
this.pluginTypesService.generateDtsFile(pluginJson.key, tsContent);
|
||||||
|
// 初始化插件
|
||||||
|
await this.reInit(pluginJson.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将插件内容保存到文件
|
||||||
|
* @param content 内容
|
||||||
|
* @param keyName 插件key
|
||||||
|
*/
|
||||||
|
async saveData(
|
||||||
|
data: {
|
||||||
|
content: {
|
||||||
|
type: 'comm' | 'module';
|
||||||
|
data: string;
|
||||||
|
};
|
||||||
|
tsContent: {
|
||||||
|
type: 'ts';
|
||||||
|
data: string;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
keyName: string
|
||||||
|
) {
|
||||||
|
const filePath = this.pluginPath(keyName);
|
||||||
|
// 确保目录存在
|
||||||
|
const dir = path.dirname(filePath);
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
// 写入文件,如果存在则覆盖
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 0), { flag: 'w' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得插件数据
|
||||||
|
* @param keyName
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getData(keyName: string): Promise<{
|
||||||
|
content: {
|
||||||
|
type: 'comm' | 'module';
|
||||||
|
data: string;
|
||||||
|
};
|
||||||
|
tsContent: {
|
||||||
|
type: 'ts';
|
||||||
|
data: string;
|
||||||
|
};
|
||||||
|
}> {
|
||||||
|
const filePath = this.pluginPath(keyName);
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
this.logger.warn(
|
||||||
|
`插件[${keyName}]文件不存在,请卸载后重新安装: ${filePath}`
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return JSON.parse(await fs.promises.readFile(filePath, 'utf-8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除插件
|
||||||
|
* @param keyName
|
||||||
|
*/
|
||||||
|
async deleteData(keyName: string) {
|
||||||
|
const filePath = this.pluginPath(keyName);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得插件路径
|
||||||
|
* @param keyName
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
pluginPath(keyName: string) {
|
||||||
|
return path.join(
|
||||||
|
this.app.getBaseDir(),
|
||||||
|
'..',
|
||||||
|
'cool',
|
||||||
|
'plugin',
|
||||||
|
`${keyName}.cool`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
256
src/modules/plugin/service/types.ts
Normal file
256
src/modules/plugin/service/types.ts
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
import { BaseService } from '@cool-midway/core';
|
||||||
|
import { App, IMidwayApplication, Inject, Provide } from '@midwayjs/core';
|
||||||
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as prettier from 'prettier';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
import { Utils } from '../../../comm/utils';
|
||||||
|
import { PluginInfoEntity } from '../entity/info';
|
||||||
|
import { PluginService } from './info';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件类型服务
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
export class PluginTypesService extends BaseService {
|
||||||
|
@App()
|
||||||
|
app: IMidwayApplication;
|
||||||
|
|
||||||
|
@InjectEntityModel(PluginInfoEntity)
|
||||||
|
pluginInfoEntity: Repository<PluginInfoEntity>;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
pluginService: PluginService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
utils: Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成d.ts文件
|
||||||
|
* @param tsContent
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async dtsContent(tsContent: string) {
|
||||||
|
let output = '';
|
||||||
|
|
||||||
|
const compilerHost: ts.CompilerHost = {
|
||||||
|
fileExists: ts.sys.fileExists,
|
||||||
|
getCanonicalFileName: ts.sys.useCaseSensitiveFileNames
|
||||||
|
? s => s
|
||||||
|
: s => s.toLowerCase(),
|
||||||
|
getCurrentDirectory: ts.sys.getCurrentDirectory,
|
||||||
|
getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
|
||||||
|
getDirectories: ts.sys.getDirectories,
|
||||||
|
getNewLine: () => ts.sys.newLine,
|
||||||
|
getSourceFile: (fileName, languageVersion) => {
|
||||||
|
if (fileName === 'file.ts') {
|
||||||
|
return ts.createSourceFile(
|
||||||
|
fileName,
|
||||||
|
tsContent,
|
||||||
|
languageVersion,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const filePath = ts.sys.resolvePath(fileName);
|
||||||
|
return ts.sys.readFile(filePath)
|
||||||
|
? ts.createSourceFile(
|
||||||
|
filePath,
|
||||||
|
ts.sys.readFile(filePath)!,
|
||||||
|
languageVersion,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
},
|
||||||
|
readFile: ts.sys.readFile,
|
||||||
|
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
|
||||||
|
writeFile: (fileName, content) => {
|
||||||
|
if (fileName.includes('file.d.ts')) {
|
||||||
|
output = content || output;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: ts.CompilerOptions = {
|
||||||
|
declaration: true,
|
||||||
|
emitDeclarationOnly: true,
|
||||||
|
outDir: './',
|
||||||
|
skipLibCheck: true,
|
||||||
|
skipDefaultLibCheck: true,
|
||||||
|
noEmitOnError: false,
|
||||||
|
target: ts.ScriptTarget.ES2018,
|
||||||
|
strict: false,
|
||||||
|
module: ts.ModuleKind.Node16,
|
||||||
|
moduleResolution: ts.ModuleResolutionKind.Node16,
|
||||||
|
types: ['node'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const program = ts.createProgram(['file.ts'], options, compilerHost);
|
||||||
|
program.emit();
|
||||||
|
|
||||||
|
if (!output) {
|
||||||
|
// Provide a default value if the output is still empty
|
||||||
|
output = '/* No declaration content generated */';
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成d.ts文件
|
||||||
|
* @param key
|
||||||
|
* @param tsContent
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async generateDtsFile(key: string, tsContent: string) {
|
||||||
|
const env = this.app.getEnv();
|
||||||
|
// 不是本地开发环境不生成d.ts文件
|
||||||
|
if (env != 'local' || !tsContent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 基础路径
|
||||||
|
const basePath = path.join(this.app.getBaseDir(), '..', 'typings');
|
||||||
|
// pluginDts文件路径
|
||||||
|
const pluginDtsPath = path.join(basePath, 'plugin.d.ts');
|
||||||
|
// plugin文件夹路径
|
||||||
|
const pluginPath = path.join(basePath, `${key}.d.ts`);
|
||||||
|
// 生成d.ts文件
|
||||||
|
const dtsContent = await this.dtsContent(tsContent);
|
||||||
|
|
||||||
|
// 读取plugin.d.ts文件内容
|
||||||
|
let pluginDtsContent = fs.readFileSync(pluginDtsPath, 'utf-8');
|
||||||
|
|
||||||
|
// 根据key判断是否在PluginMap中存在
|
||||||
|
const keyWithHyphen = key.includes('-');
|
||||||
|
const importStatement = keyWithHyphen
|
||||||
|
? `import * as ${key.replace(/-/g, '_')} from './${key}';`
|
||||||
|
: `import * as ${key} from './${key}';`;
|
||||||
|
const pluginMapEntry = keyWithHyphen
|
||||||
|
? `'${key}': ${key.replace(/-/g, '_')}.CoolPlugin;`
|
||||||
|
: `${key}: ${key}.CoolPlugin;`;
|
||||||
|
|
||||||
|
// 检查import语句是否已经存在,若不存在则添加
|
||||||
|
if (!pluginDtsContent.includes(importStatement)) {
|
||||||
|
pluginDtsContent = `${importStatement}\n${pluginDtsContent}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查PluginMap中的键是否存在,若不存在则添加
|
||||||
|
if (pluginDtsContent.includes(pluginMapEntry)) {
|
||||||
|
// 键存在则覆盖
|
||||||
|
const regex = new RegExp(
|
||||||
|
`(\\s*${keyWithHyphen ? `'${key}'` : key}:\\s*[^;]+;)`
|
||||||
|
);
|
||||||
|
pluginDtsContent = pluginDtsContent.replace(regex, pluginMapEntry);
|
||||||
|
} else {
|
||||||
|
// 键不存在则追加
|
||||||
|
const pluginMapRegex = /interface\s+PluginMap\s*{([^}]*)}/;
|
||||||
|
pluginDtsContent = pluginDtsContent.replace(
|
||||||
|
pluginMapRegex,
|
||||||
|
(match, p1) => {
|
||||||
|
return match.replace(p1, `${p1.trim()}\n ${pluginMapEntry}`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化内容
|
||||||
|
pluginDtsContent = await this.formatContent(pluginDtsContent);
|
||||||
|
|
||||||
|
// 延迟2秒写入文件
|
||||||
|
setTimeout(async () => {
|
||||||
|
// 写入d.ts文件,如果存在则覆盖
|
||||||
|
fs.writeFile(pluginPath, await this.formatContent(dtsContent), () => {});
|
||||||
|
|
||||||
|
// 写入plugin.d.ts文件
|
||||||
|
fs.writeFile(pluginDtsPath, pluginDtsContent, () => {});
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除d.ts文件中的指定key
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
async deleteDtsFile(key: string) {
|
||||||
|
const env = this.app.getEnv();
|
||||||
|
// 不是本地开发环境不删除d.ts文件
|
||||||
|
if (env != 'local') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 基础路径
|
||||||
|
const basePath = path.join(this.app.getBaseDir(), '..', 'typings');
|
||||||
|
// pluginDts文件路径
|
||||||
|
const pluginDtsPath = path.join(basePath, 'plugin.d.ts');
|
||||||
|
// plugin文件夹路径
|
||||||
|
const pluginPath = path.join(basePath, `${key}.d.ts`);
|
||||||
|
|
||||||
|
// 读取plugin.d.ts文件内容
|
||||||
|
let pluginDtsContent = fs.readFileSync(pluginDtsPath, 'utf-8');
|
||||||
|
|
||||||
|
// 根据key判断是否在PluginMap中存在
|
||||||
|
const keyWithHyphen = key.includes('-');
|
||||||
|
const importStatement = keyWithHyphen
|
||||||
|
? `import \\* as ${key.replace(/-/g, '_')} from '\\./${key}';`
|
||||||
|
: `import \\* as ${key} from '\\./${key}';`;
|
||||||
|
const pluginMapEntry = keyWithHyphen
|
||||||
|
? `'${key}': ${key.replace(/-/g, '_')}.CoolPlugin;`
|
||||||
|
: `${key}: ${key}.CoolPlugin;`;
|
||||||
|
|
||||||
|
// 删除import语句
|
||||||
|
const importRegex = new RegExp(`${importStatement}\\n`, 'g');
|
||||||
|
pluginDtsContent = pluginDtsContent.replace(importRegex, '');
|
||||||
|
|
||||||
|
// 删除PluginMap中的键
|
||||||
|
const pluginMapRegex = new RegExp(`\\s*${pluginMapEntry}`, 'g');
|
||||||
|
pluginDtsContent = pluginDtsContent.replace(pluginMapRegex, '');
|
||||||
|
|
||||||
|
// 格式化内容
|
||||||
|
pluginDtsContent = await this.formatContent(pluginDtsContent);
|
||||||
|
|
||||||
|
// 延迟2秒写入文件
|
||||||
|
setTimeout(async () => {
|
||||||
|
// 删除插件d.ts文件
|
||||||
|
if (fs.existsSync(pluginPath)) {
|
||||||
|
fs.unlink(pluginPath, () => {});
|
||||||
|
}
|
||||||
|
// 写入plugin.d.ts文件
|
||||||
|
fs.writeFile(pluginDtsPath, pluginDtsContent, () => {});
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化内容
|
||||||
|
* @param content
|
||||||
|
*/
|
||||||
|
async formatContent(content: string) {
|
||||||
|
// 使用prettier格式化内容
|
||||||
|
return prettier.format(content, {
|
||||||
|
parser: 'typescript',
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: 'all',
|
||||||
|
bracketSpacing: true,
|
||||||
|
arrowParens: 'avoid',
|
||||||
|
printWidth: 80,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新生成d.ts文件
|
||||||
|
*/
|
||||||
|
async reGenerate() {
|
||||||
|
const pluginInfos = await this.pluginInfoEntity
|
||||||
|
.createQueryBuilder('a')
|
||||||
|
.where('a.status = :status', { status: 1 })
|
||||||
|
.select(['a.id', 'a.status', 'a.tsContent', 'a.keyName'])
|
||||||
|
.getMany();
|
||||||
|
for (const pluginInfo of pluginInfos) {
|
||||||
|
const data = await this.pluginService.getData(pluginInfo.keyName);
|
||||||
|
if (!data) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const tsContent = data.tsContent?.data;
|
||||||
|
if (tsContent) {
|
||||||
|
await this.generateDtsFile(pluginInfo.keyName, tsContent);
|
||||||
|
await this.utils.sleep(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
test/README.md
Normal file
12
test/README.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# 测试方式
|
||||||
|
|
||||||
|
考虑到cool-admin采用了自动化路由技术,它与官方集成的jest测试工具并不兼容。为确保测试环境与实际的开发环境保持一致,我们并不推荐使用jest进行测试。
|
||||||
|
|
||||||
|
# 自动化测试API工具
|
||||||
|
|
||||||
|
我们为您推荐以下的自动化API测试工具:
|
||||||
|
|
||||||
|
- [Apifox](https://apifox.com/)
|
||||||
|
- [ApiPost](https://www.apipost.cn/)
|
||||||
|
|
||||||
|
同时这些工具也方便写API接口文档,更加灵活有用
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import { createApp, close, createHttpRequest } from '@midwayjs/mock';
|
|
||||||
import { Framework } from '@midwayjs/koa';
|
|
||||||
|
|
||||||
describe('test/controller/home.test.ts', () => {
|
|
||||||
|
|
||||||
it('should POST /api/get_user', async () => {
|
|
||||||
// create app
|
|
||||||
const app = await createApp<Framework>();
|
|
||||||
|
|
||||||
// make request
|
|
||||||
const result = await createHttpRequest(app).get('/api/get_user').query({ uid: 123 });
|
|
||||||
|
|
||||||
// use expect by jest
|
|
||||||
expect(result.status).toBe(200);
|
|
||||||
expect(result.body.message).toBe('OK');
|
|
||||||
|
|
||||||
// close app
|
|
||||||
await close(app);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { createApp, close, createHttpRequest } from '@midwayjs/mock';
|
|
||||||
import { Framework } from '@midwayjs/koa';
|
|
||||||
|
|
||||||
describe('test/controller/home.test.ts', () => {
|
|
||||||
|
|
||||||
it('should GET /', async () => {
|
|
||||||
// create app
|
|
||||||
const app = await createApp<Framework>();
|
|
||||||
|
|
||||||
// make request
|
|
||||||
const result = await createHttpRequest(app).get('/');
|
|
||||||
|
|
||||||
// use expect by jest
|
|
||||||
expect(result.status).toBe(200);
|
|
||||||
expect(result.text).toBe('Hello Midwayjs!');
|
|
||||||
|
|
||||||
// close app
|
|
||||||
await close(app);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -17,14 +17,14 @@
|
|||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"typings",
|
"typings",
|
||||||
"./node_modules/@types"
|
"./node_modules/@types",
|
||||||
],
|
],
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src"
|
"rootDir": "src",
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"dist",
|
"dist",
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"test"
|
"test",
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
8
typings/plugin.d.ts
vendored
Normal file
8
typings/plugin.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { BaseUpload, MODETYPE } from './upload';
|
||||||
|
type AnyString = string & {};
|
||||||
|
/**
|
||||||
|
* 插件类型声明
|
||||||
|
*/
|
||||||
|
interface PluginMap {
|
||||||
|
upload: BaseUpload;
|
||||||
|
}
|
||||||
56
typings/upload.d.ts
vendored
Normal file
56
typings/upload.d.ts
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// 模式
|
||||||
|
export enum MODETYPE {
|
||||||
|
// 本地
|
||||||
|
LOCAL = 'local',
|
||||||
|
// 云存储
|
||||||
|
CLOUD = 'cloud',
|
||||||
|
// 其他
|
||||||
|
OTHER = 'other',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传模式
|
||||||
|
*/
|
||||||
|
export interface Mode {
|
||||||
|
// 模式
|
||||||
|
mode: MODETYPE;
|
||||||
|
// 类型
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件上传
|
||||||
|
*/
|
||||||
|
export interface BaseUpload {
|
||||||
|
/**
|
||||||
|
* 获得上传模式
|
||||||
|
*/
|
||||||
|
getMode(): Promise<Mode>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得原始操作对象
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getMetaFileObj(): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载并上传
|
||||||
|
* @param url
|
||||||
|
* @param fileName 文件名
|
||||||
|
*/
|
||||||
|
downAndUpload(url: string, fileName?: string): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定Key(路径)上传,本地文件上传到存储服务
|
||||||
|
* @param filePath 文件路径
|
||||||
|
* @param key 路径一致会覆盖源文件
|
||||||
|
*/
|
||||||
|
uploadWithKey(filePath, key): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件
|
||||||
|
* @param ctx
|
||||||
|
* @param key 文件路径
|
||||||
|
*/
|
||||||
|
upload(ctx): Promise<string>;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user