用OnlyOffice在网页里预览Word、Excel、PPT、PDF,听起来像是一件很常规的事,但真要把这四类文件统一跑通,并且做到只需要双击一个index.html就能看到预览效果,我前前后后折腾了两个晚上。最后成型的demo不复杂:前端就是一个HTML页面,服务端是一个用Docker跑起来的OnlyOffice Document Server,把两样东西接到一起,浏览器里就能像打开本地Office一样阅读各种文档了。
这篇文章就是这份demo的完整复盘。适合正在给OA、网盘、知识库、工单系统做在线预览功能的人,也适合刚接触OnlyOffice、想先花半小时跑通概念的同学。我会把选型理由、运行原理、部署命令、前端接入代码、白屏排错、不同格式的实测差异全部写出来,最后再聊聊从“预览”延伸到“在线编辑”“批注读取”“多人协同”的进阶思路。
1. 为什么绕了一大圈,最后选了OnlyOffice做预览
1.1 我提前试过的那几条路,问题都在哪儿
最早接到这个需求时,我的第一反应其实是“直接iframe套一下不就行了”,结果被现实教育得很惨。对于PDF,浏览器原生就能显示,不用做任何开发;但Word、Excel、PPT这些文件扔给浏览器,它只会变成一个下载按钮,想直接在页面上阅读根本做不到。
然后我试了微软的Office Online Viewer,就是那种把文件地址拼到一个官方URL后面、自动在网页里打开预览的方式。单看效果确实不错,样式还原度很高,但它有一个致命前提:文件地址必须是公网可访问的URL。我做的是内网文档系统,文件都在公司服务器上,外网根本访问不到。而且它属于第三方在线服务,传文档过去涉及数据安全问题,这条线直接被否了。
接下来考虑过WPS的WebOffice,因为是国产软件,对国内场景适配得挺好,界面交互也顺手。但问题在于它的接入需要商务洽谈、签合同、拿授权,技术验证还没做完,流程先卡住了。对于只想快速出一个demo验证效果的人来说,这个门槛高得有点不划算。
1.2 OnlyOffice的社区版、Docker化部署,正好卡在我的需求点上
OnlyOffice打动我的点有三个。第一,它开源,社区版可以免费商用,对一个内部工具来说很友好。第二,它提供Docker镜像,一条命令就能启动一套完整的文档解析服务,不需要像LibreOffice那样自己去拼转换服务。第三,它的前端嵌入API做得非常薄,一个DocsAPI.DocEditor调用就能把编辑器骨架拉起来,Word、Excel、PPT、PDF统统走同一套接入逻辑。
最后我确定的demo最小闭环是这样:一个跑在Docker里的Document Server负责“看得懂”文档,一个写死的index.html负责提供“能双击打开”的入口,再加上一个放着示例文件的静态文件服务,三者连通之后,页面就能预览这四类文件了。整套东西没有数据库,也不需要写后端接口,属于最朴素的能跑版本。
2. 一套能预览的链路,前后端各自身上的分工是什么
2.1 别把“Document Server”和“编辑器文档”搞混
很多第一次接触OnlyOffice的人会有一个误解:以为index.html里引用的API脚本就是编辑器本身。实际不是。api.js只是前端接线的“遥控器”,真正干活的是Document Server这个服务端进程。
当你调用new DocsAPI.DocEditor("placeholder", config)时,你的页面会把一份config配置交给服务端。服务端拿到这份配置后,会先去读取config里的文档地址(也就是document.url),把原始的docx、xlsx、pptx、pdf文件拉到自己这边,然后解析成适合Web渲染的结构,再回传给前端页面显示。整个过程可以理解成“你把文件地址告诉服务端,服务端负责把文件翻译成浏览器能看懂的格式”。
这也是为什么demo里光有一个index.html是不够的——你还得有一个能跑起来的服务端,以及一个存文件的静态服务。三者之间的关系大致是这样:
浏览器双击打开index.html │ │ 1. 从Document Server加载 api.js │ 2. 提交 config(内含文档URL) ▼ OnlyOffice Document Server(地址:http://localhost:8080) │ │ 请求 config.document.url ▼ 静态文件服务(地址:http://localhost:9000/sample.docx)这个角色划分非常重要,后面的很多排错其实都是在排查这三条链路里哪一条断了。
2.2 文档地址要满足“两端可达”
用本地文件路径直接预览是不行的,因为Document Server在Docker容器里,它看不到你电脑的C:\盘路径。config里填的URL必须是一个它通过HTTP能访问到的地址。开发环境最简单的方式,就是把示例文件和index.html放到同一个静态服务里,然后用http://localhost:9000/sample.docx这样的地址来引用。上传到公司的对象存储或者图床也可以,原则只有一个:前端浏览器和服务端容器都能访问到这个URL。
3. 花三分钟把Document Server用Docker部署起来
3.1 Docker run命令和目录规划
部署Document Server这一步没什么悬念,官方提供了标准镜像,命令也基本固定。我习惯先把宿主机上的日志、数据、依赖三个目录建好,方便以后升级和排查:
mkdir -p /opt/onlyoffice/logs mkdir -p /opt/onlyoffice/data mkdir -p /opt/onlyoffice/lib然后启动容器:
docker run -d -p 8080:80 \ --name onlyoffice-docserver \ --restart=always \ -v /opt/onlyoffice/logs:/var/log/onlyoffice \ -v /opt/onlyoffice/data:/var/www/onlyoffice/Data \ -v /opt/onlyoffice/lib:/var/lib/onlyoffice \ onlyoffice/documentserver:latest端口映射我选了8080,因为80在日常开发环境里经常被占用。如果你公司内部有统一的域名和反向代理,也可以把80端口让给OnlyOffice,后面再通过nginx转发。
3.2 健康检查、日志验证、许可证问题一次说清
容器启动后不要急着写前端,先确认服务真的起来了:
curl http://localhost:8080/healthcheck如果返回true,说明Document Server的核心进程正常。然后看日志:
docker logs -f onlyoffice-docserver第一次启动时会出现很多初始化信息,比如数据库初始化、缓存构建、字体生成之类,耐心等一两分钟再检查。
这里要特别提醒一件事:默认启动的是社区版,功能上对内部预览和轻量编辑足够,但如果你准备生产商用,需要去官方网站申请商业授权。授权文件的挂载方式是-v /path/to/license.lic:/var/www/onlyoffice/Data/license.lic,重新启动容器后自动生效,不需要改代码。千万不要在生产环境白嫖社区版跑大规模业务,后面业务量大了文档响应慢,你会非常被动。
3.3 示例文件从哪里来:起一个极简静态文件服务
接着把示例文件准备好。任意目录下放几个文件,然后把这个目录变成HTTP静态服务。用Docker跑最省事:
docker run -d -p 9000:80 \ --name demo-static \ -v $(pwd):/usr/share/nginx/html:ro \ nginx:alpine如果不想启动Docker,也可以直接用Python:
cd /your/demo-files python3 -m http.server 9000这样访问http://localhost:9000/sample.docx就能直接拿到文件了。下一步写index.html时,config里的document.url就填这个地址。
4. index.html核心代码:接入预览其实只有几步
4.1 先看完整代码,再拆字段
这是整个demo最核心的部分。我把文件选择器、预览初始化、格式切换全塞进了一个页面,这样既能展示Word、Excel、PPT、PDF四种类型,又保持着“双击即可看效果”的姿势:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>OnlyOffice 文档预览 Demo</title> <style> body { margin: 0; font-family: "Microsoft YaHei", Arial, sans-serif; } #toolbar { background: #f5f5f5; padding: 10px 16px; border-bottom: 1px solid #ddd; display: flex; align-items: center; gap: 10px; } #toolbar select, #toolbar button { padding: 6px 12px; font-size: 14px; } #placeholder { width: 100%; height: calc(100vh - 54px); } </style> </head> <body> <div id="toolbar"> <select id="fileSelector"> <option value="word">Word 示例</option> <option value="excel">Excel 示例</option> <option value="ppt">PPT 示例</option> <option value="pdf">PDF 示例</option> </select> <button id="previewBtn">预览</button> </div> <div id="placeholder"></div> <script src="http://localhost:8080/web-apps/apps/api/documents/api.js"></script> <script> const sampleFiles = { word: { documentType: "word", fileType: "docx", title: "示例文档.docx", url: "http://localhost:9000/sample.docx" }, excel: { documentType: "cell", fileType: "xlsx", title: "示例表格.xlsx", url: "http://localhost:9000/sample.xlsx" }, ppt: { documentType: "slide", fileType: "pptx", title: "示例演示文稿.pptx", url: "http://localhost:9000/sample.pptx" }, pdf: { documentType: "pdf", fileType: "pdf", title: "示例文档.pdf", url: "http://localhost:9000/sample.pdf" } }; let docEditorInstance = null; function preview() { const key = document.getElementById("fileSelector").value; const file = sampleFiles[key]; const config = { document: { fileType: file.fileType, key: key + "-" + Date.now(), title: file.title, url: file.url }, documentType: file.documentType, editorConfig: { mode: "view", lang: "zh-CN", customization: { autosave: false, chat: false, comments: false, help: false } }, height: "100%", width: "100%" }; // 切换文件时先销毁上一个实例,避免重复初始化 if (docEditorInstance) { docEditorInstance.destroy(); } docEditorInstance = new DocsAPI.DocEditor("placeholder", config); } document.getElementById("previewBtn").addEventListener("click", preview); // 页面加载后默认预览 Word preview(); </script> </body> </html>这段代码没有用到任何框架,纯原生JS,直接保存为index.html就能当demo的入口页。
4.2 config里每个字段到底是干什么的
接入OnlyOffice时最怕的就是config配错,因为它的字段是一套嵌套结构。我整理了一张对应表,方便你对照自己的场景改:
| 配置字段 | 作用 | 示例值 | 注意事项 |
|---|---|---|---|
document.url | 需要预览的文档真实HTTP地址 | http://localhost:9000/sample.docx | 前端和Document Server都要能访问 |
document.fileType | 文档的格式名 | docx/xlsx/pptx/pdf | 大小写不敏感,但必须和URL实际文件一致 |
document.key | 文档的唯一缓存ID | "word-1700000000" | 保持不同文档不同key,相同key会重复使用缓存 |
document.title | 编辑器顶部显示的文件名 | "示例文档.docx" | 可以带扩展名 |
documentType | 决定用哪套编辑器类型 | word/cell/slide/pdf | 弄错会出现“白屏”或“格式不支持” |
editorConfig.mode | 打开后是只读还是可编辑 | view/edit | 预览用view,编辑再切edit |
editorConfig.lang | 界面语言 | zh-CN | 不填默认英文 |
height/width | 编辑器占位尺寸 | "100%"/"100%" | 支持百分比和像素值 |
最容易被忽视的是documentType和fileType。前者告诉OnlyOffice用编辑器哪个模块,后者告诉它解析时按什么格式处理。如果你拿一个PPT文件却把documentType填成了word,结果往往不是报错,而是页面空白或者样式乱掉。
4.3 切换文件时的实例销毁细节
第一次写完demo时,我遇到的坑在切换文件:先看Word没问题,切到Excel后页面完全没反应,黑屏转圈。原因不是配置,而是我在同一个div占位符上连续执行了多次new DocsAPI.DocEditor,前一个编辑器实例还占着DOM,后面的初始化被它挡住了。
解决办法就是文档里明确写的——每次切换前调用一下旧实例的destroy()方法。上面代码里我先用docEditorInstance存住了当前的实例,再初始化新文件前销毁旧实例,这个顺序不能反,先销毁再创建。如果你改成“先创建新的再销毁旧的”,新实例会直接覆盖占位符内容,旧实例又在异步加载文档,很容易出现抢占和错乱。
5. 从白屏到能预览:本地file协议和跨域问题的完整排查复盘
5.1 现象:双击index.html以后,页面干干净净什么都没有
这大概是demo从“写完”到“真正能打开”之间最磨人的一段。我当时把index.html放在本地目录里,双击后用file:///C:/demo/index.html打开,F12控制台里只有一行红色错误:加载api.js失败,或者加载后被CORS策略拦了。
一开始我以为是地址写错,反复核对http://localhost:8080没有问题。后来才反应过来:你的页面是通过file://协议打开的浏览器标签页,Document Server作为http://localhost:8080上的服务,两者用的是不同协议、不同来源,浏览器默认不会让你的页面跨协议去拉取JS脚本。
5.2 先看脚本加载,再看文档请求
排查这种问题我建议按两条线走。
第一条线:打开F12的Network面板,刷新页面,看api.js的请求状态。如果这个请求是红色blocked或者状态码显示CORS相关错误,就说明前端页面和Document Server之间的来源不匹配。最简单的处理方式不是去改服务器CORS配置,而是别用file://协议。把index.html放到任意HTTP静态服务里,比如我前面起的python3 -m http.server,用http://localhost访问页面,脚本跨域问题基本就消失了。
第二条线:确认Document Server能不能“回访”文档URL。因为config里填的http://localhost:9000/sample.docx是前端浏览器能访问的,但真正去下载这个文件的不是你的浏览器,而是Document Server容器。如果容器里访问不到localhost:9000,编辑器就会一直带loading图标,最后提示“无法下载文档”。
我在本机遇到过一种很典型的情况:静态服务和Document Server用的是同一个宿主机的“localhost”,这个没问题。但你把demo部署到服务器上、用IP访问页面时,会下意识把文档URL填成http://127.0.0.1:9000/sample.docx,然后容器就会觉得自己访问的就是自己,结果找不到。这种时候要改成宿主机的局域网IP,例如http://192.168.1.100:9000/sample.docx。
5.3 本机实测最顺的一种打开姿势
经过反复折腾,我发现“双击index.html”在大部分现代浏览器里确实能直接看效果,但这依赖你本机没有任何代理插件、而且浏览器没有收紧file协议跨源限制。如果你想让自己省点心,更推荐用一条Python命令把demo目录变成HTTP服务再打开:
cd /path/to/demo python3 -m http.server 8081然后访问http://localhost:8081/index.html。页面本身跑在HTTP环境里,和http://localhost:8080的Document Server属于同源局域网下的跨端口请求,浏览器的限制会宽松很多。如果线上发布,那就更简单了:把index.html扔给任意nginx静态站点托管,Document Server用域名或公网地址,文档地址用对象存储的URL,三个节点都通过公网访问,跨域问题基本不存在。
5.4 两张排错清单,建议直接收藏
我把这次踩坑的经验浓缩成两张清单。
第一张是“页面打开但是白屏”的检查顺序:
- 检查
http://localhost:8080/web-apps/apps/api/documents/api.js能否直接在浏览器打开 - 检查
curl http://localhost:8080/healthcheck是否返回true - 检查
http://localhost:9000/sample.docx能否直接下载 - 在Document Server容器里执行
curl,确认它也能访问http://localhost:9000/sample.docx - 确认
documentType和fileType是否匹配
第二张是“部分文件能预览、部分文件不行”的检查顺序:
- 确认文件本身不是损坏的,先用Office或WPS打开验证
- 确认文件名后缀和真实格式一致,不要把改了后缀的假docx传上去
- 加大文件或高并发测试时,看Document Server所在服务器的内存是否够用
6. Word、Excel、PPT、PDF四种格式实测:各自的表现和坑
6.1 Word:版式还原度最好,但要注意字体缺失
实测下来,DOCX在OnlyOffice里的还原度是最高的,分页、标题层级、页眉页脚、表格边框、批注标记都能正常显示。对中文用户来说最现实的问题是字体:如果Document Server容器里没有安装系统中文字体,预览时就会出现字体替换,比如宋体被换成默认的sans-serif,行距和字号看着会有一两像素偏差。
解决方式是在容器里安装中文字体包。以Ubuntu容器为例,执行apt-get install -y fonts-wqy-zenhei fonts-wqy-microhei,装完重启容器再预览。如果你要展示的文档用了公司定制字体,还得把字体文件单独放到容器的/usr/share/fonts目录里,刷新字体缓存。
6.2 Excel:公式和多Sheet是真能用的
用OnlyOffice预览Excel是我个人认为比PDF还要舒服的地方。多Sheet切换、冻结行列、数据筛选器、条件格式这些在页面上都能操作,公式也会在打开时重新计算。这意味着你甚至可以把一个带交互的Excel报表直接嵌到网页里,用户不用下载到本地就能看各个分Sheet的数据。
但有一点要注意:文件如果带有很重的宏(VBA),OnlyOffice目前不会执行这些宏。对预览场景这可能反而是个优点——安全。如果你预期用户会用到宏,那这个方案撑不住,只能回到桌面Office。
6.3 PPT:静态渲染,别期待动画播放
PPTX的预览让我刚开始有点不适应——动画和切换效果不会被播放,它更接近“按幻灯片的排列方式静态展示每一页”。这其实是好事,因为商业汇报场景里,用户主要看内容结构而不是炫酷动画。和Word一样,字体缺失会明显影响幻灯片的视觉效果,同样是那几个中文字体包装完就能解决。
另外,如果PPT里嵌入了视频/音频文件,预览时可能显示不出来,需要确认是不是因为容器缺少对应的解码组件。正式的部署建议是给Document Server服务器安装完整的多媒体解码库,否则PPT里的视频那一页会黑屏。
6.4 PDF:当成编辑器里的特殊“文档类型”处理
PDF在OnlyOffice里的处理逻辑和其它三类不太一样,它的documentType是pdf,而不是word/cell/slide。我见过不少人把PDF的documentType配成word,结果打开后连页面都不渲染。只要把documentType和fileType都填成PDF,基本就能正常显示。
PDF预览本身也支持查看注释、签名字段、表单内容,打开速度比Office文档快不少,因为不需要走繁重的Office解析链路。如果你的业务只需要看PDF文件,其实用浏览器原生的<iframe>就足够了;如果你希望用户能在网页端备注、填写表单,才值得走OnlyOffice。
6.5 四种格式的配置对照表
| 文件类型 | documentType | fileType | 常见表现 | 典型坑 |
|---|---|---|---|---|
| Word | word | docx/doc | 版式还原度高 | 中文字体缺失、旧版.doc兼容偶尔错位 |
| Excel | cell | xlsx/xls | Sheet切换顺畅、公式可算 | 带宏文件不执行 |
| PPT | slide | pptx/ppt | 按页静态展示 | 动画/视频不可播 |
pdf | pdf | 打开稳定、速度快 | documentType配错就直接不渲染 |
7. 预览之外还能怎么玩:编辑权限、批注读取和协同编辑
7.1 一键从预览切到编辑
预览只是绕开了“下载后打开”的那道坎,OnlyOffice真正的杀手锏是它能把同一个页面变成在线编辑器。只需要把editorConfig.mode从view改成edit,页面里就会多出完整的编辑工具栏,用户可以当场改文字、调整表格公式、加批注。
但要注意,编辑模式会触发保存机制,你需要额外配置editorConfig.callbackUrl。当用户在网页里保存文档时,Document Server会把这个文档发给回调URL。demo阶段可以不配,点保存会发生什么由服务端容忍处理;但做正式系统时,回调URL必须指向你自己的后端接口,否则文档只能临时在内存里改,刷新页面就丢了。
7.2 批注到底存在哪里,怎么取出来
网上关于“OnlyOffice批注怎么取”的提问非常多,我也被这个问题卡过一下。批注并不是一个独立的API接口直接返回给你,而是随着文档一起存在文件内容里。当用户保存文档后,你的回调URL会收到一个POST请求,里面有一个指向新文档的URL,你从那个URL把文档下载回来后,解析即可。
以Word为例,批注保存在docx压缩包里的word/comments.xml;Excel的批注则放在xlsx里的comments相关Sheet;PPT的批注逻辑也类似。你想在后端统一管理批注,就得自己解析这些XML结构。没有“一条API直接列出所有批注”的说法,绕过文件层面去取数据,基本都会被卡住。
7.3 多人协同并不是零成本换配置
最后提一下“实时协同编辑”。当多个浏览器同时打开同一份文档时,OnlyOffice的Document Server确实具备协同能力:同一个key对应同一份文档,几台设备同时在线编辑,都能看到对方的改动。docker镜像里已经包含了消息推送、Redis这些协同依赖,不需要你再单独部署一套。
但在真实系统里,你还得自己处理文档权限、并发加锁、历史版本保存这些逻辑,尤其是“谁在这个文档上编辑,谁只能看”的权限控制,需要在config里给每个用户分配editorConfig.user信息。把这些都做完,才算真正把一个可靠的在线协作文档系统搭起来。到了这一步,原先那个“双击就能预览”的demo就完成了它最重要的使命:帮你确认了OnlyOffice这条技术路径是走得通的,后面再往里填业务能力都不算难。