1 onlyoffice
1.1 简介
ONLYOFFICE Docs是一个开源办公套件,包括文本文档、电子表格、演示文稿和可填写表格的编辑器。它提供以下功能:
- 创建、编辑和查看文本文档、电子表格、演示文稿和可填写的表格;
- 与其他队友实时协作处理文件。
1.2 DOCKER安装
docker run -i -t -d -p 8831:80 --restart=always \
-v /home/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
-v /home/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
-v /home/onlyoffice/DocumentServer/lib:/var/lib/onlyoffice \
-v /home/onlyoffice/DocumentServer/db:/var/lib/postgresql onlyoffice/documentserver:6.4.2
映射端口8831,直接访问ip:8831,出现下图,证明安装成功
1.3 使用
- 使用分2部分 1.前端应用文档编辑服务器js操作文档 2.后端编写回调接口处理编辑过的文档
- 我这边项目采用的minio存储的文档,所以onlyoffice操作的是云文件,流程如下:
graph TD
项目前端代码集成onlyoffice --> 根据云文档url打开文件并编辑 --> 编辑之后调用后端提供的回调函数 -->回调函数将编辑后的文件存回文件服务器
1.3.1 前端js
引入js
- src = "https://documentserver/web-apps/apps/api/documents/api.js
- documentserver 为1.2 按照的onlyoffice 的 ip:端口 列子如下:
<script>
var href = 'http://47.111.142.000:8831'
var head = document.getElementsByTagName('head')[0]
var script = document.createElement('script')
script.type = 'text/javascript'
script.src = href + '/web-apps/apps/api/documents/api.js'
head.appendChild(script);
(() => {
var htmlRoot = document.getElementById('htmlRoot')
var theme = window.localStorage.getItem('__APP__DARK__MODE__')
if (htmlRoot && theme) {
htmlRoot.setAttribute('data-theme', theme)
theme = htmlRoot = null
}
})();
</script>
组件部分
<!--onlyoffice 编辑器-->
<template>
<div id="vabOnlyOffice"></div>
</template>
<script>
export default {
name: 'VabOnlyOffice',
props: {
option: {
type: Object,
default: () => {
return {}
},
},
},
data () {
return {
doctype: '',
docEditor: null,
}
},
beforeDestroy () {
if (this.docEditor !== null) {
this.docEditor.destroyEditor()
this.docEditor = null
}
},
watch: {
option: {
handler: function (n) {
this.setEditor(n)
this.doctype = this.getFileType(n.fileType)
},
deep: true,
},
},
mounted () {
if (this.option.url) {
this.setEditor(this.option)
}
},
methods: {
async setEditor (option) {
if (this.docEditor !== null) {
this.docEditor.destroyEditor()
this.docEditor = null
}
this.doctype = this.getFileType(option.fileType)
let config = {
document: {
//后缀
fileType: option.fileType,
key: option.key || '',
title: option.title,
permissions: {
edit: option.isEdit,//是否可以编辑: 只能查看,传false
print: option.isPrint,
download: true,
// "fillForms": true,//是否可以填写表格,如果将mode参数设置为edit,则填写表单仅对文档编辑器可用。 默认值与edit或review参数的值一致。
review: true //跟踪变化
},
url: option.url,
},
documentType: this.doctype,
editorConfig: {
callbackUrl: option.editUrl,//"编辑word后保存时回调的地址,这个api需要自己写了,将编辑后的文件通过这个api保存到自己想要的位置
lang: option.lang,//语言设置
//定制
customization: {
autosave: true,//是否自动保存
chat: true,
comments: false,
help: true,
// "hideRightMenu": false,//定义在第一次加载时是显示还是隐藏右侧菜单。 默认值为false
//是否显示插件
plugins: true,
mentionShare: true,
// logo: {
// image: "https://example.com/logo.png",
// imageDark: "https://example.com/dark-logo.png",
// url: "http://hy.kingdomsoft.cn/scmy"
// },
},
forcesave: true,
user: {
id: option.user.id,
name: option.user.name
},
mode: option.model ? option.model : 'edit',
},
width: '100%',
height: '100%',
token: option.token || ''
}
// eslint-disable-next-line no-undef,no-unused-vars
this.docEditor = new DocsAPI.DocEditor('vabOnlyOffice', config)
},
getFileType (fileType) {
let docType = ''
let fileTypesDoc = [
'doc', 'docm', 'docx', 'dot', 'dotm', 'dotx', 'epub', 'fodt', 'htm', 'html', 'mht', 'odt', 'ott', 'pdf', 'rtf', 'txt', 'djvu', 'xps',
]
let fileTypesCsv = [
'csv', 'fods', 'ods', 'ots', 'xls', 'xlsm', 'xlsx', 'xlt', 'xltm', 'xltx',
]
let fileTypesPPt = [
'fodp', 'odp', 'otp', 'pot', 'potm', 'potx', 'pps', 'ppsm', 'ppsx', 'ppt', 'pptm', 'pptx',
]
if (fileTypesDoc.includes(fileType)) {
docType = 'text'
}
if (fileTypesCsv.includes(fileType)) {
docType = 'spreadsheet'
}
if (fileTypesPPt.includes(fileType)) {
docType = 'presentation'
}
return docType
}
},
}
</script>
引用传参
<vab-only-office :option="option" />
option属性
- templateUrl 为minio文件服务器对应文件的url
- editUrl 为后端项目开发的回调函数
const getFile = (record) => {
const userinfo = useUserStore()
let type = url(record.url)
show.value = true
option.url = record.url ? record.url : templateUrl
option.isEdit = true
option.lang = 'zh-CN'
option.title = record.name
option.fileType = type
option.isPrint = false
option.user = { id: userinfo.getUserInfo.id, name: userinfo.getUserInfo.name }
option.editUrl = `${editsUrl}?reportTemplateId=` + record.id
// console.log(111)
}
1.3.2 回调函数
@PostMapping("/callback")
@ApiOperation(value = "编辑文档时回调接口")
public void saveDocumentFile(HttpServletRequest request, HttpServletResponse response,
@RequestParam("reportTemplateId") Integer reportTemplateId) throws IOException {
AssertUtil.notNull(reportTemplateId, "报告模板不可为空");
PrintWriter writer = response.getWriter();
Scanner scanner = new Scanner(request.getInputStream()).useDelimiter("\A");
String body = scanner.hasNext() ? scanner.next() : "";
log.info("body:{}", body);
JSONObject jsonObj = JSONUtil.parseObj(body);
Integer status = jsonObj.getInt("status");
/**
* 定义文档的状态。可以有以下值:
* 1 - 正在编辑文档,
* 2 - 文档已准备好保存,
* 3 - 发生文档保存错误,
* 4 - 文档已关闭,没有任何更改,
* 6 - 正在编辑文档,但保存了当前文档状态,
* 7 - 强制保存文档时发生错误。
*/
if (status == 2) {
String downloadUri = jsonObj.getStr("url");
log.info("downloadUri:{}", downloadUri);
reportTemplateService.saveReportTemplate(downloadUri,reportTemplateId);
}
writer.write("{"error":0}");
}
它在文档关闭以进行编辑后10 秒收到,该用户的标识符是最后一个将更改发送到文档编辑服务的用户。使用来自对文件进行最后更改的用户的callbackUrl 。
回调函数必须保证onlyoffice能正常访问到的,否则会提示文档无法保存。权限也会导致此问题
这样就配置好了,如下就可以在线编码文档了
api文档 api.onlyoffice.com/editors/bas…
2 集成poi-tl
poi-tl(poi template language)是Word模板引擎,使用Word模板和数据创建很棒的Word文档。
- 开发文档 deepoove.com/poi-tl/
2.1 依赖
<dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.12.0</version> </dependency>
可能会引起依赖冲突 poi-tl依赖的apache-poi版本是5.2.2+,如果你的项目引用了低版本,请升级或删除。
2.2 基础配置
- 文本 {{var}}
- 图片标签以@开始 {{@var}}
- 表格标签以#开始 {{#var}}
- 列表标签以*开始 {{*var}}
- 区块对由前后两个标签组成,开始标签以?标识,结束标签以/标识 {{?sections}}{{/sections}}
- 嵌套又称为导入、包含或者合并,以+标识 {{+var}}
2.3 多图片使用
- 模板配置
{{?videoDatas}}{{@url}}{{/videoDatas}}
- 后端数据填充
private void putPig(BisExperiment bisExperiment, Map<String, Object> map) {
String videoData = bisExperiment.getVideoData();
if (StrUtil.isNotBlank(videoData)) {
List<Map<String, PictureRenderData>> videoDatas = Lists.newArrayList();
Arrays.asList(videoData.split(StringPool.COMMA)).stream().forEach(t -> {
Map<String, PictureRenderData> pictureRenderDataHashMap = new HashMap<>();
pictureRenderDataHashMap.put("url", Pictures.ofUrl(t).create());
videoDatas.add(pictureRenderDataHashMap);
});
map.put("videoDatas", videoDatas);
}
}
2.4 表格使用
- 模板配置
- 后端数据填充
private void putEquipTable(BisExperiment bisExperiment, Map<String, Object> map, ConfigureBuilder builder) {
List<ExperimentEquipment> equipments = experimentEquipmentService.listByIds(Arrays.asList(bisExperiment.getEquipmentIds().split(StringPool.COMMA)));
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
//这里可以指定一个config类,用来指定一些规则,也可以改变模板中{{}}的这种格式
builder.bind("equipments",policy);
map.put("equipments", equipments);
}
2.5 图表使用
- 模板配置
- 插入图表
2. 图表配置
- 后端数据填充
/**
* 组装曲线数据
* @param map
*/
private void putChart(Map<String, Object> map) {
ChartMultiSeriesRenderData chart = Charts
.ofMultiSeries("ChartTitle", new String[] { "中文", "English" })
.addSeries("countries", new Double[] { 15.0, 6.0 })
.addSeries("speakers", new Double[] { 223.0, 119.0 })
.create();
map.put("barChart", chart);
}
2.6 区块对使用
区块对由前后两个标签组成,开始标签以?标识,结束标签以/标识:{{?sections}}{{/sections}}
区块对开始和结束标签中间可以包含多个图片、表格、段落、列表、图表等,开始和结束标签可以跨多个段落,也可以在同一个段落,但是如果在表格中使用区块对,开始和结束标签必须在同一个单元格内,因为跨多个单元格的渲染行为是未知的。
- 模板配置
- 后端数据填充
@Data
public class EquipmentPoiDataDTO {
@ApiModelProperty("试验设备名称")
private String equipName;
@ApiModelProperty("设定参数表格")
private TableRenderData settingParam;
@ApiModelProperty("曲线数据")
private ChartMultiSeriesRenderData lineChart;
}
map.put("equipmentDatas",equipmentPoiDataDTOS);
2.7 模板导出
- 根据url获取模板流
//下载模板的文件流
InputStream inputStream = fileService.download(template.getUrl().replace(endpoint + bucket, StringPool.EMPTY));
- 根据模板填充数据,并获取输出流
private ByteArrayInputStream getByteArrayInputStream(Map<String, Object> map, InputStream inputStream, ConfigureBuilder builder) throws Exception {
//创建文件输出流
ByteArrayOutputStream fos = new ByteArrayOutputStream();
XWPFTemplate compile = XWPFTemplate.compile(inputStream,builder.build());
compile.render(map);
compile.writeAndClose(fos);
// //创建文档
// MyXWPFDocument xwpfDocument = new MyXWPFDocument(inputStream);
// WordExportUtil.exportWord07(xwpfDocument, map);
// //上传数据
// xwpfDocument.write(fos);
//转换成输入流
return new ByteArrayInputStream(fos.toByteArray());
}
- 最后导出,或者重新存储回MINIO
3 效果展示
4 总结
通过onlyoffice在线配置模板,模板通过poi-tl语法配置我们要的效果,通过minio存储模板,和导出根据模板生成文档,最后通过onlyoffice对生成的文档进行修改,达到全程在线操作的效果.