29 KiB
海报生成问题报告复核与详细修复计划书
日期: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使实际导出尺寸失控;- 最终文件依赖浏览器合成,任务状态与可下载状态存在错位。
但上一份报告不能直接作为开发计划使用,原因是:
- 它虽然提出了三层尺寸概念,后续方案却再次把 CSS 逻辑排版宽度 与 最终 PNG 像素宽度 混在一起;
- 它把服务端 Headless Chromium 当成必选修复,没有先验证较小、较快的浏览器导出修复能否满足当前业务;
- 部分结论属于推断而不是已复现事实,例如图 4 空白一定来自当前系统、长图很容易超过 30 MB;
- 它漏掉了两个重要根因:渲染数据来源分裂,以及 fallback 背景仍会绘制文字;
- 缺少文件级任务、接口契约、兼容策略、灰度方案、回滚方案和可执行验收用例。
本计划采用“先建立可测基线,再做最小闭环修复,最后决定是否引入服务端浏览器”的顺序。推荐目标工期为 13~19 人日;若确认必须支持“用户关闭页面后仍自动产出最终海报”,再增加服务端渲染阶段,预计额外 4~7 人日。
2. 本计划的工作假设
在没有新的产品确认前,按以下假设推进,实施前第 0 阶段必须由产品负责人确认:
- 图 1~4 表示“完整方案/高信息密度”的设计语言,其中图 2 是多面板拼版参考,不直接作为连续长图的物理尺寸标准;
- 图 5~7 表示单屏竖版海报的设计语言;
- 单图首发只保留
2:3和9:16两种规格; - 连续长图使用固定逻辑宽度、内容自动高度,首发只保留一个导出宽度;
- 先保留当前 AI“只生成背景素材”的路线,文字、数字、图表和 Logo 由确定性布局渲染;
- 第一版先修好当前浏览器导出链路,服务端 Headless Chromium 是否上线由可靠性门槛决定;
- 当前工作区已经存在其他未提交修改,实施时不得覆盖或重排无关改动。
3. 对上一份报告的问题复核
3.1 尺寸分层提出正确,但目标模型仍不够准确
报告区分了最终尺寸、HTML 画布尺寸和 AI 素材尺寸,这是正确的。但它随后建议把长图 canvasWidth 直接设为 1242 或 1080,仍然混淆了两种宽度。
当前海报组件使用 28px 标题、12~18px 正文、20~32px padding,这套字号更像为约 390~540 CSS px 的逻辑画布设计。如果直接在 1080 CSS px 画布上排版,预览缩小后文字和间距会显得过小;这正是目前视觉密度不足的重要原因之一。
正确模型应为:
逻辑排版尺寸:决定文字、间距、网格和组件构图,例如 414 CSS px 宽
导出缩放倍率:例如 3
最终成品尺寸:414 × 3 = 1242 px 宽
AI 素材尺寸:由 hero/full-bleed 素材槽位和供应商能力单独决定
因此修复中必须同时存在 layoutWidth 与 outputWidth,禁止继续使用一个 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.20~4.60 MB,均远低于服务端的 30 MB 限制。固定 pixelRatio: 2 和 data URL 的确存在浏览器内存风险,但“很容易超过 30 MB”没有被样本证明。
计划应分别记录:
- PNG 压缩后文件大小;
- 解码后的像素总量;
- 浏览器合成峰值内存;
- 上传限制。
文件大小和内存占用不是同一个问题,不能混为一谈。
3.5 服务端 Headless Chromium 是可选架构决策,不是第一步
服务端 Chromium 会引入浏览器二进制、字体、worker 内存、Docker 镜像体积、内部渲染鉴权和版本一致性问题。当前项目没有 Playwright/Puppeteer 依赖,直接迁移会扩大改动面。
更稳妥的顺序是:
- 先修正布局、尺寸契约和浏览器 Blob 导出;
- 记录真实失败率和关闭页面后的业务要求;
- 如果必须支持无页面会话生成,或浏览器导出失败率超过门槛,再引入服务端渲染。
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 个副标题,最多 2~3 行;
- 3~5 个核心数据;
- 最多 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
职责:
- 返回首发格式列表;
- 校验
formatId与outputMode; - 提供逻辑尺寸、最终尺寸、最大高度和 AI asset slot;
- 拒绝未知格式,不再静默回退。
新增只读接口:
GET /insurance/poster/formats
前端从该接口加载选项,避免 TypeScript 与 Python 各维护一份尺寸清单。接口不可用时只回退到内置的三项安全规格,不开放自定义尺寸。
为了兼容旧记录,保留数据库 export_size 字段;新记录同时在 document_json 中保存 formatId、requestedOutput 和 actualOutput。第一期不为了实际宽高额外增加数据库列,避免不必要迁移。
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": ""
}
数据优先级固定为:
- 人工确认的 case
confirmed_data; - 产品小册子规则中的产品卖点和保障规则;
- 模板默认值;
- 安全空状态。
Celery prompt、前端预览、最终导出和历史恢复都读取同一份文档,不再分别拼装。
5.3 画布路由
新增轻量路由组件:
PosterCanvasRouter
├── PosterSingleCanvas
└── PosterLongCanvas
章节组件可以复用,但容器、信息预算和排版规则必须独立。PosterHtmlCanvas 不再通过 aspect-ratio + overflow:hidden 模拟单图。
模式或格式切换时必须执行:
- 重新计算兼容 layout;
- 过滤不兼容 template;
- 按内容预算规范化 sections;
- 告知用户哪些内容被隐藏,而不是静默裁切;
- 标记当前最终成品为过期,需要重新导出。
5.4 AI 背景素材
图片模型只接收素材槽位规格,不接收最终长图高度:
最终长图:1242×auto
逻辑 hero:414×360 CSS px
AI 素材:由 provider capability 映射到最接近的竖版或横版背景尺寸
图片供应商尺寸解析必须严格:
- 已支持规格明确映射;
- 未支持规格返回可诊断错误;
- 不得无提示回退到
1024x1792; - 记录 requested asset size 和 actual asset size;
- fallback 只生成无文字渐变/纹理背景。
5.5 浏览器导出 v2
第一版继续使用浏览器导出,但必须改成确定性流程:
- 等待字体完成;
- 等待全部图片 decode;
- 等待 ECharts
finished事件,而不是只等待两个 animation frame; - 读取未缩放画布的逻辑尺寸;
- 根据
outputWidth / layoutWidth计算唯一导出倍率; - 单图固定输出宽高;长图以真实内容高度计算输出高度;
- 使用 Blob 导出,不经 base64 data URL 中转;
- 客户端解码 Blob 核验宽高;
- 服务端用 Pillow 再次解码并校验;
- 校验通过后才写入
export_url并显示“可下载”。
长图安全门槛首版建议:
- 最终像素高度超过浏览器验证上限时阻止导出并给出明确提示;
- 不在本次首发中自动降采样,因为静默降低质量会再次造成记录和成品不一致;
- 分片导出作为后续功能,只有真实案例超过上限后再实现。
6. 分阶段实施计划
阶段 0:冻结基线与复现(1~2 人日)
目标
把推断变成可重复证据,确认首发格式。
任务
- 保存当前工作区差异清单,标记海报相关未提交改动的负责人;
- 固定一份脱敏计划书数据、产品规则、模板和文案作为测试 fixture;
- 对当前版本分别导出:
- single 默认规格;
- long 2160;
- long 4320;
- 记录:选择尺寸、DOM 尺寸、PNG 真实尺寸、像素总量、Blob 大小、上传结果和下载结果;
- 验证图 4 空白是否可由当前代码稳定复现;
- 验证图 2 是否属于允许的多面板设计方向;
- 确认长图首发输出宽度为 1242 或 1080;
- 确认是否存在“用户关闭页面后必须继续完成最终成品”的硬需求。
产出
tests/fixtures/poster/脱敏 fixture;- 当前版本导出基线表;
- 三个首发 formatId 的最终确认;
- 服务端 Headless 是否必须进入本期的决策记录。
完成定义
同一 fixture 在同一浏览器连续导出 3 次,能够稳定复现当前尺寸问题,并获得真实 PNG 元数据。
阶段 1:尺寸契约止血(2~3 人日)
后端任务
- 新增
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.pyapi/insurance/poster/service.pyapi/insurance/poster/image_generator.pyfrontend/src/utils/poster-api.tsfrontend/src/composables/usePosterWorkspace.tsfrontend/src/components/poster/workspace/PosterCreativePanel.vuefrontend/src/pages/PosterPage.vue
测试
- 每个 formatId 都能返回唯一逻辑/输出规格;
- 未知 formatId 返回 4xx 业务错误;
- 旧
1024x1792记录可映射到安全兼容格式; - 所有前端可见格式均被后端接受;
- fallback PNG 不包含 headline/body/CTA 绘制逻辑。
完成定义
UI 中不存在任何会被后端静默改成其他尺寸的选项。
阶段 2:统一渲染文档(2~3 人日)
后端任务
- 新增
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;
- 删除
defaultFeatures从parsedFields.key_benefits临时拼装的逻辑; - 编辑操作只修改 renderDocument 对应字段。
主要文件
api/insurance/poster/render_document_builder.py(新增)api/insurance/poster/content_builder.pyapi/insurance/poster/service.pyapi/insurance/generation/celery_tasks.pyfrontend/src/composables/usePosterWorkspace.tsfrontend/src/components/poster/workspace/PosterStage.vuefrontend/src/pages/PosterPage.vue
测试
- 人工确认数据覆盖 AI 原始解析数据;
- 产品 features 能进入前端画布;
- 同一 renderDocument 生成的 prompt、预览和导出使用相同事实;
- schema v1 旧记录仍能恢复;
- 缺失收益表时不显示空图表。
完成定义
任意一个展示字段都能追溯到 renderDocument 的唯一字段,不再从三个不同对象临时拼接。
阶段 3:拆分单图/长图版式与模板(4~6 人日)
数据和模板任务
- 为
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.pyapi/insurance/db/migrate_0xx.py(使用实际下一个编号)api/insurance/admin/ppt_admin_service.pyfrontend/src/components/poster/PosterCanvasRouter.vue(新增)frontend/src/components/poster/single/PosterSingleCanvas.vue(新增)frontend/src/components/poster/long/PosterHtmlCanvas.vuefrontend/src/components/poster/long/*.vuefrontend/src/components/poster/workspace/PosterCreativePanel.vuefrontend/src/components/poster/workspace/PosterSectionPanel.vue
视觉基准
至少建立三张基准图:
- 单图 2:3;
- 单图 9:16;
- 标准长图。
使用同一 fixture 与批准样图逐项对比:信息层级、主视觉占比、文字可读性、数据完整性、章节节奏、footer 和空白尾部。
完成定义
单图和长图在 DOM 结构、内容预算和模板白名单上均相互独立;隐藏任意章节后不会破坏剩余布局。
阶段 4:可靠导出 v2(2~3 人日)
前端任务
- 将
renderComposite()拆到独立poster-exporter.ts; - 导出时使用未应用预览 transform 的画布;
- 根据 format spec 计算导出倍率;
- 单图固定宽高,长图读取真实内容高度;
- 将 ECharts ready 纳入资源屏障;
- 使用 Blob API;
- 导出后在客户端校验真实像素;
- 导出状态明确区分
preparing、rendering、uploading、ready、failed; - 对失败给出具体错误,不直接回退下载旧文件。
后端任务
save_rendered()使用 Pillowverify()和重新打开读取尺寸;- 校验实际尺寸是否符合 format spec;
- 将 actualOutput、byteSize 和校验结果保存到 document/extraData;
- 只有验证通过才写
export_url; /download/<id>在最终文件未就绪时返回“成品仍在准备/需要重新导出”,不返回模糊的“文件不存在”。
主要文件
frontend/src/utils/poster-exporter.ts(新增)frontend/src/pages/PosterPage.vuefrontend/src/components/poster/long/PosterBenefitChart.vuefrontend/src/components/poster/workspace/PosterStage.vueapi/insurance/poster/service.pyapi/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 渲染(额外 4~7 人日)
任务
- 评估 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.pytests/poster_render_document_test.pytests/poster_export_validation_test.py
7.2 前端组件测试
当前前端未配置组件测试框架。阶段 3 开始前决定是否引入 Vitest;若不引入,至少用构建检查和 E2E 覆盖以下逻辑:
- 模式切换自动修正规格与模板;
- single 不渲染 forbidden sections;
- long 高度随内容变化;
- export scale 计算;
- 资源未 ready 时禁止导出;
- 导出失败状态和重试。
7.3 端到端与视觉回归
固定浏览器、字体和 fixture,验证:
- 新建海报;
- 选择产品并恢复确认数据;
- 切换 single/long;
- 选择兼容模板;
- 生成背景;
- 导出并下载;
- 使用 Pillow 校验 PNG 元数据;
- 与批准基准图做截图差异比较;
- 刷新页面并重新下载;
- 导出失败后旧成品仍可用。
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.py、migrate_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
前一阶段的完成定义未通过,不进入下一阶段。这样可以避免一边改模板、一边改尺寸、一边换渲染器,最终无法定位回归来源。