做Java后台的文件在线预览这种需求大概都避不开。项目交付阶段客户提了一嘴“能不能别老下载文件直接在网页里看”于是我把OnlyOffice文档服务器拉进来跟业务系统做了完整集成部署环境还是国产化麒麟系统折腾过程中踩了不少坑。这篇实操记录就把选型、麒麟环境部署、Java后端签名配置、前端嵌入方式以及一系列高频问题完整梳理一遍给后面走同样路线的朋友做个参考。1. 方案选型为什么最后选了OnlyOffice1.1 主流在线预览方案对比先交代一下背景业务系统里的文件主要就是Office三类加上PDF来源是内部上传和接口同步平时用户在线查看的频率远高于编辑。最开始我想找一个能一键部署的轻量方案但真列出来对比每个都有明显短板。kKFileView是一套很流行的开源在线预览方案本身是Java写的集成友好能处理音视频、图片、Office、PDF等多种格式。但它背后是靠OpenOffice/LibreOffice做格式转化遇到复杂排版或者比较大的Word文档转化结果经常跟原文件对不上渲染速度和还原度都不够理想。Pdf.js只解决PDF预览功能太单一业务里一旦出现Word和Excel就抓瞎。FlexPaper那套依赖Flash的老方案现在已经没人用了在国产化系统上更是不用考虑。LibreOffice OnlineCollabora跟OnlyOffice定位有点像都是在线Office编辑预览一体化的服务端。部署复杂度不相上下但接入文档和社区活跃度差别挺大OnlyOffice的Docker部署、API文档都比Collabora友好得多中文乱码的处理方案也成熟一些。几种方案对比下来结论很清楚方案格式覆盖部署复杂度还原度适合场景kkFileView多格式但Office还原一般低中等快速集成、对排版要求不高Pdf.js仅PDF极低高只需PDF预览LibreOffice OnlineOffice全系列高中等愿意深度投入的团队OnlyOfficeOffice全系列PDF中高需要预览和后续编辑能力1.2 麒麟系统集成OnlyOffice的决定因素项目要求部署在麒麟系统上这就筛掉了不少方案。国产化Linux发行版的环境跟普通CentOS虽然有相似之处但底层内核、软件源、依赖库都可能存在差异尤其在一些偏冷门的机器上源码编译安装LibreOffice这种路线非常痛苦。OnlyOffice官方提供了Docker镜像麒麟系统上只要能把Docker跑起来其他依赖基本都被容器打包好了不需要在宿主机装一堆库。这个隔离特性在国产化环境下特别省事不会污染系统自带的运行环境也不容易跟业务系统抢依赖。另一个考虑点是OnlyOffice的还原度和API设计。接入方式很标准前端加载它的api.js后端把文件URL和配置项组装好返回给前端文档服务器自己负责拉取文件并渲染。文件预览过程中业务系统只做转发和配置耦合度很低后续就算要把预览升级成协同编辑改动也不大。还有一点值得提OnlyOffice自带一套JWT签名机制从业务系统到文档服务器之间的请求是带签名的能挡住一部分直接调用文档服务的滥用场景。在政企类项目里安全审计经常问到这层有这个机制回答问题会顺畅很多。2. 麒麟系统下部署OnlyOffice Document Server2.1 环境检查与Docker安装部署前先确认两件事CPU架构和可用内存。麒麟系统可能跑在x86机器上也可能跑在ARM架构的服务器上这两类OnlyOffice的Docker镜像都支持但拉取的时候要注意系统位数不要拉错。查看系统信息cat /etc/os-release uname -m free -h生产部署建议CPU不低于4核、内存不低于8GB其中给Docker容器至少预留4GB。OnlyOffice在预览时会做格式转换内部要跑Node服务、PostgreSQL、RabbitMQ资源不够的时候不是启动失败就是预览转圈。麒麟系统自带Docker的情况不多大部分版本需要自己装。我用的是类似CentOS的yum源方式sudo yum install -y yum-utils sudo yum-config-manager --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io sudo systemctl enable --now docker如果服务器的软件源是内网定制版访问Docker官方仓库可能失败这时候需要提前准备离线rpm包或者把docker-ce.repo替换成允许访问的镜像源。安装完成后用docker info验证一下确保能正常启动。2.2 启动Document Server容器OnlyOffice文档服务器镜像名是onlyoffice/documentserver启动命令docker run -d \ --name onlyoffice-ds \ -p 8080:80 \ --restartalways \ -e JWT_ENABLEDtrue \ -e JWT_SECRETq3y9x7sL2wP4nR8vT5hG1cD6aB0eF \ onlyoffice/documentserver:latest端口映射很关键容器内部固定监听80宿主机端口可以自己定我用的是8080避免跟业务系统端口冲突。JWT_SECRET就是后面Java端用来生成token的同一个密钥一定要记下来后面要先。主要环境变量说明环境变量作用建议JWT_ENABLED是否启用签名验证建议trueJWT_SECRET签名密钥两端必须一致用随机长字符串SSL_ENABLED是否启用HTTPS反向代理层处理时不需要有些教程只配置了JWT_ENABLEDtrue没有单独指定JWT_SECRET这时候容器会随机生成一个密钥Java端根本猜不到集成之后必然报401。所以必须显式设置JWT_SECRET这个坑我在测试环境踩到过血泪教训。2.3 部署后的自检容器启动后不要急着写代码先花两分钟确认服务是真的起来了。文档服务器自带健康检查接口curl -s http://127.0.0.1:8080/healthcheck正常情况会输出truey只要返回不是这个就要去看容器日志docker logs -f onlyoffice-ds常见日志里有大量转换进程相关的输出不是报错不要慌。看到类似“ONLYOFFICE Document Server is running”就说明主服务起来了。再用浏览器访问http://服务器IP:8080能看到OnlyOffice的欢迎页面基本可以确定部署成功。注意这里访问地址要用服务器的内网IP不要用localhost因为后面业务系统访问和OnlyOffice回拉文件都要靠这个IP。3. Java后端生成预览配置与签名3.1 先理清文档预览的完整调用链很多人在这一步容易绕晕先画清楚调用链路再写代码。整个预览过程其实是浏览器、业务系统、OnlyOffice服务器三次联动用户在业务系统页面点击预览按钮前端请求业务系统的后端接口把文件标识传过去。Java后端根据文件信息生成一个预览配置对象里面包含文件的下载地址、文件名、文档类型以及由JWT签发的token。前端拿到这个配置对象后加载OnlyOffice的api.js然后调用DocsAPI.DocEditor把编辑器挂载到页面指定区域。浏览器通过api请求OnlyOffice服务器OnlyOffice服务器再根据配置里的downloadUrl从业务系统拉取原文件。OnlyOffice对文件做转换和渲染把结果展示在页面里。这里最容易被忽略的一点是OnlyOffice服务器必须能访问到文件下载地址。如果downloadUrl用的是localhost或者仅本机可达的地址浏览器这边会一直转圈页面空白半天也不知道问题出在哪。3.2 后端服务实现我这边用的是Spring Boot整体逻辑就是根据文件信息组装配置对象再生成签名。先看核心代码public class OnlyOfficeService { private static final String DOC_SERVER_URL http://10.10.8.20:8080; private static final String APP_DOWNLOAD_PREFIX http://10.10.8.15:9000/file/download?id; private static final String JWT_SECRET q3y9x7sL2wP4nR8vT5hG1cD6aB0eF; public MapString, Object buildPreviewConfig(FileInfo file) { MapString, Object config new HashMap(); config.put(documentType, detectDocumentType(file.getExt())); config.put(height, 100%); config.put(width, 100%); config.put(type, embedded); config.put(server, DOC_SERVER_URL); MapString, Object document new HashMap(); document.put(fileType, file.getExt().toLowerCase()); document.put(title, file.getDisplayName()); document.put(url, APP_DOWNLOAD_PREFIX file.getId()); document.put(key, buildFileKey(file)); config.put(document, document); MapString, Object editorConfig new HashMap(); editorConfig.put(mode, view); editorConfig.put(lang, zh-CN); editorConfig.put(callbackUrl, http://10.10.8.15:9000/api/onlyoffice/callback); config.put(editorConfig, editorConfig); String token createToken(config); config.put(token, token); return config; } private String buildFileKey(FileInfo file) { String raw file.getId() _ file.getUpdateTime() _ file.getSize(); return SecureUtil.md5(raw); } private String detectDocumentType(String ext) { String lower ext.toLowerCase(); if (lower.contains(xls) || csv.equals(lower)) { return cell; } if (lower.contains(ppt)) { return slide; } return word; } private String createToken(MapString, Object config) { return JWT.create() .withPayload(config) .withKey(JWT_SECRET.getBytes()) .sign(); } }downloadUrl这部分我强烈建议不要直接把文件存储路径暴露出来而是走一个带权限校验的下载接口。这样OnlyOffice拉取文件时业务系统还能根据来源做一次校验比裸暴露文件地址安全得多。callbackUrl在纯预览模式下其实不是必需项但建议先配上。这个接口是OnlyOffice在文档关闭或者保存场景下回调业务系统的现在预览用不上以后开启了编辑模式就会用到。3.3 document key与JWT签名的原理先说document key。OnlyOffice内部会以document key作为缓存维度同一份文档转换一次之后后续预览直接走缓存。如果key不变但文件内容已经变了会看到预览结果一直停留在旧版本这是集成中特别容易踩的坑。所以key的生成规则要跟文件内容绑定我用的规律是“文件ID 更新时间 文件大小”再做一次消息摘要文件任意一个维度变化key就会跟着变OnlyOffice就能感知到内容更新。如果你们系统里文件内容更新之后这些字段不会变就得另寻规则总之核心是让key能反映文件本身的版本变化。再说JWT。OnlyOffice的token机制就是对整个配置对象做一次HS256签名服务端收到后会验证签名签名不过直接拒绝访问。实现的时候Pub整个配置对象作为荷载签名密钥跟容器启动时设置的JWT_SECRET保持一致两边对不上就是401。这个机制的价值在于业务系统的预览接口是带权限的拿到配置之后文档服务器不再直接暴露给外网所有请求都要求签名避免有人拿公开地址绕过业务权限直接访问文件。4. 前端接入JS动态加载与iframe植入4.1 动态script方式SPA项目推荐前端接入其实比想象中简单核心就是把api.js加载进来然后用后端返回的配置对象创建编辑器实例。我用的是动态加载script的方式适合单页应用function openPreview(fileId) { axios.get(/api/onlyoffice/preview, { params: { id: fileId } }).then((res) { const config res.data; const script document.createElement(script); script.src config.server /web-apps/apps/api/documents/api.js; script.onload () { new DocsAPI.DocEditor(editorContainer, config); }; document.body.appendChild(script); }); }页面上只需要放一个div作为挂载点div ideditorContainer stylewidth: 100%; height: 100%;/divconfig.server是后端返回的OnlyOffice服务地址。api.js的路径是固定的不要自己拼错。每个文件预览时都要动态创建一次script标签但加载过一次之后浏览器会缓存实际不会重复请求。4.2 iframe方式传统多页面项目推荐如果你的业务系统还在用Thymeleaf、JSP这类服务端渲染模式用动态script方式会比较别扭更推荐iframe。让Java后端直接渲染一个独立的预览页面前端用iframe把这个页面嵌进来iframe src/onlyoffice/preview?fileId123 stylewidth: 100%; height: 700px; border: 0;/iframe后端对应的Controller返回一个包含api.js和配置对象的完整HTML页面。这种方式的好处是预览页面跟主业务页面完全隔离样式冲突少文件切换也只是换一下iframe的src。缺点是每次进入都得重新加载api.js好在浏览器缓存能兜底。我给个Thymeleaf模板结构参考实际就是把配置对象以JSON形式塞进页面里script var config /*[[${configJson}]]*/ {}; var script document.createElement(script); script.src config.server /web-apps/apps/api/documents/api.js; script.onload function () { new DocsAPI.DocEditor(editorContainer, config); }; document.body.appendChild(script); /script4.3 页面参数与多端适配代码能跑通之后还有几个细节要注意。高度设置。我见过很多刚接入的人把高度写成固定500px结果预览框在笔记本上要把页面滚好几屏才能看完整个文档。建议直接给height传“100%”同时把挂载div和父容器的高度链都设置好让预览区域跟随页面自适应。移动端场景下这个方案也是撑满页面后横向滑动体验比固定宽高好很多。浏览器兼容性。iframe方式在iOS Safari里会遇到全屏和滚动的问题如果项目需要支持移动端浏览器建议用动态script方式而不是iframe。另外不要为了省事把编辑器宽度写成固定的1920px移动端直接爆炸。混合内容拦截这个问题必须提前预防。假设业务系统页面跑在HTTPS下而OnlyOffice服务还是HTTP浏览器会直接拦截这种混合内容预览页面白屏控制台提示Mixed Content。这种情况要么给OnlyOffice配好HTTPS域名要么用反向代理把它收口到HTTPS路径下上线前就要规划好。5. 常见问题与排查技巧实录5.1 麒麟系统环境下的坑部署环境这块的问题很多把典型场景整理成了表格现象可能原因处理方式Docker启动失败或容器秒退系统内核模块不全、内存不足检查docker info和dockerd日志清理内存或加swap拉取镜像很慢或失败网络源不通配置镜像加速器或离线导入镜像tar包容器正常但访问不了8080端口防火墙和iptables拦截放行端口firewall-cmd --add-port8080/tcp --permanent服务运行一段时间后OOM分配给容器的内存不够确认容器内存限制必要时给节点加内存OnlyOffice能打开但长时间转圈宿主机DNS无法解析内网域名直接用内网IP地址避免依赖域名解析麒麟系统的防火墙策略跟标准Linux发行版有一定差异有些版本默认firewalld开启有些则是iptables服务。判断不了的时候先systemctl status firewalld如果服务存在且是active状态直接放行对应端口。内存问题我一定要单独提。OnlyOffice文档服务器在启动阶段就会占用大量内存我测试时一台4GB机器上部署容器起来后业务系统内存压力直接拉满后面文件一预览就卡死。最后调整了容器的内存限制并且给机器增加了swap空间才算稳定。这个不要指望靠JVM参数或者容器参数省出来该上配置就上配置。有一个麒麟系统特有的问题部分版本的软件源里没有containerd.io装Docker时直接提示找不到依赖。解决方法是先把containerd.io单独装好再装docker-ce或者统一采用离线rpm包方式避免依赖地狱。5.2 集成开发阶段的高频问题部署好了代码也写完了联调阶段还会暴露出一堆问题这些是我实际遇到的现象可能原因处理方式接口返回401 invalid tokenJWT_SECRET两边不一致重新生成token确认跟容器启动参数一致预览界面白屏api.js地址错误或CORS拦截检查api.js路径和OnlyOffice服务地址是否可访问文档一直加载不出来OnlyOffice无法访问downloadUrl确认downloadUrl用的是业务系统内网可达地址文件能预览但内容是最旧的document key没有跟着文件内容变key加入更新时间、大小等信息中文文件显示乱码系统缺中文字体容器内安装中文字体包预览时文件下载接口被拒绝下载接口鉴权不通过给OnlyOffice的回源请求配置放行规则401问题最常见也最隐蔽。很多人自己在容器里设置了JWT_ENABLEDtrue但部署的时候没有写JWT_SECRET导致OnlyOffice自动生成随机密钥Java端怎么签名都过不了。排查的时候就把容器删了重新建显式加上JWT_SECRET然后再跑一遍。下载接口这块容易出逻辑问题。有些团队图省事把文件下载接口直接挂在网关后面网关又有白名单限制OnlyOffice服务器发起请求时带不上用户的登录态就被拦下来了。我的做法是单独开一个不受登录态验证的下载接口但接口内部根据文件ID校验文件是否允许预览再做二次权限校验。5.3 我总结的几条避坑心得项目做完收拾现场有几个经验是文档里搜不到的写出来给大家留个参考。第一给OnlyOffice准备独立域名或独立端口。如果前端页面和OnlyOffice都在同一个域名下最好让运维给OnlyOffice单独配一个子域名配合反向代理处理HTTPS证书这是最干净的解法避免混合内容问题。我没有说服运维配合最后只能让OnlyOffice也走了HTTP回源测试环境没问题生产上挨了一顿批评。第二文件下载URL不要带中文参数。早期我把文件名拼到了下载URL的query里OnlyOffice拉文件时遇到编码问题各种报错。后来改成下载接口只认数字id文件名由后端从库里查出来问题就消失了。这看起来是小事实际排查花了不少时间。第三先看OnlyOffice容器日志再看业务系统日志。集成阶段很多问题表象是前端报错但根因在OnlyOffice回拉文件失败。docker logs -f onlyoffice-ds能看到详细的转换日志错误信息里会直接标识下载URL返回的状态码比在前端控制台瞎猜效率高太多。这个排查顺序可以少走很多弯路。最后再分享一个小技巧预览大文件时如果第一次打开特别慢其实是在做格式转换这不是故障。可以在业务系统里加一个预热逻辑上传文件后就主动调用一次预览接口让OnlyOffice提前生成缓存用户真正点击时秒开。这个优化在我们项目里效果非常明显尤其是那些几十MB的PPT和PDF文件。每次集成这种外部服务总有那么几天觉得官方文档写得不够细但折腾下来反而是对这些机制理解最深的时候。麒麟系统的兼容性问题、OnlyOffice的签名机制、缓存key的设计这些在纯理论文章里很难有切身体会只有亲手部署一遍遇到问题再解决才算真正掌握。希望这份记录能帮正在趟这条路线的人省下几个晚上。
阅读完成 · 觉得有帮助?