1. 整体架构鸟瞰
OnlyOffice Docs 的架构设计遵循一个核心原则:文档服务器本身无状态地处理文档逻辑,文件存储与用户管理完全交由外部系统负责。
这一设计使 OnlyOffice 能够以"插件"的方式嵌入几乎任何现有系统,而不要求对外部系统做侵入性改造。
在一次典型的集成部署中,涉及三个角色:
+--------------------------+ +--------------------------+| | | || 你的应用(集成方) | | 文件存储服务 || | | (你的系统负责) || - 用户认证 | | || - 文件管理 UI | | 可以是: || - 权限控制 | | - 本地磁盘 || - 生成编辑器配置 | | - S3 / OSS || | | - Nextcloud |+-----------+--------------+ +------------+-------------+ | | | 1. 页面中嵌入编辑器 JS | 3. 回调保存 / 下载文件 | 2. 传入文档 URL + 配置 | v |+-----------+------------------------------------+-------------+| || ONLYOFFICE Document Server || || Nginx → Node.js 服务 → 协同服务 → 转换服务 || |+--------------------------------------------------------------+这个三角关系贯穿了 OnlyOffice 的所有集成场景,理解它是读懂后续所有内容的前提。
2. Document Server 的组成模块
Document Server 并不是一个单一进程,而是由多个相互协作的服务组成。以 Docker 部署为例,一个完整的 Document Server 实例包含以下核心组件:
2.1 Nginx(反向代理与静态资源服务)
Nginx 是整个服务的入口,承担两类工作:
- 将 HTTP/HTTPS 请求路由到对应的后端服务
- 直接提供编辑器前端静态资源(JS、CSS、字体、图标等)的服务
编辑器的前端代码体积不小(压缩后约 10–20 MB),Nginx 对其做了 gzip 压缩与缓存策略优化。在生产部署中,也可以在 Nginx 前再加一层 CDN 来加速静态资源的分发。
2.2 Node.js 文档服务(docservice)
这是整个架构的核心,基于 Node.js 实现,主要职责包括:
- 接收并验证来自集成方的文档打开请求(JWT 校验)
- 管理文档的编辑会话(session)
- 通过 WebSocket 与浏览器端编辑器保持长连接
- 将用户操作转发给协同服务进行合并处理
- 在文档关闭后触发保存回调
Node.js 的选择不是偶然的:WebSocket 的长连接管理、高并发 I/O 处理,恰好是 Node.js 的强项。
2.3 转换服务(converter)
转换服务负责文件格式的相互转换,是一个独立运行的进程。它处理两类工作:
- 打开时转换:将非 OOXML 格式(如 .odt、.ods、.odp、.txt、.csv)转换为编辑器可处理的内部格式
- 导出时转换:将编辑完成的文档转换为目标格式(如导出为 PDF)
转换服务基于原生代码实现(C++ 核心),能够以接近原生 Office 的精度处理格式转换。这个服务也通过 REST API 对外暴露,集成方可以独立调用文件转换功能,无需打开编辑器。
2.4 拼写检查服务(spellchecker)
独立运行的多语言拼写检查服务,基于 Hunspell 引擎。编辑器中的实时拼写检查(红色下划线)依赖此服务。支持 80 余种语言的词典,词典文件可以扩展。
2.5 RabbitMQ(消息队列)
各服务之间通过 RabbitMQ 进行异步消息传递,主要用于:
- 协同操作的消息分发(将一个用户的操作广播给同一文档的其他用户)
- 转换任务的队列管理(避免大文件转换阻塞主流程)
- 服务间解耦(某一服务重启不影响整体)
在高并发场景下,RabbitMQ 是 Document Server 水平扩展能力的关键支撑。
2.6 Redis(缓存与会话存储)
Redis 在架构中承担两个角色:
- 会话缓存:保存当前活跃文档的编辑状态、在线用户列表、操作历史
- 分布式锁:在多实例部署时,保证同一文档同时只有一个协同服务在处理操作合并
2.7 PostgreSQL(持久化存储)
Document Server 使用 PostgreSQL 存储:
- 文档的操作历史记录(用于版本恢复)
- 文档缓存元数据
- 系统配置信息
需要注意的是,PostgreSQL 存储的不是文档文件本身,文档文件始终由集成方的存储系统负责。PostgreSQL 里存的是"这个文档被编辑过哪些操作、当前编辑状态是什么"这类元数据。
组件关系总览
浏览器 | | HTTP (静态资源) | WebSocket (实时操作) vNginx | +---> Node.js 文档服务 (docservice) | | | +---> RabbitMQ ---> 协同服务 | | | +---> Redis (会话/锁) | | | +---> PostgreSQL (历史/元数据) | +---> 转换服务 (converter) | +---> 拼写检查服务 (spellchecker)3. 文档生命周期:从打开到保存
以用户在浏览器中打开一个 .docx 文件为例,完整的生命周期如下:
阶段一:初始化(打开文档)
步骤 1:集成方生成编辑器配置
用户点击"在线编辑"后,集成方的后端生成一份 JSON 格式的编辑器配置,核心内容包括:
{ "document": { "fileType": "docx", "key": "唯一文档标识符(决定是否复用缓存)", "title": "合同草稿.docx", "url": "https://your-storage.com/files/contract.docx" }, "editorConfig": { "callbackUrl": "https://your-app.com/onlyoffice/callback", "user": { "id": "user-001", "name": "张三" }, "mode": "edit" }}这份配置会被签入 JWT Token 后传给浏览器页面。
步骤 2:浏览器加载编辑器
浏览器加载 Document Server 提供的 api.js,该脚本初始化编辑器,向 Document Server 发送文档打开请求。
步骤 3:Document Server 获取文件
Document Server 收到请求后,通过配置中的 document.url 主动从集成方的存储服务下载原始文档文件。这是一个重要的设计细节:文件是 Document Server 主动拉取的,而非集成方推送的。这意味着 Document Server 必须能够访问到文件 URL(网络可达)。
步骤 4:文件解析与渲染
Document Server 的转换服务将 .docx 解析为编辑器内部格式,通过 WebSocket 将渲染数据推送给浏览器端编辑器。编辑器在浏览器中以 Canvas 渲染文档内容(不是 HTML DOM 渲染,这是 OnlyOffice 与 Google Docs 的一个重要架构差异,后文会详述)。
阶段二:编辑中
用户的每一次键盘输入、格式修改、光标移动,都会产生操作指令(Operation)。这些操作以极低延迟通过 WebSocket 发送到 Document Server,经过协同服务处理后:
- 更新服务端维护的文档状态
- 将操作广播给同一文档的所有其他在线用户
- 持久化到操作历史(用于版本恢复)
阶段三:保存(关闭文档)
OnlyOffice 的保存机制与许多人的预期不同,值得单独解释。
不是实时写回,而是会话结束时回调。
当最后一个用户关闭文档后(或超过配置的空闲超时时间),Document Server 才会:
1 将内存中的最终文档状态序列化为 .docx 文件2 向集成方配置的 callbackUrl 发送 HTTP POST 请求3 回调中携带文档下载链接(Document Server 临时生成的)4 集成方的后端从该链接下载文件,保存到自己的存储系统
这个"延迟回调"的设计是合理的——频繁写回会造成大量 I/O,集中在会话结束时保存效率更高。但它也带来一个需要注意的点:如果 Document Server 在用户编辑期间崩溃,尚未保存的操作可能丢失。生产环境需要通过持久化操作日志来规避这一风险。
**强制保存(Force Save)**是针对这一问题的解决方案,允许集成方主动触发中间保存,Document Server 会立即将当前状态回调给集成方,而不等待会话结束。
4. 协同编辑的实现原理
协同编辑是 OnlyOffice 的核心能力之一,理解其实现原理有助于在集成时做出正确的架构决策。
4.1 核心问题:并发冲突
假设文档中有一行文字"Hello World",用户 A 在第 6 个字符处插入",",与此同时用户 B 删除了第 1 到第 5 个字符。
如果两个操作独立到达服务端并依次应用:
初始状态:Hello WorldA 的操作:在位置 6 插入"," → Hello ,WorldB 的操作:删除位置 1-5 → ,World(A 的操作被应用到了错误位置)这就是协同编辑的经典并发冲突问题。
4.2 操作变换(Operational Transformation,OT)
OnlyOffice 使用操作变换算法来解决这个问题。OT 的核心思路是:在应用一个操作之前,先根据已发生的并发操作对其进行变换,使其在新的文档状态下仍然表达用户的原始意图。
上述例子中,服务端收到 A 的插入操作后,需要将 B 的删除操作变换为"删除位置 1-5(考虑到 A 已插入一个字符,实际偏移不变)",保证最终两个用户的操作意图都得到正确体现。
OT 算法的正确实现极其复杂(尤其是多用户并发场景下的变换函数需要满足严格的数学性质),这是协同编辑领域长期的研究课题。OnlyOffice 选择 OT 而非更新的 CRDT(无冲突复制数据类型)算法,主要是因为 OT 在富文本编辑场景下的语义更直观,且更容易与现有的文档格式模型集成。
4.3 两种协作模式的技术含义
OnlyOffice 提供的两种协作模式在技术层面的差异:
快速模式(Fast Mode)
- 操作实时通过 WebSocket 同步到所有用户
- 服务端操作变换在毫秒级完成
- 适合人数较少(通常不超过 10 人)的高频协作
- 冲突解决对用户透明,偶发的"跳字"现象是 OT 变换的正常表现
严格模式(Strict Mode)
- 用户编辑某一段落时,该段落对其他用户锁定(只读)
- 提交后锁定解除,变更同步给所有人
- 适合多人同时编辑同一文档但分工明确的场景
- 牺牲了实时性,换取了零冲突的编辑体验
4.4 版本历史与操作回放
Document Server 将每次编辑会话的操作序列持久化到 PostgreSQL。这使得版本历史功能得以实现——通过回放操作序列,可以还原文档在任意历史时刻的状态。
版本恢复的粒度是"编辑会话"而非"每次按键",即每次文档被关闭并重新打开,会形成一个新的版本节点。这个粒度在大多数业务场景下是合理的。
5. 文件格式转换引擎
5.1 为什么 OnlyOffice 的格式兼容性更好
传统开源办公软件(如 LibreOffice)的实现路径是:将 .docx 解析后转换为自有内部格式(如 ODF),在内部格式上进行编辑,保存时再转换回 .docx。这个"解析 → 内部格式 → 重新生成"的过程会不可避免地损失 OOXML 规范中某些 LibreOffice 内部格式不支持的特性。
OnlyOffice 的做法不同:它的编辑器内部格式与 OOXML 高度对齐,避免了中间转换损失。用一个不精确但直观的类比:LibreOffice 是把英文书翻译成中文再翻译回英文,OnlyOffice 是直接用英文读写。
5.2 转换服务的技术实现
转换服务基于 C++ 实现,主要包含:
- OOXML 解析器:完整实现 ECMA-376 规范(即 .docx/.xlsx/.pptx 的底层标准)
- ODF 转换器:实现 OOXML 与 ODF 之间的双向映射
- PDF 渲染引擎:将文档转换为 PDF,基于自研的版面渲染逻辑
- 其他格式适配层:处理 .txt、.csv、.html、.epub 等格式
5.3 转换 API 的使用方式
转换服务通过 REST API 暴露,集成方可以独立调用,不需要打开编辑器界面:
POST https://your-document-server/ConvertService.ashx请求体(JSON):{ "async": false, "filetype": "docx", "key": "唯一转换任务标识", "outputtype": "pdf", "title": "output.pdf", "url": "https://your-storage.com/files/contract.docx"}转换服务同样支持异步模式(async: true),适合处理大文件或批量转换场景,通过轮询接口获取转换结果。
6. JWT 安全通信机制
6.1 为什么需要 JWT
在 OnlyOffice 的三方架构中,Document Server 需要主动访问集成方提供的文件 URL 和回调 URL。如果没有签名机制,任何人都可以伪造请求,让 Document Server 去访问任意 URL,或者伪造回调数据篡改文件。
JWT(JSON Web Token)在这里解决两个方向的安全问题:
- 集成方 → Document Server:配置信息需要签名,Document Server 验签后才处理请求
- Document Server → 集成方:回调请求携带 JWT Header,集成方验签后才接受保存操作
6.2 签名流程
集成方持有一个与 Document Server 共享的密钥(Secret)。生成编辑器配置时,将配置 JSON 用该密钥签名为 JWT Token:
原始配置 JSON | v使用 HMAC-SHA256 + Secret 签名 | vJWT Token(Header.Payload.Signature) | v以 token 字段附加在配置 JSON 中,传给浏览器Document Server 收到配置后,用相同的 Secret 验签,验证通过才继续处理。反向(Document Server 回调集成方)也是同样的机制。
6.3 没有启用 JWT 的风险
社区版默认可以不配置 JWT(方便开发调试),但生产环境务必启用。未启用 JWT 意味着:
- 任何人只要能访问 Document Server,就可以构造请求让其下载任意 URL
- 伪造的回调请求可以替换集成方存储系统中的文件内容
JWT 的配置方式会在第四篇部署文章中详细说明。
7. 三大编辑器的架构共性与差异
Document Editor、Spreadsheet Editor、Presentation Editor 共享同一套底层架构(渲染引擎、协同层、转换层),但在编辑模型上各有侧重。
7.1 共同的渲染方式:Canvas 渲染
OnlyOffice 的三大编辑器均采用 Canvas 渲染,而非将文档内容映射为 HTML DOM 树。
这个选择有其权衡:
Canvas 渲染的优势:
- 像素级控制文档版面,与打印输出高度一致(WYSIWYG)
- 不受 CSS 渲染模型限制,复杂排版(如文字绕图)实现更精确
- 减少大型文档时 DOM 节点过多导致的性能问题
Canvas 渲染的代价:
- 文档内容对屏幕阅读器(辅助技术)不友好,无障碍访问支持较弱
- 选中文本、浏览器查找(Ctrl+F)等原生浏览器功能需要自行实现
- 调试渲染问题比 HTML DOM 更困难
相比之下,Google Docs 采用 HTML 渲染(加大量 JS 控制),Notion 采用标准 contenteditable,各有取舍。
7.2 Document Editor(文字处理)
- 排版引擎实现了完整的流式布局模型:文字流、段落、分节、分页
- 光标与选区由自实现的 Hit-testing 逻辑处理(不依赖浏览器文本选区)
- 修订(Track Changes)在操作层记录,渲染时叠加显示
7.3 Spreadsheet Editor(电子表格)
- 采用虚拟化渲染:只渲染可视区域内的单元格,支持超大表格(百万行级别)
- 公式引擎独立实现,兼容 Excel 函数库(约 450 个函数)
- 图表引擎基于自研矢量渲染,支持实时数据联动
7.4 Presentation Editor(演示文稿)
- 幻灯片采用固定尺寸画布(16:9 或 4:3),布局模型比 Document Editor 简单
- 动画与过渡效果在编辑模式下以静态缩略图预览,播放模式下全效展示
- 对 .pptx 中母版(Slide Master)和版式(Slide Layout)的支持是格式兼容性的关键点
8. 各版本功能对比
在第一篇中已经提到了开源版与商业版的大致区别,这里给出更完整的技术层面对比:
| 功能维度 | 社区版(AGPL) | 企业版 | 开发者版 |
|---|---|---|---|
| 三大编辑器 | 完整支持 | 完整支持 | 完整支持 |
| 并发连接数限制 | 无限制 | 无限制 | 无限制 |
| 文件格式转换 API | 支持 | 支持 | 支持 |
| JWT 安全集成 | 支持 | 支持 | 支持 |
| 插件系统 | 支持 | 支持 | 支持 |
| 宏(JavaScript) | 支持 | 支持 | 支持 |
| 文档水印 | 不支持 | 支持 | 支持 |
| 白标定制(去品牌) | 不支持 | 支持 | 支持 |
| 文档访问权限精细控制 | 基础 | 高级 | 高级 |
| LDAP / AD 集成 | 不支持 | 支持 | 支持 |
| SSO(SAML/OAuth) | 不支持 | 支持 | 支持 |
| 集群 / 高可用部署 | 需自行实现 | 官方方案 | 官方方案 |
| 审计日志 | 不支持 | 支持 | 支持 |
| 官方技术支持 | 论坛 | SLA 支持 | SLA 支持 |
| 授权方式 | 免费开源 | 按年/按服务器 | 按年/按服务器 |
开发者版与企业版的核心区别在于使用场景的授权:企业版面向内部部署,开发者版专门授权给将 OnlyOffice 集成进商业产品的 ISV(独立软件开发商),允许对外提供服务。
9. 小结
本文从工程视角拆解了 OnlyOffice Document Server 的内部结构,核心要点如下:
架构层面:
- Document Server 是一个由 Nginx、Node.js、转换服务、RabbitMQ、Redis、PostgreSQL 组成的多进程系统
- 三方协作模型(你的应用 + Document Server + 文件存储)是所有集成场景的基础
- 文件由 Document Server 主动从集成方拉取,保存通过回调机制推送给集成方
协同编辑层面:
- 基于操作变换(OT)算法处理并发冲突
- 保存机制是"会话结束后回调",生产环境需关注强制保存配置
格式兼容层面:
- 转换服务基于 C++ 实现,内部格式与 OOXML 高度对齐,这是格式保真度的来源
- 三大编辑器均采用 Canvas 渲染,与 HTML DOM 渲染路线的系统有本质差异
安全层面:
- JWT 是三方通信的安全基础,生产环境必须启用
夜雨聆风