把“章节正文 + 图片”从 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>
This commit is contained in:
Sully
2026-03-13 11:12:10 +08:00
committed by GitHub
parent 1cb804fa37
commit 2eb066dbec
19 changed files with 1280 additions and 624 deletions

View File

@@ -0,0 +1,26 @@
-- 章节拆分为 chapter_sections每段正文 + 配图独立存储chapters 只保留封面图
-- 执行顺序: 1) 本文件 2) python -m scripts.migrate_chapters_to_sections
-- 执行方式: psql -U <user> -d <database> -f api/migrations/add_chapter_sections.sql
-- ========== 1. 新建 chapter_sections 表 ==========
CREATE TABLE IF NOT EXISTS chapter_sections (
id VARCHAR NOT NULL PRIMARY KEY,
chapter_id VARCHAR NOT NULL REFERENCES chapters(id) ON DELETE CASCADE,
order_index INTEGER NOT NULL,
content TEXT NOT NULL,
image JSONB,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS ix_chapter_sections_chapter_id ON chapter_sections(chapter_id);
CREATE INDEX IF NOT EXISTS ix_chapter_sections_order ON chapter_sections(chapter_id, order_index);
-- ========== 2. chapters 表增加 cover_image ==========
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'chapters' AND column_name = 'cover_image') THEN
ALTER TABLE chapters ADD COLUMN cover_image JSONB;
RAISE NOTICE '已添加 chapters.cover_image';
END IF;
END $$;
-- ========== 3. 回填与删列由脚本 scripts.migrate_chapters_to_sections 完成 ==========

View File

@@ -0,0 +1,32 @@
-- 图片独立表:将原先挤在 chapter.cover_image / chapter_sections.image 的 JSON 拆成独立列
-- 执行顺序: 1) 本文件 2) python -m api.scripts.run_memoir_images_migration
-- 执行方式: psql -U <user> -d <database> -f api/migrations/add_memoir_images_table.sql
-- ========== 1. 新建 memoir_images 表 ==========
CREATE TABLE IF NOT EXISTS memoir_images (
id VARCHAR NOT NULL PRIMARY KEY,
chapter_id VARCHAR NOT NULL REFERENCES chapters(id) ON DELETE CASCADE,
section_id VARCHAR NULL REFERENCES chapter_sections(id) ON DELETE CASCADE,
order_index INTEGER NOT NULL DEFAULT 0,
placeholder TEXT,
description TEXT,
status VARCHAR NOT NULL DEFAULT 'pending',
prompt TEXT,
url TEXT,
storage_key TEXT,
provider VARCHAR,
style VARCHAR,
size VARCHAR,
error TEXT,
retryable BOOLEAN,
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
COMMENT ON TABLE memoir_images IS '章节配图与封面section_id 为空表示章节封面,非空表示该 section 的配图';
COMMENT ON COLUMN memoir_images.order_index IS '章节内唯一:封面=0段落配图=1,2,3,...(对应 section.order_index+1';
CREATE INDEX IF NOT EXISTS ix_memoir_images_chapter_id ON memoir_images(chapter_id);
CREATE INDEX IF NOT EXISTS ix_memoir_images_section_id ON memoir_images(section_id);
CREATE UNIQUE INDEX IF NOT EXISTS ix_memoir_images_chapter_cover ON memoir_images(chapter_id) WHERE section_id IS NULL;
CREATE UNIQUE INDEX IF NOT EXISTS ix_memoir_images_section_unique ON memoir_images(section_id) WHERE section_id IS NOT NULL;

View File

@@ -0,0 +1,18 @@
-- section 表用 image_id 关联 memoir_images不再存 JSON
-- 执行顺序1) 本文件 2) 回填后执行 DROP 语句(或由脚本完成)
-- 执行方式: psql -U <user> -d <database> -f api/migrations/add_section_image_id_fk.sql
-- 1. 添加外键列(可空,无默认)
ALTER TABLE chapter_sections
ADD COLUMN IF NOT EXISTS image_id VARCHAR REFERENCES memoir_images(id) ON DELETE SET NULL;
-- 2. 回填:已有 memoir_images 且 section_id 指向本行的,把其 id 写入本行 image_id
UPDATE chapter_sections cs
SET image_id = sub.id
FROM (
SELECT id, section_id FROM memoir_images WHERE section_id IS NOT NULL
) sub
WHERE sub.section_id = cs.id AND cs.image_id IS NULL;
-- 3. 删除旧的 JSON 列
ALTER TABLE chapter_sections DROP COLUMN IF EXISTS image;

View File

@@ -0,0 +1,9 @@
-- 修复 memoir_images 同一章节内 order_index 重复:封面=0段落配图=1,2,3,...section.order_index+1
-- 仅更新有 section_id 的段落配图封面section_id 为空)保持 0。
-- 执行方式: psql -U <user> -d <database> -f api/migrations/fix_memoir_images_order_index.sql
UPDATE memoir_images mi
SET order_index = cs.order_index + 1
FROM chapter_sections cs
WHERE mi.section_id IS NOT NULL
AND mi.section_id = cs.id;