Files
life-echo/api/docs/数据迁移指南.md

259 lines
7.7 KiB
Markdown
Raw Normal View History

2026-03-13 15:15:40 +08:00
# 生产环境迁移操作指南
本文档描述新表结构(`chapter_sections``memoir_images`)的完整迁移步骤,适用于生产环境。
---
## 一、迁移概览
| 阶段 | 内容 | 前置条件 |
|------|------|----------|
| 1 | 创建 `chapter_sections` 表、`chapters.cover_image` 列 | 无 |
| 2 | 将 `chapters.content` + `chapters.images` 迁移到 `chapter_sections`,并删除旧列 | 阶段 1 完成 |
| 3 | 创建 `memoir_images` 表 | 阶段 2 完成(依赖 `chapter_sections` |
| 4 | 将 `chapters.cover_image``chapter_sections.image` 迁移到 `memoir_images` | 阶段 3 完成 |
| 5 | (可选)添加 `chapter_sections.image_id` 外键,删除 `chapter_sections.image` | 阶段 4 完成 |
---
## 二、前置条件检查
### 2.1 环境要求
- Python 3.x已安装项目依赖`uv sync``pip install -r requirements.txt`
- 数据库PostgreSQL
- 环境变量:`.env` 中配置正确的 `DATABASE_URL`
### 2.2 数据库状态检查
执行前确认:
```sql
-- 1. chapters 表存在且包含 content、images 列(迁移前应有)
SELECT column_name FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'chapters'
AND column_name IN ('content', 'images', 'cover_image');
-- 预期content、images 存在cover_image 可能不存在
-- 2. chapter_sections 表在阶段 1 前应不存在
SELECT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'chapter_sections'
);
-- 预期false首次迁移
-- 3. memoir_images 表在阶段 3 前应不存在
SELECT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'memoir_images'
);
-- 预期false首次迁移
```
### 2.3 备份(强烈建议)
```bash
# 生产环境务必先备份
pg_dump -U <user> -d <database> -F c -f backup_$(date +%Y%m%d_%H%M%S).dump
```
---
## 三、迁移步骤
### 阶段 1执行 SQL 建表/加列chapter_sections
**前置 SQL 条件**:无。`chapters` 表需已存在。
**方式 A使用 psql 手动执行**
```bash
cd /path/to/life-echo
psql -U <user> -d <database> -f api/migrations/add_chapter_sections.sql
```
**方式 B使用一键脚本推荐**
一键脚本 `run_chapter_sections_migration` 会自动执行该 SQL然后执行数据迁移无需单独执行本阶段。
若选择方式 A则需在阶段 2 使用 `migrate_chapters_to_sections`;若选择方式 B可跳过阶段 1 和 2 的拆分,直接执行 `run_chapter_sections_migration`
---
### 阶段 2数据迁移chapters → chapter_sections
**前置 SQL 条件**:已执行 `api/migrations/add_chapter_sections.sql`,即:
- `chapter_sections` 表已创建
- `chapters.cover_image` 列已添加(若不存在会自动添加)
**执行命令**(在 `api` 目录下):
```bash
cd /path/to/life-echo/api
uv run python -m scripts.migrate_chapters_to_sections
```
或使用一键脚本(包含阶段 1 + 2
```bash
cd /path/to/life-echo/api
uv run python -m scripts.run_chapter_sections_migration
```
**脚本行为**
-`chapters.content` 已不存在,则跳过迁移
-`chapters.content` + `chapters.images` 解析为 sections写入 `chapter_sections`
- 将每章首张图写入 `chapters.cover_image`
- 最后删除 `chapters.content``chapters.images`
---
### 阶段 3执行 SQL 建表memoir_images
**前置 SQL 条件**`chapter_sections` 表已存在(`memoir_images``section_id` 外键依赖它)。
**方式 A使用 psql 手动执行**
```bash
cd /path/to/life-echo
psql -U <user> -d <database> -f api/migrations/add_memoir_images_table.sql
```
**方式 B使用 memoir_images 迁移脚本**
`run_memoir_images_migration` 会先执行该 SQL再执行数据迁移。若 SQL 文件不存在会给出警告。
---
### 阶段 4数据迁移cover_image / section.image → memoir_images
**前置 SQL 条件**:已执行 `api/migrations/add_memoir_images_table.sql`,即 `memoir_images` 表已创建。
**执行命令**(在项目根目录或 `api` 目录下):
```bash
cd /path/to/life-echo
uv run python -m api.scripts.run_memoir_images_migration
```
或:
```bash
cd /path/to/life-echo/api
uv run python -m scripts.run_memoir_images_migration
```
**脚本行为**
- 先执行 `add_memoir_images_table.sql`(若存在)
- 迁移 `chapters.cover_image``memoir_images``section_id` 为空表示封面)
- 迁移 `chapter_sections.image``memoir_images``section_id` 非空表示段落配图)
- 已存在的记录会跳过,支持幂等执行
---
### 阶段 5可选添加 image_id 外键并删除旧 JSON 列
**前置 SQL 条件**`memoir_images` 表已有数据,且 `chapter_sections` 中对应 section 的配图已迁移完成。
**执行**
```bash
cd /path/to/life-echo
psql -U <user> -d <database> -f api/migrations/add_section_image_id_fk.sql
```
**该 SQL 会**
1.`chapter_sections` 添加 `image_id` 列(外键指向 `memoir_images`
2. 回填:将 `memoir_images``section_id` 指向本行的记录的 `id` 写入 `image_id`
3. 删除 `chapter_sections.image`
---
## 四、推荐生产执行顺序(精简版)
### 方案 A分步执行便于排查问题
```bash
# 1. 备份
pg_dump -U <user> -d <database> -F c -f backup_$(date +%Y%m%d).dump
# 2. chapter_sections 迁移SQL + 数据)
cd /path/to/life-echo/api
psql -U <user> -d <database> -f migrations/add_chapter_sections.sql
uv run python -m scripts.migrate_chapters_to_sections
# 3. memoir_images 迁移SQL + 数据)
cd /path/to/life-echo
psql -U <user> -d <database> -f api/migrations/add_memoir_images_table.sql
uv run python -m api.scripts.run_memoir_images_migration
# 4. 可选image_id 外键
psql -U <user> -d <database> -f api/migrations/add_section_image_id_fk.sql
```
### 方案 B使用一键脚本更简洁
```bash
# 1. 备份
pg_dump -U <user> -d <database> -F c -f backup_$(date +%Y%m%d).dump
# 2. chapter_sections 一键迁移(内含 SQL + 数据)
cd /path/to/life-echo/api
uv run python -m scripts.run_chapter_sections_migration
# 3. memoir_images 一键迁移(内含 SQL + 数据)
cd /path/to/life-echo
uv run python -m api.scripts.run_memoir_images_migration
# 4. 可选image_id 外键
psql -U <user> -d <database> -f api/migrations/add_section_image_id_fk.sql
```
---
## 五、前置 SQL 条件汇总
| 脚本 / SQL 文件 | 前置条件 |
|-----------------|----------|
| `add_chapter_sections.sql` | `chapters` 表已存在 |
| `migrate_chapters_to_sections` / `run_chapter_sections_migration` | 已执行 `add_chapter_sections.sql` |
| `add_memoir_images_table.sql` | `chapter_sections` 表已存在 |
| `run_memoir_images_migration` | 已执行 `add_memoir_images_table.sql`(或脚本会自动执行) |
| `add_section_image_id_fk.sql` | `memoir_images` 表已存在且数据已迁移完成 |
---
## 六、回滚说明
- 迁移会删除 `chapters.content``chapters.images`,以及(若执行阶段 5`chapter_sections.image`
- 回滚需从备份恢复,或根据业务自行编写反向迁移脚本
- 建议在测试环境完整跑通后再在生产执行
---
## 七、验证
```sql
-- 1. chapter_sections 有数据
SELECT COUNT(*) FROM chapter_sections;
-- 2. chapters 无 content/images
SELECT column_name FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'chapters'
AND column_name IN ('content', 'images');
-- 预期:无结果
-- 3. memoir_images 有数据
SELECT COUNT(*) FROM memoir_images;
-- 4. 封面与段落配图数量合理
SELECT
COUNT(*) FILTER (WHERE section_id IS NULL) AS cover_count,
COUNT(*) FILTER (WHERE section_id IS NOT NULL) AS section_count
FROM memoir_images;
```