有需求是实现前端页面可以对word文档进行编辑,并且可以进行保存,于是一顿搜索,找到开源第三方onlyoffice,实际上onlyOffice有很多功能,例如文档转化、多人协同编辑文档、文档打印等,我们只用到了文档编辑功能。
OnlyOffice的部署
部署分为docker部署方式和本地直接安装的方式,比较两种部署方式,docker是比较简单的一种,因为只要拉取相关镜像,然后启动时配置好对应的配置文件即可。
1、下载镜像
bash
docker pull onlyoffice/documentserver
# x86架构
docker pull registry.cn-hangzhou.aliyuncs.com/qiluo-images/documentserver
# arm架构
docker pull registry.cn-hangzhou.aliyuncs.com/qiluo-images/linux_arm64_documentserver
2、#创建挂载目录
bash
cd /data
mkdir onlyoffice
cd onlyoffice
mkdir logs data lib db
3、启动容器
bash
docker run -i -t -d -p 9898:80 --name onlyoffice --restart=always -e TZ="Asia/Shanghai" \
-v /data/onlyoffice/logs:/var/log/onlyoffice \
-v /data/onlyoffice/data:/var/www/onlyoffice/Data \
-v /data/onlyoffice/lib:/var/lib/onlyoffice \
-v /data/onlyoffice/db:/var/lib/postgresql \
-e JWT_ENABLED=false \
registry.cn-hangzhou.aliyuncs.com/qiluo-images/documentserver:latest
可选)如果需要开启jwt验证用户,则使用下面的命令
bash
docker run -i -t -d -p 9898:80 --name onlyoffice --restart=always -e TZ="Asia/Shanghai" \
-v /data/onlyoffice/logs:/var/log/onlyoffice \
-v /data/onlyoffice/data:/var/www/onlyoffice/Data \
-v /data/onlyoffice/lib:/var/lib/onlyoffice \
-v /data/onlyoffice/db:/var/lib/postgresql \
-e JWT_ENABLED=true \
-e JWT_SECRET=wclflow \
-e JWT_HEADER=token \
registry.cn-hangzhou.aliyuncs.com/qiluo-images/documentserver:latest
如果开启jwtsecret,则通过以下命令,查看jwtsecret是否生效
bash
docker exec onlyoffice /var/www/onlyoffice/documentserver/npm/json -f /etc/onlyoffice/documentserver/local.json 'services.CoAuthoring.secret.session.string'
4、检查是否安装成功,访问web页面
bash
http://ip:9898/welcome/

bash
问题定位:使用内部ip地址访问后端服务
解决方式:需要编辑配置文件允许私有ip通过
进入onlyoffice容器,然后编辑/etc/onlyoffice/documentserver/default.json,
搜索并修改以下字段为true:
"request-filtering-agent" : {
"allowPrivateIPAddress": true,
"allowMetaIPAddress": true
},
最后退出容器,然后重启容器后就好了
OnlyOfficeController
bash
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.Collections;
@Api(value = "OnlyOffice")
@RestController
publicclass OnlyOfficeController {
@Autowired
private IMeetingTableService meetingTableService;
private String meetingMinutesFilePath;
/**
* 传入参数 会议id,得到会议纪要文件流,并进行打开
*/
@ApiOperation(value = "OnlyOffice")
@GetMapping("/getFile/{meeting_id}")
public ResponseEntity<byte[]> getFile(HttpServletResponse response, @PathVariable Long meeting_id)
throws IOException {
MeetingTable meetingTable = meetingTableService.selectMeetingTableById(meeting_id);
meetingMinutesFilePath = meetingTable.getMeetingMinutesFilePath();
if (meetingMinutesFilePath == null || "".equals(meetingMinutesFilePath)) {
returnnull; // 当会议纪要文件为空的时候,就返回null
}
File file = new File(meetingMinutesFilePath);
FileInputStream fileInputStream = null;
InputStream fis = null;
try {
fileInputStream = new FileInputStream(file);
fis = new BufferedInputStream(fileInputStream);
byte[] buffer = newbyte[fis.available()];
fis.read(buffer);
fis.close();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment",
URLEncoder.encode(file.getName(), "UTF-8"));
returnnew ResponseEntity<>(buffer, headers, HttpStatus.OK);
} catch (Exception e) {
thrownew RuntimeException("e -> ", e);
} finally {
try {
if (fis != null) fis.close();
} catch (Exception e) {
// ignore
}
try {
if (fileInputStream != null) fileInputStream.close();
} catch (Exception e) {
// ignore
}
}
}
@CrossOrigin(origins = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS})
@PostMapping("/callback")
public ResponseEntity<Object> handleCallback(@RequestBody CallbackData callbackData) {
// 状态监听
// 参见 https://api.onlyoffice.com/editors/callback
Integer status = callbackData.getStatus();
switch (status) {
case1: {
// document is being edited 文档已经被编辑
break;
}
case2: {
// document is ready for saving, 文档已准备好保存
System.out.println("document is ready for saving");
String url = callbackData.getUrl();
try {
saveFile(url); // 保存文件
} catch (Exception e) {
System.out.println("保存文件异常");
}
System.out.println("save success.");
break;
}
case3: {
// document saving error has occurred, 保存出错
System.out.println("document saving error has occurred, 保存出错");
break;
}
case4: {
// document is closed with no changes, 未保存退出
System.out.println("document is closed with no changes, 未保存退出");
break;
}
case6: {
// document is being edited, but the current document state is saved, 编辑保存
String url = callbackData.getUrl();
try {
saveFile(url); // 保存文件
} catch (Exception e) {
System.out.println("保存文件异常");
}
System.out.println("save success.");
}
case7: {
// error has occurred while force saving the document. 强制保存文档出错
System.out.println("error has occurred while force saving the document. 强制保存文档出错");
}
default: {
// ignore
}
}
// 返回响应
return ResponseEntity.ok(Collections.singletonMap("error", 0));
}
public void saveFile(String downloadUrl) throws URISyntaxException, IOException {
HttpsKitWithProxyAuth.downloadFile(downloadUrl, meetingMinutesFilePath);
}
@Setter
@Getter
publicstaticclass CallbackData {
/** 用户与文档的交互状态 */
Object changeshistory;
Object history;
String changesurl;
String filetype;
Integer forcesavetype;
String key;
/** 文档状态。1:编辑中;2:准备保存;3:保存出错;4:无变化;6:编辑保存;7:强制保存出错 */
Integer status;
String url;
Object userdata;
String[] users;
String lastsave;
String token;
}
}
HttpsKitWithProxyAuth工具类
这是一个HTTP请求工具类,主要用于文件下载等操作(部分代码)
bash
/**
* 下载文件到本地
* @param downloadUrl 下载地址
* @param savePathAndName 保存路径和文件名
*/
public static void downloadFile(String downloadUrl, String savePathAndName) {
HttpGet httpGet = new HttpGet(downloadUrl);
httpGet.setHeader("User-Agent", USER_AGENT);
httpGet.setConfig(requestConfig);
CloseableHttpResponse response = null;
InputStream in = null;
try {
response = getHttpClient().execute(httpGet, HttpClientContext.create());
HttpEntity entity = response.getEntity();
if (entity != null) {
in = entity.getContent();
FileOutputStream out = new FileOutputStream(new File(savePathAndName));
IOUtils.copy(in, out);
out.close();
}
} catch (IOException e) {
logger.error("error", e);
} finally {
try {
if (in != null) in.close();
} catch (IOException e) {
logger.error("error", e);
}
try {
if (response != null) response.close();
} catch (IOException e) {
logger.error("error", e);
}
}
}
加载word文档失败
修改启动的配置文件,将token去除,配置文件位置:
bash
/etc/onlyoffice/documentserver
修改 local.json
将参数token都改为false,去除token:
"token": {
"enable": {
"request": {
"inbox": false,
"outbox": false
},
"browser": false
}
}
修改 default.json
将参数request-filtering-agent改为true,token也改为false,rejectUnauthorized改为false:
bash
"request-filtering-agent": {
"allowPrivateIPAddress": true,
"allowMetaIPAddress": true
},
"token": {
"enable": {
"browser": false,
"request": {
"inbox": false,
"outbox": false
}
}
},
"rejectUnauthorized": false
修改了以上配置参数后,重启服务,再次测试。
