您好,登錄后才能下訂單哦!
一:概述
onlyoffice是做什么用的在這里就不做解釋了,能看到這篇博客的我相信都是了解的,本文主要講解onlyoffice在使用過程中要注意的問題。
使用環(huán)境:window10 + VMware Workstation Pro14 + CentOS-7-x86_64-Minimal + Docker 18.03.1-ce + onlyoffice
onlyoffice官網(wǎng):https://www.onlyoffice.com/
onlyoffice API: https://api.onlyoffice.com/editors/basic
onlyoffice API要著重看下,里面闡述了onlyoffice的運(yùn)行原理,同時(shí)里面有基于多種語(yǔ)言的樣例代碼,基本理解onlyoffice的運(yùn)行原理后再結(jié)合樣例代碼驗(yàn)證必能事半功倍。在這首先闡述下API中設(shè)計(jì)到的幾個(gè)概念:
1,document manager : 文檔管理器,等同于一個(gè)界面中的文件列表,該列表就是文檔管理器【我們自己編寫,不一定需要】。
2,document storage service :文檔存儲(chǔ)服務(wù),即管理文檔存放的模塊,很多時(shí)候就是我們上傳文件然后將其保存在服務(wù)器上,網(wǎng)上也有使用nextcloud作為存儲(chǔ)的【我們自己編寫或使用存儲(chǔ)文件的軟件】。
3,document editor : 文檔編輯器,就是文檔編輯窗口【onlyoffice提供的前端頁(yè)面插件】
4,document editing service : 文檔編輯服務(wù),從文檔存儲(chǔ)服務(wù)獲取要編輯的文檔,轉(zhuǎn)換成Office OpenXML格式后傳給文檔編輯器,編輯期間文檔編輯器與文檔編輯服務(wù)長(zhǎng)期交互【onlyoffice提供的后臺(tái)服務(wù)】
二:實(shí)踐(基于樣例代碼)
首先從官網(wǎng)下載基于java開發(fā)的樣例代碼“Java Example.zip”,該代碼是使用maven構(gòu)建的webapp,在webapp目錄下有兩個(gè)文件“index.jsp”和"editor.jsp"; 同時(shí)在resources目錄下有個(gè)setting.properties配置文件,這三個(gè)文件是我們首先要著重看的地方。
如果index.jsp能順利運(yùn)行起來(lái),那界面應(yīng)該是這樣的:
首先我們可以點(diǎn)擊“Choose file”按鈕上傳一個(gè)要編輯的文件,這時(shí)會(huì)調(diào)用 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() + "\"}");
}
}
深入的不在闡述,經(jīng)過幾番周折后,最終是將文件保存在硬盤上。這個(gè)上傳操作在實(shí)際應(yīng)用onlyoffice的項(xiàng)目中也應(yīng)該會(huì)有,實(shí)現(xiàn)方法和存放的路徑根據(jù)實(shí)際調(diào)整即可。
上傳過程界面如下:
當(dāng)點(diǎn)擊“Edit”按鈕后,將會(huì)通過EditorServlet跳轉(zhuǎn)到“editor.jsp頁(yè)面”,并將需要的參數(shù)傳遞過去。其中關(guān)鍵的代碼為
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地址十分重要,也是在開發(fā)中需要重點(diǎn)關(guān)注的地方,很多報(bào)出的以下錯(cuò)誤基本都是地址不對(duì)導(dǎo)致的。
重點(diǎn)說(shuō)說(shuō)這兩個(gè)配置的作用
1)document屬性下的url配置是onlyoffice的編輯服務(wù)用于獲取文檔的地址,也就是說(shuō),我們必須保證在docker中是必須能訪問到的地址, 通過wget命令嘗試在docker所在的服務(wù)器中是否能夠訪問。
2) editorConfig屬性下的callbackUrl配置是onlyoffice的編輯服務(wù)回調(diào)的,該回調(diào)的作用是告知你編輯后文檔的下載地址,以便更新原始文件,所以我們也得保證docker能夠訪問到該地址。我們可以自己編寫個(gè)用于回調(diào)的方法,例如:
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) {
/*
* 當(dāng)我們關(guān)閉編輯窗口后,十秒鐘左右onlyoffice會(huì)將它存儲(chǔ)的我們的編輯后的文件,,此時(shí)status = 2,通過request發(fā)給我們,我們需要做的就是接收到文件然后回寫該文件。
* */
/*
* 定義要與文檔存儲(chǔ)服務(wù)保存的編輯文檔的鏈接。當(dāng)狀態(tài)值僅等于2或3時(shí),存在鏈路。
* */
String downloadUri = (String) jsonObj.get("url");
System.out.println("====文檔編輯完成,現(xiàn)在開始保存編輯后的文檔,其下載地址為:" + 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();
//更換為實(shí)際的路徑
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的服務(wù)返回{"error":"0"}的信息,這樣onlyoffice會(huì)認(rèn)為回調(diào)接口是沒問題的,這樣就可以在線編輯文檔了,否則的話會(huì)彈出窗口說(shuō)明
* */
writer.write("{\"error\":0}");
}
接下來(lái)再說(shuō)說(shuō)setting.properties配置文件,里面的配置內(nèi)容如下:
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的地址,如果訪問到,其結(jié)果應(yīng)該如下:
以上就是使用onlyoffice的兩個(gè)關(guān)鍵點(diǎn),文檔下載地址和onlyoffice回調(diào)地址
三:實(shí)踐(基于Springboot)
1,首先配置文件存放路徑和對(duì)應(yīng)暴露的訪問地址
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// addResourceLocations指的是文件放置的目錄,addResourceHandler指的是對(duì)外暴露的訪問路徑
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/");
}
/**
* 統(tǒng)一處理沒啥業(yè)務(wù)邏輯處理的controller請(qǐng)求,實(shí)現(xiàn)代碼的簡(jiǎn)潔
*
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index").setViewName("index");
}
/**
* SpringMVC的路徑參數(shù)如果帶“.”的話,“.”后面的值將被忽略 .../pathvar/xx.yy 解析得到:xx
*/
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
// 通過設(shè)置為false使其可以接受"."后的但是
configurer.setUseSuffixPatternMatch(false);
}
}
此處我們將上傳文件保存在了“D:/uploadfile/”目錄下,該地址最好是在配置文件中配置,例如上面的setting.properties文件中。
2,創(chuàng)建用于編輯文檔的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,創(chuàng)建用于保存修改后文件的Controller
/*
* 用于保存修改后的文件
* */
@Controller
@RequestMapping("/savefilectrl")
public class SaveFileController {
/**
* 文檔編輯服務(wù)使用JavaScript API通知callbackUrl,向文檔存儲(chǔ)服務(wù)通知文檔編輯的狀態(tài)。文檔編輯服務(wù)使用具有正文中的信息的POST請(qǐng)求。
* https://api.onlyoffice.com/editors/callback
* 參數(shù)示例:
{
"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) {
/*
* 當(dāng)我們關(guān)閉編輯窗口后,十秒鐘左右onlyoffice會(huì)將它存儲(chǔ)的我們的編輯后的文件,,此時(shí)status = 2,通過request發(fā)給我們,我們需要做的就是接收到文件然后回寫該文件。
* */
/*
* 定義要與文檔存儲(chǔ)服務(wù)保存的編輯文檔的鏈接。當(dāng)狀態(tài)值僅等于2或3時(shí),存在鏈路。
* */
String downloadUri = (String) jsonObj.get("url");
System.out.println("====文檔編輯完成,現(xiàn)在開始保存編輯后的文檔,其下載地址為:" + 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的服務(wù)返回{"error":"0"}的信息,這樣onlyoffice會(huì)認(rèn)為回調(diào)接口是沒問題的,這樣就可以在線編輯文檔了,否則的話會(huì)彈出窗口說(shuō)明
* */
writer.write("{\"error\":0}");
}
}
4,項(xiàng)目中使用的Freemarker,所以編輯頁(yè)面修改為
<!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的配置
#設(shè)定ftl文件路徑
spring.freemarker.template-loader-path=classpath:/templates/freemarker
spring.freemarker.suffix=.html
spring.freemarker.content-type=text/html
spring.freemarker.templateEncoding=UTF-8
以上就是核心部分的代碼,重點(diǎn)是要保證配置準(zhǔn)確。
至于docker和onlyoffice安裝過程就不贅述了,最后看下docker運(yùn)行情況
附:onlyoffice安裝過程
1,安裝docer
yum install docker -y
2,啟動(dòng)docker服務(wù)
systemctl start docker
3,拉取onlyoffice
docker pull onlyoffice/documentserver
4,啟動(dòng)Document Server鏡像,并映射80端口至本地(前面?zhèn)€80)。
sudo docker run -i -t -d -p 80:80 onlyoffice/documentserver
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。