docker 部署OnlyOffice实现在线编辑Word文档

有需求是实现前端页面可以对word文档进行编辑,并且可以进行保存,于是一顿搜索,找到开源第三方onlyoffice,实际上onlyOffice有很多功能,例如文档转化、多人协同编辑文档、文档打印等,我们只用到了文档编辑功能。

OnlyOffice的部署

部署分为docker部署方式和本地直接安装的方式,比较两种部署方式,docker是比较简单的一种,因为只要拉取相关镜像,然后启动时配置好对应的配置文件即可。

1、下载镜像

bash 复制代码
docker pull onlyoffice/documentserver
# x86架构
docker pull registry.cn-hangzhou.aliyuncs.com/qiluo-images/documentserver
# arm架构
docker pull registry.cn-hangzhou.aliyuncs.com/qiluo-images/linux_arm64_documentserver

2、#创建挂载目录

bash 复制代码
cd /data
mkdir onlyoffice
cd onlyoffice
mkdir logs data lib db

3、启动容器

bash 复制代码
docker run -i -t -d -p 9898:80 --name onlyoffice --restart=always -e TZ="Asia/Shanghai" \
-v /data/onlyoffice/logs:/var/log/onlyoffice \
-v /data/onlyoffice/data:/var/www/onlyoffice/Data \
-v /data/onlyoffice/lib:/var/lib/onlyoffice \
-v /data/onlyoffice/db:/var/lib/postgresql \
-e JWT_ENABLED=false  \
registry.cn-hangzhou.aliyuncs.com/qiluo-images/documentserver:latest

可选)如果需要开启jwt验证用户,则使用下面的命令

bash 复制代码
docker run -i -t -d -p 9898:80 --name onlyoffice --restart=always -e TZ="Asia/Shanghai" \
-v /data/onlyoffice/logs:/var/log/onlyoffice \
-v /data/onlyoffice/data:/var/www/onlyoffice/Data \
-v /data/onlyoffice/lib:/var/lib/onlyoffice \
-v /data/onlyoffice/db:/var/lib/postgresql \
-e JWT_ENABLED=true \
-e JWT_SECRET=wclflow \
-e JWT_HEADER=token \
registry.cn-hangzhou.aliyuncs.com/qiluo-images/documentserver:latest

如果开启jwtsecret,则通过以下命令,查看jwtsecret是否生效

bash 复制代码
docker exec onlyoffice /var/www/onlyoffice/documentserver/npm/json -f /etc/onlyoffice/documentserver/local.json 'services.CoAuthoring.secret.session.string'

4、检查是否安装成功,访问web页面

bash 复制代码
http://ip:9898/welcome/
bash 复制代码
问题定位:使用内部ip地址访问后端服务
解决方式:需要编辑配置文件允许私有ip通过
进入onlyoffice容器,然后编辑/etc/onlyoffice/documentserver/default.json​,
搜索并修改以下字段为true:
"request-filtering-agent" : {
	"allowPrivateIPAddress": true,
	"allowMetaIPAddress": true
},
最后退出容器,然后重启容器后就好了

OnlyOfficeController

bash 复制代码
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.Collections;

@Api(value = "OnlyOffice")
@RestController
publicclass OnlyOfficeController {
    
    @Autowired
    private IMeetingTableService meetingTableService;

    private String meetingMinutesFilePath;

    /**
     * 传入参数 会议id,得到会议纪要文件流,并进行打开
     */
    @ApiOperation(value = "OnlyOffice")
    @GetMapping("/getFile/{meeting_id}")
    public ResponseEntity<byte[]> getFile(HttpServletResponse response, @PathVariable Long meeting_id) 
            throws IOException {
        MeetingTable meetingTable = meetingTableService.selectMeetingTableById(meeting_id);
        meetingMinutesFilePath = meetingTable.getMeetingMinutesFilePath();
        
        if (meetingMinutesFilePath == null || "".equals(meetingMinutesFilePath)) {
            returnnull;   // 当会议纪要文件为空的时候,就返回null
        }
        
        File file = new File(meetingMinutesFilePath);
        FileInputStream fileInputStream = null;
        InputStream fis = null;
        
        try {
            fileInputStream = new FileInputStream(file);
            fis = new BufferedInputStream(fileInputStream);
            byte[] buffer = newbyte[fis.available()];
            fis.read(buffer);
            fis.close();
            
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", 
                URLEncoder.encode(file.getName(), "UTF-8"));
            
            returnnew ResponseEntity<>(buffer, headers, HttpStatus.OK);
        } catch (Exception e) {
            thrownew RuntimeException("e -> ", e);
        } finally {
            try {
                if (fis != null) fis.close();
            } catch (Exception e) {
                // ignore
            }
            try {
                if (fileInputStream != null) fileInputStream.close();
            } catch (Exception e) {
                // ignore
            }
        }
    }

    @CrossOrigin(origins = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS})
    @PostMapping("/callback")
    public ResponseEntity<Object> handleCallback(@RequestBody CallbackData callbackData) {
        
        // 状态监听
        // 参见 https://api.onlyoffice.com/editors/callback
        Integer status = callbackData.getStatus();
        
        switch (status) {
            case1: {
                // document is being edited  文档已经被编辑
                break;
            }
            case2: {
                // document is ready for saving, 文档已准备好保存
                System.out.println("document is ready for saving");
                String url = callbackData.getUrl();
                try {
                    saveFile(url); // 保存文件
                } catch (Exception e) {
                    System.out.println("保存文件异常");
                }
                System.out.println("save success.");
                break;
            }
            case3: {
                // document saving error has occurred, 保存出错
                System.out.println("document saving error has occurred, 保存出错");
                break;
            }
            case4: {
                // document is closed with no changes, 未保存退出
                System.out.println("document is closed with no changes, 未保存退出");
                break;
            }
            case6: {
                // document is being edited, but the current document state is saved, 编辑保存
                String url = callbackData.getUrl();
                try {
                    saveFile(url); // 保存文件
                } catch (Exception e) {
                    System.out.println("保存文件异常");
                }
                System.out.println("save success.");
            }
            case7: {
                // error has occurred while force saving the document. 强制保存文档出错
                System.out.println("error has occurred while force saving the document. 强制保存文档出错");
            }
            default: {
                // ignore
            }
        }
        
        // 返回响应
        return ResponseEntity.ok(Collections.singletonMap("error", 0));
    }

    public void saveFile(String downloadUrl) throws URISyntaxException, IOException {
        HttpsKitWithProxyAuth.downloadFile(downloadUrl, meetingMinutesFilePath);
    }

    @Setter
    @Getter
    publicstaticclass CallbackData {
        /** 用户与文档的交互状态 */
        Object changeshistory;
        Object history;
        String changesurl;
        String filetype;
        Integer forcesavetype;
        String key;
        /** 文档状态。1:编辑中;2:准备保存;3:保存出错;4:无变化;6:编辑保存;7:强制保存出错 */
        Integer status;
        String url;
        Object userdata;
        String[] users;
        String lastsave;
        String token;
    }
}

HttpsKitWithProxyAuth工具类

这是一个HTTP请求工具类,主要用于文件下载等操作(部分代码)

bash 复制代码
/**
 * 下载文件到本地
 * @param downloadUrl 下载地址
 * @param savePathAndName 保存路径和文件名
 */
public static void downloadFile(String downloadUrl, String savePathAndName) {
    HttpGet httpGet = new HttpGet(downloadUrl);
    httpGet.setHeader("User-Agent", USER_AGENT);
    httpGet.setConfig(requestConfig);

    CloseableHttpResponse response = null;
    InputStream in = null;

    try {
        response = getHttpClient().execute(httpGet, HttpClientContext.create());
        HttpEntity entity = response.getEntity();
        
        if (entity != null) {
            in = entity.getContent();
            FileOutputStream out = new FileOutputStream(new File(savePathAndName));
            IOUtils.copy(in, out);
            out.close();
        }
    } catch (IOException e) {
        logger.error("error", e);
    } finally {
        try {
            if (in != null) in.close();
        } catch (IOException e) {
            logger.error("error", e);
        }
        try {
            if (response != null) response.close();
        } catch (IOException e) {
            logger.error("error", e);
        }
    }
}

加载word文档失败

修改启动的配置文件,将token去除,配置文件位置:

bash 复制代码
/etc/onlyoffice/documentserver
修改 local.json
将参数token都改为false,去除token:
"token": {
   "enable": {
     "request": {
       "inbox": false,
       "outbox": false
     },
     "browser": false
   }
}

修改 default.json

将参数request-filtering-agent改为true,token也改为false,rejectUnauthorized改为false:

bash 复制代码
"request-filtering-agent": {
    "allowPrivateIPAddress": true,
    "allowMetaIPAddress": true
},
"token": {
    "enable": {
        "browser": false,
        "request": {
            "inbox": false,
            "outbox": false
        }
    }
},
"rejectUnauthorized": false

修改了以上配置参数后,重启服务,再次测试。

相关推荐
金刚猿11 小时前
01_虚拟机中间件部署_root 用户安装 docker 容器,配置非root用户权限
docker·中间件·容器
JH_Kong12 小时前
解决 WSL 中 Docker 权限问题:从踩坑到完整修复
docker·容器
忆~遂愿12 小时前
GE 引擎与算子版本控制:确保前向兼容性与图重写策略的稳定性
大数据·开发语言·docker
陈桴浮海14 小时前
Kustomize实战:从0到1实现K8s多环境配置管理与资源部署
云原生·容器·kubernetes
70asunflower16 小时前
Emulation,Simulation,Virtualization,Imitation 的区别?
linux·docker
ShiLiu_mtx16 小时前
k8s - 7
云原生·容器·kubernetes
春日见17 小时前
车辆动力学:前后轮车轴
java·开发语言·驱动开发·docker·计算机外设
xuhe217 小时前
[全流程详细教程]Docker部署ClawBot, 使用GLM4.7, 接入TG Bot实现私人助理. 解决Docker Openclaw Permission Denied问题
linux·docker·ai·github·tldr
星火s漫天18 小时前
第一篇: 使用Docker部署flask项目(Flask + DB 容器化)
数据库·docker·flask
MonkeyKing_sunyuhua19 小时前
docker compose up -d --build 完全使用新代码打包的方法
docker·容器·eureka