add route for app module micro

This commit is contained in:
xion 2024-11-23 23:52:21 +08:00
parent f94c9323b4
commit 01680b39c2
11 changed files with 216 additions and 19 deletions

View File

@ -85,7 +85,6 @@
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"concurrently": "^9.1.0", "concurrently": "^9.1.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"glob": "^11.0.0",
"nodemon": "^3.1.7", "nodemon": "^3.1.7",
"pm2": "^5.4.3", "pm2": "^5.4.3",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
@ -97,7 +96,6 @@
"typescript": "^5.6.3" "typescript": "^5.6.3"
}, },
"resolutions": { "resolutions": {
"glob": "latest",
"inflight": "latest", "inflight": "latest",
"rimraf": "latest", "rimraf": "latest",
"picomatch": "^4.0.2" "picomatch": "^4.0.2"

View File

@ -1,5 +1,5 @@
import path from 'path' import path from 'path'
import * as glob from 'glob' import * as glob from 'fast-glob'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url'
import { dirname } from 'path' import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url) const __filename = fileURLToPath(import.meta.url)

View File

@ -1 +1,3 @@
import './list.ts'; import './list.ts';
import './routes/manager.ts'

View File

@ -0,0 +1 @@
export * from './app-file.ts'

View File

@ -1,6 +1,6 @@
import { app } from '@/app.ts'; import { app } from '@/app.ts';
import { MicroAppModel } from './models.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'; import { loadApp } from './module/load-app.ts';
// 应用上传到 应用管理 的平台 // 应用上传到 应用管理 的平台
@ -64,7 +64,7 @@ app
ctx.throw(404, 'Invalid path'); ctx.throw(404, 'Invalid path');
} }
console.log('path', path); console.log('path', path);
const check = await appCheck({ key }); const check = await appPathCheck({ key });
if (check) { if (check) {
ctx.throw(400, 'App already exists, please remove it first'); ctx.throw(400, 'App already exists, please remove it first');
} }

View File

@ -0,0 +1,4 @@
import { app } from '@/app.ts';
import { Manager } from './module/manager.ts';
export const manager = new Manager({ mainApp: app });

View File

@ -11,7 +11,12 @@ export type InstallAppOpts = {
path?: string; path?: string;
key?: string; key?: string;
}; };
export const appCheck = async (opts: InstallAppOpts) => { /**
*
* @param opts
* @returns
*/
export const appPathCheck = async (opts: InstallAppOpts) => {
const { key } = opts; const { key } = opts;
const directory = path.join(appsPath, key); const directory = path.join(appsPath, key);
if (fileIsExist(directory)) { if (fileIsExist(directory)) {
@ -55,7 +60,5 @@ export const installApp = async (opts: InstallAppOpts) => {
app.key = key; app.key = key;
fs.writeFileSync(pkgs, JSON.stringify(pkg, null, 2)); fs.writeFileSync(pkgs, JSON.stringify(pkg, null, 2));
// fs.unlinkSync(filePath); // fs.unlinkSync(filePath);
return { path: filePath }; return { path: filePath, pkg };
}; };

View File

@ -6,7 +6,7 @@ import path from 'path';
export const appsPath = useFileStore('apps', { needExists: true }); 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); const directory = path.join(appsPath, key);
if (!fileIsExist(directory)) { if (!fileIsExist(directory)) {
throw new Error('app not found'); throw new Error('app not found');
@ -28,9 +28,16 @@ export const loadAppInfo = async (key: string) => {
} }
return { mainEntry, app }; 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) => { export const loadApp = async (key: string) => {
const { mainEntry } = await loadAppInfo(key); const { mainEntry, app } = await loadFileAppInfo(key);
// 1. 查询数据库获取app信息查看是否运行中 // 1. 查询数据库获取app信息查看是否运行中
// 2. 如果运行中,直接返回 // 2. 如果运行中,直接返回
// 3. 如果不在运行中加载app // 3. 如果不在运行中加载app

View File

@ -1,20 +1,45 @@
import { App } from '@kevisual/router'; import { App } from '@kevisual/router';
import { loadAppInfo, AppInfoConfig, saveAppInfo } from '../lib/app-file.ts'; import { loadAppInfo, AppInfoConfig, saveAppInfo } from '../lib/app-file.ts';
import { fork } from 'child_process'; 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 { export enum AppType {
SystemApp = 'system-app', SystemApp = 'system-app',
MicroApp = 'micro-app', MicroApp = 'micro-app',
GatewayApp = 'gateway-app', GatewayApp = 'gateway-app',
} }
export type AppInfo = { export type AppInfo = {
status?: 'inactive' | 'runing' | 'stop' | 'error'; // 运行状态
key: string; key: string;
status?: 'inactive' | 'running' | 'stop' | 'error'; // 运行状态
type?: AppType; // 默认类型 type?: AppType; // 默认类型
entry?: string; // 入口文件 entry?: string; // 入口文件
path?: string; // 文件路径 path?: string; // 文件路径
timestamp?: number; // 时间戳, 每次更新更新时间戳 timestamp?: number; // 时间戳, 每次更新更新时间戳
process?: any; // 进程 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 = { type managerOptions = {
mainApp: App; mainApp: App;
@ -43,13 +68,20 @@ export class Manager<T extends AppInfo = any> {
async loadApp(app: T) { async loadApp(app: T) {
const mainApp = this.mainApp; const mainApp = this.mainApp;
this.apps.set(app.key, app); 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; return;
} }
const entry = app.entry + `?timestamp=${app?.timestamp}`; const entry = app.entry + `?timestamp=${app?.timestamp}`;
// 注册路由 // 注册路由
if (app.type === AppType.MicroApp) { if (app.type === AppType.MicroApp) {
const process = fork(entry, [], {}); const process = fork(app.entry, [], {
stdio: 'inherit', // 共享主进程的标准输入输出
cwd: app.path,
});
app.process = process; app.process = process;
} else if (app.type === AppType.SystemApp) { } else if (app.type === AppType.SystemApp) {
const module = await import(entry); const module = await import(entry);
@ -92,11 +124,12 @@ export class Manager<T extends AppInfo = any> {
} }
async add(app: T) { async add(app: T) {
if (this.checkKey(app.key)) { if (this.checkKey(app.key)) {
console.error('key is loaded');
return false; return false;
} }
this.loadApp(app);
await this.saveAppInfo(app, true); await this.saveAppInfo(app, true);
this.loadApp(app);
} }
// 启动 // 启动
async start(key: string) { async start(key: string) {
@ -104,10 +137,10 @@ export class Manager<T extends AppInfo = any> {
if (!app) { if (!app) {
return; return;
} }
if (app.status === 'runing') { if (app.status === 'running') {
return; return;
} }
app.status = 'runing'; app.status = 'running';
this.loadApp(app); this.loadApp(app);
await this.saveAppInfo(app); await this.saveAppInfo(app);
} }
@ -126,4 +159,64 @@ export class Manager<T extends AppInfo = any> {
} }
await this.saveAppInfo(app); 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);
}
}
} }

View File

@ -1,4 +1,5 @@
import { app } from '@/app.ts'; import { app } from '@/app.ts';
import { manager } from '../manager-app.ts';
app app
.route({ .route({
@ -6,6 +7,50 @@ app
key: 'list', key: 'list',
}) })
.define(async (ctx) => { .define(async (ctx) => {
// const list = manager.getAllAppShowInfo();
ctx.body = list;
}) })
.addTo(app); .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);

View 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);