7.7 KiB
生产环境迁移操作指南
本文档描述新表结构(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 数据库状态检查
执行前确认:
-- 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 备份(强烈建议)
# 生产环境务必先备份
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 手动执行
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 目录下):
cd /path/to/life-echo/api
uv run python -m scripts.migrate_chapters_to_sections
或使用一键脚本(包含阶段 1 + 2):
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 手动执行
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 目录下):
cd /path/to/life-echo
uv run python -m api.scripts.run_memoir_images_migration
或:
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 的配图已迁移完成。
执行:
cd /path/to/life-echo
psql -U <user> -d <database> -f api/migrations/add_section_image_id_fk.sql
该 SQL 会:
- 为
chapter_sections添加image_id列(外键指向memoir_images) - 回填:将
memoir_images中section_id指向本行的记录的id写入image_id - 删除
chapter_sections.image列
四、推荐生产执行顺序(精简版)
方案 A:分步执行(便于排查问题)
# 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:使用一键脚本(更简洁)
# 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 - 回滚需从备份恢复,或根据业务自行编写反向迁移脚本
- 建议在测试环境完整跑通后再在生产执行
七、验证
-- 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;