简介:kkFileView 4.4.0 SNAPSHOT 打包版面向需要在自有服务器上快速搭建文档在线预览能力的后端开发与运维人员,定位于对标永中office、office365 等付费产品的开源替代方案,适用于项目内 Office、PDF 等文件免下载预览的集成场景,对具备基本 Linux 操作与 JDK 环境概念的读者更为友好。压缩包共 8 个文件、约 285.23MB,其中 4 个 sh 脚本负责依赖安装、服务启停与日志查看,1 个 jar 为主程序,另附 properties 配置、ftl 模板与 txt 说明文档,解压后按脚本顺序执行即可完成部署,服务器只需预装 JDK 1.8。目前已有 2675 人下载学习。读者拿到的是一套可直接运行的预览服务骨架:可通过配置文件调整端口与预览行为,借助启停与日志脚本快速定位启动失败、依赖缺失等常见问题,并参照说明处理中文字体缺失引起的显示异常,省去从零搭建与反复调试的时间。
1. 一份 tar.gz 引发的排查:kkFileView 解决的是哪类"打不开"
上周又有人问我:合同是 docx,发票是 pdf,图纸是 dwg,扫描件是 jpg,能不能在浏览器里点一下就看,不装 Office、不下载客户端、手机上也行。这类需求听着简单,真做起来会遇到一堆麻烦:前端只有 iframe、后端没有渲染能力、用户又不想装插件。kkFileView就是解决这个场景的文件在线预览服务,kkFileView-4.4.0-SNAPSHOT.tar.gz是它可直接部署的发行包,里面已经包含了 Spring Boot 服务、前端预览页面和转换调度逻辑。它的价值在于把"各种格式统一转成浏览器能渲染的东西"这一步固定下来:Office 文档走 LibreOffice 转 PDF,文本走编码识别,图片音视频直接透传,前端只负责一个 iframe。适合谁?内部 OA、合同系统、档案系统、工单附件的后端和运维。接下来的内容全部围绕这个压缩包怎么解、怎么配、怎么接、在哪里出错来讲。
2. 拆开这个 tar.gz:目录职责、预览链路与转换引擎选型
拿到一个几十兆的发行包,最忌讳上来就tar -zxvf然后sh startup.sh一把梭。先看清楚里面的目录分层,后面调参数才知道改哪个文件、清缓存该删哪个文件夹。
2.1 解包后的目录结构:先认路再动手
发行包和源码包不是一回事。源码包是 Maven 工程,发行包里是编译产物加配置模板,解开之后基本是"能直接跑"的形态。我一般的顺序是先看顶层,再看 config,最后看 file 目录。
# 解包到一个独立目录,避免污染当前工作区 mkdir -p /opt/kkfileview-pkg && cd /opt/kkfileview-pkg tar -zxvf kkFileView-4.4.0-SNAPSHOT.tar.gz # 看顶层结构,确认有没有 bin / config / file 这类目录 ls -lh find . -maxdepth 2 -type d | sort # 重点看配置文件,确认端口、office 路径、缓存方式 find . -name "application*.properties" -o -name "*.yml" | head -20 # 看启动脚本,确认它读的是哪个配置、jvm 参数在哪里改 cat bin/startup.sh 2>/dev/null || cat startup.sh上面这段的目的不是"跑起来",而是建立三个认知:第一,启动脚本里通常写死了JAVA_OPTS和配置路径,JVM 堆大小就在那儿改;第二,config 目录下的 properties 才是你真正要调的,别去改 jar 包里的;第三,file 目录是预览缓存和上传文件的落地位置,后面清缓存就删这里。
目录职责大致可以这样理解,不同小版本名字可能微调,但职责是固定的:
| 路径 | 职责 | 什么时候要动它 |
|---|---|---|
| bin/ 或顶层脚本 | 启动、停止、JVM 参数 | 调堆内存、加字体路径、改启动用户 |
| config/application.properties | 端口、office 路径、缓存类型、后缀白名单 | 绝大多数业务配置都在这 |
| file/ | 转换后的缓存文件、临时文件 | 排查"改了文件还是旧内容"、磁盘告警 |
| lib/ 或 jar 本体 | 编译产物,不建议改 | 只在替换定制包时动 |
| 前端静态资源 | 预览页模板、水印样式 | 改水印文案、换主题时动 |
提示:解包前先确认磁盘余量。转换缓存会随预览文件数量增长,几百兆的包跑一周吃掉几个 G 很常见,别等到
No space left on device才发现。
2.2 预览链路是怎么跑通的:从一次 URL 请求到 iframe 渲染
理解链路比记住参数更重要,因为 90% 的"转圈不出来"都能靠链路定位。一次预览请求大致经过这么几步:浏览器带着文件名请求服务 → 服务判断后缀属于哪一类(office 文档、纯文本、图片、音视频、压缩包)→ 命中缓存就直接返回,没命中就交给对应的转换器 → 转换器调用外部进程(LibreOffice 的soffice、或者图片/文本处理逻辑)产出 PDF 或 HTML → 结果落到缓存目录并返回给前端 → 前端 iframe 加载 PDF 渲染组件或直接展示。
关键点在于"外部进程"这一步。kkFileView 本身是 Java 服务,但 docx、xlsx、pptx 的转换并不在 JVM 里做,它是把活派给系统中安装的 LibreOffice,靠命令行调起进程再等结果。这就解释了两个常见现象:为什么服务器上不装 LibreOffice,Office 文档就一定失败;为什么并发一高内存就顶不住——每个转换进程都是独立的操作系统进程,不是线程。
# 手动模拟服务内部的调用,验证 LibreOffice 这条链路是否通 # 注意:用独立用户执行,避免和服务的权限模型不一致 mkdir -p /tmp/office-test && cd /tmp/office-test cp /path/to/demo.docx . soffice --headless --invisible --norestore \ --convert-to pdf --outdir /tmp/office-test demo.docx # 如果上面没产出 demo.pdf,问题一定在 LibreOffice 而不是 kkFileView ls -lh /tmp/office-test这段命令的意义是"分层排除"。--headless表示无界面运行,--invisible不显示启动画面,--norestore跳过崩溃恢复对话框(这个在服务器上不加,偶发卡死概率明显升高),--convert-to pdf指定目标格式,--outdir指定输出目录。如果手动执行能出 PDF,而服务里出不来,那问题就在服务调用的路径、用户权限或环境变量上;如果手动也出不来,就老老实实修 LibreOffice,别在 Java 侧瞎调。
实际请求的形态是这样,可以用 curl 直接验证,不用等前端联调:
# 基础预览:文件名直接拼在路径后面 curl -I "http://127.0.0.1:8012/demo.docx" # 指定转换方式为 pdf,这是兼容性最好的选择 curl -I "http://127.0.0.1:8012/demo.docx?officePreviewType=pdf" # 文件名含中文或特殊字符时,用 fullfilename 显式传原始文件名 curl -I "http://127.0.0.1:8012/preview?fullfilename=%E5%90%88%E5%90%8C%E6%A8%A1%E6%9D%BF.docx"officePreviewType这个参数决定 Office 文档的产出形态:pdf是转成 PDF 后用 PDF 渲染器展示,版式最稳;image是逐页转图片,适合不支持 PDF 嵌入的老环境;html是转 HTML,体积小但复杂排版容易走样。fullfilename用于文件名被 URL 编码后服务端还原不出来的场景,比如文件名里带#、&、空格或中文括号。这三个参数记住,联调时能省掉大量扯皮。
再补充一个容易被忽略的点:kkFileView 的缓存判定是基于文件路径加文件修改时间的。也就是说,你把源文件覆盖成同名新文件,只要修改时间变了,它就会重新转;但如果你的业务系统是把文件传到另一个路径、文件名也变了,那对它来说就是全新文件,必然重转一遍。这个机制本身合理,坑在于共享存储的时间戳精度和时钟同步——跨机器挂载 NFS 时如果两边时钟差几分钟,可能出现"明明更新了却还是旧内容"的玄学现象。
2.3 转换引擎选型:LibreOffice、OpenOffice 还是走商业方案
打包里默认对接的是 LibreOffice,这是当前最现实的选择。把它和另外两条路放一起对比,选型理由就清楚了。
| 方案 | 版式还原 | 部署成本 | 并发表现 | 适用判断 |
|---|---|---|---|---|
| LibreOffice(主流版本) | 较好,复杂表格和图文混排偶有偏移 | 单机安装即可,离线可搞定 | 进程级,需限流 | 绝大多数内部系统的默认解 |
| Apache OpenOffice | 一般,新格式支持滞后 | 安装包更老,依赖库容易冲突 | 进程级,稳定性弱于前者 | 只在已有环境强绑定时考虑 |
| 在线转换 API(商业) | 好 | 走外网,数据出域 | 受配额限制 | 有合规限制的内部文档基本排除 |
| 纯前端渲染库 | 差,只覆盖简单格式 | 零后端 | 好 | 只适合 docx 简单文本预览 |
选型时我一般看三件事:文档格式的复杂度、数据能不能出内网、并发峰值是多少。内部合同、公文这类版式敏感的场景,PDF 路线加 LibreOffice 是最稳的组合;如果是纯文本日志、代码文件,根本不需要动用转换引擎,走文本直读更快;如果是超大表格(几十万行 xlsx),转 PDF 会很慢甚至爆内存,这时候更合理的是先降级成"仅预览前 N 行"或者引导下载。
还有一个现实约束:LibreOffice 的版本要和系统 glibc 匹配。老一点的 CentOS 上装最新版,经常出现启动即崩,血泪经验是优先用发行版自带的稳定版本,或者用官方提供的便携版解压到独立目录,通过配置项指向它,而不是强行yum install最新版。
3. 把服务跑起来:JDK、LibreOffice 与最小配置改动集
这一章是最"抄作业"的部分。目标很明确:在一台干净的 Linux 机器上,从解包到用一个 curl 拿到 PDF 响应,中间每一步都可复现。
3.1 环境准备:JDK、字体、LibreOffice 三件套
顺序很重要。先装 JDK(服务本体),再装字体(影响转换版式),最后装 LibreOffice(转换引擎)。字体放最后装的话,LibreOffice 启动时缓存里没有字体,后面再加字体需要重建缓存,容易白折腾。
# 1) JDK:发行包一般要求 8 或 11,按包里说明选,别盲目上 17 java -version # 2) 中文字体:把常用字体拷进系统字体目录再刷新缓存 mkdir -p /usr/share/fonts/chinese cp /opt/fonts/*.ttf /usr/share/fonts/chinese/ chmod 644 /usr/share/fonts/chinese/*.ttf fc-cache -fv fc-list :lang=zh | head # 能列出来说明字体生效了 # 3) LibreOffice:优先用系统包管理器,版本稳定优先于版本新 # CentOS/RHEL 系 yum install -y libreoffice libreoffice-headless libreoffice-writer libreoffice-calc libreoffice-impress # Debian/Ubuntu 系 apt-get install -y libreoffice libreoffice-writer libreoffice-calc libreoffice-impress fonts-noto-cjk # 4) 验证引擎可用 soffice --versionfc-cache -fv的-f是强制重建、-v是输出详细过程,两个一起用是为了确认新字体真的被索引到。fc-list :lang=zh是验证手段,如果输出为空,说明字体没被识别,后面转换出来的 PDF 里中文就会变成方块或者被替换成别的字形——这是排版事故里最常见的一类。
LibreOffice 的组件包要装全。只装libreoffice-core的话,xlsx 和 pptx 转换会直接报错,报错信息还很隐晦,容易误判成 kkFileView 的 bug。libreoffice-headless是必须的,服务器没有图形界面,缺了它调起进程会卡在初始化。
3.2 配置项改动清单:这几行不改,跑起来也是废的
配置文件是 properties 格式,键值对,改完重启生效。下面这份是我部署时的最小改动集,其他保持默认。
# 服务端口,默认 8012,和现有业务端口冲突就改这里 server.port=8012 # 预览文件落地目录,务必给独立磁盘或大分区 file.dir=/data/kkfileview/file # LibreOffice 安装路径,装完用 which soffice 确认实际位置 office.home=/usr/lib64/libreoffice # 转换超时时间(秒),大文件要放宽,否则一直转圈最后报错 convert.timeout=180 # 缓存类型:单机用 default,多实例必须用 redis cache.type=default # 多实例部署时打开下面两行,共享转换结果,避免重复转换 # cache.type=redis # spring.redisson.address=127.0.0.1:6379参数逐条解释一下。file.dir是转换产物和临时文件的根目录,建议单独挂一块盘,因为它的增长不可控——用户预览多少文件就有多少缓存。office.home指向 LibreOffice 的安装根目录而不是soffice可执行文件本身,指错的表现是"服务启动正常,一预览 Office 文档就报转换失败"。convert.timeout是单次转换的等待上限,默认值对几十页的 docx 勉强够,但遇到带大量高清图的 pptx 就会超时,转成 PDF 的时间可能到两三分钟,这个值要给足。
cache.type是最容易忽略的一项。单机部署用默认的内存缓存没问题;一旦你前面挂了负载均衡、后面起了两个实例,用户第一次请求打到 A 节点转了 PDF 存在 A 的本地盘,第二次请求打到 B 节点又要重转一遍,表现出来就是"时快时慢"。这时候必须切 Redis,让缓存键共享。
启动和验证:
# 后台启动,日志重定向到文件方便排查 cd /opt/kkfileview-pkg nohup sh bin/startup.sh > /data/logs/kkfileview.out 2>&1 & # 等 15 秒左右,看端口是否监听 sleep 15 && ss -lntp | grep 8012 # 看启动日志有没有异常堆栈 tail -n 80 /data/logs/kkfileview.out # 放一个测试文件到预览目录,用 curl 验证整条链路 cp /tmp/office-test/demo.docx /data/kkfileview/file/ curl -s -o /tmp/out.pdf -w "%{http_code} %{size_download}\n" \ "http://127.0.0.1:8012/demo.docx?officePreviewType=pdf" file /tmp/out.pdf # 输出应为 PDF document-w "%{http_code} %{size_download}"是我必看的两项:状态码 200 且下载体积不为 0,才算真的通了。有些时候状态码是 200 但体积是几百字节,那其实是返回了一个错误页或者空的 PDF,file命令一验就露馅。
3.3 容器化与离线部署:挂载点和环境变量怎么给
发行包适合直接跑在虚机上,但很多环境是容器化的。容器里跑这套东西有两个额外约束:LibreOffice 要装进镜像,字体要装进镜像,配置文件和数据目录要通过挂载出来。
# 启动容器时的关键挂载,四个都不能少 docker run -d --name kkfileview \ -p 8012:8012 \ -v /data/kkfileview/config:/opt/kkfileview/config \ -v /data/kkfileview/file:/opt/kkfileview/file \ -v /data/kkfileview/fonts:/usr/share/fonts/chinese \ -e JAVA_OPTS="-Xms1g -Xmx2g" \ --restart=always \ kkfileview:4.4.0四个挂载各有用途:config 挂出来是为了改配置不用重建镜像;file 挂出来是缓存和临时文件,容器重启不丢;fonts 挂出来是为了临时补字体,改完fc-cache就生效;JAVA_OPTS控制 JVM 堆,-Xms和-Xmx设成一样能减少堆伸缩带来的抖动。容器内存上限要按Xmx加 LibreOffice 进程峰值来估,一般给Xmx的 2 到 2.5 倍,否则 OOM Killer 会直接把容器干掉,日志里只留一个 137 退出码,非常像"无缘无故挂了"。
离线环境还要注意依赖:LibreOffice 的 rpm/deb 包有一堆依赖,离线安装建议提前在一台联网同版本机器上把依赖拉全,或者直接用官方提供的解压即用版本,配到office.home指过去。这条路省事,但要确认目标机器 glibc 版本够。
4. 接到业务系统里:URL 拼接、鉴权与缓存的三个决定
服务自己能跑只是起点,真正花时间的是和业务对接。这一章讲三件必须提前定的事,定错了后面返工成本很高。
4.1 预览地址怎么拼:把参数规则固化成工具方法
最忌讳的是各个业务模块各写各的拼接逻辑,最后出现"这个页面能预览、那个页面 404"。做法是抽一个统一的方法,把编码、参数、类型判定都收进去。
// 统一的预览地址构造:所有业务页面都调这一个方法 function buildPreviewUrl(fileUrl, fileName, options = {}) { const base = 'https://preview.example.internal'; // 走网关域名,不直连服务端口 const params = new URLSearchParams(); // 原始文件名单独传,避免服务端从 URL 路径还原时被特殊字符打断 params.set('fullfilename', fileName); // 版式敏感文档统一走 pdf,图片类走 image,纯文本可省略 params.set('officePreviewType', options.previewType || 'pdf'); // 业务侧强制刷新(文件被覆盖但路径未变时使用,慎用,会跳过缓存) if (options.forceRefresh) params.set('forceUpdatedCache', 'true'); // 需要动态水印时传,服务端会叠加到输出上 if (options.watermark) params.set('watermarkTxt', options.watermark); // 文件地址本身要编码,否则带 ? & # 的地址会被截断 return `${base}/onlinePreview?url=${encodeURIComponent(fileUrl)}&${params.toString()}`; } // 调用示例 const url = buildPreviewUrl( 'https://files.example.internal/2024/contract-001.docx', '年度服务合同(终版).docx', { previewType: 'pdf', watermark: '内部资料' } );这段代码有三个关键决策。第一,fullfilename一定要传原始文件名,不能靠 URL 路径去猜,因为业务系统里的文件名经常带中文括号、井号、空格,这些字符在 URL 路径里是雷区。第二,encodeURIComponent处理的是"文件地址"这一整个参数,不是文件名,少了它带查询串的文件地址会被截断成半截。第三,forceUpdatedCache默认不开,只在明确知道文件被覆盖时才置 true,否则每次预览都重转,性能直接崩。
watermarkTxt这个参数在生产环境几乎是必开的。内部合同、财务报表这类文件,预览页上带使用者标识的水印,是基本要求。水印内容建议带工号或用户名,不要只写"内部资料",追责时才有意义。
4.2 别让预览地址裸奔:鉴权放在哪一层
kkFileView 本体不内置用户体系,它默认认为"能访问到这个地址的人就是有权限的人"。直接暴露到内网,等于任何知道文件名的人都能看到文件。常见的三种做法,我按推荐度排:
第一种,网关层签名。业务后端生成预览地址时附带过期时间戳和 HMAC 签名,网关校验签名通过才转发给预览服务。
// 后端生成带签名的预览地址(示例,密钥从配置中心取) const crypto = require('crypto'); function signPreviewPath(filePath, secret, ttlSeconds = 300) { const expireAt = Math.floor(Date.now() / 1000) + ttlSeconds; // 签名串 = 文件路径 + 过期时间,两者任一被篡改都会导致校验失败 const raw = `${filePath}:${expireAt}`; const sign = crypto.createHmac('sha256', secret).update(raw).digest('hex'); return `${filePath}?expire=${expireAt}&sign=${sign}`; }签名的关键在"绑定"。只签文件名不签过期时间,链接一旦泄露就永久有效;只签过期时间不签文件路径,攻击者可以换文件名复用同一个签名。两者绑在一起,并且 TTL 控制在五分钟量级,泄露窗口就很小了。网关侧用同样算法重算一遍再比对,不匹配直接 403,转发请求根本不落到预览服务上。
第二种,Nginx 反代加auth_request,把鉴权交给业务侧的校验接口。这种方式对现有代码侵入最小,但要接受每个预览请求多一跳的开销。
第三种,把预览服务藏在只有业务后端能访问的内网段,前端不直接访问,由后端做流式转发。安全性最好,代价是后端要承担带宽。
注意:无论选哪种,都不要把预览服务端口直接暴露给办公网。没有鉴权的预览服务等于一个可以遍历文件的下载器。
4.3 缓存与队列:什么时候该上 Redis
缓存的默认实现是本地的,落在file.dir下,键是文件路径的哈希。单实例够用,判断标准很简单:只有一台预览服务,就用默认;有两台及以上,就必须上 Redis。
原因在 2.2 讲过——本地缓存不共享,负载均衡会把同一文件的请求分到不同节点,各转各的。表现是用户反馈"有时候秒开、有时候等半分钟",而且难以复现。
切 Redis 的配置前面给过,这里补两个运维细节。一是 Redis 里存的是元信息不是文件本体,文件仍在各节点的本地盘,所以磁盘还是要按单节点峰值留足。二是 Redis 挂了要有降级策略,cache.type切回 default 重启即可,别让缓存故障变成整个预览不可用。
至于转换队列,kkFileView 本身不提供任务队列,它是同步等待转换完成的。并发高了之后,大量请求同时触发 LibreOffice 进程,CPU 和内存会瞬间打满。务实做法是在它前面加一层限流:Nginx 的limit_req或者网关层的并发控制,把同时进行的转换数量压在一个可接受的范围(经验值是按 CPU 核数的 1 到 1.5 倍),超出的请求排队或直接返回"正在转换请稍候"。这比事后加机器便宜得多。
5. 避坑与排查:五个翻车现场的现象、原因和解决
下面五条都是我在实际部署和联调中撞过的,按出现频率排。
第一条:中文文件名预览 404,英文名完全正常。
现象是合同模板.docx打不开,换成a.docx秒开。原因有两层:前端拼接 URL 时没做编码,中文被浏览器按某种编码发出去,服务端还原失败;或者中间有 Nginx 做了 URL 重写,把编码后的%E5%90%88又解了一次,导致路径错乱。解决分两步,前端统一走 4.1 的buildPreviewUrl,文件名用fullfilename参数显式传并编码;Nginx 侧确认没有多余的rewrite规则,必要时加proxy_pass时保留原始 URI。
第二条:Office 文档一直转圈,最后超时,日志里没有明显异常。
这是黑匣子感最强的一类。原因通常是 LibreOffice 调不起来:office.home指向了错误目录、服务运行用户没有该目录的执行权限、或者装的是缺组件的精简包。排查按 2.2 手动执行soffice --convert-to pdf那条命令走一遍,用服务的运行用户执行,而不是 root。手工能转、服务不能转,就去看启动日志里的进程调用行;手工也不能转,先修 LibreOffice。另外,异常退出后可能在临时目录留下锁文件,导致后续全部卡住,清掉file.dir下的临时目录和用户家目录里的.~lock.*再试。
第三条:PDF 里的中文变成方块,或者字体被换成了别的字形。
现象是页面能打开,但标题字号、行距全乱了,公文类文档尤其明显。原因是服务器上没有安装对应的中文字体,LibreOffice 做了字体回退。解决是装字体、刷缓存、重启服务三步,fc-cache -fv之后必须重启,因为进程启动时会把字体列表读进内存。做完之后已有的缓存 PDF 不会自动更新,需要删掉对应缓存文件或加forceUpdatedCache重转一次。这类坑没有后悔药,只能在部署清单里把字体列为必装项。
第四条:https 的业务页面里预览框一片空白,控制台报混合内容被拦截。
现象是单独打开预览地址正常,嵌在业务页面里就是白框。原因是业务站点是 https,而预览地址配的是 http,浏览器按混合内容策略直接拦掉了。解决是把预览服务也放到 https 后面,走同一张证书或者内网可信证书,前端拼接时用相对协议或者统一域名。跨域场景还要确认响应头里没有禁止嵌入的设置,iframe 相关的X-Frame-Options要放开到对应域名。
第五条:并发一上来就 500,重启后又能撑一会儿。
看日志会发现大量转换失败,系统层面能看到内存被吃光,甚至容器被 OOM Killer 干掉。根因是 LibreOffice 是进程模型,单个转换峰值能吃掉几百兆,十几个并发同时进来必然顶不住。解决是三件事一起做:限制并发(Nginx 限流)、把 JVM 堆设成固定值避免动态扩张抢内存、把convert.timeout调到一个合理值让卡死的转换尽快释放。如果业务确实有高并发预览需求,正确的方向是提前批量转换并缓存,而不是让用户在请求链路上等。
6. 进阶:把它当成一个可运维的服务而不是一个 jar
真正上线之后,关注点会从"能不能预览"变成"稳不稳、快不快、出了问题能不能定位"。这一步的差别,基本决定了这个服务是能用还是好用。
先看可观测性。转换耗时是最值得埋点的指标,因为它是用户体感的核心。我在网关层按officePreviewType和文件后缀维度统计 P95 耗时,超过阈值的记一条明细,一周下来就能看出哪类文件最拖后腿——通常是大体积 pptx 和带大量嵌入对象的 xlsx。这些文件更适合在业务侧做预处理,而不是让预览服务硬扛。
# 定期巡检:缓存目录体积、大文件、最久未访问的缓存 du -sh /data/kkfileview/file find /data/kkfileview/file -type f -size +50M -printf '%s %p\n' | sort -rn | head -20 find /data/kkfileview/file -type f -atime +30 | wc -l # 转换进程是否堆积(堆积说明有卡死的转换没被回收) ps -ef | grep -c "[s]office" # 服务端连接数与线程数,配合限流阈值一起看 ss -s这段巡检我一般做成定时任务跑。第二条命令找的是超过 50M 的缓存文件,这类文件往往对应转换异常或者超大源文件,值得单独看一下;第三条统计 30 天没被访问过的缓存数量,数字很大就说明缓存淘汰策略该调了;ps -ef | grep -c "[s]office"里那个方括号是技巧,能把自己这条 grep 命令排除掉,否则永远至少返回 1,看多了会误判。如果这个数字持续偏高不下降,说明有转换进程卡死没退出,需要看convert.timeout是不是设太大,或者 LibreOffice 本身有问题。
再看版式一致性。同一个文件在不同机器上转出来的 PDF 不完全一样,是很常见的事,根因是字体版本、LibreOffice 版本不一致。做法是把字体和引擎版本纳入镜像基线,所有节点用同一份,不允许运维各自yum install。这条听起来麻烦,但在合同、公文这类场景里,版式偏移是会被业务方当成 bug 提上来的。
最后是一个具体技巧:善用"预热"。对于已知会被频繁预览的文件(比如模板类文档、常用制度文件),在业务侧上传完成时就异步触发一次转换,把结果推进缓存。用户第一次点开就是命中缓存,体感从"等三十秒"变成"秒开"。实现上就是上传成功后由后端发一个带forceUpdatedCache的预览请求,不关心返回内容,只要它把缓存建好就行。这个改动只有几十行,效果比加机器明显。
说个自己的教训。早期部署这套东西的时候,我图省事直接把包解开、改了个端口就上线了,字体没装、缓存目录指在了系统盘、并发没限。结果第一周就赶上批量预览,系统盘被缓存写满,服务写日志失败挂掉,排查了两个小时才发现是磁盘问题。从那以后,我每次部署这类带外部进程和本地缓存的服务,都强制走一遍清单:磁盘单独挂载、字体预先安装并验证、并发上限显式配置、缓存清理策略写进定时任务。这四件事花不了半小时,但能挡掉后面绝大部分半夜的电话。希望帮到你。
本文还有配套的精品资源,点击获取