springboot集成onlyoffice(部署+开发)

前言

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

目 录

前言

1、onlyoffice的部署

2、代码逻辑开发

2.1、前端代码

2.2、后端代码

3、问题总结

3.1、访问案例失败

3.2、加载word文档失败

3.3、系统后端有token验证问题

3.4、使用文档地址访问问题

4、后记


1、onlyoffice的部署

部署分为docker部署方式和本地直接安装的方式,比较两种部署方式,docker是比较简单的一种,因为只要拉取相关镜像,然后启动时配置好对应的配置文件即可。由于搜索的时候先看到的是linux本地部署,所以采用了第二种方式,下面我将给出两个参考博客:

docker的方式:博客(我未进行尝试,对于是否能成功是不知的)

ubuntu部署方式:博客(我按照这个方式走下来是可以走通的)

2、代码逻辑开发

前端使用的element框架vue版本,后端采用springboot

2.1、前端代码

参考官方文档API

参考文档

记得添加下面的js文件

<div id="placeholder"></div>
<script type="text/javascript" src="https://documentserver/web-apps/apps/api/documents/api.js"></script>

const config = {
    
    document: {
      mode: 'edit',
      fileType: 'docx',
      key: String( Math.floor(Math.random() * 10000)),
      title: route.query.name + '.docx',
      url: import.meta.env.VITE_APP_API_URL+`/getFile/${route.query.id}`,
      permissions: {
          comment: true,
          download: true,
          modifyContentControl: true,
          modifyFilter: true,
          edit: true,
          fillForms: true,
          review: true,
      },
  },
    documentType: 'word',
    editorConfig: {
      user: {
        id: 'liu',
        name: 'liu',
      },
      // 隐藏插件菜单
      customization: {
        plugins: false,
        forcesave: true,
      },
      lang: 'zh',
      // callbackUrl: `${import.meta.env.VITE_APP_API_URL} +'/callback' `,
      callbackUrl: import.meta.env.VITE_APP_API_URL+`/callback`,
    },
    height: '100%',
    width: '100%',
  }

  new window.DocsAPI.DocEditor('onlyoffice', config)

其中import.meta.env.VITE_APP_API_URL为你实际的onlyoffice地址,http:ip:端口号/访问路径,例如我们就是:http:192.168.123.123:8089/getFile/12,其中12为会议号,用于得到文件地址。

其中import.meta.env.VITE_APP_API_URL+/callback为回调函数,即文档有什么操作后,都会通过这个函数进行回调,例如:编辑保存操作。

2.2、后端代码

pom依赖

<!-- httpclient start -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
        </dependency>

package com.ruoyi.web.controller.meetingminutes.onlyoffice;

/**
 * @Author 不要有情绪的  ljy
 * @Date 2024/10/31 20:26
 * @Description:
 */

import com.ruoyi.system.domain.MeetingTable;
import com.ruoyi.system.service.IMeetingTableService;
import com.ruoyi.web.controller.meetingminutes.utils.HttpsKitWithProxyAuth;
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 = "OnlyOfficeController")
@RestController
public class OnlyOfficeController {
    @Autowired
    private IMeetingTableService meetingTableService;

    //这里仅写死路径测试
    //    private String meetingMinutesFilePath = "C:\Users\qrs-ljy\Desktop\王勋\c1f15837-d8b4-4380-8161-b85e970ad174\123435_会议纪要(公开).docx"; //这里仅写死路径测试
    private String meetingMinutesFilePath;


    /**
     * 传入参数 会议id,得到会议纪要文件流,并进行打开
     *
     * @param response
     * @param meeting_id
     * @return
     * @throws IOException
     */
    @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)) {
            return null;   //当会议纪要文件为空的时候,就返回null
        }
        File file = new File(meetingMinutesFilePath);
        FileInputStream fileInputStream = null;
        InputStream fis = null;
        try {
            fileInputStream = new FileInputStream(file);
            fis = new BufferedInputStream(fileInputStream);
            byte[] buffer = new byte[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"));
            return new ResponseEntity<>(buffer, headers, HttpStatus.OK);
        } catch (Exception e) {
            throw new RuntimeException("e -> ", e);
        } finally {
            try {
                if (fis != null) fis.close();
            } catch (Exception e) {

            }
            try {
                if (fileInputStream != null) fileInputStream.close();
            } catch (Exception e) {

            }
        }

    }

    @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) {
            case 1: {
                //document is being edited  文档已经被编辑
                break;
            }
            case 2: {
                //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;
            }
            case 3: {
                //document saving error has occurred,保存出错
                System.out.println("document saving error has occurred,保存出错");
                break;
            }
            case 4: {
                //document is closed with no changes,未保存退出
                System.out.println("document is closed with no changes,未保存退出");
                break;
            }
            case 6: {
                //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.");
            }
            case 7: {
                //error has occurred while force saving the document. 强制保存文档出错
                System.out.println("error has occurred while force saving the document. 强制保存文档出错");
            }
            default: {

            }
        }
        // 返回响应
        return ResponseEntity.<Object>ok(Collections.singletonMap("error", 0));

    }

    public void saveFile(String downloadUrl) throws URISyntaxException, IOException {

        HttpsKitWithProxyAuth.downloadFile(downloadUrl, meetingMinutesFilePath);

    }

    @Setter
    @Getter
    public static class CallbackData {
        /**
         * 用户与文档的交互状态。0:用户断开与文档共同编辑的连接;1:新用户连接到文档共同编辑;2:用户单击强制保存按钮
         */
//        @IsArray()
//        actions?:IActions[] =null;

        /**
         * 字段已在 4.2 后版本废弃,请使用 history 代替
         */
        Object changeshistory;

        /**
         * 文档变更的历史记录,仅当 status 等于 2 或者 3 时该字段才有值。其中的 serverVersion 字段也是 refreshHistory 方法的入参
         */
        Object history;

        /**
         * 文档编辑的元数据信息,用来跟踪显示文档更改记录,仅当 status 等于 2 或者 2 时该字段才有值。该字段也是 setHistoryData(显示与特定文档版本对应的更改,类似 Git 历史记录)方法的入参
         */
        String changesurl;

        /**
         * url 字段下载的文档扩展名,文件类型默认为 OOXML 格式,如果启用了 assemblyFormatAsOrigin(https://api.onlyoffice.com/editors/save#assemblyFormatAsOrigin) 服务器设置则文件以原始格式保存
         */
        String filetype;

        /**
         * 文档强制保存类型。0:对命令服务(https://api.onlyoffice.com/editors/command/forcesave)执行强制保存;1:每次保存完成时都会执行强制保存请求,仅设置 forcesave 等于 true 时生效;2:强制保存请求由计时器使用服务器中的设置执行。该字段仅 status 等于 7 或者 7 时才有值
         */
        Integer forcesavetype;

        /**
         * 文档标识符,类似 id,在 Onlyoffice 服务内部唯一
         */
        String key;

        /**
         * 文档状态。1:文档编辑中;2:文档已准备好保存;3:文档保存出错;4:文档没有变化无需保存;6:正在编辑文档,但保存了当前文档状态;7:强制保存文档出错
         */
        Integer status;

        /**
         * 已编辑文档的链接,可以通过它下载到最新的文档,仅当 status 等于 2、3、6 或 7 时该字段才有值
         */
        String url;

        /**
         * 自定义参数,对应指令服务的 userdata 字段
         */
        Object userdata;

        /**
         * 打开文档进行编辑的用户标识列表,当文档被修改时,该字段将返回最后编辑文档的用户标识符,当 status 字段等于 2 或者 6 时有值
         */
        String[] users;

        /**
         * 最近保存时间
         */
        String lastsave;

        /**
         * 加密令牌
         */
        String token;
    }
}

代码中使用了其他类,这儿贴出(我也是参考的别人的博客,后面会给出参考链接)

package com.ruoyi.web.controller.meetingminutes.utils;

/**
 * @Author 不要有情绪的  ljy
 * @Date 2024/10/31 20:34
 * @Description:
 */

import java.io.File;
import java.io.Fi
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值