一:概述javascript
onlyoffice是作什麼用的在這裏就不作解釋了,能看到這篇博客的我相信都是瞭解的,本文主要講解onlyoffice在使用過程當中要注意的問題。css
使用環境:window10 + VMware Workstation Pro14 + CentOS-7-x86_64-Minimal + Docker 18.03.1-ce + onlyofficehtml
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開發的樣例代碼「Java Example.zip」,該代碼是使用maven構建的webapp,在webapp目錄下有兩個文件「index.jsp」和"editor.jsp"; 同時在resources目錄下有個setting.properties配置文件,這三個文件是咱們首先要着重看的地方。web
若是index.jsp能順利運行起來,那界面應該是這樣的:spring
首先咱們能夠點擊「Choose file」按鈕上傳一個要編輯的文件,這時會調用 controllers.IndexServlet中的doPost方法。其處理方法爲:docker
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的項目中也應該會有,實現方法和存放的路徑根據實際調整便可。json
上傳過程界面以下:api
當點擊「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 block e.printStackTrace(); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } /* * status = 1,咱們給onlyoffice的服務返回{"error":"0"}的信息,這樣onlyoffice會認爲回調接口是沒問題的,這樣就能夠在線編輯文檔了,不然的話會彈出窗口說明 * */ writer.write("{\"error\":0}"); }
接下來再說說setting.properties配置文件,裏面的配置內容以下:
filesize-max=5242880 storage-folder=app_data files.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=120000 files.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 { @Override public 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 */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); registry.addViewController("/index").setViewName("index"); } /** * SpringMVC的路徑參數若是帶「.」的話,「.」後面的值將被忽略 .../pathvar/xx.yy 解析獲得:xx */ @Override public 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()) ; //要編輯的文檔訪問url model.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 block e.printStackTrace(); } catch (ParseException e) { // TODO Auto-generated catch block e.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