Files
life-echo/api/scripts/migrate_chapters_to_sections.py
Sully 2eb066dbec 把“章节正文 + 图片”从 chapters 单表/JSON 结构,重构为“章节 chapter + 段落 section + 图片 memoir_images 独立表”的新数据模型,同时联动修改接口、PDF 导出、异步任务、迁移脚本、测试,以及修复 Android 端聊天列表显示问题。 (#9)
* refactor: 表结构重构,新增段落section和图片image新表

* fix: fix android app import error

* refactor: 重构文件名

* fix: 优化提示词

* fix: 消息气泡显示位置异常问题

---------

Co-authored-by: yangshilin <2157598560@qq.com>
2026-03-13 11:12:10 +08:00

99 lines
4.0 KiB
Python
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.
"""
将 chapters 的 content + images 迁移到 chapter_sections并删除 chapters.content / chapters.images。
前置:已执行 api/migrations/add_chapter_sections.sql创建 chapter_sections 表、chapters.cover_image 列)。
用法(在 api 目录下):
python -m scripts.migrate_chapters_to_sections
"""
import json
import logging
import os
import sys
import uuid
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import text
from database.database import engine
from services.memoir_images.parser import split_narrative_to_sections
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger(__name__)
def run():
with engine.connect() as conn:
# 检查是否还存在 content 列(若已删则跳过)
r = conn.execute(text("""
SELECT column_name FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'chapters' AND column_name = 'content'
"""))
if r.fetchone() is None:
logger.info("chapters.content 已不存在,跳过迁移")
return
# 读取所有有 content 的章节(原始列)
rows = conn.execute(text("""
SELECT id, content, images FROM chapters WHERE content IS NOT NULL AND trim(content) != ''
""")).fetchall()
for row in rows:
ch_id, content, images_raw = row[0], row[1], row[2]
images = json.loads(images_raw) if isinstance(images_raw, str) else (images_raw or [])
if not isinstance(images, list):
images = []
sections = split_narrative_to_sections(content or "")
if not sections:
# 无占位符:整段为一条 section无图
section_id = str(uuid.uuid4()).replace("-", "")[:32]
conn.execute(text("""
INSERT INTO chapter_sections (id, chapter_id, order_index, content, image, updated_at)
VALUES (:id, :ch_id, 0, :content, NULL, NOW())
"""), {"id": section_id, "ch_id": ch_id, "content": (content or "").strip()})
conn.commit()
logger.info("章节 %s: 1 条 section无图", ch_id)
continue
first_cover = None
img_index = 0
for order_idx, seg in enumerate(sections):
section_id = str(uuid.uuid4()).replace("-", "")[:32]
seg_content = seg.get("content") or ""
ph = seg.get("placeholder_info")
image_json = None
if ph is not None and img_index < len(images):
image_json = json.dumps(images[img_index]) if isinstance(images[img_index], dict) else None
if first_cover is None and image_json:
first_cover = image_json
img_index += 1
conn.execute(text("""
INSERT INTO chapter_sections (id, chapter_id, order_index, content, image, updated_at)
VALUES (:id, :ch_id, :ord, :content, :img::jsonb, NOW())
"""), {
"id": section_id,
"ch_id": ch_id,
"ord": order_idx,
"content": seg_content,
"img": image_json,
})
if first_cover:
conn.execute(
text("UPDATE chapters SET cover_image = :img::jsonb WHERE id = :id"),
{"img": first_cover, "id": ch_id},
)
conn.commit()
logger.info("章节 %s: %d 条 sections", ch_id, len(sections))
# 删除 chapters.content 和 chapters.images
conn.execute(text("ALTER TABLE chapters DROP COLUMN IF EXISTS content"))
conn.execute(text("ALTER TABLE chapters DROP COLUMN IF EXISTS images"))
conn.commit()
logger.info("已删除 chapters.content 与 chapters.images")
if __name__ == "__main__":
run()