irpas技术客

Spring-boot+Vue 前后端快速集成onlyoffice_colagy wang_springboot 集成onlyoffice

未知 1762

只介绍集成 不介绍搭建环境

一、整体流程

onlyoffice服务使用docker部署 onlyoffice-documentserver: 文档服务 文件转换服务 编辑器服务 rabbitmq: 消息队列 postgres:9.5: 数据库

docker-compose.yml 文件

version: '2' services: onlyoffice-documentserver: container_name: onlyoffice-documentserver image: sgcc-dky-smartky-onlyoffice_onlyoffice-documentserver depends_on: - onlyoffice-postgresql - onlyoffice-rabbitmq environment: - DB_TYPE=postgres - DB_HOST=onlyoffice-postgresql - DB_PORT=5432 - DB_NAME=onlyoffice - DB_USER=onlyoffice - AMQP_URI=amqp://guest:guest@onlyoffice-rabbitmq # Uncomment strings below to enable the JSON Web Token validation. #- JWT_ENABLED=true #- JWT_SECRET=secret #- JWT_HEADER=Authorization #- JWT_IN_BODY=true ports: - '8088:80' - '443:443' stdin_open: true restart: always stop_grace_period: 60s volumes: - /var/www/onlyoffice/Data - /var/log/onlyoffice - /var/lib/onlyoffice/documentserver/App_Data/cache/files - /var/www/onlyoffice/documentserver-example/public/files - /usr/share/fonts onlyoffice-rabbitmq: container_name: onlyoffice-rabbitmq image: rabbitmq restart: always expose: - '5672' ports: - '5672:5672' onlyoffice-postgresql: container_name: onlyoffice-postgresql image: postgres:9.5 environment: - POSTGRES_DB=onlyoffice - POSTGRES_USER=onlyoffice - POSTGRES_HOST_AUTH_METHOD=trust restart: always expose: - '5432' ports: - '5432:5432' volumes: - postgresql_data:/var/lib/postgresql volumes: postgresql_data: 访问文档时序图

文档保存时序图


二、前端集成 前端集成过程

OnlyOffice前端由onlyOffice-documentServer提供,后端会生成api.js文件,前端引入这个js就可以集成onlyOffice的编辑器。

前端项目结构

env.js 是配置文件 配置onlyoffice编辑器以及文档服务的地址

var merge = require('webpack-merge') var prodEnv = require('./prod.env') module.exports = merge(prodEnv, { NODE_ENV: '"development"', BASE_URL: '"/api"', // onlyoffice document-server 服务端口 // 编辑地址 ONLYOFFICE_DOCUMENT_HOST: '"http://127.0.0.1"', // 预览地址 ONLYOFFICE_DOCUMENT_PREVIEW_HOST: '"http://127.0.0.1"', ONLYOFFICE_DOCUMENT_PORT: 8080, // onlyoffice前端编辑器地址 ONLYOFFICE_DOCUMENT_URL: '"/web-apps/apps/api/documents/api.js"' });

onlyofficeView是onlyoffice 基础组件 封装了核心的onlyoffice编辑器

<template> <div id="monitorOffice"></div> </template> <script> import {handleDocType} from "./docType"; export default { name: "onlyofficeView", ...省略, methods: { initEditor() { // 加入 onlyoffice api.js 脚本 const apiScriptDom = document.getElementById("onlyoffice-document-api"); const script = document.createElement("script"); const {protocol} = window.location; const ONLYOFFICE_DOCUMENT_PORT=process.env.ONLYOFFICE_DOCUMENT_PORT; const ONLYOFFICE_DOCUMENT_URL=process.env.ONLYOFFICE_DOCUMENT_URL; script.setAttribute("id", "onlyoffice-document-api"); script.setAttribute( "src", `${this.documentUrl + ":" + ONLYOFFICE_DOCUMENT_PORT + ONLYOFFICE_DOCUMENT_URL}` ); document.body.appendChild(script); const _self = this; script.onload = () => { _self.scriptReady = true; console.log("initEditor 初始化组件完成!"); if (_self.option.url) { _self.setEditor(_self.option); } }; }, setEditor(option) { const { $store: { state: { user: {userId, nickname} } } } = this; this.doctype = handleDocType(option.fileType); } }, watch: { option: { handler: function (n, o) { this.doctype = handleDocType(n.fileType); if (this.scriptReady) { this.setEditor(n); } }, deep: true } } }; </script>

onlineCat onlineEdit 是集成了onlyoffice编辑器的编辑和预览 主要是根据各种业务场景初始化的配置

<template> <div style="height:100%"> <onlyoffice-view ref="onlyOffcieView" :option="option" :document-url="documentUrl"></onlyoffice-view> </div> </template>
三、后端集成

1.核心服务接口

核心方法 下载 download() 保存 save() 获取onlyoffice Token getToken() 校验token verifyToken()

/** * onlyoffice 服务接口 * * @author: colagy * 2021-03-26 16:06 */ public interface IOnlyofficeService { /** * onlyoffice document server 下载文件 * @param id * @param scene * @param userId * @param timeStamp * @param token * @param response * @throws IOException */ void download(String id, Enum scene, String userId, Long timeStamp, String token, HttpServletResponse response) throws IOException; /** * 文件回写 * * @param request onlyoffce document server 请求 * @param eClass 文档场景枚举 类对象 * @param <E> onlyofficeScene 文档场景枚举 * @return * @throws IOException */ <E extends Enum> JSONObject save(HttpServletRequest request, Class<E> eClass) throws IOException; /** * @return */ OnlyofficeToken getToken(); boolean verifyToken(String userId, Long timeStamp, String token, Long expire); } 2.服务基类

定义了两个 抽象方法 子类需要实现并返回 存储方法 和 token过期校验

@Service public abstract class SimpleOnlyofficeService implements IOnlyofficeService { private final static Logger log = LoggerFactory.getLogger(SimpleOnlyofficeService.class); private static final String tokenPrefix = "onlyoffice_token_profix_node_server"; public abstract IOnlyofficeStorage getOnlyofficeStorage(); public abstract long getTokenExpire(); ... }

默认实现的download()方法 调用getOnlyofficeStorage 获取存储的实现类 可以是oss 或者本地文件存储 调用onlyofficeStorage.get()方法 返回值是inputStream写入响应流返回给onlyofficeDocumentServer

@Override public void download(String id, Enum scene, String userId, Long timeStamp, String token, HttpServletResponse response) throws IOException { // token过期时间默认10分钟 long tokenExpire = getTokenExpire(); if (tokenExpire <= 0) { tokenExpire = 10 * 60 * 1000L; } if (!verifyToken(userId, timeStamp, token, tokenExpire)) { throw new CustomGenericException("授权已过期或授权失败"); } IOnlyofficeStorage onlyofficeStorage = getOnlyofficeStorage(); InputStream inputStream = onlyofficeStorage.get(id, scene); FileUtil.nioTransferTo(inputStream, response.getOutputStream()); // TODO 需要storage 返回更多信息 FileUtil.parseDownloadResponse(response, String.valueOf(timeStamp), "application/octet-stream"); }

默认实现的save()方法 需要返回 {“error”:0} 给onlyoffice确认 调用 onlyofficeStorage.put(id, scene, inputStream, bytes.length)方法 将onlyOfficeServer传入的流保存

@Override public <E extends Enum> JSONObject save(HttpServletRequest request, Class<E> eClass) throws IOException { Scanner scanner = new Scanner(request.getInputStream()).useDelimiter("\\A"); String bodyStr = scanner.hasNext() ? scanner.next() : ""; Map<String, String[]> parameterMap = request.getParameterMap(); String[] ids = parameterMap.get("id"); String[] scenes = parameterMap.get("scene"); String id = null; if (ArrayUtils.isNotEmpty(ids)) { id = ids[0]; } if (Objects.isNull(id)) { JSONObject res = new JSONObject(); res.put("error", -1); return res; } Enum scene = null; if (ArrayUtils.isNotEmpty(scenes) && EnumUtils.isValidEnum(eClass, scenes[0])) { try { scene = Enum.valueOf(eClass, scenes[0]); } catch (Exception e) { String msg = "onlyoffice save方法 未指定scene"; log.error(ErrorUtil.getErrorStack(e, msg)); } } if (Objects.isNull(scene)) { JSONObject res = new JSONObject(); res.put("error", -1); return res; } JSONObject body = JSONObject.parseObject(bodyStr); int status = body.getInteger("status"); if (status == 2) { String downloadUrl = body.getString("url"); if (StringUtils.isNotBlank(downloadUrl)) { IOnlyofficeStorage onlyofficeStorage = getOnlyofficeStorage(); byte[] bytes = HttpUtil.downloadBytes(downloadUrl); InputStream inputStream = new ByteArrayInputStream(bytes); // 默认方法 可以实现beforeSave beforeSave(id, scene, parameterMap, body, inputStream); onlyofficeStorage.put(id, scene, inputStream, bytes.length); afterSave(id, scene, parameterMap, body, inputStream); } } // 正常处理需要返回 {"error":0} 给onlyoffice确认 JSONObject res = new JSONObject(); res.put("error", 0); return res; }
四、权限验证

服务基类定义getUserId()方法 实现类需要返回一个用户唯一标识

/** * 必须实现返回当前登录用户id的方法 用于token获取以及校验 * * @return */ protected abstract String getUserId(); /** * 获取token * * @return */ @Override public OnlyofficeToken getToken() { String userId = getUserId(); long timeStamp = System.currentTimeMillis(); String tokenStr = parseToken(userId, timeStamp); OnlyofficeToken token = new OnlyofficeToken(userId, timeStamp, tokenStr); return token; } private String parseToken(String userId, Long timeStamp) { String tokenStr = "使用一些加密的方法将userId timeStamp 加密返回一个加密后的token,可以参加jwt"; return tokenStr; } /** * 校验token 并判断过期时间 * * @param token token * @param expire 过期时间(ms) 默认 10*60*000ms 10分钟 * @return */ @Override public boolean verifyToken(String userId, Long timeStamp, String token, Long expire) { if (StringUtils.isBlank(userId)) { log.warn("onlyofficeToken 验证失败 userId为空"); return false; } if (Objects.isNull(timeStamp) || timeStamp == 0) { log.warn("onlyofficeToken 验证失败 timeStamp为空"); return false; } if (Objects.isNull(expire)) { expire = 10 * 60 * 1000L; } long now = System.currentTimeMillis(); if (now - timeStamp > expire) { log.error("onlyoffice token已过期 , userId:" + userId + ", token:" + token + ", timeStamp:" + timeStamp + ", now:" + now + ", expire:" + expire); return false; } String realToken = parseToken(userId, timeStamp); boolean isValid = StringUtils.equals(token, realToken); if (!isValid) { log.error("onlyoffice token验证失败! " + " token:" + token + ", realToken:" + realToken + ", userId:" + userId + ", timeStamp:" + timeStamp + ", expire:" + expire); } return StringUtils.equals(token, realToken); }
五、储存集成 1.存储接口

主要是两个方法 get() 返回字节流 put() 传入字节流 可以用oss实现 也可以用本地文件实现 只要能返回字节流和写入字节流就可以

/** * onlyoffice 在线编制的 储存方式需要实现这个接口 * * @author: colagy * 2021-03-29 11:03 */ public interface IOnlyofficeStorage { /** * 获取文件字节流 * * @param id 文件唯一标识 * @param scene 使用场景 * @return * @throws IOException */ InputStream get(String id, Enum scene) throws IOException; /** * 写入文件字节流 * * @param id 文件唯一标识 * @param scene 使用场景 * @param inputStream 输入流 * @param inSize 流大小(用于nio拷贝) * @throws IOException */ void put(String id, Enum scene, InputStream inputStream, long inSize) throws IOException; } 2.本地文件基类

子类需要实现 getFilePath() 根据资源唯一标识返回文件的储存路径

/** * 文件系统存储抽象类 子类需要实现通过id获取存储路径的方法 * * @author: colagy * 2021-03-29 11:07 */ public abstract class FsStorage implements IOnlyofficeStorage { public abstract String getFilePath(String id, Enum scene); @Override public InputStream get(String id, Enum scene) throws IOException { String saveFilePath = getFilePath(id, scene); Assert.notBlank(saveFilePath); File file = new File(saveFilePath); File parentFile = file.getParentFile(); if (Objects.nonNull(parentFile)) { if (!parentFile.exists()) { parentFile.mkdirs(); } } if (!file.exists()) { file.createNewFile(); } Assert.isTrue(file.exists()); FileInputStream fileInputStream = new FileInputStream(file); return fileInputStream; } @Override public void put(String id, Enum scene, InputStream inputStream, long inSize) throws IOException { String filePath = getFilePath(id, scene); FileUtil.nioTransferTo(inputStream, inSize, filePath); } }
六、场景集成

目前文件存储需要集成文件场景 不同的场景可以有不同的方式获取文件路径

/** * 场景需要实现这个接口 * * @author: colagy * 2021-06-11 17:10 */ public interface IFsScene { String getFilePath(String id); }

文件场景默认实现类 base64方式 只做示例不推荐使用

/** * 文件路径base64 * * @author: colagy * 2021-06-15 12:03 */ @Service(value = "FS_BASE64") public class Base64FsScene implements IFsScene { /** * id为filePath的base64值 把base64解析就是filePath * TODO 前端base64需要使用 window.btoa(window.encodeURIComponent("/filepath/文档.doc")) 转base64 * * @param filePathBase64 filePath的base64值 * @return file path */ @Override public String getFilePath(String filePathBase64) { if (StringUtils.isBlank(filePathBase64)) { return ""; } try { Base64.Decoder decoder = Base64.getDecoder(); byte[] decode = decoder.decode(filePathBase64); String urlEncodeFilePath = new String(decode); String filePath = URLDecoder.decode(urlEncodeFilePath, "utf-8"); return filePath; } catch (Exception e) { return ""; } } }


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #springboot #集成onlyoffice #只介绍集成 #不介绍搭建环境可以引用