add route for app module micro
This commit is contained in:
parent
f94c9323b4
commit
01680b39c2
@ -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"
|
||||||
|
@ -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)
|
||||||
|
@ -1 +1,3 @@
|
|||||||
import './list.ts';
|
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 { 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');
|
||||||
}
|
}
|
||||||
|
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;
|
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 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
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