feat: app manager

This commit is contained in:
2024-11-23 00:39:43 +08:00
parent 503727c6c4
commit f94c9323b4
9 changed files with 431 additions and 88 deletions

View File

@@ -1,22 +1,45 @@
import { useFileStore } from '@abearxiong/use-file-store';
import glob from 'fast-glob';
import fs from 'fs';
import { getConfigFile } from '@kevisual/use-config';
import path from 'path';
import fs from 'fs';
export const appsPath = useFileStore('apps', { needExists: true });
export const loadAppInfo = async () => {
const apps = await glob(`${appsPath}/*/\.config.json`, {
cwd: appsPath,
});
const result = apps.map((app) => {
const json = fs.readFileSync(path.join(appsPath, app), 'utf-8');
const pkg = JSON.parse(json);
const { name, version, app: appInfo } = pkg;
if (!name || !version || !appInfo) {
throw new Error('Invalid package.json');
}
return { key: app.split('/')[0], ...appInfo };
});
return result;
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({}));
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,10 +0,0 @@
import fs from 'fs';
export const fileIsExist = (filePath: string): boolean => {
try {
// 使用 F_OK 检查文件或目录是否存在
fs.accessSync(filePath, fs.constants.F_OK);
return true;
} catch (err) {
return false;
}
};

View File

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

View File

@@ -1,6 +1,7 @@
import { minioClient } from '@/app.ts';
import { bucketName } from '@/modules/minio.ts';
import { fileIsExist } from '../lib/file.ts';
import { fileIsExist } from '@kevisual/use-config';
import fs from 'fs';
import path from 'path';
import * as tar from 'tar';

View File

@@ -1,4 +1,5 @@
import { fileIsExist } from '../lib/file.ts';
import { fileIsExist } from '@kevisual/use-config';
import { useFileStore } from '@abearxiong/use-file-store';
import fs from 'fs';
import path from 'path';

View File

@@ -1,20 +1,32 @@
import { App } from '@kevisual/router';
import { loadAppInfo, AppInfoConfig, saveAppInfo } from '../lib/app-file.ts';
import { fork } from 'child_process';
export enum AppType {
SystemApp = 'system-app',
MicroApp = 'micro-app',
GatewayApp = 'gateway-app',
}
export type AppInfo = {
status?: 'active' | 'inactive'; // 默认运行状态
status?: 'inactive' | 'runing' | 'stop' | 'error'; // 运行状态
key: string;
type?: AppType; // 默认类型
entry?: string; // 入口文件
path?: string; // 文件路径
timestamp?: number; // 时间戳, 每次更新更新时间戳
process?: any; // 进程
};
type managerOptions = {
mainApp: App;
};
export class Manager<T extends AppInfo = any> {
apps: Map<string, T>;
constructor() {
mainApp: App;
appInfo: AppInfoConfig;
constructor(opts: managerOptions) {
this.apps = new Map();
this.mainApp = opts.mainApp;
this.appInfo = {} as any;
}
/**
* 检查key是否存在
@@ -28,10 +40,90 @@ export class Manager<T extends AppInfo = any> {
* 获取app信息
* @param key
*/
add(app: T) {
async loadApp(app: T) {
const mainApp = this.mainApp;
this.apps.set(app.key, app);
if (app.status === 'inactive') {
return;
}
const entry = app.entry + `?timestamp=${app?.timestamp}`;
// 注册路由
if (app.type === AppType.MicroApp) {
const process = fork(entry, [], {});
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');
}
}
load() {
/**
* 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();
const list = appInfos?.list || [];
for (const app of list) {
try {
this.loadApp(app);
} catch (e) {
console.error('load app', e);
}
}
}
async add(app: T) {
if (this.checkKey(app.key)) {
return false;
}
this.loadApp(app);
await this.saveAppInfo(app, true);
}
// 启动
async start(key: string) {
const app = this.apps.get(key);
if (!app) {
return;
}
if (app.status === 'runing') {
return;
}
app.status = 'runing';
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);
}
}