|
|
@@ -0,0 +1,322 @@
|
|
|
+---
|
|
|
+name: create-db-migration
|
|
|
+description: TypeORM 数据库迁移工作流 - 双 Data Source 架构下的标准化迁移创建流程
|
|
|
+---
|
|
|
+
|
|
|
+# TypeORM 数据库迁移工作流
|
|
|
+
|
|
|
+**目标:** 在双 Data Source 架构下,标准化 TypeORM 数据库迁移的创建和执行流程。
|
|
|
+
|
|
|
+**你的角色:** 数据库迁移专家,熟悉 TypeORM CLI、双 Data Source 架构、PostgreSQL 数据库操作。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 工作流架构
|
|
|
+
|
|
|
+这是一个**单流程、检查清单驱动**的工作流:
|
|
|
+
|
|
|
+- 每个步骤必须按顺序完成
|
|
|
+- 每个步骤都有明确的验证标准
|
|
|
+- 失败时停止并解决问题后再继续
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 背景:双 Data Source 架构
|
|
|
+
|
|
|
+### 为什么需要两个 Data Source?
|
|
|
+
|
|
|
+**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"` |
|
|
|
+
|
|
|
+**关键约束:**
|
|
|
+- TypeORM CLI **不支持**直接传递类数组
|
|
|
+- CLI 必须使用 Glob 模式加载实体
|
|
|
+- 两个文件都有存在必要,不可合并
|
|
|
+
|
|
|
+**好消息:**
|
|
|
+- CLI 的 Glob 已配置覆盖所有模块路径
|
|
|
+- 新增实体**无需修改**任何配置
|
|
|
+- 迁移文件自动被 CLI 发现
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 执行流程
|
|
|
+
|
|
|
+### 步骤 1:前置检查
|
|
|
+
|
|
|
+**检查实体文件是否已创建:**
|
|
|
+
|
|
|
+```bash
|
|
|
+# 确认实体文件存在
|
|
|
+ls allin-packages/[module-name]/src/entities/[entity-name].entity.ts
|
|
|
+```
|
|
|
+
|
|
|
+**检查实体是否在 Glob 覆盖范围内:**
|
|
|
+
|
|
|
+查看 `packages/server/src/data-source-cli.ts` 确认实体路径已被包含:
|
|
|
+```typescript
|
|
|
+entities: [
|
|
|
+ "../allin-packages/[your-module]/src/entities/*.ts", // 确认你的模块在这里
|
|
|
+ // ...
|
|
|
+]
|
|
|
+```
|
|
|
+
|
|
|
+**检查表是否已存在于数据库:**
|
|
|
+
|
|
|
+```bash
|
|
|
+psql -h 127.0.0.1 -U postgres -c "\dt [table_name]"
|
|
|
+```
|
|
|
+
|
|
|
+如果表已存在,**停止工作流** - 考虑是否需要修改表而不是创建新表。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 步骤 2:生成迁移文件
|
|
|
+
|
|
|
+**进入 server 目录:**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd packages/server
|
|
|
+```
|
|
|
+
|
|
|
+**运行 TypeORM 迁移生成命令:**
|
|
|
+
|
|
|
+```bash
|
|
|
+pnpm migration:generate -- -n [MigrationName]
|
|
|
+```
|
|
|
+
|
|
|
+**迁移命名规范:**
|
|
|
+- 使用 PascalCase
|
|
|
+- 描述性名称,如 `CreateDisabledPersonPhoneTable`
|
|
|
+- 如涉及数据迁移,添加 `AndMigrateData` 后缀
|
|
|
+
|
|
|
+**预期输出:**
|
|
|
+```
|
|
|
+Migration /path/to/packages/server/migrations/[timestamp]-[MigrationName].ts has been generated successfully.
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 步骤 3:审查生成的迁移文件
|
|
|
+
|
|
|
+**打开生成的迁移文件:**
|
|
|
+
|
|
|
+```bash
|
|
|
+# 迁移文件位于
|
|
|
+packages/migrations/[timestamp]-[MigrationName].ts
|
|
|
+```
|
|
|
+
|
|
|
+**检查清单:**
|
|
|
+
|
|
|
+- [ ] `up()` 方法包含正确的 `CREATE TABLE` 或 `ALTER TABLE` 语句
|
|
|
+- [ ] `down()` 方法包含正确的回滚逻辑
|
|
|
+- [ ] 列类型、约束、索引与实体定义一致
|
|
|
+- [ ] 外键关系正确设置
|
|
|
+- [ ] CASCADE 删除策略符合预期
|
|
|
+
|
|
|
+**常见问题修复:**
|
|
|
+
|
|
|
+| 问题 | 原因 | 解决方案 |
|
|
|
+|------|------|---------|
|
|
|
+| 迁移为空 | 实体与数据库表已同步 | 检查是否真的需要迁移 |
|
|
|
+| 缺少索引 | 实体 `@Index()` 未被识别 | 手动添加 `queryRunner.createIndex()` |
|
|
|
+| 列类型不匹配 | TypeORM 推断错误 | 手动修改列类型定义 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 步骤 4:执行迁移
|
|
|
+
|
|
|
+**确认当前数据库状态(可选):**
|
|
|
+
|
|
|
+```bash
|
|
|
+pnpm migration:show
|
|
|
+```
|
|
|
+
|
|
|
+**运行迁移:**
|
|
|
+
|
|
|
+```bash
|
|
|
+pnpm migration:run
|
|
|
+```
|
|
|
+
|
|
|
+**预期输出:**
|
|
|
+```
|
|
|
+query: SELECT * FROM "migrations" "migrations" ...
|
|
|
+query: CREATE TABLE "[table_name]" ...
|
|
|
+Migration [MigrationName] has been executed successfully.
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 步骤 5:验证迁移结果
|
|
|
+
|
|
|
+**检查表是否创建成功:**
|
|
|
+
|
|
|
+```bash
|
|
|
+psql -h 127.0.0.1 -U postgres -c "\d+ [table_name]"
|
|
|
+```
|
|
|
+
|
|
|
+**验证表结构:**
|
|
|
+
|
|
|
+- [ ] 列名和类型正确
|
|
|
+- [ ] 主键存在
|
|
|
+- [ ] 外键约束正确
|
|
|
+- [ ] 索引已创建
|
|
|
+- [ ] 默认值设置正确
|
|
|
+
|
|
|
+**如涉及数据迁移,验证数据:**
|
|
|
+
|
|
|
+```bash
|
|
|
+psql -h 127.0.0.1 -U postgres -c "SELECT COUNT(*) FROM [table_name];"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 步骤 6:测试回滚(可选但推荐)
|
|
|
+
|
|
|
+**在开发环境中测试回滚:**
|
|
|
+
|
|
|
+```bash
|
|
|
+pnpm migration:revert
|
|
|
+```
|
|
|
+
|
|
|
+**验证表已删除/还原**
|
|
|
+
|
|
|
+**重新运行迁移:**
|
|
|
+
|
|
|
+```bash
|
|
|
+pnpm migration:run
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 常见场景
|
|
|
+
|
|
|
+### 场景 A:新建实体的表迁移
|
|
|
+
|
|
|
+1. 实体已创建 → 执行步骤 2 生成迁移
|
|
|
+2. 审查迁移(通常无需修改)
|
|
|
+3. 运行并验证
|
|
|
+
|
|
|
+### 场景 B:添加新列到现有表
|
|
|
+
|
|
|
+1. 修改实体添加 `@Column()`
|
|
|
+2. 生成迁移
|
|
|
+3. 审查 `ALTER TABLE` 语句
|
|
|
+4. 如涉及现有数据,考虑默认值
|
|
|
+5. 运行并验证
|
|
|
+
|
|
|
+### 场景 C:带数据迁移的表结构变更
|
|
|
+
|
|
|
+1. 生成迁移(只含结构变更)
|
|
|
+2. 手动编辑迁移文件,在 `up()` 中添加数据迁移 SQL
|
|
|
+3. 示例:
|
|
|
+
|
|
|
+```typescript
|
|
|
+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
|
|
|
+ `);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 场景 D:重命名列或表
|
|
|
+
|
|
|
+TypeORM 有时检测不到重命名,会生成 DROP + ADD。
|
|
|
+
|
|
|
+**手动优化:**
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 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');
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 注意事项
|
|
|
+
|
|
|
+### ⚠️ 生产环境迁移
|
|
|
+
|
|
|
+1. **先在开发/测试环境验证**
|
|
|
+2. **备份数据库**
|
|
|
+3. **评估迁移时间**(大表操作可能很慢)
|
|
|
+4. **准备回滚计划**
|
|
|
+5. **在低峰时段执行**
|
|
|
+
|
|
|
+### ⚠️ 数据迁移
|
|
|
+
|
|
|
+1. **分批处理**:大表数据迁移分批执行
|
|
|
+2. **事务控制**:考虑是否需要关闭自动提交
|
|
|
+3. **验证脚本**:先 SELECT 预览,再 UPDATE
|
|
|
+4. **保留原字段**:先保留,验证无误后再清理
|
|
|
+
|
|
|
+### ⚠️ 外键约束
|
|
|
+
|
|
|
+1. **先创建被引用的表**
|
|
|
+2. **CASCADE 设置要谨慎**
|
|
|
+3. **考虑数据的完整性**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 参考命令
|
|
|
+
|
|
|
+```bash
|
|
|
+# 查看迁移状态
|
|
|
+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 任务中记录:**
|
|
|
+
|
|
|
+```yaml
|
|
|
+- [x] Task: 数据库迁移
|
|
|
+ - [x] 检查实体在 glob 覆盖范围内
|
|
|
+ - [x] 使用 pnpm migration:generate 生成迁移
|
|
|
+ - [x] 审查并调整迁移文件
|
|
|
+ - [x] 运行 pnpm migration:run
|
|
|
+ - [x] 验证表结构和数据
|
|
|
+```
|