本期敖行客研发实战日记,邀请传奇后端人物------GGB,分享文件预览服务上线踩过的一堆阴间大坑,完整复盘生产环境 5 个藏到离谱的疑难 bug 排坑全过程。解决本地跑丝滑、一部署上线直接原地翻车的诡异问题,深挖病根同时附上落地解法。帮各位后端同行躲开中间件、容器部署里防不胜防的隐形坑,以后再遇上玄学报错,也能从容上手排查,告别排查 bug 熬秃脑袋的日子
GGB 江湖人称"后端补刀位"。常年与服务端、容器和各种诡异 bug 为伍,信奉"日志不会骗人,骗人的是写日志的人"。
最大的爱好是把别人眼里的"玄学问题"一层层扒到根因,再用最少的改动把它摁死。找不到他的时候,他大概率正盯着一段堆栈发呆.......
文件预览服务(基于开源的 kkFileView),就是把 Office、PDF、图片、代码等各种格式在浏览器里直接预览。听起来是"拿来即用",真正接入生产后才发现,坑一个接一个,而且大多在网上搜不到现成答案。下面按踩坑顺序复盘。
一、最隐蔽的坑:多副本部署下"第一次打开必 404,第二次就好了"
现象很妖:
同一个文件、同一个地址,第一次预览报 404,过几分钟再点就正常。后台日志却明明白白写着"转换成功",而且只花了几百毫秒。
排查了一圈编码、缓存、前端时序,最后靠时间戳锁定真相------404 返回的时间,正好是转换完成时间往后推 120 秒(等待超时)。也就是说:文件确实转出来了,但请求它的那个进程一直没找到。
真相:
服务是 K8s 多副本部署,转换后的 PDF 只落在"产生它的那个 Pod"的本地磁盘上。负载均衡把预览请求发给 Pod A 完成转换,下一个取文件的请求却被转发到 Pod B,Pod B 本地根本没有这个文件,于是 404。
为什么第二次就好了? 纯属运气,负载均衡又把我扔回A了😅
经验:凡是"先转换、再按 URL 取产物"的服务,多副本部署必须用共享存储(NFS/NAS)挂载产物目录,否则本地磁盘 + 内存状态天然不一致,本地单实例永远复现不出来,最容易被忽略。

二、本地能跑、上线就崩:环境差异集中爆发
#1. office.home 配置
LibreOffice 的安装路径,本地是 Windows 的 C:/Program Files/LibreOffice,服务器容器里是 /usr/lib/libreoffice。代码里如果写死了路径,上线直接找不到。
办法:用环境变量占位符 ${KK_OFFICE_HOME:默认值} 兼容两端:有环境变量用环境变量,没有用默认值,一份配置两边通用。
#2. 编译打包的 JDK 版本
项目要求 Java 21,但 maven-compiler-plugin 的 release=21 参数,在旧版 Maven 上会触发"不支持发行版本 21"的假报错,错误信息还被 fork 模式藏起来。去掉 release、改用 source/target 后顺利打包。
一句话:报错信息看不全时,先想办法把它逼出来,别盲改。
三、带批注的 Word 一转就崩
带批注(comments)的 docx 转 PDF 时,容器里的 LibreOffice 直接 abort 崩溃(Signal 6),堆栈指向构建批注 UI 部件时找不到资源。本地完整版 LibreOffice 没问题,容器里的精简版缺了 UI 资源文件。
一度以为关掉"导出批注"就行,结果没用------只要文档带批注,LibreOffice打开时就要构建那个UI部件,跟导不导出无关。
最终结论是:治本要在容器里补装完整的 LibreOffice +中文字体。
经验:第三方组件的崩溃,先分清是"调用方用法"问题还是"组件自身安装不完整"问题,方向错了白费力气。

四、Excel 预览中文全乱码变天书
xlsx 转成 HTML 后,中文全变成"项目åˆ---表"这种乱码。典型的 UTF-8 被当成别的编码处理。
根因藏得很深 :代码用了一个"自动嗅探编码"的工具去读 LibreOffice 转出的 HTML,这个工具自作聪明,结果把本来是 UTF-8 的文件误判成了 Latin1,用错编码读进内存就把中文读坏了,再写回文件,乱码就永久固化了------文件头声明的还是 utf-8,所以浏览器也救不回来。
改法:不再瞎猜,直接读取文件头里自己声明的字符集(LibreOffice 明确写了 charset=utf-8),按它正确读取。
经验:编码问题要分清是"读的时候坏的"还是"显示的时候坏的",两者修法完全不同,靠看一眼乱码字节就能判断。

五、自己挖的坑:拦截器误伤静态资源
为了解决转换时序问题,我加了个拦截器:请求图片/PDF 时如果文件还没转好就等待。但它把 xlsx 预览页的 CSS 图标、PPT 懒加载图片,这些资源本来就是"先请求,不存在就404,前端会自动重试"的设计。拦截器把它们也拦下来死等 120 秒,把样式搞坏了。
修正:拦截器只在"文件确实正在转换中"时才等待,其他情况一律放行、自己绝不返回 404,交给后续处理器决定。
经验:给系统加"等待/重试"这类兜底逻辑时,一定要想清楚边界,否则兜底反而成了新的故障源。
六、小结

- 环境差异是生产事故的最大来源:本地能跑不代表线上能跑,编码、路径、组件完整性、部署副本数,每一项都可能翻车。
- 排查靠证据不靠猜:时间戳、堆栈、乱码字节,这些都是能直接指向根因的硬线索。
- 改动要最小、要治本:能一行配置解决的不动代码,能定位根因的不打补丁。
至此,文件预览服务的几个核心问题已逐一解决,预览体验从"时好时坏"变成稳定可用。复杂系统没有银弹,能做的就是把每个坑都扒到底、记下来,下次少踩一个。