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

7.7 KiB
Raw Blame History

生产环境迁移操作指南

本文档描述新表结构(chapter_sectionsmemoir_images)的完整迁移步骤,适用于生产环境。


一、迁移概览

阶段 内容 前置条件
1 创建 chapter_sections 表、chapters.cover_image
2 chapters.content + chapters.images 迁移到 chapter_sections,并删除旧列 阶段 1 完成
3 创建 memoir_images 阶段 2 完成(依赖 chapter_sections
4 chapters.cover_imagechapter_sections.image 迁移到 memoir_images 阶段 3 完成
5 (可选)添加 chapter_sections.image_id 外键,删除 chapter_sections.image 阶段 4 完成

二、前置条件检查

2.1 环境要求

  • Python 3.x已安装项目依赖uv syncpip 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.contentchapters.images

阶段 3执行 SQL 建表memoir_images

前置 SQL 条件chapter_sections 表已存在(memoir_imagessection_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_imagememoir_imagessection_id 为空表示封面)
  • 迁移 chapter_sections.imagememoir_imagessection_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 会

  1. chapter_sections 添加 image_id 列(外键指向 memoir_images
  2. 回填:将 memoir_imagessection_id 指向本行的记录的 id 写入 image_id
  3. 删除 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.contentchapters.images,以及(若执行阶段 5chapter_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;