演示

模块生命周期

后端插件的打包、加载与卸载——@Module 以及生命周期钩子 @OnInit、@OnAllModuleLoaded、@OnUninstall。

IPlugin 入口

backend/src/index.ts 默认导出实现 IPlugin 的类,返回根 @Module 类以及来自 module.metadata.json 的元数据。

TSXbackend/src/index.ts
1import type { IPlugin, PluginMetadata } from "@quan-erp/shared-types"; 2import metadata from "../module.metadata.json" with { type: "json" }; 3import { MyPluginModule } from "./my-plugin.module.js"; 4 5export default class MyPlugin implements IPlugin { 6 getRootModule() { return MyPluginModule; } 7 getName() { return metadata.name; } 8 getVersion() { return metadata.pluginVersion; } 9 getMetadata() { return metadata as PluginMetadata; } 10}

根 @Module

注册 providers、controllers、entities(plugin: "default")以及可选的 websocket 处理器。可在同一类上搭配 @Cache。生命周期钩子通常写在该类(或某个 @Service)上。

  • @Module({ name, providers, controllers, entities, websocket? })
  • @Cache(options) — 内存或 redis;只能加在 @Module 类上
TSXmodule
1import { Cache, Module } from "@quan-erp/shared-backend-core"; 2import metadata from "../../module.metadata.json" with { type: "json" }; 3import { ChatWebsocket } from "./chat.websocket.js"; 4import { MyController } from "./my.controller.js"; 5import { MyEntity } from "./my.entity.js"; 6import { MyService } from "./my.service.js"; 7 8@Module({ 9 name: metadata.name, 10 providers: [MyService], 11 controllers: [MyController], 12 entities: [{ plugin: "default", entities: [MyEntity] }], 13 websocket: [ChatWebsocket], 14}) 15@Cache({ type: "in-memory", checkperiod: 1000 }) 16export class MyPluginModule {}

生命周期注解

三个钩子覆盖启动与卸载。写在根 @Module(或受 DI 管理的 @Service)的方法上。

  • @OnInit() — 本模块/服务就绪;适合 seed / 本地初始化
  • @OnAllModuleLoaded() — 所有模块完成 @OnInit;适合 worker、cron 监听、跨插件调用
  • @OnUninstall() — 卸载插件时;断开队列/停止 worker

@OnInit

宿主模块或服务完成初始化后执行一次。适合默认配置与 seed。不要在此启动依赖其他插件的 worker — 请用 @OnAllModuleLoaded。

TSX@OnInit on module
1import { 2 ContainerRegistryManager, 3 DataSeedHistoryService, 4 Inject, 5 InjectBuiltinLogger, 6 Module, 7 OnInit, 8} from "@quan-erp/shared-backend-core"; 9import type { Loggable } from "@quan-erp/shared-backend-core"; 10import metadata from "../../module.metadata.json" with { type: "json" }; 11import { MyService } from "./my.service.js"; 12 13@Module({ 14 name: metadata.name, 15 providers: [MyService], 16 controllers: [], 17 entities: [], 18}) 19export class MyPluginModule { 20 @InjectBuiltinLogger() 21 logger: Loggable; 22 23 @Inject(DataSeedHistoryService, ContainerRegistryManager.BUILTIN_PLUGIN) 24 dataSeedHistoryService: DataSeedHistoryService; 25 26 @OnInit() 27 async init() { 28 const alreadySeeded = await this.dataSeedHistoryService.find( 29 metadata.name, 30 metadata.pluginVersion, 31 "init", 32 ); 33 if (alreadySeeded) { 34 this.logger.log("Seeding skipped (already initialized)"); 35 return; 36 } 37 // … seed defaults … 38 await this.dataSeedHistoryService.add({ 39 data: { 40 pluginName: metadata.name, 41 pluginVersion: metadata.pluginVersion, 42 name: "init", 43 }, 44 }); 45 this.logger.log("Module seeded successfully"); 46 } 47}
TSX@OnInit on service
1import { InjectBuiltinLogger, OnInit, Service } from "@quan-erp/shared-backend-core"; 2import type { Loggable } from "@quan-erp/shared-backend-core"; 3 4@Service() 5export class CacheWarmupService { 6 @InjectBuiltinLogger() 7 logger: Loggable; 8 9 @OnInit() 10 async init() { 11 this.logger.log("Warming local caches"); 12 // … 13 } 14}

@OnAllModuleLoaded

所有已安装模块完成 @OnInit 后执行一次。用于后台 worker、CronJobService 事件监听,或需要其他插件服务已就绪的工作。

TSX@OnAllModuleLoaded
1import { 2 ContainerRegistryManager, 3 CronJobService, 4 Inject, 5 InjectBuiltinLogger, 6 Module, 7 OnAllModuleLoaded, 8} from "@quan-erp/shared-backend-core"; 9import type { Loggable } from "@quan-erp/shared-backend-core"; 10import metadata from "../../module.metadata.json" with { type: "json" }; 11import { MaturedSweepService } from "./matured-sweep.service.js"; 12 13@Module({ 14 name: metadata.name, 15 providers: [MaturedSweepService], 16 controllers: [], 17 entities: [], 18}) 19export class MyPluginModule { 20 @InjectBuiltinLogger() 21 logger: Loggable; 22 23 @Inject(CronJobService, ContainerRegistryManager.BUILTIN_PLUGIN) 24 cronJobService: CronJobService; 25 26 @Inject(MaturedSweepService) 27 sweep: MaturedSweepService; 28 29 @OnAllModuleLoaded() 30 async onAllModuleLoaded() { 31 this.logger.log("All modules loaded — attaching cron listeners"); 32 this.cronJobService.addEventListener( 33 metadata.name, 34 "matured-sweep", 35 () => this.sweep.run(), 36 ); 37 } 38}

@OnUninstall

插件从系统卸载时执行。断开在 @OnInit / @OnAllModuleLoaded 中启动的 Redis/BullMQ worker、socket 等资源。

TSX@OnUninstall
1import { 2 InjectBuiltinLogger, 3 Module, 4 OnUninstall, 5} from "@quan-erp/shared-backend-core"; 6import type { Loggable } from "@quan-erp/shared-backend-core"; 7import metadata from "../../module.metadata.json" with { type: "json" }; 8import { BackgroundWorker } from "./background.worker.js"; 9 10@Module({ 11 name: metadata.name, 12 providers: [BackgroundWorker], 13 controllers: [], 14 entities: [], 15}) 16export class MyPluginModule { 17 @InjectBuiltinLogger() 18 logger: Loggable; 19 20 private worker = new BackgroundWorker(); 21 22 @OnUninstall() 23 async cleanup() { 24 this.logger.log("Uninstalling — stopping worker"); 25 await this.worker.disconnect(); 26 } 27}

启动与卸载顺序

  1. 01

    扫描已安装插件

    仅从 installed-plugins 加载 installed=true 的模块。

  2. 02

    导入 module.js

    实例化 IPlugin 并调用 getRootModule()。

  3. 03

    DI + 实体

    装配服务并同步表结构。

  4. 04

    @OnInit

    按模块/服务初始化 — seed 与本地设置。

  5. 05

    @OnAllModuleLoaded

    跨模块钩子 — worker、cron 监听。

  6. 06

    @OnUninstall

    卸载插件时 — 清理资源。

构建生命周期

使用 quan-erp watch <plugin>(部分仓库为 quan-erp watch)将 backend + frontend 编译到 base/available-plugins/,再从 ERP UI 安装。