关于springboot:SpringBoot集成onlyoffice实现word文档编辑保存

1次阅读

共计 12571 个字符,预计需要花费 32 分钟才能阅读完成。

阐明

onlyoffice 为一款开源的 office 在线编辑组件,提供 word/excel/ppt 编辑保留操作

  • 以下操作均基于 centos8 零碎,officeonly 镜像版本 7.1.2.23
  • 镜像下载地址:https://yunpan.360.cn/surl_y8…(提取码:1f92)
  • 已破解 20 连贯限度
  • 已导入罕用中文字体,批改了字号
  • 已勾销上传文件大小的限度,批改为 500M
  • 暗藏所有操作按钮,暗藏 onlyoffice 图标和名称,只保根底操作栏目
  • 仅用于 word 文件和 excel 文件编辑 / 保留 / 查看

Linux 装置

yum 设置

  • 进入 yum 的 repos 目录 cd /etc/yum.repos.d/
cd /etc/yum.repos.d/
  • 批改所有的 CentOS 文件内容
sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*

sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
  • 更新 yum 源为阿里镜像
wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo

yum clean all

yum makecache
  • yum 装置测试是否能够 yum 装置
yum install wget –y
  • 装置依赖包
sudo yum install -y yum-utils device-mapper-persistent-data lvm2 

docker 装置

  • 设置镜像源
sudo yum-config-manager --add-repo
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  • 装置 docker-ce 社区版
sudo yum install docker-ce --allowerasing
  • 创立用户组
 建设 Docker 用户组
sudo groupadd docker
增加以后用户到 docker 组
sudo usermod -aG docker $USER
启动 docker
systemctl start docker.service
服务开机启动
systemctl enable docker.service
  • 装置 docker 图形化治理页面
docker volume create portainer_data

docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

拜访 http://localhost:9000/ 进行治理端初始化设置

onlyoffice 部署

  • 上传镜像文件到服务器
  • 载入镜像
docker load < onlyoffice.tar
  • 查看镜像
docker images
  • 启动容器
sudo docker run --name=onlyoffice -i -t -d -p  8088:80 --restart=always 镜像 id

Windows 装置

  • 装置 VMWare 虚拟机,装置 centos8 零碎,参照上述步骤
  • 网络配置成 NAT 模式,虚拟机进入 centos 零碎后配置固定 ip
  • NAT 网卡设置端口转发,转发 9000 和 8088 端口,主机端口和虚构端口统一

增加字体

  • 找到对应字体,如果是 windows 下的字体进入 C:\Windows\Fonts 搜寻
  • 装置 High-Logic FontCreator 运行 Keygen.exe 点击 generate 获取激活码
  • 工具下载:https://yunpan.360.cn/surl_y8…(提取码:7059)
  • 关上字体 font=>properties
  • 批改 font family 为 custom 中对应的中文,导出字体
  • 上传批改后的字体到 liunx 文件下 eg:/home/fonts/
  • 查看 OnlyOffice 容器 id
docker ps
  • 进入 OnlyOffice 容器
docker exec -it 容器 id /bin/bash
  • 删除字体缓存
cd /var/www/onlyoffice/documentserver/fonts

rm -rf *
  • 复制字体到 /var/www/onlyoffice/documentserver/core-fonts 下
docker cp /home/fonts onlyoffice:/var/www/onlyoffice/documentserver/core-fonts
  • 进入 onlyoffice 容器执行刷新
/usr/bin/documentserver-generate-allfonts.sh
  • 浏览器革除缓存从新刷新

对接业务零碎

onlyfiice 部署结束后会有一个服务地址,例如 http://localhost:8088/

引入 api.js

不能下载文件本地援用,肯定要在线援用

<script type="text/javascript" src="http://localhost:8088/web-apps/apps/api/documents/api.js"></script>

申明 dom

页面增加一个 div,用于渲染编辑器

<div id="placeholder" class="nav" style="width: 100%; height: 100%"></div>

页面渲染

参数阐明

key:对应文档的一个标识,倡议前端随机生成,避免反复

url:打开文档的地址,返回流数据

fileType:文档类型,例如:doc/docx

title:文件名称,例如:2022 年工作计划.docx

model:关上模式,例如:edit(编辑模式)/view(浏览模式)

callbackUrl:文件批改后保留回调地址

function initDoc(key, url, fileType, title, model, callbackUrl) {
                let config = {
                    "document": {
                        "documentType": "text",
                        "width": "100%", // 关上窗口宽度
                        "height": "100%", // 关上窗口高度
                        "fileType": fileType, // 文档类型
                        "key": key, // 定义用于服务辨认文档的惟一文档标识符。每次编辑和保存文档时,都必须从新生成密钥。长度限度为 128 个符号。"title": title, // 为查看或编辑的文档定义所需的文件名,该文件名也将在下载文档时用作文件名。长度限度为 128 个符号。"url": url, // 定义存储原始查看或编辑的文档的相对 URL
                        "info": {
                            "owner": "王重阳", // 文件创建者名称
                            "sharingSettings": [ // 文件对应用户的操作权限配置
                                {
                                    "permissions": "Full Access", // 齐全操作权限 -Full Access, 只读权限 -Read Only 回绝拜访 -Deny Access
                                    "user": "林朝英" // 有次权限的用户
                                },
                                {
                                    "permissions": "Read Only",
                                    "user": "周伯通"
                                },
                            ],
                            "uploaded": "2010-07-07 3:46 PM" // 文件创建工夫
                        },
                        // 文档权限参数
                        "permissions": {
                            "edit": true, //(文件是否能够编辑,false 时文件不可编辑)"fillForms": true, // 定义是否能在文档中填充表单
                            "print": true, // 定义文档是否能打印
                            "review": false, // 第一是否显示审阅文档菜单
                            "comment": true, // 定义是否能够正文文档。如果正文权限设置为“true”,则文档侧栏将蕴含“正文”菜单选项;只有将 mode 参数设置为 edit 时才失效,默认值与 edit 参数的值统一。"copy": true, // 是否容许您将内容复制到剪贴板。默认值为 true。"download": true, // 定义是否能够下载文档或仅在线查看或编辑文档。如果下载权限设置为“false”下载为菜单选项将没有。默认值为 true。"modifyContentControl": true, // 定义是否能够更改内容控件设置。仅当 mode 参数设置为 edit 时,内容控件批改才可用于文档编辑器。默认值为 true。"modifyFilter": true, // 定义过滤器是否能够全局利用(true)影响所有其余用户,或部分利用(false),即仅实用于以后用户。如果将 mode 参数设置为 edit,则过滤器批改仅对电子表格编辑器可用。默认值为 true。}
                    },
                    // type: "embedded",
                    // 打开文档类型
                    // text 对应各种文档类型 (.doc, .docm, .docx, .dot, .dotm, .dotx, .epub, .fodt, .htm, .html, .mht, .odt, .ott, .pdf, .rtf, .txt, .djvu, .xps)
                    //spreadsheet 对应表格类型 (.csv, .fods, .ods, .ots, .xls, .xlsm, .xlsx, .xlt, .xltm, .xltx)
                    //presentation 对应 PPT 类型 (.fodp, .odp, .otp, .pot, .potm, .potx, .pps, .ppsm, .ppsx, .ppt, .pptm, .pptx)
                    "editorConfig": { // 编辑配置
                        "createUrl": "http://docServer:port/url-to-create-document/", // 指定创立文件的页面, 增加该配置后文档服务器插件才会显示新建文件按钮
                        "mode": model, // 文档操作模式 view 视图模式不可编辑  edit 编辑模式可编辑文档
                        "callbackUrl": callbackUrl, // 保留文件时的回调地址
                        "lang": "zh-CN", // 语言环境
                        "customization": { // 定制局部容许自定义编辑器界面,使其看起来像您的其余产品,并更改是否存在其余按钮,链接,更改徽标和编辑者所有者详细信息。"help": false, // 定义是显示还是暗藏“帮忙”菜单按钮。默认值为 true。"hideRightMenu": false, // 定义在第一次加载时是显示还是暗藏右侧菜单。默认值为 false。"autosave": false, // 定义是启用还是禁用“主动保留”菜单选项。请留神,如果您在菜单中更改此选项,它将被保留到浏览器的 localStorage 中。默认值为 true。"forcesave": true, // 定义保留按钮是否显示默认 false
                            "chat": false, // 定义“聊天”菜单按钮是显示还是暗藏;请留神,如果您暗藏“聊天”按钮,则相应的聊天性能也将被禁用。默认值为 true。"commentAuthorOnly": false, // 定义用户是否只能编辑和删除他的评论。默认值为 false。"comments": false, // 定义是显示还是暗藏“正文”菜单按钮;请留神,如果您暗藏“评论”按钮,则相应的评论性能将仅可用于查看,评论的增加和编辑将不可用。默认值为 true。"compactHeader": false, // 定义是否将菜单栏放在在徽标旁边使界面更加紧凑默认 false。"compactToolbar": false, // 定义显示的顶部工具栏类型是残缺(false)还是紧凑 true。默认值为 false 多余菜单将在右侧折叠点击显示。"compatibleFeatures": false, // 定义仅与 OOXML 格局兼容的性能的应用。例如,不要在整个文档上应用正文。默认值为 false。"macros": false, // 定义是否将运行文档宏以及可用的宏设置。默认值为 true。"macrosMode": "warn", // 定义是否将运行文档宏。能够采纳以下值:disable - 基本不运行;enable - 主动运行所有宏;warn - 正告宏并申请容许运行。默认值为 original。"plugins": false, // 定义是否将启动插件并可用。默认值为 true。"showReviewChanges": false, // 定义在加载编辑器时是否主动显示或暗藏审阅更改面板。默认值为 false。"spellcheck": false, // 定义在加载编辑器时是否主动关上或敞开拼写查看器。拼写查看器仅实用于文档编辑器和演示文稿编辑器。默认值为 true。"toolbarNoTabs": false, // 定义是突出显示顶部工具栏选项卡款式。默认值为 false。"unit": "cm", // 定义在标尺和对话框中应用的度量单位。能够采纳以下值:cm - 厘米,pt- 点,inch - 英寸。默认值为厘米(cm)。"zoom": 100, // 定义以百分比为单位的文档显示缩放值。能够取大于 0 的值。对于文本文档和演示文稿,能够将此参数设置为 -1(使文档适宜页面选项)或 -2(使文档页面宽度适宜编辑器页面)。默认值为 100。"customer": { // 对于 文档编辑器的显示信息
                                "address": "My City, 123a-45", // 有权拜访编辑或编辑作者的公司或集体的邮政地址,"info": "Some additional information", // 无关您心愿其他人意识的公司或集体的一些其余信息,"logo": "https://example.com/logo-big.png", // 图片徽标的门路(此文件没有特地倡议,然而如果应用通明背景的.png 格局会更好)。图片必须具备以下尺寸:432x70,"mail": "[email protected]", // 有权拜访编辑者或编辑者的公司或集体的电子邮件地址
                                "name": "欧阳锋", // 该公司或集体的谁能够拜访编辑或编辑作者,名称
                                "www": "example.com" // 以上公司或集体的家庭网站地址,},
                            "feedback": { // 反馈配置信息
                                "url": "https://example.com", // 单击“反馈和反对”菜单按钮时将关上的网站地址的相对 URL,"visible": false // 显示或暗藏“反馈和反对”菜单按钮,},
                            "goback": { // 定义“关上文件地位”菜单按钮和右上角按钮的设置。该对象具备以下参数:"blank": true, // 在新的浏览器选项卡 / 窗口(如果值设置为 true)或以后选项卡(如果值设置为 false)中关上网站。默认值为 true,"requestClose": false, // 定义如果单击“关上文件地位”按钮,则调用 events.onRequestClose 事件,而不是关上浏览器选项卡或窗口。默认值为 false,"text": "Open file location", // 将在“关上文件地位”菜单按钮和右上角按钮(即,而不是“转到文档”)上显示的文本,"url": "https://example.com" // 单击“关上文件地位”菜单按钮时将关上的网站地址的相对 URL,},
                            "logo": {
                                "image": "https://example.com/logo.png", // 图像文件的门路,用于在一般工作模式下显示(即,在所有编辑器的查看和编辑模式下)。图片必须具备以下尺寸:172x40,"imageEmbedded": "https://example.com/logo_em.png", // 用于以嵌入式模式显示的图像文件的门路(请参阅 config 局部以理解如何定义嵌入式文档类型)。图片必须具备以下尺寸:248x40,"url": "https://www.baidu.com" // 某人单击徽标图像时将应用的相对 URL(可用于转到您的网站等)。保留为空字符串或 null 以使徽标不可单击,},
                        },
                        "user": { // 用户信息
                            "id": "admin", // 用户 ID
                            "name": "操作员" // 用户全名称
                        },
                        "embedded": { //Embedded 局部仅实用于嵌入式文档类型(请参阅 config 局部以理解如何定义嵌入式文档类型)。它容许更改设置,这些设置定义嵌入式模式下按钮的行为。"embedUrl": "https://example.com/embedded?doc=exampledocument1.docx", // 定义文档的相对 URL,以作为嵌入到网页中的文档的源文件
                            "fullscreenUrl": "https://example.com/embedded?doc=exampledocument1.docx#fullscreen", // 定义将以全屏模式关上的文档的相对 URL。"saveUrl": "https://example.com/download?doc=exampledocument1.docx", // 定义容许将文档保留到用户集体计算机上的相对 URL。"shareUrl": "https://example.com/view?doc=exampledocument1.docx", // 定义容许其余用户共享此文档的相对 URL。"toolbarDocked": "top" // 定义嵌入式查看器工具栏的地位,能够为 top 或 bottom。}
                    },

                    "events": { // 事件配置
                        // onAppReady,//- 将应用程序加载到浏览器时调用的函数。// onCollaborativeChanges //- 当文档由其余用户在严格独特编辑模式下独特编辑时调用的函数。// onDocumentReady,//- 将应用程序加载到浏览器时调用的函数。// onDocumentStateChange,//- 批改文档时调用的函数。这就是所谓的应用参数:{真正的“数据”} 在以后用户编辑文档以及与参数:{“数据”:假} 在以后用户的更改发送到文档编辑服务。// onDownloadAs,//- 调用 downloadAs 办法时,应用指向已编辑文件的相对 URL 调用的函数。在 data 参数中发送要下载的文档的相对 URL。// onError,//- 产生谬误或其余特定事件时调用的函数。谬误音讯在 data 参数中发送。// onInfo,//- 应用程序关上文件时调用的函数。该模式在 data.mode 参数中发送。能够查看或编辑。// onMetaChange,//- 通过 meta 命令更改文档的元信息时调用的函数。文档名称通过 data.title 参数发送。// onOutdatedVersion,//- 应用旧的 document.key 值打开文档进行编辑时,显示谬误后调用的函数,该值用于编辑先前的文档版本并已胜利保留。调用此事件时,必须应用新的 document.key 从新初始化编辑器。// onReady,//- 将应用程序加载到浏览器时调用的函数。自从 5.0 版本不举荐应用,请应用 onAppReady 代替
                        // onRequestClose,//- 完结编辑器的工作并且必须敞开编辑器时调用的函数。// onRequestCompareFile,//- 用户尝试通过单击“存储中的文档”按钮来抉择要比拟的文档时调用的函数。要抉择要比拟的文档,必须调用 setRevisedFile 办法。如果未声明该办法,则不会显示“来自存储的文档”按钮。// onRequestCreateNew,//- 用户尝试通过单击“新建”按钮来创立文档时调用的函数。应用此办法代替 createUrl 字段。如果未声明该办法且未指定 createUrl,则将不会显示“创立新”按钮。// onRequestEditRights,//- 用户尝试通过单击“编辑文档”按钮尝试将文档从视图切换到编辑模式时调用的函数。调用该函数时,必须在编辑模式下再次初始化编辑器。如果未声明该办法,则不会显示“编辑”按钮。// onRequestHistory,//- 用户尝试通过单击“版本历史记录”按钮显示文档版本历史记录时调用的函数。要显示文档版本历史,您必须调用 refreshHistory 办法。如果未声明该办法和 onRequestHistoryData 办法,则不会显示“版本历史记录”按钮。// onRequestHistoryClose,//- 当用户尝试通过单击“敞开历史记录”按钮来查看文档版本历史记录时,试图调用该文档时调用的函数。调用该函数时,必须在编辑模式下再次初始化编辑器。如果未声明该办法,则不会显示“敞开历史记录”按钮。// onRequestHistoryData,//- 用户尝试单击文档版本历史记录中的特定文档版本时调用的函数。// onRequestInsertImage,//- 用户尝试通过单击“保留图像”按钮插入图像时调用的函数。图像插入的类型在参数 data.c 中指定。// onRequestRename,//- 用户尝试通过单击“重命名...”按钮重命名文件时调用的函数。// onRequestRestore,//- 用户单击版本历史记录中的“还原”按钮来还原文件版本时调用的函数。// onRequestSaveAs,//- 用户尝试通过单击“另存为...”按钮保留文件时调用的函数。文档的题目和要下载的文档的相对 URL 在 data 参数中发送。如果未声明该办法,则不会显示“另存为...”按钮。// onRequestSharingSettings,//- 用户单击“更改拜访权限”按钮来治理文档拜访权限时调用的函数。必须调用 setSharingSettings 办法来更新无关容许与其余用户共享文档的设置的信息。如果未声明该办法,则不会显示“更改拜访权限”按钮。// onRequestUsers,//- 评论者能够抉择要在评论中提及的其余用户时调用的函数。要设置用户列表,必须调用 setUsers 办法。// onWarning,//- 产生正告时调用的函数。正告音讯在 data 参数中发送。// "onDocumentStateChange": function() {//}, // 文档扭转后的回调
                        //"onDocumentReady" : onDocumentReady, // 文档初始化筹备好后的回调
                    },
                };
                var docEditor = new DocsAPI.DocEditor("placeholder", config);
            }

数据接口

  • 下载文件

返回数据流即可,示例如下

    @GetMapping("/download")
    @ResponseBody
    public void download(@RequestParam("attguid") String attguid, HttpServletRequest request, HttpServletResponse response) throws Exception {AttachmentDO attachment = attachmentService.selectOne(attguid);
        String filePath = "";
        // 云上传的附件
        if (attachment.getVirtualpath().contains("ReadAlOSS")) {if (attachment.getCanedit() == null || attachment.getCanedit() == 20) {String fileurl = aliUtil.readOSSFile(attachment);
                if (!StringUtil.isEmpty(fileurl)) {response.sendRedirect(fileurl);
                }
            } else if (attachment.getCanedit() == 30) {String fileurl = huaWeiUtil.readOBSFile(attachment);
                System.out.println(fileurl);
                if (!StringUtil.isEmpty(fileurl)) {response.sendRedirect(fileurl);
                }
            } else if (attachment.getCanedit() == 40) {String fileurl = minioUtil.readMinioFile(attachment);
                System.out.println(fileurl);
                if (!StringUtil.isEmpty(fileurl)) {response.sendRedirect(fileurl);
                }
            }
        }
        // 本地文件
        String configPath = frameConfig.getAttachPath();
        filePath = configPath + attachment.getVirtualpath();

        File file = new File(filePath);
        if (file.exists()) {String filename = attachment.getFilename();
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "utf-8"));
            response.setCharacterEncoding("utf-8");
            response.setContentLength((int) file.length());

            byte[] buff = new byte[(int) file.length()];
            BufferedInputStream bufferedInputStream = null;
            OutputStream outputStream = null;
            try {outputStream = response.getOutputStream();
                bufferedInputStream = new BufferedInputStream(new FileInputStream(file));
                int i = 0;
                while ((i = bufferedInputStream.read(buff)) != -1) {outputStream.write(buff, 0, i);
                    outputStream.flush();}
            } catch (IOException e) {e.printStackTrace();
            } finally {

                try {bufferedInputStream.close();
                } catch (IOException e) {e.printStackTrace();
                }
            }
        }
    }
  • 保留文件

解析传递的参数,获取文件 url 下载到本地后,进行自定义业务操作

@PostMapping("/save")
    @ResponseBody
    public void save(@RequestParam Map<String, String> map, HttpServletRequest request, HttpServletResponse response) {
        PrintWriter writer = null;
        String body = "";
        String attguid = request.getParameter("attguid");
        try {writer = response.getWriter();
            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()) {throw new CustomerRuntimeException("ONLYOFFICE 回调保留申请体未空");
        }

        JSONObject jsonObj = JSONObject.parseObject(body);
        int status = (Integer) jsonObj.get("status");
        int saved = 0;
        String key = jsonObj.get("key").toString();
        if (status == 2 || status == 3 || status == 6) //MustSave, Corrupted
        {String downloadUri = (String) jsonObj.get("url");
            System.out.println(downloadUri);
            try {
                String filePath = "tempfiles/onlyoffice/savedownload/";
                FileUtil.initfloderPath(filePath);
                String fileName = CommonUtil.getNewGuid();
                HttpUtil.downLoadFromUrl(downloadUri, filePath, fileName);
                attachLogic.updateAttachContent(attguid, FileUtil.getBytes(filePath + fileName));
            } catch (Exception ex) {
                saved = 1;
                ex.printStackTrace();}
        }
        writer.write("{\"error\":" + saved + "}");
    }

内部按钮接入

以保留按钮为例

获取编辑器 iframe 按钮中的 slot-btn-dt-save 节点元素,定位 div 下的 button 按钮,进行 js 模仿点击实现保留操作

通过监听 iframe 的 message 来捕捉到保留完结页面弹出自定义提醒

上述操作因编辑器 html 页面和 onlyoffice 服务存在跨域问题,须要配置 nginx 代理到对立 ip 端口下

  function HandleSave() {var frameDocument = document.getElementsByTagName("iframe")[0].contentDocument;
        frameDocument.getElementById("slot-btn-dt-save").getElementsByTagName("button")[0].click();}

    window.onmessage = function (event) {var data = JSON.parse(event.data);
        if (data.event == "onDocumentStateChange" && data.data == false) {OpenSuccessMessage("保留胜利")
        }
    }
正文完
 0