baodan/docs/海报生成问题报告复核与详细修复计划书_20260731.md

29 KiB
Raw Blame History

海报生成问题报告复核与详细修复计划书

日期2026-07-31
复核对象:docs/海报生成尺寸模板与导出问题修复报告_20260731.md
实施范围:api/insurance/poster/api/insurance/generation/ 中的海报任务、frontend/src/components/poster/frontend/src/pages/PosterPage.vue 及对应测试
约束:不修改 BaoDan/Dify 基座文件;所有后端自研改动继续位于 api/insurance/;本计划不直接修改业务代码

1. 执行摘要

上一份报告对以下主问题判断正确:

  • 单图和长图共用一套排版骨架;
  • 前端尺寸选项与图片模型支持尺寸不一致;
  • 模板只有风格提示能力,没有真正的版式能力;
  • pixelRatio: 2 使实际导出尺寸失控;
  • 最终文件依赖浏览器合成,任务状态与可下载状态存在错位。

但上一份报告不能直接作为开发计划使用,原因是:

  1. 它虽然提出了三层尺寸概念,后续方案却再次把 CSS 逻辑排版宽度最终 PNG 像素宽度 混在一起;
  2. 它把服务端 Headless Chromium 当成必选修复,没有先验证较小、较快的浏览器导出修复能否满足当前业务;
  3. 部分结论属于推断而不是已复现事实,例如图 4 空白一定来自当前系统、长图很容易超过 30 MB
  4. 它漏掉了两个重要根因:渲染数据来源分裂,以及 fallback 背景仍会绘制文字
  5. 缺少文件级任务、接口契约、兼容策略、灰度方案、回滚方案和可执行验收用例。

本计划采用“先建立可测基线,再做最小闭环修复,最后决定是否引入服务端浏览器”的顺序。推荐目标工期为 1319 人日;若确认必须支持“用户关闭页面后仍自动产出最终海报”,再增加服务端渲染阶段,预计额外 47 人日

2. 本计划的工作假设

在没有新的产品确认前,按以下假设推进,实施前第 0 阶段必须由产品负责人确认:

  • 图 14 表示“完整方案/高信息密度”的设计语言,其中图 2 是多面板拼版参考,不直接作为连续长图的物理尺寸标准;
  • 图 57 表示单屏竖版海报的设计语言;
  • 单图首发只保留 2:39:16 两种规格;
  • 连续长图使用固定逻辑宽度、内容自动高度,首发只保留一个导出宽度;
  • 先保留当前 AI“只生成背景素材”的路线文字、数字、图表和 Logo 由确定性布局渲染;
  • 第一版先修好当前浏览器导出链路,服务端 Headless Chromium 是否上线由可靠性门槛决定;
  • 当前工作区已经存在其他未提交修改,实施时不得覆盖或重排无关改动。

3. 对上一份报告的问题复核

3.1 尺寸分层提出正确,但目标模型仍不够准确

报告区分了最终尺寸、HTML 画布尺寸和 AI 素材尺寸,这是正确的。但它随后建议把长图 canvasWidth 直接设为 12421080,仍然混淆了两种宽度。

当前海报组件使用 28px 标题、1218px 正文、2032px padding这套字号更像为约 390540 CSS px 的逻辑画布设计。如果直接在 1080 CSS px 画布上排版,预览缩小后文字和间距会显得过小;这正是目前视觉密度不足的重要原因之一。

正确模型应为:

逻辑排版尺寸:决定文字、间距、网格和组件构图,例如 414 CSS px 宽
导出缩放倍率:例如 3
最终成品尺寸414 × 3 = 1242 px 宽
AI 素材尺寸:由 hero/full-bleed 素材槽位和供应商能力单独决定

因此修复中必须同时存在 layoutWidthoutputWidth,禁止继续使用一个 exportSize 表达全部含义。

3.2 1242px 只能作为候选导出宽度,不能直接当成产品标准

图 1、3、4 都是 1242px 宽,但这也可能来自手机截图或三倍屏导出。没有渠道规范、文件上传限制和目标设备信息,不能仅凭样图认定所有长图必须输出 1242px

计划中将 1242px 作为首选候选,同时保留在第 0 阶段改为 1080px 的决策点。两者不应同时首发,避免继续扩大测试矩阵。

3.3 图 2 和图 4 的结论过于确定

  • 图 2 是 1024×1536 的多面板拼版。它可能是设计参考,也可能是模型错误输出;没有生成上下文时不能直接判定为失败。
  • 图 4 的空白尾部确实符合错误截取范围,但尚未证明它由当前 min-height 直接生成。

修复时应把这两项变成复现用例,而不是把推断当成已确认根因。

3.4 30 MB 风险没有数据支撑

7 张样图实际文件大小约为 0.204.60 MB,均远低于服务端的 30 MB 限制。固定 pixelRatio: 2 和 data URL 的确存在浏览器内存风险,但“很容易超过 30 MB”没有被样本证明。

计划应分别记录:

  • PNG 压缩后文件大小;
  • 解码后的像素总量;
  • 浏览器合成峰值内存;
  • 上传限制。

文件大小和内存占用不是同一个问题,不能混为一谈。

3.5 服务端 Headless Chromium 是可选架构决策,不是第一步

服务端 Chromium 会引入浏览器二进制、字体、worker 内存、Docker 镜像体积、内部渲染鉴权和版本一致性问题。当前项目没有 Playwright/Puppeteer 依赖,直接迁移会扩大改动面。

更稳妥的顺序是:

  1. 先修正布局、尺寸契约和浏览器 Blob 导出;
  2. 记录真实失败率和关闭页面后的业务要求;
  3. 如果必须支持无页面会话生成,或浏览器导出失败率超过门槛,再引入服务端渲染。

3.6 审计健康分数对本次决策帮助有限

6/20 混入了可访问性、主题和响应式等维度,但本次核心是成品尺寸、版式和导出可靠性。没有运行时页面和用户任务数据,分数存在伪精确感。

本计划改用可验证门槛:真实像素是否一致、内容是否裁切、空白是否超标、关闭页面后是否需要完成、下载成功率和视觉基准是否通过。

3.7 报告漏掉了渲染数据来源分裂

当前至少存在三份内容来源:

  • 前端画布直接使用 draft.parsedFields
  • Celery 的 build_poster_content() 使用 product_rules.get("facts") or product_rules
  • 最终 document_json 又保存 case 的 confirmed_data

poster_content 只用于 AI prompt并没有成为 HTML 画布的统一输入。产品小册子的 features、计划书确认数据、字段画像和前端 sections 因而可能不一致。

这会造成“AI 背景理解的是一组卖点,画布展示的是另一组数据”。修复必须先建立唯一 renderDocument,不能只改 CSS。

3.8 报告漏掉了 fallback 图片会绘制文字

正常 prompt 要求 AI 只生成无文字背景,但 generate_fallback() 会在图片中绘制 headline、body 和 CTA。该图片随后又作为 hero 背景,前端 HTML 会再次绘制同样文案,可能产生重复文字、裁切文字或乱码。

fallback 必须改为无文字的纯背景素材,或者在背景生成失败时直接使用模板渐变,不能再生成“半成品海报”。

3.9 报告缺少落地约束

原报告没有明确:

  • 首发到底支持哪些规格;
  • 哪些旧记录继续使用 v1
  • 模板数据如何迁移;
  • 模式切换后 sections 和 template 如何处理;
  • 如何验证 ECharts 已完成渲染;
  • 如何灰度、监控和回滚;
  • 每个阶段改哪些文件以及完成定义。

以下计划补齐这些内容。

4. 目标产品规格

4.1 输出模式与规格

首发建议只支持三种格式,避免继续开放不可验证的“自定义尺寸”:

formatId 用途 逻辑尺寸 最终像素 高度策略
single_2_3 朋友圈/私聊单图 512×768 CSS px 1024×1536 固定
single_9_16 竖屏故事/企微 H5 540×960 CSS px 1080×1920 固定
long_1242_auto 完整方案长图 414×auto CSS px 1242×auto 内容驱动

第 0 阶段如果确认渠道统一要求 1080px 长图,则把最后一项改为 long_1080_auto,逻辑宽度仍保持独立,不改变组件排版原则。

首发明确不支持横版、方图、A4、自定义宽高、固定 2160/3240/4320 高度。以后增加格式时,每增加一个格式都必须同时增加视觉基准和导出测试。

4.2 内容预算

单图:

  • 1 个品牌区;
  • 1 个主标题,建议不超过 24 个中文字符;
  • 1 个副标题,最多 23 行;
  • 35 个核心数据;
  • 最多 3 个卖点;
  • 1 个 CTA
  • 1 条精简免责声明;
  • 不展示完整收益折线图和多年度卡片矩阵。

长图:

  • hero
  • 计划摘要;
  • 收益趋势图(有至少 3 个有效节点时);
  • 里程碑数据卡;
  • 最多 6 个产品卖点;
  • 适配/不适配说明;
  • CTA
  • 完整免责声明;
  • 高度完全由实际可见章节决定,不设置人为 min-height

4.3 模板定义

首发不做无限可配置模板系统,只增加解决当前问题所需的最少字段:

layoutKey          single_hero_data | single_story | long_editorial
supportedModes     [single] | [long]
supportedFormats   [single_2_3, ...]
themeTokens        背景、文字、卡片、边框、主色、强调色
backgroundSlot     hero | full_bleed
version            整数

section_schema、任意拖拽布局和管理员可视化模板编辑器不进入本次范围。

5. 目标技术模型

5.1 格式注册表

在后端建立唯一格式注册表,例如:

api/insurance/poster/format_registry.py

职责:

  • 返回首发格式列表;
  • 校验 formatIdoutputMode
  • 提供逻辑尺寸、最终尺寸、最大高度和 AI asset slot
  • 拒绝未知格式,不再静默回退。

新增只读接口:

GET /insurance/poster/formats

前端从该接口加载选项,避免 TypeScript 与 Python 各维护一份尺寸清单。接口不可用时只回退到内置的三项安全规格,不开放自定义尺寸。

为了兼容旧记录,保留数据库 export_size 字段;新记录同时在 document_json 中保存 formatIdrequestedOutputactualOutput。第一期不为了实际宽高额外增加数据库列,避免不必要迁移。

5.2 统一渲染文档

新增后端构建器,例如:

api/insurance/poster/render_document_builder.py

统一输出:

{
  "schemaVersion": 2,
  "formatId": "single_2_3",
  "outputMode": "single",
  "layoutKey": "single_hero_data",
  "copy": {},
  "facts": {},
  "features": [],
  "benefits": [],
  "theme": {},
  "sections": [],
  "background": {},
  "complianceRevision": ""
}

数据优先级固定为:

  1. 人工确认的 case confirmed_data
  2. 产品小册子规则中的产品卖点和保障规则;
  3. 模板默认值;
  4. 安全空状态。

Celery prompt、前端预览、最终导出和历史恢复都读取同一份文档不再分别拼装。

5.3 画布路由

新增轻量路由组件:

PosterCanvasRouter
├── PosterSingleCanvas
└── PosterLongCanvas

章节组件可以复用,但容器、信息预算和排版规则必须独立。PosterHtmlCanvas 不再通过 aspect-ratio + overflow:hidden 模拟单图。

模式或格式切换时必须执行:

  1. 重新计算兼容 layout
  2. 过滤不兼容 template
  3. 按内容预算规范化 sections
  4. 告知用户哪些内容被隐藏,而不是静默裁切;
  5. 标记当前最终成品为过期,需要重新导出。

5.4 AI 背景素材

图片模型只接收素材槽位规格,不接收最终长图高度:

最终长图1242×auto
逻辑 hero414×360 CSS px
AI 素材:由 provider capability 映射到最接近的竖版或横版背景尺寸

图片供应商尺寸解析必须严格:

  • 已支持规格明确映射;
  • 未支持规格返回可诊断错误;
  • 不得无提示回退到 1024x1792
  • 记录 requested asset size 和 actual asset size
  • fallback 只生成无文字渐变/纹理背景。

5.5 浏览器导出 v2

第一版继续使用浏览器导出,但必须改成确定性流程:

  1. 等待字体完成;
  2. 等待全部图片 decode
  3. 等待 ECharts finished 事件,而不是只等待两个 animation frame
  4. 读取未缩放画布的逻辑尺寸;
  5. 根据 outputWidth / layoutWidth 计算唯一导出倍率;
  6. 单图固定输出宽高;长图以真实内容高度计算输出高度;
  7. 使用 Blob 导出,不经 base64 data URL 中转;
  8. 客户端解码 Blob 核验宽高;
  9. 服务端用 Pillow 再次解码并校验;
  10. 校验通过后才写入 export_url 并显示“可下载”。

长图安全门槛首版建议:

  • 最终像素高度超过浏览器验证上限时阻止导出并给出明确提示;
  • 不在本次首发中自动降采样,因为静默降低质量会再次造成记录和成品不一致;
  • 分片导出作为后续功能,只有真实案例超过上限后再实现。

6. 分阶段实施计划

阶段 0冻结基线与复现12 人日)

目标

把推断变成可重复证据,确认首发格式。

任务

  1. 保存当前工作区差异清单,标记海报相关未提交改动的负责人;
  2. 固定一份脱敏计划书数据、产品规则、模板和文案作为测试 fixture
  3. 对当前版本分别导出:
    • single 默认规格;
    • long 2160
    • long 4320
  4. 记录选择尺寸、DOM 尺寸、PNG 真实尺寸、像素总量、Blob 大小、上传结果和下载结果;
  5. 验证图 4 空白是否可由当前代码稳定复现;
  6. 验证图 2 是否属于允许的多面板设计方向;
  7. 确认长图首发输出宽度为 1242 或 1080
  8. 确认是否存在“用户关闭页面后必须继续完成最终成品”的硬需求。

产出

  • tests/fixtures/poster/ 脱敏 fixture
  • 当前版本导出基线表;
  • 三个首发 formatId 的最终确认;
  • 服务端 Headless 是否必须进入本期的决策记录。

完成定义

同一 fixture 在同一浏览器连续导出 3 次,能够稳定复现当前尺寸问题,并获得真实 PNG 元数据。

阶段 1尺寸契约止血23 人日)

后端任务

  • 新增 format_registry.py
  • 新增 GET /poster/formats
  • PosterService.generate_poster() 改为接收 formatId
  • 旧请求中的 size 仅用于兼容映射,无法映射时返回参数错误;
  • PosterImageGenerator 将最终格式与 AI asset size 分离;
  • 删除未知尺寸默认回退;
  • generate_fallback() 改成无文字背景。

前端任务

  • PosterDraft 新增 formatId
  • PosterCreativePanel.vue 从 formats API 渲染规格;
  • 删除 A4、横版、方图和自定义尺寸入口
  • outputMode 切换时自动选择第一个兼容格式;
  • buildPosterDocument() 和自动保存 watcher 纳入 formatId

主要文件

  • api/insurance/poster/format_registry.py(新增)
  • api/insurance/poster/routes.py
  • api/insurance/poster/service.py
  • api/insurance/poster/image_generator.py
  • frontend/src/utils/poster-api.ts
  • frontend/src/composables/usePosterWorkspace.ts
  • frontend/src/components/poster/workspace/PosterCreativePanel.vue
  • frontend/src/pages/PosterPage.vue

测试

  • 每个 formatId 都能返回唯一逻辑/输出规格;
  • 未知 formatId 返回 4xx 业务错误;
  • 1024x1792 记录可映射到安全兼容格式;
  • 所有前端可见格式均被后端接受;
  • fallback PNG 不包含 headline/body/CTA 绘制逻辑。

完成定义

UI 中不存在任何会被后端静默改成其他尺寸的选项。

阶段 2统一渲染文档23 人日)

后端任务

  • 新增 render_document_builder.py
  • case confirmed facts、product rules、copy、template、profile 合并为 schema v2
  • build_poster_content() 明确接收 case facts 与 product rules修复当前来源混用
  • record 创建时立即保存完整 renderDocument
  • Celery 从 renderDocument 构建背景 prompt
  • 历史恢复返回原始 schemaVersion禁止用最新规则偷偷改变旧成品。

前端任务

  • PosterDraft 增加 renderDocument 或等价强类型字段;
  • 画布从 renderDocument 读取 facts、features、benefits、theme 和 sections
  • 删除 defaultFeaturesparsedFields.key_benefits 临时拼装的逻辑;
  • 编辑操作只修改 renderDocument 对应字段。

主要文件

  • api/insurance/poster/render_document_builder.py(新增)
  • api/insurance/poster/content_builder.py
  • api/insurance/poster/service.py
  • api/insurance/generation/celery_tasks.py
  • frontend/src/composables/usePosterWorkspace.ts
  • frontend/src/components/poster/workspace/PosterStage.vue
  • frontend/src/pages/PosterPage.vue

测试

  • 人工确认数据覆盖 AI 原始解析数据;
  • 产品 features 能进入前端画布;
  • 同一 renderDocument 生成的 prompt、预览和导出使用相同事实
  • schema v1 旧记录仍能恢复;
  • 缺失收益表时不显示空图表。

完成定义

任意一个展示字段都能追溯到 renderDocument 的唯一字段,不再从三个不同对象临时拼接。

阶段 3拆分单图/长图版式与模板46 人日)

数据和模板任务

  • poster_templates 增加最少兼容字段;迁移编号使用实施时的下一个可用编号,避免与当前未提交迁移冲突;
  • 为现有模板补齐默认 layoutKey、supportedModes 和 supportedFormats
  • 旧模板无法识别时回退到明确的 legacy layout不自动套新布局。

前端任务

  • 新增 PosterCanvasRouter.vue
  • 新增 single/PosterSingleCanvas.vue
  • 将现有 long/PosterHtmlCanvas.vue 收敛为真正的 PosterLongCanvas.vue
  • 抽取共享的事实格式化、免责声明和品牌组件,不抽象视觉结构;
  • 建立画布级 theme CSS variables
  • summary/chart/features 等组件移除硬编码绿色;
  • 单图只渲染允许的信息预算;
  • 长图移除 minHeight: export height
  • 模板选择按 mode/format 过滤;
  • 切换模式时显示内容调整提示。

单图构图要求

  • 主视觉是第一视觉焦点;
  • 标题、核心数据条和 3 个卖点在一屏内完整可读;
  • footer 和免责声明始终可见;
  • 不依赖 overflow:hidden 裁掉正文。

长图构图要求

  • 章节间有明确的色块和节奏变化;
  • 背景和 theme 贯穿全部章节;
  • 内容结束后立即进入 footer不存在人为尾部留白
  • 图表和数据卡具有适合 414 CSS px 逻辑宽度的字号。

主要文件

  • api/insurance/models/poster_template_model.py
  • api/insurance/db/migrate_0xx.py(使用实际下一个编号)
  • api/insurance/admin/ppt_admin_service.py
  • frontend/src/components/poster/PosterCanvasRouter.vue(新增)
  • frontend/src/components/poster/single/PosterSingleCanvas.vue(新增)
  • frontend/src/components/poster/long/PosterHtmlCanvas.vue
  • frontend/src/components/poster/long/*.vue
  • frontend/src/components/poster/workspace/PosterCreativePanel.vue
  • frontend/src/components/poster/workspace/PosterSectionPanel.vue

视觉基准

至少建立三张基准图:

  • 单图 2:3
  • 单图 9:16
  • 标准长图。

使用同一 fixture 与批准样图逐项对比信息层级、主视觉占比、文字可读性、数据完整性、章节节奏、footer 和空白尾部。

完成定义

单图和长图在 DOM 结构、内容预算和模板白名单上均相互独立;隐藏任意章节后不会破坏剩余布局。

阶段 4可靠导出 v223 人日)

前端任务

  • renderComposite() 拆到独立 poster-exporter.ts
  • 导出时使用未应用预览 transform 的画布;
  • 根据 format spec 计算导出倍率;
  • 单图固定宽高,长图读取真实内容高度;
  • 将 ECharts ready 纳入资源屏障;
  • 使用 Blob API
  • 导出后在客户端校验真实像素;
  • 导出状态明确区分 preparingrenderinguploadingreadyfailed
  • 对失败给出具体错误,不直接回退下载旧文件。

后端任务

  • save_rendered() 使用 Pillow verify() 和重新打开读取尺寸;
  • 校验实际尺寸是否符合 format spec
  • 将 actualOutput、byteSize 和校验结果保存到 document/extraData
  • 只有验证通过才写 export_url
  • /download/<id> 在最终文件未就绪时返回“成品仍在准备/需要重新导出”,不返回模糊的“文件不存在”。

主要文件

  • frontend/src/utils/poster-exporter.ts(新增)
  • frontend/src/pages/PosterPage.vue
  • frontend/src/components/poster/long/PosterBenefitChart.vue
  • frontend/src/components/poster/workspace/PosterStage.vue
  • api/insurance/poster/service.py
  • api/insurance/poster/routes.py

测试

  • single_2_3 必须严格输出 1024×1536
  • single_9_16 必须严格输出 1080×1920
  • long_1242_auto 宽度严格为 1242高度等于内容测量值乘导出倍率
  • 图表在导出图中非空;
  • 服务端拒绝损坏 PNG、错误尺寸和不匹配 formatId 的文件;
  • 导出失败不会覆盖上一版有效文件;
  • 同一文档连续导出三次尺寸一致。

完成定义

UI 规格、renderDocument、PNG 真实尺寸和服务端记录四者一致。

阶段 5可靠性决策门0.5 人日评审)

完成阶段 4 后,使用至少 20 次真实或仿真导出评估:

  • 浏览器最终合成成功率是否达到 99%
  • 长图最大像素下是否出现黑图/空图/截断;
  • 是否存在明确业务要求:用户提交后关闭页面,仍必须自动完成最终海报;
  • worker/Docker 是否允许增加 Chromium 的资源成本。

只有满足以下任一条件,才进入服务端 Headless 阶段:

  • 关闭页面后自动成品是硬需求;
  • 浏览器导出成功率低于 99%
  • 多端浏览器结果无法保持一致;
  • 历史任务必须支持无人值守重新渲染。

阶段 6条件性服务端 Headless 渲染(额外 47 人日)

任务

  • 评估 Playwright/Chromium 进入 worker 镜像的体积和内存;
  • 建立只接受短时签名 token 的内部 render route
  • 固定字体、浏览器版本、viewport、device scale 和 locale
  • Celery 在背景素材完成后打开 render route生成最终 PNG
  • 最终任务状态只有在 PNG 校验并落盘后才变为 done
  • 增加超时、重试、幂等和旧成品保护;
  • 保留浏览器本地导出作为人工应急能力。

完成定义

用户生成后立即关闭页面,任务仍能在历史记录中完成,并下载与预览一致的最终海报。

7. 测试计划

7.1 后端单元测试

新增或扩展:

  • format registry 映射和拒绝规则;
  • renderDocument 数据优先级;
  • single/long 字段预算;
  • template 兼容过滤;
  • fallback 无文字;
  • PNG 解码、尺寸和格式校验;
  • schema v1/v2 兼容恢复。

建议文件:

  • tests/poster_format_registry_test.py
  • tests/poster_render_document_test.py
  • tests/poster_export_validation_test.py

7.2 前端组件测试

当前前端未配置组件测试框架。阶段 3 开始前决定是否引入 Vitest若不引入至少用构建检查和 E2E 覆盖以下逻辑:

  • 模式切换自动修正规格与模板;
  • single 不渲染 forbidden sections
  • long 高度随内容变化;
  • export scale 计算;
  • 资源未 ready 时禁止导出;
  • 导出失败状态和重试。

7.3 端到端与视觉回归

固定浏览器、字体和 fixture验证

  1. 新建海报;
  2. 选择产品并恢复确认数据;
  3. 切换 single/long
  4. 选择兼容模板;
  5. 生成背景;
  6. 导出并下载;
  7. 使用 Pillow 校验 PNG 元数据;
  8. 与批准基准图做截图差异比较;
  9. 刷新页面并重新下载;
  10. 导出失败后旧成品仍可用。

7.4 性能边界

以像素总量而不是文件 MB 作为主要边界:

  • 标准单图;
  • 标准长图;
  • 2 倍典型长图内容;
  • 最大允许长图;
  • 低内存移动设备只做预览,不要求在移动端导出最大长图时必须成功;如业务要求移动端导出,则服务端渲染直接升级为硬需求。

8. 兼容、灰度与回滚

8.1 旧记录

  • schemaVersion 缺失视为 v1
  • v1 记录继续使用 legacy renderer不用 v2 规则重新排版;
  • v1 可下载文件保持原路径;
  • 用户主动选择“升级并重新生成”时才转为 v2。

8.2 功能开关

增加项目级配置 poster_render_v2

  • 关闭:继续使用旧布局和旧导出;
  • 开启:新建记录使用 format registry、renderDocument v2 和新布局;
  • 灰度:仅管理员/测试用户开启。

8.3 数据迁移

  • 模板字段只做增量新增,不删除旧字段;
  • 给现有模板补兼容默认值;
  • 不重写旧 document_json
  • 迁移脚本编号必须在实施时检查 api/insurance/db/ 当前最大编号,避免与未提交的 migrate_029.pymigrate_030.py 冲突。

8.4 回滚

  • 关闭 poster_render_v2 即可停止新链路;
  • 新增字段保持 nullable旧代码可忽略
  • 新导出文件使用新 revision 文件名,不覆盖旧文件;
  • 任何阶段不得删除用户已有海报或背景素材。

9. 监控与诊断信息

每次导出至少记录:

recordId
schemaVersion
formatId
layoutKey
logicalWidth/logicalHeight
requestedWidth/requestedHeight
actualWidth/actualHeight
pixelCount
blobByteSize
backgroundProvider/backgroundModel
backgroundRequestedSize/backgroundActualSize
renderDurationMs
uploadDurationMs
validationResult
failureStage/failureCode

首发观察指标:

  • 背景生成成功率;
  • 最终合成成功率;
  • 最终文件验证通过率;
  • 下载成功率;
  • 各阶段 P50/P95 时长;
  • 浏览器导出失败原因分布。

10. 文件级修改矩阵

文件/目录 计划改动 阶段
api/insurance/poster/format_registry.py 统一格式规格与校验 1
api/insurance/poster/render_document_builder.py 构建唯一 renderDocument 2
api/insurance/poster/content_builder.py 修正 case facts/product rules 合并 2
api/insurance/poster/image_generator.py asset size 严格映射、无文字 fallback 1/2
api/insurance/poster/service.py format 校验、document v2、PNG 校验 1/2/4
api/insurance/poster/routes.py formats API、明确下载错误 1/4
api/insurance/generation/celery_tasks.py 使用 renderDocument 生成背景 2
api/insurance/models/poster_template_model.py 最少模板兼容字段 3
api/insurance/db/migrate_0xx.py 增量模板字段迁移 3
frontend/src/composables/usePosterWorkspace.ts formatId、renderDocument、v1/v2 恢复 1/2
frontend/src/utils/poster-api.ts formats 和 v2 document API 1/2
frontend/src/utils/poster-exporter.ts 独立确定性导出器 4
frontend/src/components/poster/PosterCanvasRouter.vue 单图/长图布局路由 3
frontend/src/components/poster/single/ 单图专用布局 3
frontend/src/components/poster/long/ 长图布局与主题 token 3
frontend/src/components/poster/workspace/PosterCreativePanel.vue 兼容格式和模板选择 1/3
frontend/src/components/poster/workspace/PosterStage.vue 预览与未缩放画布管理 3/4
frontend/src/pages/PosterPage.vue 简化为编排、状态与持久化 1/2/4
tests/poster_* 单元、契约、导出验证 全阶段

11. 明确不做的事项

为了控制改动范围,本期不做:

  • 任意自定义宽高;
  • A4/横版/方图;
  • 所见即所得自由拖拽编辑器;
  • 管理员可视化搭建模板;
  • 自动生成多页拼版;
  • 无真实案例支撑的长图自动分片;
  • 在阶段 5 决策前直接引入 Chromium
  • 重构无关 PPT、认证、产品推荐或基座代码。

12. 最终验收清单

只有以下项目全部通过,才能认为修复完成:

  • 首发仅显示已确认的三种格式;
  • 未知格式不会静默回退;
  • CSS 逻辑尺寸与 PNG 输出尺寸明确分离;
  • single 和 long 使用不同的画布组件与内容预算;
  • 单图没有被 overflow:hidden 静默裁掉的内容;
  • 长图内容结束后没有人为 min-height 空白;
  • 模板只出现在兼容 mode/format 中;
  • 模板主题贯穿所有章节,不再固定白底绿色;
  • AI 和 fallback 都不绘制任何文案、数字或 Logo
  • prompt、预览、导出读取同一 renderDocument
  • 单图 PNG 像素严格符合规格;
  • 长图 PNG 宽度严格符合规格,高度来自真实内容;
  • 图表、字体和图片全部 ready 后才导出;
  • 服务端能拒绝损坏或错误尺寸的 PNG
  • 导出失败不会覆盖上一版有效成品;
  • 历史记录能正确恢复 v1/v2 文档;
  • 灰度开关与回滚路径验证通过;
  • 是否引入服务端 Headless Chromium 已通过阶段 5 数据决策。

13. 推荐执行顺序

严格按以下顺序实施,不并行修改同一链路:

复现与规格确认
→ 格式契约
→ 统一 renderDocument
→ 单图/长图布局
→ 确定性导出与服务端校验
→ 真实可靠性评估
→ 条件性服务端 Headless

前一阶段的完成定义未通过,不进入下一阶段。这样可以避免一边改模板、一边改尺寸、一边换渲染器,最终无法定位回归来源。