演示

数据库

Quark ERP 在 PostgreSQL 上使用 TypeORM。多数表优先用共享默认 DataSource。插件还可用 @Database 打开一个或多个自定义连接,其他插件可通过所有者插件名(及可选连接名)注入并复用这些连接。

技术栈

  • 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