temp remove local-microapp
This commit is contained in:
@@ -1,3 +1 @@
|
||||
import './list.ts';
|
||||
|
||||
import './routes/manager.ts'
|
||||
import './list.ts';
|
||||
@@ -1,45 +1,3 @@
|
||||
import { useFileStore } from '@abearxiong/use-file-store';
|
||||
import { getConfigFile } from '@kevisual/use-config';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { useFileStore } from '@kevisual/use-config/file-store';
|
||||
|
||||
export const appsPath = useFileStore('apps', { needExists: true });
|
||||
|
||||
export type AppInfoConfig = {
|
||||
list: any[];
|
||||
[key: string]: any;
|
||||
};
|
||||
/**
|
||||
* 加载应用信息
|
||||
* @returns
|
||||
*/
|
||||
export const loadAppInfo = async (): Promise<AppInfoConfig> => {
|
||||
const pkgs = getConfigFile();
|
||||
let configFile = getConfigFile('apps.config.json');
|
||||
if (!pkgs) {
|
||||
console.error('未找到配置文件');
|
||||
return;
|
||||
}
|
||||
const basePath = path.dirname(pkgs);
|
||||
if (!configFile) {
|
||||
configFile = path.join(basePath, 'apps.config.json');
|
||||
fs.writeFileSync(configFile, JSON.stringify({ list: [] }));
|
||||
return { list: [] };
|
||||
}
|
||||
const config = fs.readFileSync(configFile, 'utf-8');
|
||||
return JSON.parse(config);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* 保存应用信息
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const saveAppInfo = async (data: any) => {
|
||||
const configFile = getConfigFile('apps.config.json');
|
||||
if (!configFile) {
|
||||
return;
|
||||
}
|
||||
fs.writeFileSync(configFile, JSON.stringify(data));
|
||||
};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { app } from '@/app.ts';
|
||||
import { MicroAppModel } from './models.ts';
|
||||
import { appPathCheck, installApp } from './module/install-app.ts';
|
||||
import { getAppPathKeys, installAppFromKey } from './module/manager.ts';
|
||||
import { loadApp } from './module/load-app.ts';
|
||||
import { manager } from './manager-app.ts';
|
||||
|
||||
// 应用上传到 应用管理 的平台
|
||||
@@ -82,29 +80,6 @@ app
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
// curl http://localhost:4002/api/router?path=micro-app&key=load
|
||||
app
|
||||
.route({
|
||||
path: 'micro-app',
|
||||
key: 'load',
|
||||
description: 'Load micro app, no use',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const { key } = ctx.query?.data;
|
||||
// const key = 'mark';
|
||||
try {
|
||||
const main = await loadApp(key);
|
||||
if (main?.loadApp) {
|
||||
await main.loadApp(app);
|
||||
ctx.body = 'success';
|
||||
return;
|
||||
}
|
||||
ctx.throw(400, 'Invalid app');
|
||||
} catch (e) {
|
||||
ctx.throw(400, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
// curl http://localhost:4002/api/router?path=micro-app&key=unload
|
||||
app
|
||||
.route({
|
||||
@@ -122,39 +97,3 @@ app
|
||||
ctx.body = main;
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'micro-app',
|
||||
key: 'detect',
|
||||
description: 'Detect micro app,检测apps的没有加载进来的app模块',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const list = manager.getAllAppShowInfo();
|
||||
const appPathKeys = await getAppPathKeys();
|
||||
const notIn = appPathKeys.filter((key) => !list.find((item) => item.key === key));
|
||||
console.log('Not in', notIn);
|
||||
const loadInfo = [];
|
||||
if (notIn.length <= 0) {
|
||||
loadInfo.push('ok');
|
||||
ctx.body = {
|
||||
data: loadInfo,
|
||||
};
|
||||
ctx.message = 'All apps are loaded';
|
||||
return;
|
||||
}
|
||||
for (const key of notIn) {
|
||||
try {
|
||||
const { showAppInfo } = await installAppFromKey(key);
|
||||
await manager.add(showAppInfo);
|
||||
loadInfo.push(`Load ${key} success`);
|
||||
} catch (e) {
|
||||
loadInfo.push(`Load ${key} error`);
|
||||
}
|
||||
}
|
||||
ctx.body = {
|
||||
data: loadInfo,
|
||||
notIn,
|
||||
};
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
import { app } from '@/app.ts';
|
||||
import { Manager } from './module/manager.ts';
|
||||
import { manager, loadManager, app as ManagerApp } from '@kevisual/local-app-manager';
|
||||
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 const manager = new Manager({ mainApp: app });
|
||||
export { manager };
|
||||
|
||||
export const manager = new Manager({ mainApp: app });
|
||||
console.log('app equal', app === ManagerApp);
|
||||
loadManager();
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { fileIsExist } from '@kevisual/use-config';
|
||||
|
||||
import { useFileStore } from '@abearxiong/use-file-store';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const appsPath = useFileStore('apps', { needExists: true });
|
||||
|
||||
export const loadFileAppInfo = async (key: string) => {
|
||||
const directory = path.join(appsPath, key);
|
||||
if (!fileIsExist(directory)) {
|
||||
throw new Error('app not found');
|
||||
}
|
||||
const pkgs = path.join(directory, 'package.json');
|
||||
if (!fileIsExist(pkgs)) {
|
||||
throw new Error('Invalid package.json');
|
||||
}
|
||||
const json = fs.readFileSync(pkgs, 'utf-8');
|
||||
const pkg = JSON.parse(json);
|
||||
const { name, version, app } = pkg;
|
||||
if (!name || !version || !app) {
|
||||
throw new Error('Invalid package.json');
|
||||
}
|
||||
|
||||
const mainEntry = path.join(directory, app.entry);
|
||||
if (!fileIsExist(mainEntry)) {
|
||||
throw new Error('Invalid main entry');
|
||||
}
|
||||
return { mainEntry, app };
|
||||
};
|
||||
export const deleteFileAppInfo = async (key: string) => {
|
||||
const directory = path.join(appsPath, key);
|
||||
if (!fileIsExist(directory)) {
|
||||
return;
|
||||
}
|
||||
fs.rmSync(directory, { recursive: true });
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param key
|
||||
* @returns
|
||||
*/
|
||||
export const loadApp = async (key: string) => {
|
||||
const { mainEntry, app } = await loadFileAppInfo(key);
|
||||
// 1. 查询数据库,获取app信息,查看是否运行中
|
||||
// 2. 如果运行中,直接返回
|
||||
// 3. 如果不在运行中,加载app
|
||||
// 3.1 查看app的类型,如果是 system-app,直接加载
|
||||
// 3.2 如果是 micro-app,查找相关的依赖,如果依赖不存在,先加载依赖
|
||||
// 3.3 使用fork加载app
|
||||
// 4. 记录app的运行状态,程序重新启动时,重新加载
|
||||
const main = await import(mainEntry);
|
||||
return main;
|
||||
};
|
||||
@@ -1,258 +1,8 @@
|
||||
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';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { appsPath } from '../lib/index.ts';
|
||||
|
||||
// 共享
|
||||
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 = {
|
||||
key: string;
|
||||
status?: 'inactive' | 'running' | 'stop' | 'error'; // 运行状态
|
||||
version?: string; // 版本
|
||||
type?: AppType; // 默认类型
|
||||
description?: string; // 描述
|
||||
timestamp?: number; // 时间戳, 每次更新更新时间戳
|
||||
process?: any; // 进程
|
||||
|
||||
origin?: Record<string, any>; // 原始数据
|
||||
entry?: string; // 入口文件
|
||||
path?: string; // 文件路径
|
||||
};
|
||||
export const onAppShowInfo = (app: AppInfo) => {
|
||||
return {
|
||||
key: app.key,
|
||||
status: app.status,
|
||||
type: app.type,
|
||||
description: app.description,
|
||||
version: app.version,
|
||||
};
|
||||
};
|
||||
export const createAppShowInfo = (app: any) => {
|
||||
return {
|
||||
key: app.key,
|
||||
status: app.status,
|
||||
type: app.type,
|
||||
description: app.description,
|
||||
version: app.version,
|
||||
};
|
||||
};
|
||||
type managerOptions = {
|
||||
mainApp: App;
|
||||
};
|
||||
export class Manager<T extends AppInfo = any> {
|
||||
apps: Map<string, T>;
|
||||
mainApp: App;
|
||||
appInfo: AppInfoConfig;
|
||||
constructor(opts: managerOptions) {
|
||||
this.apps = new Map();
|
||||
this.mainApp = opts.mainApp;
|
||||
this.appInfo = {} as any;
|
||||
}
|
||||
/**
|
||||
* 检查key是否存在
|
||||
* @param key
|
||||
* @returns
|
||||
*/
|
||||
checkKey(key: string) {
|
||||
return this.apps.has(key);
|
||||
}
|
||||
/*
|
||||
* 获取app信息
|
||||
* @param key
|
||||
*/
|
||||
async loadApp(app: T) {
|
||||
const mainApp = this.mainApp;
|
||||
this.apps.set(app.key, app);
|
||||
if (app.status !== 'running') {
|
||||
return;
|
||||
}
|
||||
if (!fileIsExist(app.path)) {
|
||||
console.error('app is not found');
|
||||
return;
|
||||
}
|
||||
const pathEntry = path.join(app.path, app.entry);
|
||||
if (!fileIsExist(pathEntry)) {
|
||||
console.error('file entry not found');
|
||||
return;
|
||||
}
|
||||
const entry = app.entry + `?timestamp=${app?.timestamp}`;
|
||||
// 注册路由
|
||||
if (app.type === AppType.MicroApp) {
|
||||
const process = fork(app.entry, [], {
|
||||
stdio: 'inherit', // 共享主进程的标准输入输出
|
||||
cwd: app.path,
|
||||
});
|
||||
app.process = process;
|
||||
} else if (app.type === AppType.SystemApp) {
|
||||
const module = await import(entry);
|
||||
if (module.loadApp) {
|
||||
await module.loadApp?.(mainApp);
|
||||
}
|
||||
} else if (app.type === AppType.GatewayApp) {
|
||||
console.log('gateway app not support');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* create new app info
|
||||
* @param app
|
||||
*/
|
||||
async saveAppInfo(app: T, newTimeData = false) {
|
||||
const list = this.appInfo.list || [];
|
||||
if (newTimeData) {
|
||||
app.timestamp = Date.now();
|
||||
}
|
||||
const { process, ...info } = app;
|
||||
list.push(info);
|
||||
this.appInfo.list = list;
|
||||
await saveAppInfo(this.appInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化应用的时候加载
|
||||
*/
|
||||
async load() {
|
||||
// 从apps文件夹列表当中中加载app信息
|
||||
const appInfos = await loadAppInfo();
|
||||
this.appInfo = appInfos;
|
||||
const list = appInfos?.list || [];
|
||||
for (const app of list) {
|
||||
try {
|
||||
const loaded = await this.loadApp(app);
|
||||
if (!loaded) {
|
||||
// 加载失败,如果是running状态,设置为error
|
||||
if (app.status === 'running') {
|
||||
app.status = 'error';
|
||||
console.log('load app error', app); // save app error info
|
||||
await this.saveAppInfo(app);
|
||||
}
|
||||
} else {
|
||||
// console.log('load app success', app);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('load app', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
async add(app: T) {
|
||||
if (this.checkKey(app.key)) {
|
||||
console.error('key is loaded');
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.saveAppInfo(app, true);
|
||||
this.loadApp(app);
|
||||
}
|
||||
// 启动
|
||||
async start(key: string) {
|
||||
const app = this.apps.get(key);
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
if (app.status === 'running') {
|
||||
return;
|
||||
}
|
||||
app.status = 'running';
|
||||
this.loadApp(app);
|
||||
await this.saveAppInfo(app);
|
||||
}
|
||||
// 停止
|
||||
async stop(key: string) {
|
||||
const app = this.apps.get(key);
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
if (app.status === 'stop') {
|
||||
return;
|
||||
}
|
||||
app.status = 'stop';
|
||||
if (app.process) {
|
||||
app.process.kill();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const installAppFromKey = async (key: string) => {
|
||||
const directory = path.join(appsPath, key);
|
||||
if (!fileIsExist(directory)) {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { app } from '@/app.ts';
|
||||
import { manager } from '../manager-app.ts';
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'micro-app-manager',
|
||||
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);
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useFileStore } from '@abearxiong/use-file-store';
|
||||
import { useFileStore } from '@kevisual/use-config/file-store';
|
||||
import { PageModel } from '../models/index.ts';
|
||||
import { ContainerModel } from '@/routes/container/models/index.ts';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
Reference in New Issue
Block a user