OnlyOffice基础实践
一:概述
onlyoffice是做什么用的在这里就不做解释了,能看到这篇博客的我相信都是了解的,本文主要讲解onlyoffice在使用过程中要注意的问题。
使用环境:window10 + VMware Workstation Pro14 + CentOS-7-x86_64-Minimal + Docker 18.03.1-ce + onlyoffice
onlyoffice官网:https://www.onlyoffice.com/
onlyoffice API: https://api.onlyoffice.com/editors/basic
onlyoffice API要着重看下,里面阐述了onlyoffice的运行原理,同时里面有基于多种语言的样例代码,基本理解onlyoffice的运行原理后再结合样例代码验证必能事半功倍。在这首先阐述下API中设计到的几个概念:
1,document manager : 文档管理器,等同于一个界面中的文件列表,该列表就是文档管理器【我们自己编写,不一定需要】。
2,document storage service :文档存储服务,即管理文档存放的模块,很多时候就是我们上传文件然后将其保存在服务器上,网上也有使用nextcloud作为存储的【我们自己编写或使用存储文件的软件】。
3,document editor : 文档编辑器,就是文档编辑窗口【onlyoffice提供的前端页面插件】
4,document editing service : 文档编辑服务,从文档存储服务获取要编辑的文档,转换成Office OpenXML格式后传给文档编辑器,编辑期间文档编辑器与文档编辑服务长期交互【onlyoffice提供的后台服务】
二:实践(基于样例代码)
首先从官网下载基于java开发的样例代码“Java Example.zip”,该代码是使用maven构建的webapp,在webapp目录下有两个文件“index.jsp”和"editor.jsp"; 同时在resources目录下有个setting.properties配置文件,这三个文件是我们首先要着重看的地方。
如果index.jsp能顺利运行起来,那界面应该是这样的:
首先我们可以点击“Choose file”按钮上传一个要编辑的文件,这时会调用 controllers.IndexServlet中的doPost方法。其处理方法为:
protected void proce***equest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{String action = request.getParameter("type");if(action == null) {request.getRequestDispatcher("index.jsp").forward(request, response);return;}DocumentManager.Init(request, response);PrintWriter writer = response.getWriter();switch (action.toLowerCase()){case "upload":Upload(request, response, writer);break;case "convert":Convert(request, response, writer);break;case "track":Track(request, response, writer);break;}}
private static void Upload(HttpServletRequest request, HttpServletResponse response, PrintWriter writer){response.setContentType("text/plain");try{Part httpPostedFile = request.getPart("file");String fileName = "";for (String content : httpPostedFile.getHeader("content-disposition").split(";")){if (content.trim().startsWith("filename")){fileName = content.substring(content.indexOf('=') + 1).trim().replace("\"", "");}}long curSize = httpPostedFile.getSize();if (DocumentManager.GetMaxFileSize() < curSize || curSize <= 0){writer.write("{ \"error\": \"File size is incorrect\"}");return;}String curExt = FileUtility.GetFileExtension(fileName);if (!DocumentManager.GetFileExts().contains(curExt)){writer.write("{ \"error\": \"File type is not supported\"}");return;}InputStream fileStream = httpPostedFile.getInputStream();fileName = DocumentManager.GetCorrectName(fileName);//文件保存路径String fileStoragePath = DocumentManager.StoragePath(fileName, null);File file = new File(fileStoragePath);try (FileOutputStream out = new FileOutputStream(file)){int read;final byte[] bytes = new byte[1024];while ((read = fileStream.read(bytes)) != -1){out.write(bytes, 0, read);}out.flush();}writer.write("{ \"filename\": \"" + fileName + "\"}");}catch (IOException | ServletException e){writer.write("{ \"error\": \"" + e.getMessage() + "\"}");}}
深入的不在阐述,经过几番周折后,最终是将文件保存在硬盘上。这个上传操作在实际应用onlyoffice的项目中也应该会有,实现方法和存放的路径根据实际调整即可。
上传过程界面如下:
当点击“Edit”按钮后,将会通过EditorServlet跳转到“editor.jsp页面”,并将需要的参数传递过去。其中关键的代码为
docEditor = new DocsAPI.DocEditor("iframeEditor",{width: "100%",height: "100%",type: "${type}",documentType: "<%= Model.GetDocumentType() %>",document: {title: fileName,url: "<%= Model.GetFileUri() %>",fileType: fileType,key: "<%= Model.GetKey() %>",info: {author: "Me",created: "<%= new SimpleDateFormat("MM/dd/yyyy").format(new Date()) %>",},permissions: {edit: <%= Boolean.toString(DocumentManager.GetEditedExts().contains(FileUtility.GetFileExtension(Model.GetFileName()))).toLowerCase() %>,download: true,}},editorConfig: {mode: "<%= DocumentManager.GetEditedExts().contains(FileUtility.GetFileExtension(Model.GetFileName())) && !"view".equals(request.getAttribute("mode")) ? "edit" : "view" %>",lang: "en",callbackUrl: "<%= Model.GetCallbackUrl() %>",user: {id: "<%= Model.CurUserHostAddress() %>",name: "John Smith",},embedded: {saveUrl: "<%= Model.GetFileUri() %>",embedUrl: "<%= Model.GetFileUri() %>",shareUrl: "<%= Model.GetFileUri() %>",toolbarDocked: "top",},customization: {about: true,feedback: true,goback: {url: "<%= Model.GetServerUrl() %>/IndexServlet",},},},events: {"onReady": onReady,"onDocumentStateChange": onDocumentStateChange,'onRequestEditRights': onRequestEditRights,"onError": onError,"onOutdatedVersion": onOutdatedVersion,}});};
其中 document属性下的url地址 和 editorConfig属性下的callbackUrl地址十分重要,也是在开发中需要重点关注的地方,很多报出的以下错误基本都是地址不对导致的。
重点说说这两个配置的作用
1)document属性下的url配置是onlyoffice的编辑服务用于获取文档的地址,也就是说,我们必须保证在docker中是必须能访问到的地址, 通过wget命令尝试在docker所在的服务器中是否能够访问。
2) editorConfig属性下的callbackUrl配置是onlyoffice的编辑服务回调的,该回调的作用是告知你编辑后文档的下载地址,以便更新原始文件,所以我们也得保证docker能够访问到该地址。我们可以自己编写个用于回调的方法,例如:
public void saveFile(HttpServletRequest request, HttpServletResponse response) {PrintWriter writer = null;System.out.println("===saveeditedfile------------") ;try {writer = response.getWriter();Scanner scanner = new Scanner(request.getInputStream()).useDelimiter("\\A");String body = scanner.hasNext() ? scanner.next() : "";JSONObject jsonObj = (JSONObject) new JSONParser().parse(body);System.out.println("===saveeditedfile:" + jsonObj.get("status")) ;/*0 - no document with the key identifier could be found,1 - document is being edited,2 - document is ready for saving,3 - document saving error has occurred,4 - document is closed with no changes,6 - document is being edited, but the current document state is saved,7 - error has occurred while force saving the document.* */if ((long) jsonObj.get("status") == 2) {/** 当我们关闭编辑窗口后,十秒钟左右onlyoffice会将它存储的我们的编辑后的文件,,此时status = 2,通过request发给我们,我们需要做的就是接收到文件然后回写该文件。* *//** 定义要与文档存储服务保存的编辑文档的链接。当状态值仅等于2或3时,存在链路。* */String downloadUri = (String) jsonObj.get("url");System.out.println("====文档编辑完成,现在开始保存编辑后的文档,其下载地址为:" + downloadUri);//解析得出文件名String fileName = downloadUri.substring(downloadUri.lastIndexOf('/')+1);System.out.println("====下载的文件名:" + fileName);URL url = new URL(downloadUri);java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();InputStream stream = connection.getInputStream();//更换为实际的路径File savedFile = new File("e:\\");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();}connection.disconnect();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (ParseException e) {// TODO Auto-generated catch blocke.printStackTrace();}/** status = 1,我们给onlyoffice的服务返回{"error":"0"}的信息,这样onlyoffice会认为回调接口是没问题的,这样就可以在线编辑文档了,否则的话会弹出窗口说明* */writer.write("{\"error\":0}");}
接下来再说说setting.properties配置文件,里面的配置内容如下:
filesize-max=5242880
storage-folder=app_datafiles.docservice.viewed-docs=.pdf|.djvu|.xps
files.docservice.edited-docs=.docx|.xlsx|.csv|.pptx|.ppsx|.txt
files.docservice.convert-docs=.docm|.dotx|.dotm|.dot|.doc|.odt|.fodt|.xlsm|.xltx|.xltm|.xlt|.xls|.ods|.fods|.pptm|.ppt|.ppsm|.pps|.potx|.potm|.pot|.odp|.fodp|.rtf|.mht|.html|.htm|.epub
files.docservice.timeout=120000files.docservice.url.converter=http://192.168.10.129/ConvertService.ashx
files.docservice.url.tempstorage=http://192.168.10.129/ResourceService.ashx
files.docservice.url.api=http://192.168.10.129/web-apps/apps/api/documents/api.js
files.docservice.url.preloader=http://192.168.10.129/web-apps/apps/api/documents/cache-scripts.html
其中的192.168.10.29是访问onlyoffice document server的地址,如果访问到,其结果应该如下:
以上就是使用onlyoffice的两个关键点,文档下载地址和onlyoffice回调地址
三:实践(基于Springboot)
1,首先配置文件存放路径和对应暴露的访问地址
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {// addResourceLocations指的是文件放置的目录,addResourceHandler指的是对外暴露的访问路径registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/static/");registry.addResourceHandler("/file/").addResourceLocations("file:D:/uploadfile/");registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}/*** 统一处理没啥业务逻辑处理的controller请求,实现代码的简洁* * @param registry*/@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/").setViewName("index");registry.addViewController("/index").setViewName("index");}/*** SpringMVC的路径参数如果带“.”的话,“.”后面的值将被忽略 .../pathvar/xx.yy 解析得到:xx*/@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {// 通过设置为false使其可以接受"."后的但是configurer.setUseSuffixPatternMatch(false);}
}
此处我们将上传文件保存在了“D:/uploadfile/”目录下,该地址最好是在配置文件中配置,例如上面的setting.properties文件中。
2,创建用于编辑文档的Controller
@Controller
public class EditorController {@RequestMapping("/EditorServlet")public ModelAndView index(HttpServletRequest request,HttpServletResponse response,Model model,ModelMap modelMap) throws Exception {String fileName = "";if (request.getParameterMap().containsKey("fileName")) {fileName = request.getParameter("fileName");}String fileExt = null;if (request.getParameterMap().containsKey("fileExt")) {fileExt = request.getParameter("fileExt");}if (fileExt != null) {try {DocumentManager.Init(request, response);fileName = DocumentManager.CreateDemo(fileExt);} catch (Exception ex) {return new ModelAndView(new FastJsonJsonView(),"Error: " + ex.getMessage(), ex) ;}}String mode = "";if (request.getParameterMap().containsKey("mode")){mode = request.getParameter("mode");}Boolean desktopMode = !"embedded".equals(mode);FileModel file = new FileModel();file.SetTypeDesktop(desktopMode);file.SetFileName(fileName);System.out.println("==========EditorController==========");DocumentManager.Init(request, response);//要编辑的文件名model.addAttribute("fileName", fileName) ;//要编辑的文件类型model.addAttribute("fileType", FileUtility.GetFileExtension(fileName).replace(".", "")) ;//要编辑的文档类型model.addAttribute("documentType",FileUtility.GetFileType(fileName).toString().toLowerCase()) ;//要编辑的文档访问urlmodel.addAttribute("fileUri",DocumentManager.GetFileUri(fileName)) ;model.addAttribute("fileKey",ServiceConverter.GenerateRevisionId(DocumentManager.CurUserHostAddress(null) + "/" + fileName)) ;model.addAttribute("callbackUrl", DocumentManager.GetCallback(fileName)) ;model.addAttribute("serverUrl", DocumentManager.GetServerUrl()) ;model.addAttribute("editorMode", DocumentManager.GetEditedExts().contains(FileUtility.GetFileExtension(fileName)) && !"view".equals(request.getAttribute("mode")) ? "edit" : "view") ;model.addAttribute("editorUserId",DocumentManager.CurUserHostAddress(null)) ;model.addAttribute("type", desktopMode ? "desktop" : "embedded");model.addAttribute("docserviceApiUrl", ConfigManager.GetProperty("files.docservice.url.api"));model.addAttribute("docServiceUrlPreloader", ConfigManager.GetProperty("files.docservice.url.preloader")) ;model.addAttribute("currentYear", "2018") ;model.addAttribute("convertExts", String.join(",", DocumentManager.GetConvertExts())) ;model.addAttribute("editedExts", String.join(",", DocumentManager.GetEditedExts())) ;model.addAttribute("documentCreated", new SimpleDateFormat("MM/dd/yyyy").format(new Date())) ;model.addAttribute("permissionsEdit", Boolean.toString(DocumentManager.GetEditedExts().contains(FileUtility.GetFileExtension(fileName))).toLowerCase()) ;return new ModelAndView("editor") ;}
}
3,创建用于保存修改后文件的Controller
/** 用于保存修改后的文件* */
@Controller
@RequestMapping("/savefilectrl")
public class SaveFileController {/*** 文档编辑服务使用JavaScript API通知callbackUrl,向文档存储服务通知文档编辑的状态。文档编辑服务使用具有正文中的信息的POST请求。* https://api.onlyoffice.com/editors/callback* 参数示例:{"actions": [{"type": 0, "userid": "78e1e841"}],"changesurl": "https://documentserver/url-to-changes.zip","history": {"changes": changes,"serverVersion": serverVersion},"key": "Khirz6zTPdfd7","status": 2,"url": "https://documentserver/url-to-edited-document.docx","users": ["6d5a81d0"]}* @throws ParseException */@RequestMapping("/saveeditedfile")public void saveFile(HttpServletRequest request, HttpServletResponse response) {PrintWriter writer = null;System.out.println("===saveeditedfile------------") ;try {writer = response.getWriter();Scanner scanner = new Scanner(request.getInputStream()).useDelimiter("\\A");String body = scanner.hasNext() ? scanner.next() : "";JSONObject jsonObj = (JSONObject) new JSONParser().parse(body);System.out.println("===saveeditedfile:" + jsonObj.get("status")) ;/*0 - no document with the key identifier could be found,1 - document is being edited,2 - document is ready for saving,3 - document saving error has occurred,4 - document is closed with no changes,6 - document is being edited, but the current document state is saved,7 - error has occurred while force saving the document.* */if ((long) jsonObj.get("status") == 2) {/** 当我们关闭编辑窗口后,十秒钟左右onlyoffice会将它存储的我们的编辑后的文件,,此时status = 2,通过request发给我们,我们需要做的就是接收到文件然后回写该文件。* *//** 定义要与文档存储服务保存的编辑文档的链接。当状态值仅等于2或3时,存在链路。* */String downloadUri = (String) jsonObj.get("url");System.out.println("====文档编辑完成,现在开始保存编辑后的文档,其下载地址为:" + downloadUri);//解析得出文件名String fileName = downloadUri.substring(downloadUri.lastIndexOf('/')+1);System.out.println("====下载的文件名:" + fileName);URL url = new URL(downloadUri);java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();InputStream stream = connection.getInputStream();File savedFile = new File("e:\\");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();}connection.disconnect();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (ParseException e) {// TODO Auto-generated catch blocke.printStackTrace();}/** status = 1,我们给onlyoffice的服务返回{"error":"0"}的信息,这样onlyoffice会认为回调接口是没问题的,这样就可以在线编辑文档了,否则的话会弹出窗口说明* */writer.write("{\"error\":0}");}
}
4,项目中使用的Freemarker,所以编辑页面修改为
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>ONLYOFFICE</title><link rel="icon" href="asstes/favicon.ico" type="image/x-icon" /><link rel="stylesheet" type="text/css" href="assets/css/editor.css" /><script type="text/javascript" src="${docserviceApiUrl}"></script><script type="text/javascript" language="javascript">var docEditor;var fileName = "${fileName}";var fileType = "${fileType}";var innerAlert = function (message) {if (console && console.log)console.log(message);};var onReady = function () {innerAlert("Document editor ready");};var onDocumentStateChange = function (event) {var title = document.title.replace(/\*$/g, "");document.title = title + (event.data ? "*" : "");};var onRequestEditRights = function () {location.href = location.href.replace(RegExp("action=view\&?", "i"), "");};var onError = function (event) {if (event)innerAlert(event.data);};var onOutdatedVersion = function (event) {location.reload(true);};var сonnectEditor = function () {docEditor = new DocsAPI.DocEditor("iframeEditor",{width: "100%",height: "100%",type: "${type}",documentType: "${documentType}",document: {title:"${fileName}", url: "${fileUri}",fileType: "${fileType}",key: "${fileKey}",info: {author: "Me",created: "${documentCreated}",},permissions: {edit: ${permissionsEdit},download: true,}},editorConfig: {mode: "${editorMode}",lang: "en",callbackUrl: "${callbackUrl}",user: {id: "${editorUserId}",name: "John Smith",},embedded: {saveUrl: "${fileUri}",embedUrl: "${fileUri}",shareUrl: "${fileUri}",toolbarDocked: "top",},customization: {about: true,feedback: true,goback: {url: "${serverUrl}/IndexServlet",},},},events: {"onReady": onReady,"onDocumentStateChange": onDocumentStateChange,'onRequestEditRights': onRequestEditRights,"onError": onError,"onOutdatedVersion": onOutdatedVersion,}});};if (window.addEventListener) {window.addEventListener("load", сonnectEditor);} else if (window.attachEvent) {window.attachEvent("load", сonnectEditor);}function getXmlHttp() {var xmlhttp;try {xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");} catch (e) {try {xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");} catch (ex) {xmlhttp = false;}}if (!xmlhttp && typeof XMLHttpRequest !== "undefined") {xmlhttp = new XMLHttpRequest();}return xmlhttp;}</script></head><body><div class="form"><div id="iframeEditor"></div></div></body>
</html>
其Freemarker的配置
#设定ftl文件路径
spring.freemarker.template-loader-path=classpath:/templates/freemarker
spring.freemarker.suffix=.html
spring.freemarker.content-type=text/html
spring.freemarker.templateEncoding=UTF-8
以上就是核心部分的代码,重点是要保证配置准确。
至于docker和onlyoffice安装过程就不赘述了,最后看下docker运行情况
附:onlyoffice安装过程
1,安装docer
yum install docker -y
2,启动docker服务
systemctl start docker
3,拉取onlyoffice
docker pull onlyoffice/documentserver
4,启动Document Server镜像,并映射80端口至本地(前面个80)。
sudo docker run -i -t -d -p 80:80 onlyoffice/documentserver
转载于:https://blog.51cto.com/dengshuangfu/2154826
OnlyOffice基础实践相关推荐
- Tensor基础实践
Tensor基础实践 飞桨(PaddlePaddle,以下简称Paddle)和其他深度学习框架一样,使用Tensor来表示数据,在神经网络中传递的数据均为Tensor. Tensor可以将其理解为多维 ...
- 零基础实践深度学习之数学基础
零基础实践深度学习之数学基础 深度学习常用数学知识 数学基础知识 高等数学 线性代数 行列式 矩阵 向量 线性方程组 矩阵的特征值和特征向量 二次型 概率论和数理统计 随机事件和概率 随机变量及其概率 ...
- 零基础实践深度学习之Python基础
零基础实践深度学习之Python基础 Python数据结构 数字 字符串 列表 元组 字典 Python面向对象 Python JSON Python异常处理 Python文件操作 常见Linux命令 ...
- 大学计算机应用技术基础,大学计算机应用技术基础实践教程(Windows7+Office2010/21世纪高等学校计算机规划教材...
<大学计算机应用技术基础实践教程(Windows7+Office2010/21世纪高等学校计算机规划教材>是<大学计算机应用技术基础教程>的配套实验教材,是对教学内容的必要补充 ...
- 20145227鄢曼君《网络对抗》Web安全基础实践
20145227鄢曼君<网络对抗>Web安全基础实践 实验后回答问题 1.SQL注入攻击原理,如何防御? SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是 ...
- 20155202《网络对抗》Exp9 web安全基础实践
20155202<网络对抗>Exp9 web安全基础实践 实验前回答问题 (1)SQL注入攻击原理,如何防御 SQL注入产生的原因,和栈溢出.XSS等很多其他的攻击方法类似,就是未经检查或 ...
- 20145206邹京儒《网络对抗》逆向及Bof基础实践
20145206邹京儒<网络对抗>逆向及Bof基础实践 1 逆向及Bof基础实践说明 1.1 实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:ma ...
- 东北农业大学大学计算机基础作业答案,大学计算机基础实践教学改革的研究
针对目前大学计算机基础实践教学中存在的问题,提出了教学内容.教学方法.教学模式.考试形式等方面的改革方案,并在实践中不断践行和完善,达到预期的教学效果,为提高实践教学质量提供参考. 第 o第. 卷期 ...
- python 从入门到实践 pdf_python入门基础实践课,带你有效的学习python
你还在枯燥的一个人学python吗?你尝试过python入门基础实践课吗? python入门实践课,带你快速了解python,它与你平时学python,有什么不一样的地方呢: 1.完全免费 第一次学p ...
最新文章
- Python游戏开发,Pygame模块,Python从零开始带大家实现魔塔小游戏
- mpvue 小程序如何开启下拉刷新,上拉加载?
- 汇编中的word ptr
- sizeof()使用注意
- jsp九大内置对象与servlet中java对象
- windows Tracert命令
- 潘在亮:给业务开发提供黑科技装备的“测试Q博士”
- 复数类--重载运算符3+
- redis学习-分布式数据库CAP原理
- Apache 2.4.28的安装
- BOM offset client scroll
- 用把位帮助记忆吉他音阶在指板上的分布
- Shell判断路径是否存在
- 计算机键盘交替换键是,用了这么久电脑,还不知道键盘上的Ctrl、Tab、Alt是啥英文?快来涨知识!...
- 玩转Excel系列-index+match查找函数用法
- 如何将py文件转化为exe
- 微信公众号开启服务器配置流程及注意事项
- 课程设计 --- 黑白棋中的 AI
- 外贸ERP系统的操作有什么特点?中小企业适合选择吗?
- 知乎热议: Java, Go和Python那个前景好?
热门文章
- 斐波拉契(Fibonacci)数列
- 开天机型系统镜像及软件资源网盘链接
- 王多鱼噩梦:MIT的机器狗学会守门了,拦截率87.5%,比顶尖运动员还高!
- %3c?php+eval,callback噩梦:解析著名CMS框架Drupal SQL注入漏洞
- css3字体闪烁炫酷,css3 scale动画 字体、输入框等会闪烁,怎么解决?
- vue+summernote富文本编辑器
- dell灵越笔记本后盖怎么拆_戴尔灵越5000笔记本拆解 内部做工相当出众
- 换零钱程序c语言,《SICP》换零钱的递归法与迭代法
- AI智慧工地视频分析系统 yolov7
- 购买mysql服务器时需要考虑的问题