把“章节正文 + 图片”从 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

@@ -13,6 +13,7 @@ from reportlab.lib.units import inch
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Image as ReportLabImage
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from services.memoir_images.serializers import memoir_image_to_dict
from services.memoir_images.parser import PLACEHOLDER_RE
from services.memoir_images.schema import IMAGE_STATUS_COMPLETED, normalize_image_assets
from services.memoir_images.storage import (
@@ -48,6 +49,28 @@ def split_content_blocks(content: str, images: list[dict]) -> list[dict]:
return blocks
def sections_to_blocks(sections: list, prepare_fn=None) -> list[dict]:
"""
从 chapter_sections 生成 PDF 用的 blocks按 order_index 顺序,每段正文 + 可选一张图。
prepare_fn(images) 用于解析签名 URL默认 _prepare_pdf_image_assets。
"""
if prepare_fn is None:
prepare_fn = _prepare_pdf_image_assets
blocks: list[dict] = []
for section in sorted(sections, key=lambda s: getattr(s, "order_index", 0)):
content = (getattr(section, "content", None) or "").strip()
if content:
blocks.append({"type": "text", "value": content})
img = None
if getattr(section, "image_record", None):
img = memoir_image_to_dict(section.image_record)
if img:
prepared = prepare_fn([img])
if prepared and prepared[0].get("url"):
blocks.append({"type": "image", "url": prepared[0]["url"]})
return blocks
def _prepare_pdf_image_assets(images: list[dict]) -> list[dict]:
storage = TencentCosStorageService.from_env()
prepared_assets: list[dict] = []
@@ -148,8 +171,12 @@ class PDFService:
story.append(Paragraph(chapter.title, heading_style))
story.append(Spacer(1, 0.2 * inch))
images = _prepare_pdf_image_assets(getattr(chapter, "images", None) or [])
blocks = split_content_blocks(chapter.content, images)
sections = getattr(chapter, "sections", None) or []
if sections:
blocks = sections_to_blocks(sections)
else:
images = _prepare_pdf_image_assets(getattr(chapter, "images", None) or [])
blocks = split_content_blocks(getattr(chapter, "content", "") or "", images)
for block in blocks:
if block["type"] == "text":