temp remove local-microapp

This commit is contained in:
2024-12-06 22:36:08 +08:00
parent b2d968b70d
commit 3efc3b3cc5
14 changed files with 362 additions and 560 deletions

View File

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

View File

@@ -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));
};

View File

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

View File

@@ -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();

View File

@@ -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;
};

View File

@@ -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)) {

View File

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

View File

@@ -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';