From 01680b39c2296860872c30b27995a29c7d13c8a0 Mon Sep 17 00:00:00 2001 From: xion Date: Sat, 23 Nov 2024 23:52:21 +0800 Subject: [PATCH] add route for app module micro --- package.json | 2 - src/load-apps.ts | 2 +- src/routes/micro-app/index.ts | 2 + src/routes/micro-app/lib/index.ts | 1 + src/routes/micro-app/list.ts | 4 +- src/routes/micro-app/manager-app.ts | 4 + src/routes/micro-app/module/install-app.ts | 11 ++- src/routes/micro-app/module/load-app.ts | 11 ++- src/routes/micro-app/module/manager.ts | 107 +++++++++++++++++++-- src/routes/micro-app/routes/manager.ts | 47 ++++++++- src/routes/micro-app/test/m.test.ts | 44 +++++++++ 11 files changed, 216 insertions(+), 19 deletions(-) create mode 100644 src/routes/micro-app/manager-app.ts create mode 100644 src/routes/micro-app/test/m.test.ts diff --git a/package.json b/package.json index 8a6fd56..f4cea1d 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/load-apps.ts b/src/load-apps.ts index 7702dc7..482fae8 100644 --- a/src/load-apps.ts +++ b/src/load-apps.ts @@ -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) diff --git a/src/routes/micro-app/index.ts b/src/routes/micro-app/index.ts index 83ec5cd..4369fe9 100644 --- a/src/routes/micro-app/index.ts +++ b/src/routes/micro-app/index.ts @@ -1 +1,3 @@ import './list.ts'; + +import './routes/manager.ts' \ No newline at end of file diff --git a/src/routes/micro-app/lib/index.ts b/src/routes/micro-app/lib/index.ts index e69de29..46d3772 100644 --- a/src/routes/micro-app/lib/index.ts +++ b/src/routes/micro-app/lib/index.ts @@ -0,0 +1 @@ +export * from './app-file.ts' \ No newline at end of file diff --git a/src/routes/micro-app/list.ts b/src/routes/micro-app/list.ts index 27dd9ff..39fa5be 100644 --- a/src/routes/micro-app/list.ts +++ b/src/routes/micro-app/list.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'); } diff --git a/src/routes/micro-app/manager-app.ts b/src/routes/micro-app/manager-app.ts new file mode 100644 index 0000000..320e66c --- /dev/null +++ b/src/routes/micro-app/manager-app.ts @@ -0,0 +1,4 @@ +import { app } from '@/app.ts'; +import { Manager } from './module/manager.ts'; + +export const manager = new Manager({ mainApp: app }); diff --git a/src/routes/micro-app/module/install-app.ts b/src/routes/micro-app/module/install-app.ts index 9ee6648..9ae9364 100644 --- a/src/routes/micro-app/module/install-app.ts +++ b/src/routes/micro-app/module/install-app.ts @@ -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 }; }; - - diff --git a/src/routes/micro-app/module/load-app.ts b/src/routes/micro-app/module/load-app.ts index c3b74ba..b557084 100644 --- a/src/routes/micro-app/module/load-app.ts +++ b/src/routes/micro-app/module/load-app.ts @@ -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 diff --git a/src/routes/micro-app/module/manager.ts b/src/routes/micro-app/module/manager.ts index 8eeb5fe..d5b8ff4 100644 --- a/src/routes/micro-app/module/manager.ts +++ b/src/routes/micro-app/module/manager.ts @@ -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 { 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 { } 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 { 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 { } 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) { + 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); + } + } } diff --git a/src/routes/micro-app/routes/manager.ts b/src/routes/micro-app/routes/manager.ts index bdaf9b7..cd62f5d 100644 --- a/src/routes/micro-app/routes/manager.ts +++ b/src/routes/micro-app/routes/manager.ts @@ -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); diff --git a/src/routes/micro-app/test/m.test.ts b/src/routes/micro-app/test/m.test.ts new file mode 100644 index 0000000..848f426 --- /dev/null +++ b/src/routes/micro-app/test/m.test.ts @@ -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);