From 92e034b57cb2385639dd1ce648e79dc876980553 Mon Sep 17 00:00:00 2001 From: Jerry Date: Tue, 8 Jul 2025 18:41:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pnpm-workspace.yaml | 2 + src/core/app/core.ts | 60 +++++++++++++++++++++++++++++ src/core/app/loader.ts | 46 +++++++++++++++++++++- src/core/types/plugin.ts | 23 ++++++++--- src/plugins/example/index.ts | 26 +++++++++++++ src/plugins/example/main.service.ts | 7 ++++ src/plugins/example/package.json | 10 +++++ tsconfig.json | 6 ++- 8 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 pnpm-workspace.yaml create mode 100644 src/core/app/core.ts create mode 100644 src/plugins/example/index.ts create mode 100644 src/plugins/example/main.service.ts create mode 100644 src/plugins/example/package.json diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..1d9dd6d --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'src/plugins/*' \ No newline at end of file diff --git a/src/core/app/core.ts b/src/core/app/core.ts new file mode 100644 index 0000000..9f1384c --- /dev/null +++ b/src/core/app/core.ts @@ -0,0 +1,60 @@ +class Core { + private static _instance: Core; + private _services: Map = 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; diff --git a/src/core/app/loader.ts b/src/core/app/loader.ts index 039c8f5..1cf2f4a 100644 --- a/src/core/app/loader.ts +++ b/src/core/app/loader.ts @@ -3,9 +3,10 @@ import fs from 'fs/promises'; import simpleGit, { SimpleGit } from 'simple-git'; import { Application } from 'express'; import { Server } from 'http'; -import Plugin from '../types/plugin'; +import Plugin from './../types/plugin'; import logger from '../utils/system/logger'; import paths from '../utils/system/path'; +import Core from './core'; class PluginLoader { private readonly pluginsDir: string; @@ -21,11 +22,13 @@ class PluginLoader { public async loadPlugins(): Promise { try { + logger.info('正在加载插件..'); const pluginFolders = await fs.readdir(this.pluginsDir); await Promise.all( pluginFolders.map(async (folder) => { const pluginPath = path.join(this.pluginsDir, folder); + logger.debug(`加载${folder}插件..`); const stat = await fs.stat(pluginPath); if (stat.isDirectory()) { @@ -40,6 +43,11 @@ class PluginLoader { } } + /** + * 加载插件 + * @param pluginPath + * @private + */ private async loadPlugin(pluginPath: string): Promise { try { const pluginName = path.basename(pluginPath); @@ -63,6 +71,13 @@ class PluginLoader { return; } + try { + await this.checkDependencies(plugin); + } catch (err) { + logger.error(`插件依赖检查失败: ${plugin.name}`, err); + return; + } + if (plugin.initialize) { await plugin.initialize(this.app, this.server); } @@ -78,6 +93,11 @@ class PluginLoader { } } + /** + * 检查更新 + * @param pluginPath + * @private + */ private async checkPluginUpdates(pluginPath: string): Promise { try { const git = simpleGit(pluginPath); @@ -99,6 +119,11 @@ class PluginLoader { } } + /** + * 更新插件 + * @param pluginPath + * @private + */ private async updatePlugin(pluginPath: string): Promise { try { const git = this.gitInstances.get(pluginPath); @@ -116,6 +141,25 @@ class PluginLoader { } } + /** + * 检查依赖 + * @param plugin + * @private + */ + private async checkDependencies(plugin: Plugin): Promise { + 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 { await this.invokePluginHooks('onClose'); this.loadedPlugins.clear(); diff --git a/src/core/types/plugin.ts b/src/core/types/plugin.ts index 0c1221b..e5a4f3f 100644 --- a/src/core/types/plugin.ts +++ b/src/core/types/plugin.ts @@ -1,22 +1,33 @@ import { Application } from 'express'; import { Server } from 'http'; -interface Plugin { +export default interface Plugin { + // 必须字段 name: string; version: string; + + // 可选描述 description?: string; - // 初始化插件 + + // 依赖的核心服务 (如 ['logger', 'redis']) + dependencies?: string[]; + + // 初始化方法 initialize?: (app: Application, server?: Server) => void | Promise; - // 路由挂载点 + + // 路由注册方法 routes?: (app: Application) => void; + // 生命周期钩子 onReady?: () => void | Promise; onClose?: () => void | Promise; onError?: (error: Error) => void; - // 自动更新相关 + + // 自动更新配置 autoUpdateEnabled?: boolean; checkForUpdates?: () => Promise; applyUpdate?: () => Promise; -} -export default Plugin; + // 其他配置 + [key: string]: any; +} diff --git a/src/plugins/example/index.ts b/src/plugins/example/index.ts new file mode 100644 index 0000000..d2b2252 --- /dev/null +++ b/src/plugins/example/index.ts @@ -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; diff --git a/src/plugins/example/main.service.ts b/src/plugins/example/main.service.ts new file mode 100644 index 0000000..d43b2d0 --- /dev/null +++ b/src/plugins/example/main.service.ts @@ -0,0 +1,7 @@ +import logger from '../../core/utils/system/logger'; + +export default class MainService { + constructor() { + logger.info('示例插件初始化..'); + } +} diff --git a/src/plugins/example/package.json b/src/plugins/example/package.json new file mode 100644 index 0000000..00d8a4e --- /dev/null +++ b/src/plugins/example/package.json @@ -0,0 +1,10 @@ +{ + "name": "plugin-name", + "version": "1.0.0", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "crystelf": { + "type": "plugin", + "dependencies": [] + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 1efdceb..7596273 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,9 @@ "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, - "outDir": "dist" + "outDir": "dist", + "rootDir": ".", }, - "include": ["src"] + "include": ["src/**/*", "src/plugins/**/*"], + "exclude": ["node_modules"] }