2527 字
约 8 分钟
13
S27-X13 · OnlyOffice 与在线文档预览的接入方式

"能不能不下载就打开这个 docx"——一句话需求,底下是三条完全不同的路:接云端 Office、上 OnlyOffice、或自建"转 PDF + 前端预览"。工作量差一个量级,坑也各不相同。

以 OnlyOffice 为主线:架构 → 接入流程 → 签名 → 地址可达性 → 回调语义 → 并发锁,最后给"什么时候别用它"的判据。


1. 四条路线与代价

路线 能看 能改 数据是否出网 主要代价
Office Online / 云文档 是 是 出网(文件要给对方) 依赖外网、账号体系、合规沟通
OnlyOffice Document Server 是 是(协同编辑) 可完全内网 自建资源、回调对接、网络打通
WPS 在线预览/编辑 是 部分 多为云服务 授权与账号绑定
自建"转 PDF/图片" 是 否 不出网 只读,依赖转换工具链

关键判断点有三个:要不要编辑、文件能不能出内网、愿不愿意维护一个文档服务。"要编辑 + 不能出网",基本只剩 OnlyOffice。


2. 架构:Document Server + 你的后端 + 回调

# docker-compose.yml(自建 Document Server 的最小形态)
services:
  documentserver:
    image: onlyoffice/documentserver:latest   # 版本按当前官方标签核对
    container_name: onlyoffice-ds
    ports: ["8081:80"]
    environment:
      - JWT_ENABLED=true
      - JWT_SECRET=${ONLYOFFICE_JWT_SECRET}
      - JWT_HEADER=Authorization
    volumes:
      - ds_data:/var/www/onlyoffice/Data
    restart: unless-stopped
volumes:
  ds_data:
  ds_logs:
组件 职责 常见误解
Document Server 渲染文档、提供编辑器前端、保存时回调后端 不存你的文件,文件始终在你的存储里
你的业务后端 生成编辑器配置、签发 JWT、实现下载与保存回调 只做"给地址 + 收结果",不参与渲染
浏览器 加载编辑器 iframe,与 DS 直连 浏览器也要能访问 DS,不只是后端
文件存储 原始文件与保存后的新版本 支持版本覆盖或另存

两个方向的网络都必须通:浏览器 → Document Server(加载编辑器与静态资源),Document Server → 你的文件 URL(拉取与回写)。只打通一个方向是最常见的失败原因。


3. 接入流程:配置 → 编辑器 → 回调

3.1 后端生成带签名的配置

// 生成编辑器配置(字段名按当前版本的 API 文档核对)
Map<String, Object> config = new LinkedHashMap<>();
config.put("documentType", "word");
config.put("document", Map.of(
        "fileType", "docx",
        "key", docKey,                       // 同一文件的同一版本必须同一个 key
        "title", "季度报告.docx",
        "url", "https://files.example.com/api/docs/123/download?token=" + fileToken,
        "permissions", Map.of("edit", true, "print", true)
));
config.put("editorConfig", Map.of(
        "mode", "edit",                      // view / edit
        "callbackUrl", "https://api.example.com/api/docs/123/callback",
        "user", Map.of("id", "u-1001", "name", "张三"),
        "customization", Map.of("autosave", true, "forcesave", false)
));

String payload = objectMapper.writeValueAsString(config);
String token = jwtSigner.sign(payload);      // HS256,密钥与 DS 的 JWT_SECRET 一致

Map<String, Object> body = Map.of("config", config, "token", token);

3.2 前端加载编辑器

<div id="placeholder" style="height:100vh"></div>
<script src="https://ds.example.com/web-apps/apps/api/documents/api.js"></script>
<script>
  new DocsAPI.DocEditor("placeholder", {
    document:   { fileType: "docx", key: KEY, title: TITLE, url: DOC_URL },
    editorConfig: { mode: "edit", callbackUrl: CALLBACK_URL,
                    user: { id: USER_ID, name: USER_NAME } },
    token: TOKEN            // 与后端签名一致,否则编辑器直接报错
  });
</script>

3.3 保存回调:status 是全部语义所在

status 含义 该做什么
1 文档被打开/有人在编辑 记审计即可,不动文件
2 编辑结束,有变更待保存 用回调里的 url 下载新文件并覆盖/另存
3 保存出错 告警,别静默吞掉
4 无变更关闭 无需处理
6 强制保存(forcesave) 同 2,用来做定时快照
7 强制保存出错 同上
@PostMapping("/api/docs/{id}/callback")
public Map<String, Object> callback(@PathVariable Long id, @RequestBody JsonNode body,
                                    @RequestHeader(value = "Authorization", required = false) String auth) {
    verifyJwt(auth);                                  // 校验 DS 发来的签名
    int status = body.path("status").asInt();
    if (status == 2 || status == 6) {
        storage.saveVersion(id, body.path("key").asText(), body.path("url").asText());
    }
    return Map.of("error", 0);                        // 必须回 {"error":0}
}

回调响应体必须是 {"error":0};返回非 0 或非 JSON,DS 会判定保存失败并重试。


4. JWT 签名:为什么必须有

DS 默认对配置与回调做 JWT 校验,作用有二:防伪造 document.url 让你的服务器拉任意文件,以及确认回调真的来自你的 DS。

配置项 作用 不一致的后果
JWT_ENABLED 是否开启校验 一端开一端关,编辑器打不开或回调被拒
JWT_SECRET HS256 密钥 必须两边完全相同,含首尾空格
# 验证签名链路:解出 token 的 payload,看是否与发给前端的 config 一致
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .

签名覆盖整个 config 的 JSON 序列化结果:字段顺序、空值、数字与字符串都会影响签名,所以要复用同一个 config 对象序列化。


5. 文档存储与地址可达性:第一大坑

Document Server 是从它自己的容器内部访问 document.url 的:对浏览器可达,对 DS 容器不一定可达。

你写的 URL 浏览器 DS 容器 结果
http://localhost:8080/file/1 可达 不可达(localhost 是容器自己) 编辑器打开后一直转圈
http://host.docker.internal:8080/file/1 视系统 通常可达(Docker Desktop) Linux 上需额外配置
https://files.example.com/file/1 可达 可达(走域名解析) 推荐

结论:给 DS 用的 URL 要走"两边都能解析"的域名,而不是 localhost。容器网络里常见做法是给 DS 配 extra_hosts,把业务域名指到宿主机 IP:

    extra_hosts:
      - "files.example.com:172.17.0.1"

另一个条件是免登录可下载:DS 不带你的会话 Cookie,document.url 必须自带短期签名参数,且有效期覆盖整个编辑会话。


6. 格式支持与"只读预览"的轻量方案

需求 方案 说明
docx/xlsx/pptx 在线编辑 OnlyOffice 完整度最高,需 DS
只要看不要改 转 PDF + 浏览器 PDF 阅读器 工具链自选,成本最低
要保留排版地看 转 PDF(headless 转换) 字体缺失会导致排版偏移
# 轻量预览:headless 转换,注意中文字体
soffice --headless --convert-to pdf --outdir /data/preview /data/upload/report.docx
````soffice` 转换最常见的问题是**服务器没装中文字体**,转出来全是方块;装字体后要清缓存或重启转换进程。预览占九成、编辑极少的场景,这条路线能省掉整个 Document Server。

---

## 7. 并发编辑与锁

| 概念 | 机制 | 工程含义 |
|---|---|---|
| `document.key` | 标识"某个文件的某个版本" | 同 key 才能协同;内容变了必须换 key |
| 协同会话 | DS 内部维护,同 key 即同会话 | 业务层不必再加编辑锁 |
| 覆盖写 | 回调给你新文件 | 建议"另存版本 + 切指针",别原地覆盖 |

`key` 最容易出错:用内容 hash 或"文件 id + 版本号",**不要用时间戳**——那会让两个人各拿一个 key,协同直接失效,每次刷新还多一个编辑会话。

---

## 8. 自建部署的资源要求

| 项目 | 说明 |
|---|---|
| 内存 | DS 是 Node 服务 + 转换进程,转大文件时内存峰值明显;按当前版本官方文档核对 |
| CPU | 转换吃单核性能,并发编辑吃多核 |
| 磁盘 | 日志与缓存持续增长,需配轮转 |

| 反代 | 必须支持 WebSocket 升级,否则协同失效 |
| 字体 | 装业务用到的中文字体,否则渲染与导出差错 |

```nginx
# 反代要点:WebSocket 必须放行
location / {
    proxy_pass http://127.0.0.1:8081;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 3600s;
}

9. 排错:七个高频现场

现象 原因 处理
编辑器一直转圈 DS 容器访问不到 document.url 换两边都能解析的域名,查 extra_hosts
报"文档安全令牌无效" JWT 密钥不一致,或前端 token 与后端 config 不同源 统一 JWT_SECRET,token 由后端一次生成
编辑后内容没保存 callbackUrl 不可达,或回调没回 {"error":0} 从 DS 容器内 curl 验证
保存提示文件被占用 原文件被锁定,或覆盖时未释放流 落新版本再切指针
协同编辑只有一个人能改 反代没放行 WebSocket 补 Upgrade/Connection 头
浏览器报混合内容 页面 https,DS 或文件 URL 是 http 全链路 https,或同域反代
中文显示为方块 DS 容器缺中文字体 挂载字体目录并刷新字体缓存
# 从 DS 容器内部验证文件 URL 是否可达——这一步能排掉大半问题
docker exec onlyoffice-ds curl -sS -o /dev/null -w '%{http_code}\n' "https://files.example.com/api/docs/123/download?token=XXX"

10. 边界:什么时候别上 OnlyOffice

场景 更合适 理由
只需预览,不需要编辑 转 PDF + 内置阅读器 省掉一整套服务与回调

一句话:要编辑 + 要内网 + 愿意养一个常驻服务才上 OnlyOffice;只要看,转 PDF 更省事。


11. 自测

  1. 浏览器与 Document Server 之间、DS 与文件 URL 之间,哪两个方向的网络都必须通?
  2. document.key 用什么值生成,为什么不能用时间戳?
  3. 回调里 status=2 与 status=4 的处理差别是什么?
  4. 回调接口必须返回什么响应体,返回别的会怎样?
  5. 为什么给 DS 用的文件 URL 不能写 localhost?
  6. 反代少配哪个头会导致协同编辑失效?
S27-X13 · OnlyOffice 与在线文档预览的接入方式
https://www.clxhxhhr.top/posts/5791/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。