SpringBoot中整合ONLYOFFICE在线编辑

news2024/11/17 3:39:43

SpringBoot整合OnlyOffice

  • SpringBoot整合OnlyOffice实现在线编辑
    • 1. 搭建私有的OnlyOffice的服务
    • 2. SpringBoot进行交互
      • 2.1 环境
      • 2.2 我们的流程
      • 2.3 接口规划
        • 2.3.1 获取编辑器配置的接口
        • 2.3.2 文件下载地址
        • 2.3.3 文件下载地址
    • 3. 总结
      • 4. 注意
        • 4.1 你的项目的地址一定一定要和onlyoffice可以正常通讯,如果不行则一直不可能成功。
        • 4.2 TOKEN是可以可选项,建议一开始不要使用,后面有需要的时候再去添加。
        • 4.3 一定要看一下官网文档,文档真的很全很重要
        • 4.4 协同的话只要参数就是一个KEY,如果需要超过20个的限制直接重新编译即可,大神一大堆,很容易就可以找到。

SpringBoot整合OnlyOffice实现在线编辑

公司有一个需求,就是实现 *Word* , *Excel* ,等文件的在线编辑,市场上面进行了多方面的选型,考虑了 *[OpenOffice](https://openoffice.apache.org/)* , *[Office Online](https://www.microsoft.com/zh-cn/microsoft-365/free-office-online-for-the-web?legRedir=true&CorrelationId=13c8a865-b9b0-48ff-b3ed-3ea9ec31cd55)*, 但是最终还是选择了 *[OnlyOffice](https://www.onlyoffice.com/zh/)* 这个产品。
他的一个很大的优势在于开源,支持协同,社区比较活跃。api比较全面,还有中文的文档。还有一点比较好的就是支持协同,并且支持协同,虽然协同在社区版中存在限制,但是支持代码修改,可以重新编译。社区的大佬很多,很赞。唯一遗憾的就是效率比较低,在使用私有对象存储的时候存在延迟。其他的没有使用到,所以不进行评论。中文文档:[https://api.onlyoffice.com/zh/editors/basic](https://api.onlyoffice.com/zh/editors/basic)

1. 搭建私有的OnlyOffice的服务

搭建过程这里就不进行涉猎了,建议使用docker进行搭建,下载官方镜像包即可,(现在dockerhub被墙,自行解决,不建议自己再次打包,因为我在尝试的时候总是出现莫名奇妙的问题可能是我的问题。推荐使用官网原版镜像)。根据官方文档一步步操作即可。搭建过程中,如果是自己玩建议不要开启 **JWT** ,生产环境建议开一下。但是开的成本就是你对接的时候需要获取token然后在进行交互。

2. SpringBoot进行交互

2.1 环境

java: 17
boot: 3.0.5
页面:一个h5页面即可
需要的其他依赖

<!-- ... 其他的依赖自行添加即可,不重要,比如 fastjson2,jackson 等 -->

<!-- 这个JAR 主要的作用是与OnlyOffice交互的时候生成token使用的 -->
<dependency>
     <groupId>com.inversoft</groupId>
     <artifactId>prime-jwt</artifactId>
     <version>1.3.1</version>
 </dependency>
        

2.2 我们的流程

我们使用一个 H5 页面即可,页面通过加载一个 app.js 。然后通过一个 config 进行渲染,就可以实现一个编辑。app.js 是核心js文件

  1. only office 我只使用他的一个编辑的功能(这是一个核心,就是编辑文件,文件的来源和存储与它无关)
  2. 被编辑的文件从哪里获取?从 config 对象中的配置获取,这里就需要自行实现。
  3. 编辑后的文件如何获取?config对象中有一个回调地址,这个地址会给到服务器一个编辑的状态,并且携带一个获取编辑后文件的url(这个url就是only office 服务中的一个文件下载地址),根据这个url来获取编辑后的文件。然后在对这个文件进行存储。
    回调的实现参考:https://api.onlyoffice.com/zh/editors/callback#status

在这里插入图片描述

2.3 接口规划

一共设计三个接口,

  1. 获取编辑器的配置
  2. 获取需要编辑的文件流
  3. 编辑后保存文件的回调
    保存后的文件:注意,这里编辑后的文件并不是在回调里面以流的形式给,而是在回调接口里面给服务器一个状态,根据状态去获取一个下载编辑后文件的一个地址,然后根据地址去主动的获取文件。
2.3.1 获取编辑器配置的接口

/**
 * 被编辑文件的下载连接
 * 这里就是自己服务的配置地址
 * only office 调用你的服务的地址,一定是 onlyoffice服务可以ping通的你的项目地址。ping不通=白搭
 */
@Value("${only.office.downUrl}")
private String downFileUrl = "";
/**
 * 这里是回调地址:例如 http://192.168.0.10:8080/office/edit/callback/{fileId}
 * 自行定义即可(就是后面自己编写的接口,但是一定要通可onlyoffice服务互通)
 * only office 调用你的服务的地址,一定是 onlyoffice服务可以ping通的你的项目地址。ping不通=白搭
 */
@Value("${only.office.callBackUrl}")
private String editCallBackUrl = "";

@Operation(summary = "根据文件的ID来获取在线编辑的配置和token")
@PostMapping("/token/{fileId}")
@Parameters({
        @Parameter(name = "fileId", description = "不是对象ID是文件的ID", in = ParameterIn.PATH)
})
public ResultVo<?> getToken(@PathVariable String fileId) {
    String fileKey ;
    if (redisUtil.hHasKey(RedisName.ONLY_OFFICE_FILE_KYE,fileId)) {
        fileKey = redisUtil.hget(RedisName.ONLY_OFFICE_FILE_KYE,fileId).toString();
        //return ResultVo.error(CustomExceptionType.ONLY_OFFICE_COORDINATION_ERROR);
    }else{
        fileKey = fileId + RandomUtil.randomNumbers(10);
    }
    String json = """
            {
                "document": {
                        "title": "%s",
                        "key": "%s",
                        "fileType":"%s",
                        "lang": "zh-CN",
                        "permissions": {
                            "comment": true,
                            "commentGroups": {
                                "edit": ["Group2", "Group1"],
                                "remove": [""],
                                "view": ""
                            },
                            "copy": true,
                            "deleteCommentAuthorOnly": false,
                            "download": true,
                            "edit": true,
                            "editCommentAuthorOnly": false,
                            "fillForms": true,
                            "modifyContentControl": true,
                            "modifyFilter": true,
                            "print": true,
                            "review": true,
                            "reviewGroups": ["Group1", "Group2", ""]
                        },
                        "url": "%s"
                    },
                "editorConfig": {
                    "customization":{
                        "autosave": true,
                        "forcesave": true
                    }
                    "lang": "zh-CN",
                    "callbackUrl": "%s",
                    "onEditing": {
                         "mode": "fast",
                         "change": true
                    },
                    "mode": "edit",
                    "user": {
                        "group": "Group1",
                        "id": "%s",
                        "name": "%s"
                    }
                }
            }
            """;
    // TODO 这里文件的key可以通过redis进行保存,这样可以支持多人在线协同,现在不做处理

    json = String.format(json, fileInfo.getFileName(),
            fileKey,
            // TODO 这里是文件类型,自行定义
            'xlsx',
            // TODO 这里是文件下载地址,fileId 为文件的唯一标识,自行定义
            downFileUrl + fileId, 
            // TODO 这里是定义回调地址,fileId 为文件的唯一标识用来区分是那个文件编辑的回调。
            editCallBackUrl + fileId,
            "userid","username");
    Map<String, Object> map = JSONObject.parseObject(json, new TypeToken<Map<String, Object>>() {
    }.getType());
    // TODO 这里是获取onlyoffice 交互的token,自己写的建议直接注释
    // String token = jwtManager.createToken(map);
    // map.put("token", token);
    // TODO 这个key可以直接注释,这里主要作用是协同
    redisUtil.hset(RedisName.ONLY_OFFICE_FILE_KYE,fileId,fileKey,60*60*24);
    

    return ResultVo.success(map);
}

2.3.2 文件下载地址

这个接口的作用就是获取一个文件流,根据ID来获取一个文件流

这里的地址就是上一个接口中下载文件的地址。

@GetMapping("down/file/{fileId}")
@Operation(summary = "根据参数下载一个文件")
public void downFolderById(@PathVariable String fileId, HttpServletResponse response){
	// TODO 1. 根据文件的唯一ID来获取数据库中的记录
    EtmfFileInfo fileInfo = fileInfoOpt.getById(fileId);
    // TODO 2. 根据下载路径从 minio 中获取文件流 (因为我们使用的是minio,其他的自行切换即可)
    try (InputStream inputStream = smoMinIoUtils.downloadFile(fileInfo.getFileUrl())) {
        downFileInfo(response, fileInfo, inputStream);
    } catch (ServerException | ErrorResponseException | InsufficientDataException | IOException |
             NoSuchAlgorithmException | InvalidKeyException | InvalidResponseException | XmlParserException |
             InternalException e) {
        JwtUtil.responseError(response, 500L, "文件下载失败:" + e.getMessage());
    }
}

public static void downFileInfo(HttpServletResponse response, EtmfFileInfo fileInfo, InputStream inputStream) throws IOException {
    response.setCharacterEncoding("UTF-8");
    response.setContentType("application/octet-stream; charset=UTF-8");
    response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileInfo.getFileName(), StandardCharsets.UTF_8));
    ServletOutputStream stream = response.getOutputStream();
    IOUtils.copy(inputStream,stream);
    stream.flush();
    stream.close();
}


2.3.3 文件下载地址

这里是文件的回调地址,主要就是获取一个状态码,然后根据状态码判定是否保存文件。


@Operation(summary = "文件编辑之后的回调")
@Parameters({
        @Parameter(name = "fileId", description = "文件的ID", in = ParameterIn.PATH)
})
@PostMapping("/edit/callback/{fileId}")
public void editCallBack(@PathVariable String fileId, HttpServletRequest request, HttpServletResponse response) {
    try {
        PrintWriter writer = response.getWriter();
        String body;
        try {
            Scanner scanner = new Scanner(request.getInputStream());
            scanner.useDelimiter("\\A");
            body = scanner.hasNext() ? scanner.next() : "";
            scanner.close();
        } catch (Exception ex) {
            writer.write("get request.getInputStream error:" + ex.getMessage());
            return;
        }
        if (body.isEmpty()) {
            writer.write("empty request.getInputStream");
            return;
        }
        JSONObject jsonObj = JSON.parseObject(body);
        int status = (Integer) jsonObj.get("status");
        log.debug("================文件编辑获取到的参数是:{}", JSON.toJSONString(jsonObj));
        int saved = 0;
        if (List.of(2,3,6).contains(status)) {
            String downloadUri = (String) jsonObj.get("url");
            log.debug("================文件进行保存处理,需要保存的状态值是:{},可以获取到文件的路径是:{}", status,downloadUri);
            try {
                URL url = new URL(downloadUri);
                // 根据文件下载地址来获取编辑后的文件流
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                InputStream stream = connection.getInputStream();
                if (stream == null) {
                    throw new Exception("Stream is null");
                }
                // TODO 根据文件的唯一标识获取数据库中文件记录
                EtmfFileInfo fileInfo = fileInfoOpt.getById(fileId);
                // TODO 根据文件流创建一个文件
                File savedFile = new File(fileInfo.getFileName());
                try (FileOutputStream out = new FileOutputStream(savedFile)) {
                    int read;
                    final byte[] bytes = new byte[1024];
                    while ((read = stream.read(bytes)) != -1) {
                        out.write(bytes, 0, read);
                    }
                    out.flush();
                }
                // TODO 根据文件上传到 MINIO中
                boolean b = smoMinIoUtils.uploadFile(fileInfo.getFileUrl(), savedFile);
                log.info("编辑文件后,文件上传状态:{},上传的文件是:{},Id是:{}",b,fileInfo.getFileName(),fileId);
                savedFile.delete();
                connection.disconnect();
            } catch (Exception ex) {
                saved = 1;
                ex.printStackTrace();
            }finally {
                // 正常保存的时候剔除掉redis缓存
                if (status == TWO) {
                    redisUtil.hdel(RedisName.ONLY_OFFICE_FILE_KYE,fileId);
                }
            }
        }

        writer.write("{\"error\":" + saved + "}");
        writer.flush();
        writer.close();
        log.debug("======================编辑完成--------------返回值是:{}","{\"error\":" + saved + "}");
    } catch (IOException e) {
        e.printStackTrace();
        throw new SmoGlobalException(CustomExceptionType.OTHER_ERROR);
    }
}



3. 总结

文件的在线编辑主要就是依托与onlyoffice实现的,而编辑器的配置是通过我们的接口来定义的,接口中的配置可以自由的定义编辑器的文件类型,窗口大小,文件来源,回调地址,保存类型等等。
你需要编辑的文件可以放在任意的位置,只要你的接口可以通过流的方式给到onlyofiice编辑器即可。
文件编辑后的处理都是在回调中处理的,最好先看一下文档的回调写法。回调的时候记得打印日志,观察一下接口的内容,一定要记得是通过回调中的url参数来获取编辑后的文件流的,并不是通过回调接口直接把文件流给到你。我在这里没有注意看饶了弯路。所以提醒一下。

4. 注意

4.1 你的项目的地址一定一定要和onlyoffice可以正常通讯,如果不行则一直不可能成功。
4.2 TOKEN是可以可选项,建议一开始不要使用,后面有需要的时候再去添加。
4.3 一定要看一下官网文档,文档真的很全很重要
4.4 协同的话只要参数就是一个KEY,如果需要超过20个的限制直接重新编译即可,大神一大堆,很容易就可以找到。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1884692.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详细django框架+SIMPLEUI+import_export设计web管理后台(四)

目录 1.项目简介 2.搭建django框架 3.引入 SIMPLEUI插件 3.1安装simpleui 3.2 修改设置 3.3 克隆静态资源 3.4登陆测试 4.优化页面 4.1 修改后台名称显示 4.2 增加页面LOGO图标 4.3增加网址图标&#xff1a;目前主要的浏览器都支持favicon.ico图标 4.4 修改APP名称显…

用摄像头实现识别道路中的车道线、行人与车辆检测(级联分类器、HOG+SVM、行人检测)

基于树莓派的智能小车&#xff0c;用摄像头实现识别道路中的车道线识别、行人检测与车辆检测。 本项目旨在开发一套基于摄像头的智能道路环境感知系统&#xff0c;该系统能够实时识别道路中的车道线、行人与车辆&#xff0c;为自动驾驶汽车、智能交通管理以及辅助驾驶系统提供关…

Go语言数据类型--常量、iota枚举、数据类型分类

变量&#xff1a;程序运行期间&#xff0c;可以改变的量&#xff0c;变量声明需要var关键字。 常量&#xff1a;程序运行期间&#xff0c;不可以改变的量&#xff0c;变量声明需要const关键字。 自动推导 常量的自动推导不能加:&#xff1b; 不同类型数据的声明 可以使用…

华为OD机试 - 表演赛游戏分组 - 动态规划(Java 2024 D卷 200分)

华为OD机试 2024D卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;D卷C卷A卷B卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测…

目标检测算法讲解:从传统方法到深度学习,全面解析检测技术的演进与应用!

在计算机视觉领域&#xff0c;目标检测是一个基本且关键的任务&#xff0c;它不仅涉及图像中对象的识别&#xff0c;还包括确定这些对象的具体位置。这一任务通常通过算法来实现&#xff0c;这些算法能够识别出图像中的一个或多个目标&#xff0c;并给出每个目标的类别和位置。…

【面试系列】产品经理高频面试题及详细解答

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题. ⭐️ AIGC时代的创新与未来&#xff1a;详细讲解AIGC的概念、核心技术、…

4.BeanFactory

可以看出BeanFactory表面上只有getBean相关的方法。 实际上控制反转、基本的依赖注入、Bean的生命周期的各种功能&#xff0c;都是由BeanFactory的实现类来实现的。&#xff08;DefaultListableBeanFactory&#xff09; DefaultListableBeanFactory管理单例对象DefaultSinglet…

第11章 规划过程组(11.6规划进度管理)

第11章 规划过程组&#xff08;二&#xff09;11.6规划进度管理&#xff0c;在第三版教材第385页&#xff1b;#软考中级##中级系统集成项目管理师# 文字图片音频方式 第一个知识点&#xff1a;主要输出 1、进度管理计划 准确度 定义活动持续时间估算的可接受区间&#xff0…

springboot拦截器,ThreadLocal(每个线程的公共区域)

拦截器 配置信息&#xff08;拦截所有请求&#xff09; 其实这种可以作为springAOP作日志记录

flask数据连接池、定制命令

【 一 】数据库连接池 【 1 】flask操作mysql 基本的使用不使用连接池 from flask import Flask, jsonify import pymysqlapp Flask(__name__) app.debug Trueapp.route(/) def index():conn pymysql.connect(userroot,password"123123",host127.0.0.1,databas…

计算两个经纬度之间的球面距离(基于Mysql和PHP实现)

计算两个经纬度之间的球面距离 1、MySQL实现方式 - 基于空间函数(ST_Distance_Sphere)实现 前置条件&#xff1a;确保您使用的是 MySQL 8.0 或更高版本&#xff0c;因为较早的版本对地理空间的支持有限。 1.1 创建表和索引 说明&#xff1a;设置 location 为 point 类型 #…

Wireshark - tshark支持iptables提供数据包

tshark现在的数据包获取方式有两种&#xff0c;分别是读文件、网口监听&#xff08;af-packet原始套接字&#xff09;。两种方式在包获取上&#xff0c;都是通过读文件的形式&#xff1b;存在文件io操作&#xff0c;在专门处理大流量的情境下&#xff0c; 我们复用wireshark去做…

DNS访问百度

DNS&#xff0c;英文全称是 domain name system&#xff0c;域名解析系统&#xff0c;它的作用也很明确&#xff0c;就是域名和 IP 相互映射。 假设你要查询 baidu.com 的 IP 地址: 首先会查找浏览器的缓存,看看是否能找到 baidu.com 对应的IP地址&#xff0c;找到就直接返回&…

【NOI-题解】1326. 需要安排几位师傅加工零件1228. 排队打水问题1229. 拦截导弹的系统数量求解

文章目录 一、前言二、问题问题&#xff1a;1326. 需要安排几位师傅加工零件问题&#xff1a;1228. 排队打水问题问题&#xff1a;1229. 拦截导弹的系统数量求解 三、感谢 一、前言 本章节主要对贪心问题进行讲解&#xff0c;包括《1326. 需要安排几位师傅加工零件》《1228. 排…

【嵌入式】探索嵌入式世界:在ARM上构建俄罗斯方块游戏的奇妙之旅

文章目录 前言&#xff1a;1. 简介2. 总体设计思路及功能描述2.1 设计思路2.2 功能描述2.3 程序流程图 3. 各部分程序功能及详细说明3.1 游戏界面函数3.1.1 游戏界面中的图片显示3.1.2 游戏开始界面3.1.3 游戏主界面3.1.4 游戏结束广告界面3.1.5 游戏界面中的触摸反馈3.1.6 游戏…

关于 Mybatis 的开启二级缓存返回对象不一致问题

做实验报告的时候&#xff0c;跟着学习&#xff0c;发现我已经将 开启 二级缓存的 配置都配置好了&#xff0c;但是返回值地址不一致&#xff0c;说明对象不一致&#xff0c;二级缓存命中失败。 跟着流程配置&#xff1a; mybatis-config <settings><!-- 启用 myba…

mst[讲课留档]

最小生成树(Minimum Spanning Tree) (1)概念 我们知道&#xff0c;树是有 n n n个结点&#xff0c; n − 1 n-1 n−1条边的无向无环的连通图。 一个连通图的生成树是一个极小的连通子图&#xff0c;它包含图中全部的 n n n个顶点&#xff0c;但只有构成一棵树的 n − 1 n-1 …

实验五 计数器的设计与仿真

仿真 链接&#xff1a;https://pan.baidu.com/s/1N1nR39Gws59laVZY2slzBw 提取码&#xff1a;01ct 一、实验目的 1、通过实验&#xff0c;能熟悉QUARTUS开发环境&#xff0c;能够掌握VHDL设计电路&#xff0c;掌握使用相关仿真工具进行功能和时序仿真的方法&#xff1b; 2、通…

.js.map文件泄露/Springboot信息泄露

目录 框架识别 Webpack 简述 .js.map文件泄露 利用 Spring boot 很多网站都使用的是现有的框架进行开发的&#xff0c;因此相当于很多目录和文件的路径都是开源可知的&#xff0c;因此我们就可以直接访问对应的路径&#xff0c;如果网站没有进行限制就有可能会导致敏感信…

Mac搭建anaconda环境并安装深度学习库

1. 下载anaconda安装包 根据自己的操作系统不同&#xff0c;选择不同的安装包Anaconda3-2024.06-1-MacOSX-x86_64.pkg&#xff0c;我用的还是旧的intel所以下载这个&#xff0c;https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/&#xff0c;如果mac用的是M1&#xff0…