)
目录一、今日概览二、快速查询前端接入一个配置该不该复用的判断配置独立的决策副线菜单导入生产的层级丢失三、数据看板从整页空白到局部降级全局兜底 vs 局部处理四、PDF 导出排障一次方案的三段反复第一段要不要装浏览器第二段能不能干脆不装浏览器纯前端第三段回到后端渲染但修了 Host 坑五、导出报告前端化原生 HTML拒绝截图六、发版与收尾七、复盘一条暗线脱敏说明本文内部项目以化名出现——lab-web前端、lab-aidoc/lab-biz后端、lab-agentAgent 服务、agent-web另一个前端项目。业务模块泛化为某识别模块/某核验模块等具体角色、测试文件已化名。chromedp、pandoc、k3s、Chromium、UmiJS 等开源/通用技术名词保留原称。今天的工作密度不小21 个 codex 会话里真正落地的开发有五摊一摊前端接入、一摊容错改造、两摊都围绕导出打转外加发版。有意思的是看似散活的几件事背后有同一条暗线——“渲染质量取决于数据是原生还是截图而架构位置决定了运维成本”。这个到复盘再串先按时间顺序展开。一、今日概览模块事项类型lab-web快速查询quick-query前端接入从 agent-web 迁移页面功能lab-aidoc快速查询硬编码超时改为配置可调且配置独立不复用 OCR 配置重构lab-web数据看板局部降级单个服务失败不再导致整页空白修复lab-biz/aidocPDF 导出生产排障google-chrome not found→ k3s 独立 chromium 服务 Host 头修复排障lab-web三个模块导出报告从后端渲染改为前端原生 HTML 导出重构后台菜单导入生产层级丢失修复、菜单全量移除 icon修复全栈lab-agent、lab-web 发版发版二、快速查询前端接入一个配置该不该复用的判断快速查询的后端模块早就实现了但前端一直没接。这摊活本以为是搬页面实际牵出一个值得记的配置取舍。页面本身从agent-web迁过来问题不大用一份 PDF 样本跑通流程后重点转到后端快速查询的 OCR走不走版面感知layout_aware流程、超时怎么管。配置独立的决策超时原本是硬编码的要改成配置可调。第一版顺手把它挂到了现成的 OCR LLM 配置ocrllm下面——反正都是 LLM 调用复用一套配置省事。但这个省事很快被否掉了理由很直接快速查询应该是它自己本身而不是 OCR 服务的附庸。第一版复用最终独立被否快速查询≠OCR快速查询超时配置放哪?ocrllm 配置省事但耦合quick_query 自己的配置段独立可演化这是个挺典型的判断复用不是看现在像不像而是看以后会不会各自演化。快速查询和 OCR 固然都要调 LLM但它们的模型选择、超时容忍度、并发模式迟早会分叉。一旦塞进同一个配置段未来任何一方调整都得顾忌另一方。趁现在还没有历史依赖把它独立出来成本最低。副线菜单导入生产的层级丢失接入过程中还插了一个后台问题把菜单导出再导入生产环境后只有数据中心保留了原来的层级结构其余模块全摊平成了一级菜单。根因不在导入逻辑本身而在只导了菜单表、漏了角色绑定生产的唯一角色化名某角色丢失了对父级菜单的层级绑定。修复是重建core_role_menu_ref——把那 30 条菜单按最终结构重新绑回角色。顺带把所有菜单的icon字段全量清空前端icon为空就不渲染纯文本菜单更干净。一个小教训导数据只导主表是常见坑。菜单的层级关系除了菜单表自身的parent_code还依赖角色-菜单绑定表漏了关联表结构就会在导入端塌掉。三、数据看板从整页空白到局部降级数据看板页面有个恼人的现象后端任意一个服务接口报错整个页面就空白其它正常的服务数据也一起看不到了。需求很明确一个服务出错只把那一块显示为错误别拖垮整页。全局兜底 vs 局部处理一开始想的是一劳永逸的路子——在请求层的全局回调里统一兜底或者加个统一封装。但这条路的代价是侵入公共代码要动utils/request.ts这种被全局引用的基础设施影响面太大而且局部降级的策略每个页面其实不一样看板是分块容错别的页面可能是整页重试全局兜底很难精确表达。全局兜底局部 catch数据看板: 多个服务分块某服务失败整页空白原行为仅该区块显示错误其它正常展示权衡之后走了指定页面局部处理在每个分块请求处单独 catch失败的那块降级成错误态其余照常渲染。代价是每个用了多服务的页面都得自己写一遍 catch但它零侵入公共代码且容错策略完全由各页面自己说了算。这个取舍背后有个原则容错策略是页面级语义不是请求级通用。一个 HTTP 500 在不同页面的含义不同——在看板里是这一格暂时没数据在表单提交里可能是必须重试。把它强行收进全局回调等于让基础设施去猜业务语义猜不准就会在某个页面出错。所以宁可每个页面多写几行 catch也不让公共代码背上它不该懂的语义。顺带澄清了一个小疑惑登录后落地页其实是数据看板history.replace(/data-board)那个带3 条热点新闻/国产化率目标的欢迎页是纯静态硬编码、不调任何接口的备用入口——所以它永远不会空白。四、PDF 导出排障一次方案的三段反复这摊最有故事性。生产环境arm下载报告 PDF 直接报错chromedp PDF 生成失败: exec: google-chrome: executable file not found in $PATH后端用 chromedp 驱动 Chrome 把页面渲染成 PDF但生产镜像里压根没装 Chrome。围绕怎么让它能用方案来回摆了三下。镜像爆炸大别的服务也需要不想装浏览器截图实现→模糊分页生硬报错: google-chrome not found方案1: 给业务镜像装 Chrome方案2: k3s 内独立 chromium 服务方案3: 纯前端导出截图质量不达标Host 头校验坑修: 服务名解析成 ClusterIP用 IP 请求✅ 后端渲染方案打通第一段要不要装浏览器给业务镜像塞 Chrome 是最直觉的但立刻被否——镜像会膨胀得很难看而且不止这一个服务需要浏览器。于是倾向k3s 里跑一个独立的 chromium 服务大家共用。第二段能不能干脆不装浏览器纯前端期间认真评估了纯前端导出想借此甩掉浏览器依赖。但实测下来踩了两个坑截图方式导出画面发虚栅格化的天然短板而且分页很生硬把一整页 DOM 截成图片再切表格/文字会被拦腰截断。对于内容是长文本情报速递一期几十条新闻全文的报告截图方案的质量过不了关。这一段的价值不在结论而在把纯前端的边界摸清了截图导出只适合所见即所得的单屏内容不适合长内容、要分页、要清晰文字的报告。第三段回到后端渲染但修了 Host 坑最终情报速递还是走后端 chromedp k3s 独立 chromium 服务。但独立服务又撞出一个隐蔽的坑Chrome 的远程调试端口会校验 Host 头只接受localhost或 IP。用 k8s 服务名比如某chromium服务去请求DNS 解析没问题、路由也通请求确实到了浏览器 Pod却被 Chrome 自己的 Host 校验挡了回来。修复不在 k8s 配置层而在代码里后端把服务名解析成 ClusterIP再用http://ClusterIP:9222发请求——Host 头变成 IPChrome 就放行了。Service → Pod 的转发 k8s 本来就处理好了所以 yaml 一行不用改。http://某chromium服务:9222Host服务名Host 校验拒绝只认 localhost/IP先解析服务名→ClusterIPhttp://IP:9222HostIPHostIP 放行后端Chrome 远程调试端口❌ 被挡ClusterIPChrome✅ 渲染成功这是个非常容易误判的坑——报错像网络问题其实是浏览器自己的安全校验。如果一味去查 k8s 网络/DNS/Service会绕很大弯子。最后还差一块节点上得放中文字体思源黑体否则渲染出来的 PDF 中文全是方块。这是 arm 生产环境常见配置缺失和代码无关但少了它整套方案就白搭。五、导出报告前端化原生 HTML拒绝截图排障期间顺带推动了另一个方向的重构把三个模块情报速递、某识别模块、某核验模块的导出报告从后端渲染改成前端导出。这里有个明确的硬约束必须用原生导出不能用截图等捷径——这正是上一节截图模糊/分页生硬教训的直接延伸。也评估过 pandocHTML → PDF但最终选定原生 HTML 渲染 浏览器打印/下载这条路建一个共享导出工具渲染一份 HTML 文档走浏览器原生的打印/下载保留原报告模板的样式不是另起炉灶而是把后端模板移植到前端某核验模块的字段原本是后端预渲染好的改造成后端把原始字段放开给前端前端用原模板渲染——这样前端拿的是原生数据而非截图这条路和第四节的 chromedp 方案看似矛盾其实是按场景分工情报速递内容长、要分页、且已有成熟后端渲染保留 chromedp而这三个模块的报告结构更适合前端原生渲染、且能省掉对浏览器服务的依赖。没有银弹按每个导出的内容形态选方案。六、发版与收尾lab-agent前后端发版lab-web提交推送数据看板降级、快速查询接入等PDF Host 修复提交发版构建 #139新镜像含修复七、复盘一条暗线把今天几摊活串起来看有一条暗线渲染质量取决于数据是原生还是截图而在哪里渲染决定了运维成本。截图永远是下策。数据看板的降级、PDF 导出的反复、导出前端化的硬约束三次都指向同一个结论截图/栅格化会丢掉原生 DOM 的清晰度和分页能力。需要可读、可分页的内容必须用原生数据 原生渲染。在哪里渲染是架构选择题不是技术细节。后端渲染chromedp质量好但要养一个浏览器服务、还附带 Host 校验/字体/镜像体积等运维成本前端渲染省依赖但要自己处理分页和样式。今天的每个导出模块都按自己的内容形态在这个光谱上选了位置没有一刀切。复用看未来不看现在。快速查询配置要不要挂到 OCR 配置下看的不是现在都调 LLM而是以后会不会各自演化。耦合一旦形成拆的成本永远比现在独立高。容错是页面语义别塞进基础设施。全局请求回调懂不了这一格失败该不该拖垮整页所以局部降级留给页面自己写。公共代码保持不懂业务的克制。报错像网络问题未必是网络问题。chromedp 的 Host 头校验坑伪装成了 k8s 连通性问题。遇到路由通了但被拒先想想是不是目标服务自己的安全校验。