技术栈
- PostgreSQL — 主持久化存储(环境变量来自 PluginEnv / base .env)
- TypeORM — 实体、仓储、QueryRunner、迁移
- 默认数据源 — 共享核心连接;多数插件用 plugin: "default" 并入
- 自定义数据源 — 在根 @Module 上用 @Database;同一插件可声明多个命名连接
- 跨插件复用 — @InjectDatabaseSource("other-plugin", "connection-name?")
@InjectDatabaseSource
在服务属性上注入 TypeORM DataSource(不支持构造函数注入)。第一个参数是拥有该连接的插件;第二个参数是可选连接名(该插件声明了多个 @Database 时使用)。
- DataSourceManager.DEFAULT_PLUGIN — 主核心数据源
- metadata.name — 本插件自己的默认自定义连接
- "other-plugin" — 其他插件创建的连接
- 第二个参数 name — 所有者插件有多个 @Database 时的命名连接(如 "analytics")
TSXinject
1import { DataSource } from "typeorm";
2import {
3 InjectDatabaseSource,
4 DataSourceManager,
5 Service,
6} from "@quan-erp/shared-backend-core";
7import metadata from "../../module.metadata.json" with { type: "json" };
8import { OrderEntity } from "./order.entity.js";
9
10@Service()
11export class OrderService {
12 // Shared core Postgres
13 @InjectDatabaseSource(DataSourceManager.DEFAULT_PLUGIN)
14 source: DataSource;
15
16 // This plugin’s own connection (default name)
17 @InjectDatabaseSource(metadata.name)
18 pluginSource: DataSource;
19
20 // This plugin’s named connection
21 @InjectDatabaseSource(metadata.name, "analytics")
22 analyticsSource: DataSource;
23
24 // Connection owned by another plugin
25 @InjectDatabaseSource("warehouse", "replica")
26 warehouseReplica: DataSource;
27
28 private get repo() {
29 return this.source.getRepository(OrderEntity);
30 }
31}定义实体
表名必须以插件名开头。在模板字符串中使用 metadata.name。始终继承 BaseEntity 以获得创建/更新/软删除列。
TSXentity
1import { BaseEntity } from "@quan-erp/shared-backend-core";
2import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
3import metadata from "../../../../module.metadata.json" with { type: "json" };
4
5@Entity(`${metadata.name}_driver_document`)
6export class DriverDocumentEntity extends BaseEntity {
7 @PrimaryGeneratedColumn("increment")
8 id: number;
9
10 @Column()
11 documentName: string;
12}在模块中注册
按数据源分组实体。主共享连接用 plugin: "default"。自定义 @Database 时,plugin(及可选 name)须与该连接匹配。
TSXdefault datasource
1import { Module } from "@quan-erp/shared-backend-core";
2import metadata from "../../module.metadata.json" with { type: "json" };
3import { OrderController } from "./order.controller.js";
4import { OrderService } from "./order.service.js";
5import { DriverDocumentEntity } from "./driver-document.entity.js";
6
7@Module({
8 name: metadata.name,
9 providers: [OrderService],
10 controllers: [OrderController],
11 entities: [
12 {
13 plugin: "default",
14 entities: [DriverDocumentEntity],
15 },
16 ],
17})
18export class MyPluginModule {}自定义数据源(@Database)
在根模块上用一个或多个 @Database 声明 TypeORM 连接。仅在确实需要隔离(独立主机/schema、报表库、副本等)时使用。凭据从环境变量读取,不要硬编码。
- @Database({ name?, options }) 加在根 @Module 类上
- name — 同一插件打开多个库时需要
- options — TypeORM DataSourceOptions
- 实体 — 用匹配的 plugin + name 注册
- 同一模块类上多个 @Database = 多条连接
TSXmultiple connections in one plugin
1import {
2 Database,
3 Module,
4} from "@quan-erp/shared-backend-core";
5import metadata from "../../module.metadata.json" with { type: "json" };
6import { AnalyticsEntity } from "./analytics.entity.js";
7import { WarehouseEntity } from "./warehouse.entity.js";
8import { AnalyticsService } from "./analytics.service.js";
9
10@Database({
11 name: "analytics",
12 options: {
13 type: "postgres",
14 host: process.env.ANALYTICS_DB_HOST,
15 port: Number(process.env.ANALYTICS_DB_PORT ?? 5432),
16 username: process.env.ANALYTICS_DB_USERNAME,
17 password: process.env.ANALYTICS_DB_PASSWORD,
18 database: process.env.ANALYTICS_DB_SCHEMA,
19 synchronize: process.env.ANALYTICS_DB_SYNC === "true",
20 },
21})
22@Database({
23 name: "replica",
24 options: {
25 type: "postgres",
26 host: process.env.REPLICA_DB_HOST,
27 port: Number(process.env.REPLICA_DB_PORT ?? 5432),
28 username: process.env.REPLICA_DB_USERNAME,
29 password: process.env.REPLICA_DB_PASSWORD,
30 database: process.env.REPLICA_DB_SCHEMA,
31 synchronize: false,
32 },
33})
34@Module({
35 name: metadata.name,
36 providers: [AnalyticsService],
37 controllers: [],
38 entities: [
39 {
40 plugin: metadata.name,
41 name: "analytics",
42 entities: [AnalyticsEntity],
43 },
44 {
45 plugin: metadata.name,
46 name: "replica",
47 entities: [WarehouseEntity],
48 },
49 ],
50})
51export class MyPluginModule {}使用其他插件的连接
连接按所有者插件(+ 可选 name)登记在进程级 DataSource 注册表中。消费方插件不要为同一库再声明 @Database,而是注入所有者的连接;需要时把实体注册到相同的 plugin + name 范围。
- 所有者插件 — 声明 @Database,并先启动(写入 pluginDependencies)
- 消费方 — @InjectDatabaseSource("owner-plugin", "connection-name")
- 外键连接上的实体 — plugin: "owner-plugin",name: "connection-name"
- 不要在每个消费方重复声明相同的 @Database
- 所有者插件安装并加载后,消费方才能注入其 DataSource
TSXconsumer plugin service
1import { DataSource } from "typeorm";
2import { InjectDatabaseSource, Service } from "@quan-erp/shared-backend-core";
3
4@Service()
5export class ReportService {
6 // "warehouse" plugin declared @Database({ name: "replica", ... })
7 @InjectDatabaseSource("warehouse", "replica")
8 warehouseReplica: DataSource;
9}TSXconsumer module entities on foreign connection
1@Module({
2 name: metadata.name,
3 providers: [ReportService],
4 controllers: [],
5 entities: [
6 {
7 plugin: "warehouse",
8 name: "replica",
9 entities: [ReportSnapshotEntity],
10 },
11 ],
12})
13export class ReportPluginModule {}CRUD 模式
- 创建:repo.insert(...) → 从 identifiers 返回 { id }
- 更新:repo.update 并检查 affected
- 删除:优先 repo.softDelete + 检查 affected