53522可以改成你的端口(云服务器记得开权限),注意/var/lib/onlyoffice/documentserver/App_Data/cache/files
cache/files
,是通过docker exec -it 3a3afa942911(这个是docker ps看到的container的id) bash
find / -name *.docx
这样搜索所有的docx格式的文件,找到的cache files,这里存的是修改后还未保存的filemkdir -p app/onlyoffice/DocumentServer/logs
mkdir -p app/onlyoffice/DocumentServer/data
mkdir -p app/onlyoffice/DocumentServer/lib
mkdir -p app/onlyoffice/DocumentServer/cache/files
mkdir -p app/onlyoffice/DocumentServer/db
运行docker:
sudo docker run -i -t -d -p 53522:80 --restart=always \
-v `pwd`/app/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
-v `pwd`/app/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
-v `pwd`/app/onlyoffice/DocumentServer/lib:/var/lib/onlyoffice \
-v `pwd`/app/onlyoffice/DocumentServer/cache/files:/var/lib/onlyoffice/documentserver/App_Data/cache/files \
-v `pwd`/app/onlyoffice/DocumentServer/db:/var/lib/postgresql onlyoffice/documentserver
git clone https://github.com/ONLYOFFICE/document-server-integration.git
cd document-server-integration/web/documentserver-example/java-spring
# server.address=127.0.0.1
# 8181防火墙开放是的
server.port=8181
files.docservice.url.site=http://xxx.xxx.xxx.xxx/
mvn clean
mvn spring-boot:run
<!--onlyoffice 编辑器-->
<template>
<div id="editorDiv"></div>
</template>
<script>
import {handleDocType} from '../../../utils'
export default {
name: 'editor',
props: {
option: {
type: Object,
default: () => {
return {}
}
}
},
data() {
return {
doctype: '',
}
},
created() {
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = window.SITE_CONFIG.onlyOfficeUrl + '/web-apps/apps/api/documents/api.js';
document.getElementsByTagName('head')[0].appendChild(script);
console.log(document.getElementsByTagName('head')[0])
},
mounted() {
if (this.option.url) {
this.setEditor(this.option)
}
},
methods: {
setEditor(option) {
this.doctype = handleDocType(option.fileType)
let config = {
document: {
fileType: option.fileType,
key: option.key,
title: option.title,
permissions: {
comment: true,
download: true,
modifyContentControl: true,
modifyFilter: true,
print: false,
edit: option.isEdit,
fillForms: true,
review: true
},
url: option.url
},
documentType: this.doctype,
editorConfig: {
callbackUrl: option.editUrl,
lang: 'en',
customization: {
commentAuthorOnly: false,
comments: true,
compactHeader:false,
compactToolbar:true,
feedback:false,
plugins:true
},
user:{
id:option.user.id,
name:option.user.name
},
mode:option.model ? option.model : 'edit',
},
width: '100%',
height: '100%',
token:option.token
}
console.log(`setEditor ==> option:`, option, 'config:', config)
this.$nextTick(() => {
setTimeout(function () {
let docEditor = new window.DocsAPI.DocEditor('editorDiv', config)
}, 1500);
})
},
},
watch: {
option: {
handler: function (n, o) {
this.setEditor(n)
this.doctype = handleDocType(n.fileType)
},
deep: true,
}
}
}
</script>
<style scoped>
</style>
使用前面的editor
<template>
<el-container direction="vertical">
<el-row>
<editor ref="fileEditor" :option="option"></editor>
</el-row>
</el-container>
</template>
<script>
import editor from './editor'
import Vue from 'vue'
import {getStorageLocation} from '../../../service/fileService'
export default {
name: 'fView',
components: { editor },
data() {
return {
fileList: [],
headers: {
token: Vue.cookie.get("token")
},
uploadUrl: '',
serverPath: '',
fileName: '',
storagePath: '',
id: 0,
option: {
url: '',
isEdit: true,
fileType: '',
title: '',
token: Vue.cookie.get("token"),// 看实际的请求,没有也可以Vue.ls.get(ACCESS_TOKEN)
user: {
id: '',
name: ''
},
mode: 'view',
editUrl: '',
key: ''
},
};
},
created() {
this.id = this.$route.params.id;
this.fileName = this.$route.query.fileName;
let userInfo = {} //Vue.ls.get(USER_INFO)
this.option.user.id = '1'; //userInfo.id
this.option.user.name = 'John Smith'; //userInfo.realname
let that = this;
setTimeout(function () {
that.getFile()
}, 1500);
},
methods: {
updateOption () {
this.option.fileType = this.fileName.split(".")[1]
this.option.url = this.serverPath + '/download?fileName=' + this.fileName + '&userAddress=' + this.storagePath
this.option.title = this.fileName
console.log(`this.option:`, this.option)
},
getFile() {
let storagePath;
getStorageLocation().then(res => {
storagePath = res.data;
})
this.option.url = window.SITE_CONFIG.onlyOfficeServerPath + '/previewFile?filePath=' + this.fileName
this.option.title = this.fileName
this.option.fileType = this.fileName.split(".")[1]
this.option.model = 'edit' //docx txt excel编辑测试ok
this.option.editUrl = window.SITE_CONFIG.onlyOfficeCallBackUrl + '?token=' + Vue.cookie.get("token") + '&fileName=' + this.fileName
setTimeout(function () {
if(document.getElementsByName('frameEditor')[0]){
document.getElementsByName('frameEditor')[0].setAttribute('width', '100%')
document.getElementsByName('frameEditor')[0].setAttribute('height', screen.availHeight * 0.9+'px')
}
}, 3000);
}
},
watch: {
}
}
</script>
<style scoped>
</style>
如果用的是shiro,要放开权限
filterMap.put(“/onlyOffice/**”, “anon”);//从docker中请求后端没有带token,这里不放行会报错
filterMap.put(“/plan/previewFile”, “anon”);//从docker中请求后端没有带token,这里不放行会报错
filterMap.put(“/plan/saveOnlyOfficeFile”, “anon”);//从docker中请求后端没有带token,这里不放行会报错
在保存时,我遇到了403错误,访问docker中的cache/files失败,所以前面把cache/files暴露出来,访问docker中的cache/files转为访问本地的cache/files中对应的文件。
整理了一半有点累了…直接放controller。。。
后面再说完整的弄到一个项目吧。。。
本质上就是,从本地文件系统中下载文件(download接口)
保存接口则是:普通修改,status是1,socket一直建立连接。文档关闭后10s,会产生status为2的回调,走下面的track接口,从cache中获取output.docx,覆盖到之前的路径,也就是保存成功!
其它都是文件系统的操作
更多见官方的示例 https://github.com/ONLYOFFICE/document-server-integration.git
package com.ironpan.integration.modules.office.controller;
import com.alibaba.fastjson.JSONObject;
import com.ironpan.integration.modules.office.utils.DefaultFileUtility;
import com.ironpan.integration.modules.office.utils.FileStorageMutator;
import com.ironpan.integration.modules.office.utils.LocalFileStorage;
import com.ironpan.integration.modules.office.entity.R;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Scanner;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
@CrossOrigin("*")
@Controller
public class FileController {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private DefaultFileUtility fileUtility;
@Autowired
private FileStorageMutator storageMutator;
@Autowired
private LocalFileStorage localFileStorage;
@Value("${cache.file.path}")
private String cacheFilePath;
@GetMapping(path = "/download")
public ResponseEntity<Resource> download(HttpServletRequest request, @RequestParam("fileName") String fileName){
try{
logger.info("in /download");
return downloadFile(fileName);
} catch(Exception e){
return null;
}
}
@PostMapping("/upload")
@ResponseBody
public R upload(@RequestParam("file") MultipartFile file){
try {
if (file.isEmpty()) {
return R.error("文件不能为空");
}
logger.info("开始文件上传");
String fullFileName = file.getOriginalFilename(); // get file name
String fileExtension = fileUtility.getFileExtension(fullFileName); // get file extension
long fileSize = file.getSize(); // get file size
byte[] bytes = file.getBytes(); // get file in bytes
// check if the file size exceeds the maximum file size or is less than 0
if(fileUtility.getMaxFileSize() < fileSize || fileSize <= 0){
return R.error("File size is incorrect");
}
// check if file extension is supported by the editor
if(!fileUtility.getFileExts().contains(fileExtension)){
return R.error("File type is not supported");
}
String fileNamePath = storageMutator.updateFile(fullFileName, bytes); // update a file
logger.info(" fileNamePath:{}, fullFileName:{}", fileNamePath, fullFileName);
if (StringUtils.isBlank(fileNamePath)){
throw new IOException("Could not update a file"); // if the file cannot be updated, an error occurs
}
fullFileName = fileUtility.getFileNameWithoutExtension(fileNamePath) + fileExtension; // get full file name
return R.ok().put("upload", createUserMetadata(fullFileName)); // 第一个参数uid, create user metadata and return it
} catch (Exception e) {
e.printStackTrace();
}
return R.error("Something went wrong when uploading the file");
}
@RequestMapping(value = "/track", method = RequestMethod.POST)
public void saveOnlyOfficeFile(HttpServletRequest request, HttpServletResponse response,
@RequestParam("fileName") String fileName) throws IOException, ServletException {
String planPath = localFileStorage.getStorageLocation();
PrintWriter writer = response.getWriter();
String pathForSave = planPath + "/" + fileName;//这里应该是加上文件名,这样才能把修改后的文件覆盖上去
Scanner scanner = new Scanner(request.getInputStream()).useDelimiter("\\A");
String body = scanner.hasNext() ? scanner.next() : "{}";
logger.info("接收的回调body参数为[{}]", body);
// logger.info("接收的回调ReportProjectPlan参数为[{}]", JSON.toJSON(params));
JSONObject jsonObj = JSONObject.parseObject(body);
//普通修改,status是1,socket一直建立连接。文档关闭后10s,会产生status为2的回调,走下面,从cache中获取output.docx,覆盖到之前的路径,也就是保存成功
if( jsonObj.getInteger("status") != null &&
(jsonObj.getInteger("status") == 2 || jsonObj.getInteger("status") == 6))
{
String downloadUri = jsonObj.getString("url");
logger.info("downloadUri: " + downloadUri);
// URL url = new URL(downloadUri);
// java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
//java.io.IOException: Server returned HTTP response code: 403 for URL:
//http://10.10.111.22:53522/ cache/files/ce2a410b5a7fdc8b224d_8816/output.docx/output.docx?md5=TbOcPEu8ZR3yKmljGQSwSA&expires=1650540479&filename=output.docx
String[] split = downloadUri.split("/");
int filesIndex = -1;
for(int i = 0; i < split.length; i++){
if(split[i].equals("files")) {
filesIndex = i;
}
}
//替换成 this.cacheFilePath + ce2a410b5a7fdc8b224d_8816/output.docx
String newPath = this.cacheFilePath + split[filesIndex + 1] + "/" +split[filesIndex + 2];
Path filePath = Paths.get(newPath);
URI uri = filePath.toUri();
File file = new File(uri);
logger.info("新的url:{}, File(uri).exists():{}", newPath, file.exists());
InputStream stream = new FileInputStream(file);
// InputStream stream = connection.getInputStream();
File fileToSave = new File(pathForSave);
if (!fileToSave.exists()) { //文件不存在则创建文件,先创建目录
File dir = new File(fileToSave.getParent());
dir.mkdirs();
fileToSave.createNewFile();
logger.info("文件路径:{} 对应的文件不存在,先创建目录", pathForSave);
}
try (FileOutputStream out = new FileOutputStream(fileToSave)) {//默认覆盖
int read;
final byte[] bytes = new byte[1024];
while ((read = stream.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
out.flush();
logger.info("写入文件成功");
}
// connection.disconnect();
}else{
logger.info("不支持的status, jsonObj.getInteger(\"status\"): {}", jsonObj.getInteger("status"));
}
writer.write("{\"error\":0}");
}
@GetMapping(path = "/storageLocation")
@ResponseBody
public String getStorageLocation(){
return localFileStorage.getStorageLocation();
}
// create user metadata //第一个参数:String uid,
private String createUserMetadata(String fullFileName) {//Integer.parseInt(uid)
String documentType = fileUtility.getDocumentType(fullFileName).toString().toLowerCase(); // get document type
return "{ \"filename\": \"" + fullFileName + "\", \"documentType\": \"" + documentType + "\" }";
}
// download data from the specified file
private ResponseEntity<Resource> downloadFile(String fileName) throws IOException {
Resource resource = storageMutator.loadFileAsResource(fileName); // load the specified file as a resource
String contentType = "application/octet-stream";
logger.info("fileName:" + fileName + " MediaType.parseMediaType(contentType):" + MediaType.parseMediaType(contentType)
+ ", HttpHeaders.CONTENT_DISPOSITION:" + HttpHeaders.CONTENT_DISPOSITION
+ ", headerValues:" + "attachment; filename=\"" + resource.getFilename() + "\""
+ ", resource.isFile:" + resource.isFile()
+ ", resource.getURI:" + resource.getURI()
+ ", resource.getURL:" + resource.getURL()
+ ", resource.getFilename:" + resource.getFilename()
);
// create a response with the content type, header and body with the file data
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
}
添加路由:
{path: '/fileEditor/:id', name: 'fileEditor', component: () => import('@/views/modules/training/fView.vue')},
使用:
<el-button type="primary" size="mini" @click="viewFile(fileInfo)">预览</el-button>
viewFile (fileInfo) {
console.log(`文件信息:`, fileInfo);
let routeUrl = this.$router.resolve({
path: '/fileEditor/' + fileInfo.attachId,
query: {
id : this.fileId, fileName: fileInfo.attachTitle
}
});
window.open(routeUrl.href,'_blank')
}
参考链接:
先在服务器上用docker配置
How to integrate online editors into your own website on Java Spring
java+vue+onlyoffice的简单集成
Springboot Vue element-ui前后端分离实现onlyoffice在线协同编辑Demo
OnlyOffice - 在webpack项目的页面上展示 PowerPoint
How it works-Opening file