Collabora实现在线文档编辑及协作

1、运行collabora

version: '3'
services:
  collabora:
    image: collabora/code:25.04.3.1.1
    cap_add:
      - MKNOD
    ports:
      - "9980:9980"
    environment:
      - extra_params= >
          --o:ssl.enable=false
          --o:server_name=192.168.88.18:9000
          --o:storage.wopi.host[0]=192.168.88.18:8001
          --o:storage.wopi.host[1]=192.168.88.18:9000
          --o:user_interface.use_integration_theme=false
      - domain=192\\.168\\.88\\.18\\:9000
    extra_hosts:
      - "host.docker.internal:host-gateway"

注意,这个domain是允许wopi服务访问collobora的相关api。这里的192.168.88.18是部署collabora的docker服务器地址。

验证collaboca部署是否成功,通过(http://192.168.88.18/hosting/discovery)并且获取collabora的urlsrc地址,这个地址(http://192.168.88.18/browser/e724e42045/cool.html?)我们需要在后续生成iframe的地址时使用。

2、编写wopi服务

import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Map;

@RestController
@RequestMapping("/wopi/files")
public class WopiController {

    @GetMapping("/{fileId}")
    public ResponseEntity<?> checkFileInfo(@PathVariable String fileId) {
        // 校验 token 是否有效
        // 返回 JSON 包含文件信息
        return ResponseEntity.ok(Map.of(
                "BaseFileName", "tcn100安全.docx",
                "Size", 12345,
                "OwnerId", "user1",
                "UserId", "user1",
                "UserFriendlyName", "User One",
                "UserCanWrite", true,
                "SupportsLocks", true,
                "SupportsUpdate", true
        ));
    }

    @GetMapping("/{fileId}/contents")
    public ResponseEntity<Resource> getFile(@PathVariable String fileId, @RequestParam("access_token") String token) throws IOException {
        // 从存储中获取文件
        File file = new File("E:\\temp2\\tcn100安全.docx");
        return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(new FileSystemResource(file));
    }

    @PostMapping("/{fileId}/contents")
    public ResponseEntity<?> putFile(@PathVariable String fileId, @RequestParam("access_token") String token, InputStream fileStream) throws IOException {
        // 将新内容保存到文件中
        Files.copy(fileStream, Paths.get("/data/" + fileId), StandardCopyOption.REPLACE_EXISTING);
        return ResponseEntity.ok().build();
    }

    @PostMapping("/{fileId}")
    public ResponseEntity<?> handlePostOps(@PathVariable String fileId, @RequestHeader("X-WOPI-Override") String operation) {
        // 处理 Lock/Unlock/RefreshLock 等
        return ResponseEntity.ok().build();
    }

3、配置nginx

注意:collabora与wopi服务必须使用nginx代理到同一个ip或域名下,否则会出现跨域错误。

    server {
        listen 9000;
        server_name 192.168.88.18;  # 或使用 IP,如 192.168.88.100

        location / {
            root   /opt/www;
            index  index.html index.htm;
        }

        # Collabora 其它 API 转发  /hosting/discovery
        location ^~ /hosting/ {
            proxy_pass http://192.168.88.18:9980/hosting/;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        location ^~ /browser/ {
            proxy_pass http://192.168.88.18:9980/browser/;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host;
        }

        # WOPI 服务(Spring Boot)
        location /wopi/ {
            proxy_pass http://192.168.88.60:8001/wopi/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        # main websocket path
        location /cool/ {
            proxy_pass http://192.168.88.18:9980;
            proxy_set_header Host $host;

            # WebSocket 关键头部
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";

            # 其他常规头部
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_buffering off;
            proxy_request_buffering off;
            proxy_read_timeout 36000s;
        }

4、编写一个前端页面,用于测试

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
</head>
<body>
    <form action="http://192.168.88.18:9000/browser/e724e42045/cool.html?WOPISrc=http%3A%2F%2F192.168.88.18:9000%2Fwopi%2Ffiles%2F184c286689ad71c17864480299806084" 
            target="my-iframe" enctype="multipart/form-data" method="post">
        <input name="access_token" value="test" type="hidden" />
        <input type="submit" value="Load Collabora Online" />
    </form>
</body>
</html>

注:http%3A%2F%2F192.168.88.18%2Fwopi%2Ffiles%2Ftest是【http://192.168.88.18/wopi/files/test】地址进行URL编码后的字符串,collabora的api要求是必须使用URL编译wopi服务路径。

使用nginx代理(非80端口的配置)

    server {
        listen 9000;
        server_name 192.168.88.18;  # 或使用 IP,如 192.168.88.100

        location / {
            root   /opt/www;
            index  index.html index.htm;
        }

        # Collabora 其它 API 转发  /hosting/discovery
        location ^~ /hosting/ {
            proxy_pass http://192.168.88.18:9980/hosting/;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        location ^~ /browser/ {
            proxy_pass http://192.168.88.18:9980/browser/;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host;
        }

        # WOPI 服务(Spring Boot)
        location /wopi/ {
            proxy_pass http://192.168.88.60:8001/wopi/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        # main websocket path
        location /cool/ {
            proxy_pass http://192.168.88.18:9980;
            proxy_set_header Host $host;

            # WebSocket 关键头部
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";

            # 其他常规头部
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_buffering off;
            proxy_request_buffering off;
            proxy_read_timeout 36000s;
        }

    }

文中配置的一些内容

如果你使用frp网络穿透,则需要将docker运行脚本作如下变更即可

version: '3'
services:
  collabora:
    image: collabora/code:25.04.3.1.1
    cap_add:
      - MKNOD
    ports:
      - "9980:9980"
    volumes:
      - /dev/null:/usr/share/loolwsd/loleaflet/dist/admin/admin.html
    environment:
      - extra_params= >
          --o:ssl.enable=false
          --o:server_name=dybai.net:6001
          --o:storage.wopi.host[0]=172.18.17.16:8001
          --o:storage.wopi.host[1]=dybai.net:6001
          --o:user_interface.use_integration_theme=false
      - domain=172\\.18\\.17\\.16\\:18080
      - aliasgroup1=http:\\//dybai\\.net
    extra_hosts:
      - "host.docker.internal:host-gateway"