去年公司内部知识库上线的时候,产品经理提了个需求:所有上传的文档必须能在浏览器里直接打开,不能下载到本地再查看。当时觉得这个功能挺简单,结果一做就是三周,踩了无数坑。今天把三种主流方案的部署过程和踩坑记录整理出来,希望能帮到同样在做文件预览服务的同学。
先说下背景,我们的技术栈是 Spring Boot + Vue3,文件存储在 MinIO 上,需要预览的格式主要是 PDF、Word、Excel、PPT 这几类。下面分别介绍三种方案的部署方式和实际使用体验。
PDF.js —— 最轻量的 PDF 预览方案
如果你只需要预览 PDF 文件,PDF.js 是上手最快的方案。Mozilla 开源,纯前端渲染,不需要后端服务。
部署方式也很简单,直接用官方提供的 viewer.html 就行:
# 拉取 PDF.js release 包wgethttps://github.com/mozilla/pdf.js/releases/download/v4.2.67/pdfjs-4.2.67-dist.zipunzippdfjs-4.2.67-dist.zip-d/opt/pdfjs# Nginx 代理配置server{listen8080;server_name preview.internal;location /pdfjs/{alias/opt/pdfjs/;}location /files/{proxy_pass http://minio:9000/bucket/;add_header Access-Control-Allow-Origin *;}}前端调用只需要拼接 URL:
constviewerUrl=`/pdfjs/web/viewer.html?file=${encodeURIComponent(fileUrl)}`;window.open(viewerUrl);踩坑记录:PDF.js v4.0 以后对跨域限制变严了,如果你的文件服务和 viewer 不在同一个域名下,必须在文件服务器端配置 CORS 头。我当时就是忘了加Access-Control-Allow-Origin,控制台报了一堆PDFWorker的跨域错误,排查了整整半天。
这个方案的优点是轻量、稳定、渲染效果接近原生。缺点也很明显——只能处理 PDF,Word 和 Excel 完全不支持。
LibreOffice Headless —— 万能但沉重的转换引擎
要支持 Office 全家桶,最直接的思路是先把文档转成 PDF,再用 PDF.js 渲染。LibreOffice 的 headless 模式就是干这个的。
Docker 部署:
dockerrun-d\--namelibreoffice-converter\-p3001:3000\-eTZ=Asia/Shanghai\-v/tmp/preview:/tmp/preview\gotenberg/gotenberg:8.5.0这里用的是 Gotenberg,一个封装了 LibreOffice 的 API 服务。调用方式:
# 把 Word 转成 PDFcurl--requestPOST\--urlhttp://localhost:3001/forms/libreoffice/convert\--formfiles=@/tmp/test.docx\-o/tmp/output.pdfSpring Boot 里的调用代码:
@PostMapping("/preview")publicResponseEntity<byte[]>preview(@RequestParamStringfileKey){// 1. 从 MinIO 下载原始文件byte[]fileData=minioClient.getObject(bucketName,fileKey);// 2. 调用 Gotenberg 转 PDFHttpHeadersheaders=newHttpHeaders();headers.setContentType(MediaType.MULTIPART_FORM_DATA);MultiValueMap<String,Object>body=newLinkedMultiValueMap<>();body.add("files",newByteArrayResource(fileData){@OverridepublicStringgetFilename(){return"source.docx";}});ResponseEntity<byte[]>response=restTemplate.postForEntity("http://localhost:3001/forms/libreoffice/convert",newHttpEntity<>(body,headers),byte[].class);// 3. 返回 PDF 流给前端 PDF.js 渲染returnResponseEntity.ok().header("Content-Type","application/pdf").body(response.getBody());}这个方案最大的问题是资源消耗。一个 LibreOffice 进程大约吃 200-400MB 内存,并发转换的时候直接 OOM。我们在生产环境限制并发数为 3,超过的请求排队等待。2025 年 3 月那次线上事故,就是因为市场部批量上传了 200 多个 PPT,转换队列堆积导致整个服务假死,GC 停顿超过 30 秒。
另外复杂 Excel 的排版容易错乱,合并单元格和条件格式基本都会丢。PPT 里的动画和嵌入字体也别指望能保留。
OnlyOffice —— 在线编辑+预览一体化方案
如果预算充裕,OnlyOffice 是体验最好的选择。它不仅能预览,还支持在线编辑,界面和桌面版 Office 几乎一致。
Docker Compose 部署:
version:'3.8'services:onlyoffice:image:onlyoffice/documentserver:8.2.0container_name:onlyofficeports:-"8443:443"environment:-JWT_SECRET=your_jwt_secret_herevolumes:-./logs:/var/log/onlyoffice-./data:/var/www/onlyoffice/Datarestart:always启动后等待服务就绪(首次启动要拉字体包,大概 3-5 分钟):
# 检查服务是否就绪curl-khttps://localhost:8443/healthcheck# 返回 true 说明可以用了前端集成需要加载 JS SDK:
<scriptsrc="https://preview.internal/web-apps/apps/api/documents/api.js"></script><divid="placeholder"></div><script>newDocsAPI.DocEditor("placeholder",{document:{fileType:"docx",key:"unique_document_key",title:"测试文档.docx",url:"https://your-backend/api/files/download?fileKey=xxx"},documentType:"word",editorConfig:{mode:"view",// view 只读预览,edit 可编辑lang:"zh-CN"},token:"jwt_token_here"// JWT 安全校验});</script>OnlyOffice 的坑主要在 JWT 配置上。v7.0 之后默认开启 JWT 验证,如果前后端的 secret 不一致,页面会白屏且不报任何错误。我第一次部署的时候对着白屏发呆了两个小时,最后翻/var/log/onlyoffice/documentserver/converter/err.log才看到 JWT 验证失败的日志。
还有一个隐藏问题:OnlyOffice 对文档 key 很敏感。如果同一个 key 对应的文档内容变了但 key 没更新,打开的还是缓存版本。所以每次文档修改后必须生成新的 key。
三种方案怎么选
简单总结下我的选型建议:
- 只需要 PDF 预览 → PDF.js,没有之一
- 需要预览 Office 文档但不需要编辑 → LibreOffice + PDF.js,注意控制并发
- 需要在线编辑 + 预览 → OnlyOffice,但要准备好至少 4GB 内存的服务器
如果觉得自建整套预览服务太折腾,其实也可以考虑现成的方案。我们后来调研了一圈,发现巴别鸟企业云盘直接支持 200 多种格式的在线预览,Word、Excel、PPT、PDF、CAD 图纸、思维导图、视频、音频都能在浏览器里打开,不需要本地安装任何软件。而且它有 32 个维度的权限控制,可以精确到"只能在线查看不能下载"这种颗粒度,正好满足我们产品经理的需求。专业版一年 2000 块钱包含 1T 存储不限用户数(babel.cc/p/price.do),比起自己维护一套预览服务的服务器成本和人力成本,其实性价比挺高的。
不过话说回来,自建方案的好处是数据完全在自己手里,适合对数据安全有强要求的企业。两种路线各有优劣,看具体场景选择。
FAQ
Q1:LibreOffice 转换出现中文乱码怎么解决?
Docker 容器里默认没有中文字体,需要手动安装。在 Dockerfile 里加一行:
RUN apt-get update && apt-get install -y fonts-wqy-zenhei fonts-wqy-microhei && fc-cache -fv或者直接把宿主机的字体目录挂载进去:-v /usr/share/fonts:/usr/share/fonts。注意 Gotenberg 的镜像基于 Debian,字体包名和 CentOS 不一样。
Q2:OnlyOffice 文档打开慢,怎么优化?
几个方向:一是开启 Redis 缓存,避免每次都重新转换;二是用 Nginx 做静态资源缓存,字体文件和 JS SDK 都可以缓存 7 天以上;三是升级到 v8.x 版本,新版对大文件的加载速度优化了不少,我们实测 50MB 的 PPT 打开时间从 15 秒降到了 6 秒左右。
Q3:PDF.js 预览大文件(超过 50MB)时浏览器崩溃怎么办?
PDF.js v4.x 支持disableAutoFetch配置,不会一次性加载整个文件:
constloadingTask=pdfjsLib.getDocument({url:fileUrl,disableAutoFetch:true,disableStream:false});配合 Range 请求,可以实现按页加载,内存占用会小很多。
Q4:能不能用 WPS 开放平台替代 OnlyOffice?
可以,WPS 开放平台提供了 WebOffice SDK,体验和桌面端 WPS 一致。但它是 SaaS 服务,文档需要上传到 WPS 的服务器(或者配置私有化部署),对数据合规性有要求的企业需要评估下。而且私有化部署的价格不便宜,适合预算充足的中大型团队。
Q5:LibreOffice 转换的 PDF 和原始文档排版差异大吗?
Word 文档的排版还原度大约 85-90%,简单的文字和表格基本没问题。主要问题是字体回退(如果文档用了容器里没有的字体会用默认字体替代)、复杂表格边框丢失、页眉页脚位置偏移。Excel 的问题更大一些,图表和条件格式大概率会变形。如果排版还原度要求高,建议直接上 OnlyOffice 或者用商业方案。