add route for app module micro
This commit is contained in:
parent
f94c9323b4
commit
01680b39c2
@ -85,7 +85,6 @@
|
||||
"@types/uuid": "^10.0.0",
|
||||
"concurrently": "^9.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"glob": "^11.0.0",
|
||||
"nodemon": "^3.1.7",
|
||||
"pm2": "^5.4.3",
|
||||
"rimraf": "^6.0.1",
|
||||
@ -97,7 +96,6 @@
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"glob": "latest",
|
||||
"inflight": "latest",
|
||||
"rimraf": "latest",
|
||||
"picomatch": "^4.0.2"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import path from 'path'
|
||||
import * as glob from 'glob'
|
||||
import * as glob from 'fast-glob'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { dirname } from 'path'
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
|
@ -1 +1,3 @@
|
||||
import './list.ts';
|
||||
|
||||
import './routes/manager.ts'
|
@ -0,0 +1 @@
|
||||
export * from './app-file.ts'
|
@ -1,6 +1,6 @@
|
||||
import { app } from '@/app.ts';
|
||||
import { MicroAppModel } from './models.ts';
|
||||
import { appCheck, installApp } from './module/install-app.ts';
|
||||
import { appPathCheck, installApp } from './module/install-app.ts';
|
||||
import { loadApp } from './module/load-app.ts';
|
||||
|
||||
// 应用上传到 应用管理 的平台
|
||||
@ -64,7 +64,7 @@ app
|
||||
ctx.throw(404, 'Invalid path');
|
||||
}
|
||||
console.log('path', path);
|
||||
const check = await appCheck({ key });
|
||||
const check = await appPathCheck({ key });
|
||||
if (check) {
|
||||
ctx.throw(400, 'App already exists, please remove it first');
|
||||
}
|
||||
|
4
src/routes/micro-app/manager-app.ts
Normal file
4
src/routes/micro-app/manager-app.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { app } from '@/app.ts';
|
||||
import { Manager } from './module/manager.ts';
|
||||
|
||||
export const manager = new Manager({ mainApp: app });
|
@ -11,7 +11,12 @@ export type InstallAppOpts = {
|
||||
path?: string;
|
||||
key?: string;
|
||||
};
|
||||
export const appCheck = async (opts: InstallAppOpts) => {
|
||||
/**
|
||||
* 检测路径是否存在
|
||||
* @param opts
|
||||
* @returns
|
||||
*/
|
||||
export const appPathCheck = async (opts: InstallAppOpts) => {
|
||||
const { key } = opts;
|
||||
const directory = path.join(appsPath, key);
|
||||
if (fileIsExist(directory)) {
|
||||
@ -55,7 +60,5 @@ export const installApp = async (opts: InstallAppOpts) => {
|
||||
app.key = key;
|
||||
fs.writeFileSync(pkgs, JSON.stringify(pkg, null, 2));
|
||||
// fs.unlinkSync(filePath);
|
||||
return { path: filePath };
|
||||
return { path: filePath, pkg };
|
||||
};
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@ import path from 'path';
|
||||
|
||||
export const appsPath = useFileStore('apps', { needExists: true });
|
||||
|
||||
export const loadAppInfo = async (key: string) => {
|
||||
export const loadFileAppInfo = async (key: string) => {
|
||||
const directory = path.join(appsPath, key);
|
||||
if (!fileIsExist(directory)) {
|
||||
throw new Error('app not found');
|
||||
@ -28,9 +28,16 @@ export const loadAppInfo = async (key: string) => {
|
||||
}
|
||||
return { mainEntry, app };
|
||||
};
|
||||
export const deleteFileAppInfo = async (key: string) => {
|
||||
const directory = path.join(appsPath, key);
|
||||
if (!fileIsExist(directory)) {
|
||||
return;
|
||||
}
|
||||
fs.rmSync(directory, { recursive: true });
|
||||
};
|
||||
|
||||
export const loadApp = async (key: string) => {
|
||||
const { mainEntry } = await loadAppInfo(key);
|
||||
const { mainEntry, app } = await loadFileAppInfo(key);
|
||||
// 1. 查询数据库,获取app信息,查看是否运行中
|
||||
// 2. 如果运行中,直接返回
|
||||
// 3. 如果不在运行中,加载app
|
||||
|
@ -1,20 +1,45 @@
|
||||
import { App } from '@kevisual/router';
|
||||
import { loadAppInfo, AppInfoConfig, saveAppInfo } from '../lib/app-file.ts';
|
||||
import { fork } from 'child_process';
|
||||
|
||||
import { merge } from 'lodash-es';
|
||||
import { deleteFileAppInfo } from './load-app.ts';
|
||||
import { fileIsExist } from '@kevisual/use-config';
|
||||
// 共享
|
||||
export const existDenpend = [
|
||||
'sequelize', // commonjs
|
||||
'pg', // commonjs
|
||||
'@kevisual/router', // 共享模块
|
||||
'ioredis', // commonjs
|
||||
'socket.io', // commonjs
|
||||
'minio', // commonjs
|
||||
'pino', // commonjs
|
||||
'pino-pretty', // commonjs
|
||||
'@msgpack/msgpack', // commonjs
|
||||
];
|
||||
export enum AppType {
|
||||
SystemApp = 'system-app',
|
||||
MicroApp = 'micro-app',
|
||||
GatewayApp = 'gateway-app',
|
||||
}
|
||||
export type AppInfo = {
|
||||
status?: 'inactive' | 'runing' | 'stop' | 'error'; // 运行状态
|
||||
key: string;
|
||||
status?: 'inactive' | 'running' | 'stop' | 'error'; // 运行状态
|
||||
type?: AppType; // 默认类型
|
||||
entry?: string; // 入口文件
|
||||
path?: string; // 文件路径
|
||||
timestamp?: number; // 时间戳, 每次更新更新时间戳
|
||||
process?: any; // 进程
|
||||
description?: string; // 描述
|
||||
version?: string; // 版本
|
||||
};
|
||||
export const onAppShowInfo = (app: AppInfo) => {
|
||||
return {
|
||||
key: app.key,
|
||||
status: app.status,
|
||||
type: app.type,
|
||||
description: app.description,
|
||||
version: app.version,
|
||||
};
|
||||
};
|
||||
type managerOptions = {
|
||||
mainApp: App;
|
||||
@ -43,13 +68,20 @@ export class Manager<T extends AppInfo = any> {
|
||||
async loadApp(app: T) {
|
||||
const mainApp = this.mainApp;
|
||||
this.apps.set(app.key, app);
|
||||
if (app.status === 'inactive') {
|
||||
if (app.status !== 'running') {
|
||||
return;
|
||||
}
|
||||
if (!fileIsExist(app.entry)) {
|
||||
console.error('file not found');
|
||||
return;
|
||||
}
|
||||
const entry = app.entry + `?timestamp=${app?.timestamp}`;
|
||||
// 注册路由
|
||||
if (app.type === AppType.MicroApp) {
|
||||
const process = fork(entry, [], {});
|
||||
const process = fork(app.entry, [], {
|
||||
stdio: 'inherit', // 共享主进程的标准输入输出
|
||||
cwd: app.path,
|
||||
});
|
||||
app.process = process;
|
||||
} else if (app.type === AppType.SystemApp) {
|
||||
const module = await import(entry);
|
||||
@ -92,11 +124,12 @@ export class Manager<T extends AppInfo = any> {
|
||||
}
|
||||
async add(app: T) {
|
||||
if (this.checkKey(app.key)) {
|
||||
console.error('key is loaded');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.loadApp(app);
|
||||
await this.saveAppInfo(app, true);
|
||||
this.loadApp(app);
|
||||
}
|
||||
// 启动
|
||||
async start(key: string) {
|
||||
@ -104,10 +137,10 @@ export class Manager<T extends AppInfo = any> {
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
if (app.status === 'runing') {
|
||||
if (app.status === 'running') {
|
||||
return;
|
||||
}
|
||||
app.status = 'runing';
|
||||
app.status = 'running';
|
||||
this.loadApp(app);
|
||||
await this.saveAppInfo(app);
|
||||
}
|
||||
@ -126,4 +159,64 @@ export class Manager<T extends AppInfo = any> {
|
||||
}
|
||||
await this.saveAppInfo(app);
|
||||
}
|
||||
/**
|
||||
* 获取app信息, 用于展示
|
||||
* @param key
|
||||
* @returns
|
||||
*/
|
||||
getAppShowInfo(key: string) {
|
||||
const app = this.apps.get(key);
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
return onAppShowInfo(app);
|
||||
}
|
||||
/**
|
||||
* 获取所有app信息, 用于展示
|
||||
* @returns
|
||||
*/
|
||||
getAllAppShowInfo() {
|
||||
const list = [];
|
||||
for (const [key, value] of this.apps) {
|
||||
list.push(onAppShowInfo(value));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
/**
|
||||
* 更新app信息, 用于展示, 加上一些功能,启动,停止程序
|
||||
* @param key
|
||||
* @param info
|
||||
* @returns
|
||||
*/
|
||||
async updateAppInfo(key: string, info: Partial<T>) {
|
||||
const app = this.apps.get(key);
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
merge(app, info);
|
||||
this.loadApp(app);
|
||||
await this.saveAppInfo(app);
|
||||
return onAppShowInfo(app);
|
||||
}
|
||||
/**
|
||||
* 删除app信息
|
||||
* @param key
|
||||
* @returns
|
||||
*/
|
||||
async removeApp(key: string) {
|
||||
const app = this.apps.get(key);
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
if (app.process) {
|
||||
app.process.kill();
|
||||
}
|
||||
this.apps.delete(key);
|
||||
await this.saveAppInfo(app);
|
||||
try {
|
||||
deleteFileAppInfo(key);
|
||||
} catch (e) {
|
||||
console.error('delete file app error', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { app } from '@/app.ts';
|
||||
import { manager } from '../manager-app.ts';
|
||||
|
||||
app
|
||||
.route({
|
||||
@ -6,6 +7,50 @@ app
|
||||
key: 'list',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
//
|
||||
const list = manager.getAllAppShowInfo();
|
||||
ctx.body = list;
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'micro-app-manager',
|
||||
key: 'updateStatus',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const { status, key } = ctx.query;
|
||||
if (!status || !key) {
|
||||
ctx.body = 'status or key is required';
|
||||
return;
|
||||
}
|
||||
if (status === 'start') {
|
||||
await manager.start(key);
|
||||
} else if (status === 'stop') {
|
||||
await manager.stop(key);
|
||||
}
|
||||
const appShow = manager.getAppShowInfo(key);
|
||||
ctx.body = appShow;
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'micro-app-manager',
|
||||
key: 'update',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const { key } = ctx.query.data || {};
|
||||
if (!key) {
|
||||
ctx.body = 'key is required';
|
||||
return;
|
||||
}
|
||||
const appInfo = await manager.updateAppInfo(key, ctx.query.data);
|
||||
ctx.body = appInfo;
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
setTimeout(() => {
|
||||
manager.load().then(() => {
|
||||
console.log('load success');
|
||||
});
|
||||
}, 1000);
|
||||
|
44
src/routes/micro-app/test/m.test.ts
Normal file
44
src/routes/micro-app/test/m.test.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { App } from '@kevisual/router';
|
||||
import { Manager } from '../module/manager.ts';
|
||||
import { loadFileAppInfo } from '../module/load-app.ts';
|
||||
import path from 'path';
|
||||
|
||||
const app = new App();
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'test',
|
||||
key: 'test',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
ctx.body = 'test';
|
||||
});
|
||||
|
||||
const manager = new Manager({ mainApp: app });
|
||||
|
||||
await manager.load();
|
||||
|
||||
const { mainEntry, app: appConfig } = await loadFileAppInfo('mark');
|
||||
|
||||
// const appInfo = {
|
||||
// key: 'mark',
|
||||
// type: 'system-app',
|
||||
// entry: mainEntry,
|
||||
// status: 'running',
|
||||
// path: path.dirname(mainEntry),
|
||||
// };
|
||||
|
||||
// await manager.add(appInfo);
|
||||
|
||||
const client = await loadFileAppInfo('micro-client');
|
||||
const appInfoMicro = {
|
||||
key: 'micro-client',
|
||||
type: 'micro-app',
|
||||
entry: client.mainEntry,
|
||||
status: 'running',
|
||||
path: path.dirname(client.mainEntry),
|
||||
};
|
||||
|
||||
await manager.add(appInfoMicro);
|
||||
|
||||
app.listen(3005);
|
Loading…
x
Reference in New Issue
Block a user