# 生产环境迁移操作指南 本文档描述新表结构(`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 -d -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 -d -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 -d -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 -d -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 -d -F c -f backup_$(date +%Y%m%d).dump # 2. chapter_sections 迁移(SQL + 数据) cd /path/to/life-echo/api psql -U -d -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 -d -f api/migrations/add_memoir_images_table.sql uv run python -m api.scripts.run_memoir_images_migration # 4. (可选)image_id 外键 psql -U -d -f api/migrations/add_section_image_id_fk.sql ``` ### 方案 B:使用一键脚本(更简洁) ```bash # 1. 备份 pg_dump -U -d -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 -d -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; ```