IPlugin 入口
backend/src/index.ts 默认导出实现 IPlugin 的类,返回根 @Module 类以及来自 module.metadata.json 的元数据。
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 类上
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。
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}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 事件监听,或需要其他插件服务已就绪的工作。
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 等资源。
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}启动与卸载顺序
- 01
扫描已安装插件
仅从 installed-plugins 加载 installed=true 的模块。
- 02
导入 module.js
实例化 IPlugin 并调用 getRootModule()。
- 03
DI + 实体
装配服务并同步表结构。
- 04
@OnInit
按模块/服务初始化 — seed 与本地设置。
- 05
@OnAllModuleLoaded
跨模块钩子 — worker、cron 监听。
- 06
@OnUninstall
卸载插件时 — 清理资源。
构建生命周期
使用 quan-erp watch <plugin>(部分仓库为 quan-erp watch)将 backend + frontend 编译到 base/available-plugins/,再从 ERP UI 安装。