目标与何时使用
多数插件表应留在共享核心 Postgres。仅在需要单独主机、库、报表库或副本时添加自定义源。
- 优先默认 — 用 plugin: "default" 注册实体(无需 @Database)
- 自定义 — 仅在根 @Module 上使用 @Database({ name?, options })
- 复用 — 其他插件注入所有者的连接,不要重复声明 @Database
1. 前置条件
需要可用的插件后端模块,以及通过环境变量(PluginEnv / base .env)提供的凭证。
- 已有根 @Module({ name: metadata.name, … })
- 主机、端口、用户名、密码、库名来自环境变量 — 不要硬编码密钥
- 自定义源设置 synchronize: false,并用迁移交付 schema
- 若注入其他插件的数据库,在 pluginDependencies 中列出所有者
2. 优先默认 DataSource
除非需要隔离,否则合并到共享连接。用 DataSourceManager.DEFAULT_PLUGIN(或字符串 "default")注入。
- 表名仍必须以 metadata.name 开头
- 扩展 BaseEntity 以获得创建 / 更新 / 软删除列
- 无需 @Database — 核心已拥有 "default"
TSXentities on default
1@Module({
2 name: metadata.name,
3 providers: [OrderService],
4 controllers: [OrderController],
5 entities: [
6 {
7 plugin: "default",
8 entities: [OrderEntity],
9 },
10 ],
11})
12export class MyPluginModule {}TSXinject default
1import { DataSource } from "typeorm";
2import {
3 InjectDatabaseSource,
4 DataSourceManager,
5 Service,
6} from "@quan-erp/shared-backend-core";
7
8@Service()
9export class OrderService {
10 @InjectDatabaseSource(DataSourceManager.DEFAULT_PLUGIN)
11 source: DataSource;
12}3. 添加自定义 @Database
在根 @Module 上声明连接。用 plugin: metadata.name 与相同的可选 name 注册实体。用 @InjectDatabaseSource(metadata.name, name?) 注入。
- @Database({ name?, options }) — 所有者由正在加载的插件填充
- name — 同一插件打开多个连接时需要
- options — TypeORM DataSourceOptions;凭证来自环境变量
- 预发 / 生产自定义源使用 synchronize: false
- 实体 — 匹配的 plugin + name;不要依赖 options.entities
TSXroot module — custom source
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 { AnalyticsService } from "./analytics.service.js";
8
9@Database({
10 name: "analytics",
11 options: {
12 type: "postgres",
13 host: process.env.ANALYTICS_DB_HOST,
14 port: Number(process.env.ANALYTICS_DB_PORT ?? 5432),
15 username: process.env.ANALYTICS_DB_USERNAME,
16 password: process.env.ANALYTICS_DB_PASSWORD,
17 database: process.env.ANALYTICS_DB_SCHEMA,
18 synchronize: false,
19 },
20})
21@Module({
22 name: metadata.name,
23 providers: [AnalyticsService],
24 controllers: [],
25 entities: [
26 {
27 plugin: metadata.name,
28 name: "analytics",
29 entities: [AnalyticsEntity],
30 },
31 ],
32})
33export class MyPluginModule {}TSinject custom
@InjectDatabaseSource(metadata.name, "analytics")
analyticsSource: DataSource;4. 跨插件注入
连接按所有者插件(+ 可选 name)登记在进程级注册表中。消费方注入所有者的 DataSource,并可按相同的 plugin + name 注册自己的实体。
- 所有者 — 声明 @Database 并先启动(写入 pluginDependencies)
- 消费方 — @InjectDatabaseSource("owner-plugin", "connection-name?")
- 外部实体 — entities 使用 plugin: "owner-plugin" 与 name
- 不要在每个消费方重复声明相同的 @Database
TSconsumer service
1@Service()
2export class ReportService {
3 // "warehouse" plugin declared @Database({ name: "replica", ... })
4 @InjectDatabaseSource("warehouse", "replica")
5 warehouseReplica: DataSource;
6}5. 验证
- 插件启动无 DI / DataSource 错误
- 注入的 DataSource 已定义且 getRepository(Entity) 可用
- 实体出现在目标连接上(default vs 自定义 name)
- 环境凭证在 Docker / 预发与本地一致
常见错误
- 把 @Database 加在 service 或嵌套模块上 — 必须是根 @Module
- 把自定义库实体注册为 plugin: "default"(或错误的 name)
- 硬编码密码而不是 PluginEnv / base .env
- 生产环境自定义源使用 synchronize: true
- 在消费方重复声明 @Database 而不是用 @InjectDatabaseSource
- 忘记 pluginDependencies,导致所有者在消费方之后加载