演示

数据库迁移

通过 IPlugin.getMigrations() 交付 schema 变更,而不是依赖自动同步。每个迁移实现带 up/down 的 IDatabaseMigration,并在 QueryRunner 上执行。

何时使用迁移

实体注册仍会为运行时声明表,但真实 schema 演进请优先用迁移。生产升级不要依赖 synchronize。

  • 增删改 schema(列、索引、约束、重命名)
  • 必须随插件升级只运行一次的数据回填
  • 需要在 staging / 生产可回滚的变更

目录布局

迁移类放在 backend/src/migrations/(或 backend/migrations/)。从插件入口导出,以便 getMigrations() 返回它们。

TSlayout
1backend/ 2 src/ 3 index.ts # IPlugin — getMigrations() 4 migrations/ 5 001-initial.migration.ts 6 002-add-status.migration.ts 7 feature/ 8 my.module.ts

IDatabaseMigration

实现 getName()、up()、down() 与 getSource()。getSource() 告诉核心该变更属于哪个数据源(通常 plugin: "default")。

TSXmigration
1import type { IDatabaseMigration } from "@quan-erp/shared-types"; 2import type { QueryRunner } from "typeorm"; 3import metadata from "../../module.metadata.json" with { type: "json" }; 4 5export class AddStatusMigration implements IDatabaseMigration { 6 getName() { 7 return `${metadata.name}_002_add_status`; 8 } 9 10 getSource() { 11 return { plugin: "default", name: "default" }; 12 } 13 14 async up(queryRunner: QueryRunner): Promise<void> { 15 await queryRunner.query( 16 `ALTER TABLE ${metadata.name}_order ADD COLUMN IF NOT EXISTS status varchar(32)` 17 ); 18 } 19 20 async down(queryRunner: QueryRunner): Promise<void> { 21 await queryRunner.query( 22 `ALTER TABLE ${metadata.name}_order DROP COLUMN IF EXISTS status` 23 ); 24 } 25}

在 IPlugin 上注册

在 backend/src/index.ts 的默认导出插件类中,由 getMigrations() 返回迁移类。需要时用 onMigrate() 做迁移后钩子。

TSXbackend/src/index.ts
1import type { GetMigrationsType, IPlugin } from "@quan-erp/shared-types"; 2import { InitialMigration } from "./migrations/001-initial.migration.js"; 3import { AddStatusMigration } from "./migrations/002-add-status.migration.js"; 4 5export default class MyPlugin implements IPlugin { 6 getMigrations(): GetMigrationsType { 7 return [InitialMigration, AddStatusMigration]; 8 } 9 10 async onMigrate(): Promise<void> { 11 // optional: seed / repair after migrations apply 12 } 13 14 // … getRootModule, getName, getVersion, getMetadata, … 15}

执行顺序

  1. 01

    安装 / 升级插件

    核心加载 module.js 并读取 getMigrations()。

  2. 02

    应用未执行迁移

    每个尚未用过的 getName() 会在 getSource() 上执行 up(queryRunner)。

  3. 03

    onMigrate()

    迁移批次完成后的可选插件钩子。

  4. 04

    模块初始化

    实体、DI 与 @OnInit 照常继续。

最佳实践

  • 迁移名以 metadata.name 为前缀,保证跨插件唯一
  • 保持 up/down 成对;down 应撤销同一变更
  • 本地/开发优先用 IF EXISTS / IF NOT EXISTS(或等效检查)以便安全重跑
  • 已上线的迁移不要改——新增编号迁移
  • 实体定义需与迁移最终 schema 对齐