This commit is contained in:
Jerry 2025-07-08 18:41:21 +08:00
parent d43b05bae5
commit 92e034b57c
8 changed files with 171 additions and 9 deletions

2
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,2 @@
packages:
- 'src/plugins/*'

60
src/core/app/core.ts Normal file
View File

@ -0,0 +1,60 @@
class Core {
private static _instance: Core;
private _services: Map<string, any> = new Map();
[key: string]: any;
private constructor() {}
public static get logger() {
return Core.getInstance()._services.get('logger');
}
public static get redis() {
return Core.getInstance()._services.get('redis');
}
public static get ws() {
return Core.getInstance()._services.get('ws');
}
public static get tools() {
return Core.getInstance()._services.get('tools');
}
public static get system() {
return Core.getInstance()._services.get('system');
}
public static get response() {
return Core.getInstance()._services.get('response');
}
public static get file() {
return Core.getInstance()._services.get('file');
}
/**
*
* @param name
* @param service
*/
public static registerService(name: string, service: any): void {
Core.getInstance()._services.set(name, service);
}
private static getInstance(): Core {
if (!Core._instance) {
Core._instance = new Core();
}
return Core._instance;
}
/**
*
* @param name
*/
public static hasService(name: string): boolean {
return Core.getInstance()._services.has(name);
}
}
export default Core;

View File

@ -3,9 +3,10 @@ import fs from 'fs/promises';
import simpleGit, { SimpleGit } from 'simple-git'; import simpleGit, { SimpleGit } from 'simple-git';
import { Application } from 'express'; import { Application } from 'express';
import { Server } from 'http'; import { Server } from 'http';
import Plugin from '../types/plugin'; import Plugin from './../types/plugin';
import logger from '../utils/system/logger'; import logger from '../utils/system/logger';
import paths from '../utils/system/path'; import paths from '../utils/system/path';
import Core from './core';
class PluginLoader { class PluginLoader {
private readonly pluginsDir: string; private readonly pluginsDir: string;
@ -21,11 +22,13 @@ class PluginLoader {
public async loadPlugins(): Promise<void> { public async loadPlugins(): Promise<void> {
try { try {
logger.info('正在加载插件..');
const pluginFolders = await fs.readdir(this.pluginsDir); const pluginFolders = await fs.readdir(this.pluginsDir);
await Promise.all( await Promise.all(
pluginFolders.map(async (folder) => { pluginFolders.map(async (folder) => {
const pluginPath = path.join(this.pluginsDir, folder); const pluginPath = path.join(this.pluginsDir, folder);
logger.debug(`加载${folder}插件..`);
const stat = await fs.stat(pluginPath); const stat = await fs.stat(pluginPath);
if (stat.isDirectory()) { if (stat.isDirectory()) {
@ -40,6 +43,11 @@ class PluginLoader {
} }
} }
/**
*
* @param pluginPath
* @private
*/
private async loadPlugin(pluginPath: string): Promise<void> { private async loadPlugin(pluginPath: string): Promise<void> {
try { try {
const pluginName = path.basename(pluginPath); const pluginName = path.basename(pluginPath);
@ -63,6 +71,13 @@ class PluginLoader {
return; return;
} }
try {
await this.checkDependencies(plugin);
} catch (err) {
logger.error(`插件依赖检查失败: ${plugin.name}`, err);
return;
}
if (plugin.initialize) { if (plugin.initialize) {
await plugin.initialize(this.app, this.server); await plugin.initialize(this.app, this.server);
} }
@ -78,6 +93,11 @@ class PluginLoader {
} }
} }
/**
*
* @param pluginPath
* @private
*/
private async checkPluginUpdates(pluginPath: string): Promise<boolean> { private async checkPluginUpdates(pluginPath: string): Promise<boolean> {
try { try {
const git = simpleGit(pluginPath); const git = simpleGit(pluginPath);
@ -99,6 +119,11 @@ class PluginLoader {
} }
} }
/**
*
* @param pluginPath
* @private
*/
private async updatePlugin(pluginPath: string): Promise<void> { private async updatePlugin(pluginPath: string): Promise<void> {
try { try {
const git = this.gitInstances.get(pluginPath); const git = this.gitInstances.get(pluginPath);
@ -116,6 +141,25 @@ class PluginLoader {
} }
} }
/**
*
* @param plugin
* @private
*/
private async checkDependencies(plugin: Plugin): Promise<boolean> {
if (!plugin.dependencies) return true;
for (const dep of plugin.dependencies) {
if (!Core.hasService(dep)) {
throw new Error(`插件 ${plugin.name} 缺少依赖服务: ${dep}`);
}
}
return true;
}
/**
*
*/
public async closePlugins(): Promise<void> { public async closePlugins(): Promise<void> {
await this.invokePluginHooks('onClose'); await this.invokePluginHooks('onClose');
this.loadedPlugins.clear(); this.loadedPlugins.clear();

View File

@ -1,22 +1,33 @@
import { Application } from 'express'; import { Application } from 'express';
import { Server } from 'http'; import { Server } from 'http';
interface Plugin { export default interface Plugin {
// 必须字段
name: string; name: string;
version: string; version: string;
// 可选描述
description?: string; description?: string;
// 初始化插件
// 依赖的核心服务 (如 ['logger', 'redis'])
dependencies?: string[];
// 初始化方法
initialize?: (app: Application, server?: Server) => void | Promise<void>; initialize?: (app: Application, server?: Server) => void | Promise<void>;
// 路由挂载点
// 路由注册方法
routes?: (app: Application) => void; routes?: (app: Application) => void;
// 生命周期钩子 // 生命周期钩子
onReady?: () => void | Promise<void>; onReady?: () => void | Promise<void>;
onClose?: () => void | Promise<void>; onClose?: () => void | Promise<void>;
onError?: (error: Error) => void; onError?: (error: Error) => void;
// 自动更新相关
// 自动更新配置
autoUpdateEnabled?: boolean; autoUpdateEnabled?: boolean;
checkForUpdates?: () => Promise<boolean>; checkForUpdates?: () => Promise<boolean>;
applyUpdate?: () => Promise<void>; applyUpdate?: () => Promise<void>;
}
export default Plugin; // 其他配置
[key: string]: any;
}

View File

@ -0,0 +1,26 @@
import Plugin from '../../core/types/plugin';
import MainService from './main.service';
import Core from './../../core/app/core';
const plugin: Plugin = {
name: 'example',
version: '1.0.0',
dependencies: ['logger'],
initialize() {
this.service = new MainService();
Core.registerService('example', this.service);
},
routes(app) {
app.get('/api/example', (req, res) => {
Core.response.success(res, { message: 'Hello from plugin' });
});
},
onClose() {
this.service.cleanup();
},
};
export default plugin;

View File

@ -0,0 +1,7 @@
import logger from '../../core/utils/system/logger';
export default class MainService {
constructor() {
logger.info('示例插件初始化..');
}
}

View File

@ -0,0 +1,10 @@
{
"name": "plugin-name",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"crystelf": {
"type": "plugin",
"dependencies": []
}
}

View File

@ -6,7 +6,9 @@
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"strict": true, "strict": true,
"skipLibCheck": true, "skipLibCheck": true,
"outDir": "dist" "outDir": "dist",
"rootDir": ".",
}, },
"include": ["src"] "include": ["src/**/*", "src/plugins/**/*"],
"exclude": ["node_modules"]
} }