Files
life-echo/api/docs/数据迁移指南.md
2026-03-13 15:15:40 +08:00

259 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 生产环境迁移操作指南
本文档描述新表结构(`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;
```