name: create-db-migration
目标: 在双 Data Source 架构下,标准化 TypeORM 数据库迁移的创建和执行流程。
你的角色: 数据库迁移专家,熟悉 TypeORM CLI、双 Data Source 架构、PostgreSQL 数据库操作。
这是一个单流程、检查清单驱动的工作流:
TypeORM CLI 的技术限制:
| Data Source | 文件 | 用途 | 实体加载方式 |
|---|---|---|---|
| API Data Source | packages/server/src/data-source.ts |
运行时 API 使用 | 类数组 Object.values(entities) |
| CLI Data Source | packages/server/src/data-source-cli.ts |
TypeORM CLI 迁移 | Glob 模式 "../**/*.entity.ts" |
关键约束:
好消息:
检查实体文件是否已创建:
# 确认实体文件存在
ls allin-packages/[module-name]/src/entities/[entity-name].entity.ts
检查实体是否在 Glob 覆盖范围内:
查看 packages/server/src/data-source-cli.ts 确认实体路径已被包含:
entities: [
"../allin-packages/[your-module]/src/entities/*.ts", // 确认你的模块在这里
// ...
]
检查表是否已存在于数据库:
psql -h 127.0.0.1 -U postgres -c "\dt [table_name]"
如果表已存在,停止工作流 - 考虑是否需要修改表而不是创建新表。
进入 server 目录:
cd packages/server
运行 TypeORM 迁移生成命令:
pnpm migration:generate -- -n [MigrationName]
迁移命名规范:
CreateDisabledPersonPhoneTableAndMigrateData 后缀预期输出:
Migration /path/to/packages/server/migrations/[timestamp]-[MigrationName].ts has been generated successfully.
打开生成的迁移文件:
# 迁移文件位于
packages/migrations/[timestamp]-[MigrationName].ts
检查清单:
up() 方法包含正确的 CREATE TABLE 或 ALTER TABLE 语句down() 方法包含正确的回滚逻辑常见问题修复:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 迁移为空 | 实体与数据库表已同步 | 检查是否真的需要迁移 |
| 缺少索引 | 实体 @Index() 未被识别 |
手动添加 queryRunner.createIndex() |
| 列类型不匹配 | TypeORM 推断错误 | 手动修改列类型定义 |
确认当前数据库状态(可选):
pnpm migration:show
运行迁移:
pnpm migration:run
预期输出:
query: SELECT * FROM "migrations" "migrations" ...
query: CREATE TABLE "[table_name]" ...
Migration [MigrationName] has been executed successfully.
检查表是否创建成功:
psql -h 127.0.0.1 -U postgres -c "\d+ [table_name]"
验证表结构:
如涉及数据迁移,验证数据:
psql -h 127.0.0.1 -U postgres -c "SELECT COUNT(*) FROM [table_name];"
在开发环境中测试回滚:
pnpm migration:revert
验证表已删除/还原
重新运行迁移:
pnpm migration:run
@Column()ALTER TABLE 语句up() 中添加数据迁移 SQL示例:
public async up(queryRunner: QueryRunner): Promise<void> {
// 1. 结构变更
await queryRunner.addColumn(
'disabled_person',
new TableColumn({
name: 'new_field',
type: 'varchar',
length: '50',
})
);
// 2. 数据迁移
await queryRunner.query(`
UPDATE disabled_person
SET new_field = SUBSTRING(old_field, 1, 50)
WHERE new_field IS NULL
`);
}
TypeORM 有时检测不到重命名,会生成 DROP + ADD。
手动优化:
// TypeORM 生成的(不理想)
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('table', 'old_name');
await queryRunner.addColumn('table', new TableColumn({ name: 'new_name', ... }));
}
// 手动优化为重命名
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.renameColumn('table', 'old_name', 'new_name');
}
问题:迁移文件必须以数字结尾才能被 Glob 模式匹配。
| 文件名 | 是否匹配 *[0-9].ts |
说明 |
|---|---|---|
Migrate...1737260000000.ts |
✅ 匹配 | 以数字结尾 |
1768968781678-Create...Table.ts |
❌ 不匹配 | 以字母结尾 |
正确命名格式:
[描述性名称][时间戳].ts
示例:CreateDisabledPersonPhoneTable1768968781678.ts
export class CreateDisabledPersonPhoneTable1768968781678 implements MigrationInterface {
name = 'CreateDisabledPersonPhoneTable1768968781678'; // 必须有!
public async up(queryRunner: QueryRunner): Promise<void> {
// ...
}
}
没有 name 属性会导致迁移无法被 TypeORM CLI 识别。
问题:不同表可能使用不同的主键列名。
// ❌ 错误:假设所有表都用 id 作为主键
referencedColumnNames: ["id"]
// ✅ 正确:检查实际表的主键列名
// disabled_person 表的主键是 person_id,不是 id!
referencedColumnNames: ["person_id"]
解决方法:在创建外键前,先检查被引用表的实际主键列名:
psql -h 127.0.0.1 -U postgres -c "\d+ table_name"
问题:TypeORM 有时无法检测实体变化,报告 "No changes in database schema were found"。
可能原因:
解决方案:手动创建迁移文件
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm";
export class YourMigrationTimestamp implements MigrationInterface {
name = 'YourMigrationTimestamp';
public async up(queryRunner: QueryRunner): Promise<void> {
// 手动编写迁移逻辑
await queryRunner.createTable(
new Table({
name: "your_table",
columns: [/* ... */],
}),
true
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable("your_table", true);
}
}
创建迁移文件后,必须更新 packages/server/migrations/index.ts:
import { YourMigrationTimestamp } from './YourMigrationTimestamp';
export const migrations: any[] = [
MigrateNotWorkingToPreWorking1737260000000,
YourMigrationTimestamp, // 按时间戳顺序添加
]
# 查看迁移状态
pnpm migration:show
# 生成迁移
pnpm migration:generate -- -n MigrationName
# 运行迁移
pnpm migration:run
# 回滚最后一次迁移
pnpm migration:revert
# 查看表结构
psql -h 127.0.0.1 -U postgres -c "\d+ table_name"
# 查看所有表
psql -h 127.0.0.1 -U postgres -c "\dt"
当所有步骤通过验证后,工作流完成。
在 Story 任务中记录:
- [x] Task: 数据库迁移
- [x] 检查实体在 glob 覆盖范围内
- [x] 使用 pnpm migration:generate 生成迁移
- [x] 审查并调整迁移文件
- [x] 运行 pnpm migration:run
- [x] 验证表结构和数据