0


springboot+vue2整合onlyoffice实现文档在线协同编辑

Springboot+Vue2整合onlyoffice实现文档在线协同编辑

目录

  1. docker部署onlyoffice镜像
  2. vue2整合onlyoffice
  3. springboot回调接口配置

1.docker部署onlyoffice

  1. # 使用docker拉取并启动onlyoffice镜像docker run -itd--name onlyoffice -p10086:80 -eJWT_ENABLED=true -eJWT_SECRET=mlzhilu_secret onlyoffice/documentserver:8.0
注意:
  • 自7.2版本以后,onlyoffice默认开启jwt,可以手动设置JWT_ENABLED=false以关闭jwt校验,但是关闭jwt校验可能导致链接不安全,本文默认使用8.0版本
  • 如果启用jwt校验的话,需要手动设置secret,否则onlyoffice会随机生成secret,这种情况下就需要等容器启动后到容器内部的/etc/onlyoffice/documentserver/local.json文件中查看
  • documentserver服务默认使用http链接,因此内部端口为80,同时也支持https链接,内部端口443,如需开启https,需要手动添加相应环境变量并生成SSL证书(请自行了解)

2.vue2整合onlyoffice

  • (1)新建vue页面onlyoffice/index.vue
  1. # onlyoffice/index.vue文件内容
  2. <template>
  3. <div style="overflow: scroll;height: calc(100vh - 84px);">
  4. <div :id="onlineEditorId"></div>
  5. </div>
  6. </template>
  7. <script>
  8. import {getOnlyOfficeConfig} from "@/api/documents/menu";
  9. export default {
  10. name: "OnlineEditor",
  11. data() {
  12. return {
  13. // 文档ID
  14. documentId: '',
  15. // 文档版本号
  16. versionId: '',
  17. // 打开文件的方式,true-编辑模式,false-预览模式
  18. isEdit: true,
  19. docEditor: null,
  20. onlineEditorId: 'onlineEditor',
  21. }
  22. },
  23. watch: {
  24. documentId: {
  25. handler: function () {
  26. this.loadScript();
  27. this.initEditor();
  28. },
  29. deep: true,
  30. },
  31. },
  32. activated() {
  33. if (this.documentId) {
  34. this.loadScript();
  35. this.initEditor();
  36. }
  37. },
  38. created() {
  39. // 从路由中获取参数
  40. const documentId = this.$route.query.documentId;
  41. const versionId = this.$route.query.versionId;
  42. const isEdit = this.$route.query.isEdit;
  43. if (versionId) this.versionId = versionId;
  44. if (isEdit) this.isEdit = isEdit;
  45. if (documentId) {
  46. this.documentId = documentId;
  47. this.onlineEditorId += this.documentId;
  48. this.loadScript();
  49. this.initEditor();
  50. }
  51. },
  52. methods: {
  53. // 动态加载onlyoffice api脚本
  54. async loadScript(){
  55. const scriptId = "script_"+this.documentId;
  56. if (document.getElementById(scriptId)===null){
  57. const script = document.createElement('script')
  58. script.id = scriptId;
  59. script.src = "http://10.49.47.24:10086/web-apps/apps/api/documents/api.js"
  60. script.type = "text/javascript"
  61. document.head.appendChild(script);
  62. }
  63. },
  64. // 初始化onlyoffice编辑器
  65. async initEditor(){
  66. const scriptId = "script_"+this.documentId;
  67. if (document.getElementById(scriptId)===null){
  68. await this.loadScript();
  69. }
  70. // 保证每次刷新页面时重新加载onlyoffice对象,避免缓存问题
  71. if (this.docEditor){
  72. this.docEditor.destroyEditor();
  73. this.docEditor = null;
  74. }
  75. const param = {
  76. documentId: this.documentId,
  77. versionId: this.versionId,
  78. isEdit: this.isEdit
  79. }
  80. // 从后端获取onlyoffice配置,避免配置被修改
  81. await getOnlyOfficeConfig(param).then(res=>{
  82. let data = res.data;
  83. this.docEditor = new window.DocsAPI.DocEditor(this.onlineEditorId, data);
  84. })
  85. },
  86. },
  87. // 关闭页面销毁onlyoffice对象
  88. beforeDestroy() {
  89. if (this.docEditor){
  90. this.docEditor.destroyEditor();
  91. this.docEditor = null;
  92. }
  93. }
  94. }
  95. </script>
  96. <style scoped lang="scss">
  97. </style>
  • (2)父组件页面路由调用
  1. # 通过点击时间出发路由跳转,并传递参数
  2. handleEdit(item){const route =this.$router.resolve({path:"/components/edit/office",query:{documentId: item.id,isEdit:true},});// 在新窗口打开页面
  3. window.open(route.href,"_blank");},

3.SpringBoot回调接口配置

为了保证onlyoffice配置不被修改,我这里将onlyoffice配置信息通过后端接口的形式获取,这里将onlyoffice配置信息配置在SpringBoot的配置文件中,如果不需要的话可以将这些配置直接写在前端的js代码中。

  • (1) 在配置文件(如:application.yml或application.properties)中添加如下配置
  1. # onlyoffice配置only-office:secret: devops_20240521
  2. config:document:# 文档下载接口,这个接口需要在springboot后端中实现url: http://10.49.47.24:10010/dev-api/documents/only/office/download
  3. permissions:# 是否可以编辑edit:trueprint:falsedownload:true# 是否可以填写表格,如果将mode参数设置为edit,则填写表单仅对文档编辑器可用。 默认值与edit或review参数的值一致。fillForms:false# 跟踪变化review:trueeditorConfig:# onlyoffice回调接口,这个接口也需要在springboot后端中实现callbackUrl: http://10.49.47.24:10010/dev-api/documents/only/office/callbackToSaveFile
  4. lang: zh-CN
  5. coEditing:mode: fast,change:true# 定制化配置customization:forcesave:trueautosave:falsecomments:truecompactHeader:falsecompactToolbar:falsecompatibleFeatures:falsecustomer:address: 中国北京市海淀区
  6. info: xxxxx文档在线写作平台
  7. logo: https://example.com/logo-big.png
  8. logoDark: https://example.com/dark-logo-big.png
  9. mail: xxx@xxx.com
  10. name: xxxxx平台
  11. phone:123456789www: www.example.com
  12. features:# 是否开启拼写检查spellcheck:mode:truechange:trueregion: zh-CN
  13. type: desktop
  • (2)OnlyOfficeConfig配置类
  1. # OnlyOfficeConfig.java内容
  2. /**
  3. * onlyOffice配置
  4. * 这里的配置会从 application.yml或application.properties 中读取
  5. */@Data@AllArgsConstructor@NoArgsConstructor@Configuration@ConfigurationProperties(prefix ="only-office")publicclassOnlyOfficeConfigimplementsSerializable{privatestaticfinallong serialVersionUID =1L;privateString secret;privateConfig config;@Data@AllArgsConstructor@NoArgsConstructorpublicstaticclassConfigimplementsSerializable{privatestaticfinallong serialVersionUID =1L;privateDocument document;privateEditorConfig editorConfig;privateString type;privateString token;privateString documentType;privateString height ="100%";privateString width ="100%";@Data@AllArgsConstructor@NoArgsConstructorpublicstaticclassDocumentimplementsSerializable{privatestaticfinallong serialVersionUID =1L;privateString title;privateString fileType;privateString key;privateString url;privatePermissions permissions;@Data@AllArgsConstructor@NoArgsConstructorpublicstaticclassPermissionsimplementsSerializable{privatestaticfinallong serialVersionUID =1L;privateBoolean edit;privateBoolean print;privateBoolean download;privateBoolean fillForms;privateBoolean review;}}@Data@AllArgsConstructor@NoArgsConstructorpublicstaticclassEditorConfigimplementsSerializable{privatestaticfinallong serialVersionUID =1L;privateString callbackUrl;privateString lang;privateCoEditing coEditing;privateCustomization customization;privateString region;privateUser user;publicUsergetUser(){returnStringUtils.isNull(user)?newUser():user;}@Data@AllArgsConstructor@NoArgsConstructorpublicstaticclassCoEditingimplementsSerializable{privatestaticfinallong serialVersionUID =1L;privateString mode;privateBoolean change;}@Data@AllArgsConstructor@NoArgsConstructorpublicstaticclassCustomizationimplementsSerializable{privatestaticfinallong serialVersionUID =1L;privateBoolean forcesave;privateBoolean autosave;privateBoolean comments;privateBoolean compactHeader;privateBoolean compactToolbar;privateBoolean compatibleFeatures;privateCustomer customer;privateFeatures features;@Data@AllArgsConstructor@NoArgsConstructorpublicstaticclassCustomerimplementsSerializable{privatestaticfinallong serialVersionUID =1L;privateString address;privateString info;privateString logo;privateString logoDark;privateString mail;privateString name;privateString phone;privateString www;}@Data@AllArgsConstructor@NoArgsConstructorpublicstaticclassFeaturesimplementsSerializable{privatestaticfinallong serialVersionUID =1L;privateSpellcheck spellcheck;@Data@AllArgsConstructor@NoArgsConstructorpublicstaticclassSpellcheckimplementsSerializable{privatestaticfinallong serialVersionUID =1L;privateBoolean mode;privateBoolean change;}}}@Data@AllArgsConstructor@NoArgsConstructorpublicstaticclassUserimplementsSerializable{privatestaticfinallong serialVersionUID =1L;privateString id;privateString name;privateString image;privateString group;}}}}
  • (3)Controller接口

这里需要注意的是:在对onlyoffice配置进行jwt加密时需要用到一个依赖

  1. prime-jwt

,坐标如下:

  1. <dependency><groupId>com.inversoft</groupId><artifactId>prime-jwt</artifactId><version>1.3.1</version></dependency>
  1. # OnlyOfficeController.java内容
  2. /**
  3. * onlyoffice接口类
  4. */@RestController@RequestMapping("/only/office")publicclassOnlyOfficeController{@ResourceprivateOnlyOfficeConfig onlyOfficeConfig;@ResourceprivateOnlyOfficeServiceImpl onlyOfficeService;privatestaticfinalHashMap<String,List<String>> extensionMap =newHashMap<>();// 初始化扩展名映射static{
  5. extensionMap.put("word",Arrays.asList("doc","docm","docx","docxf","dot","dotm","dotx","epub","fb2","fodt","htm","html","mht","mhtml","odt","oform","ott","rtf","stw","sxw","txt","wps","wpt","xml"));
  6. extensionMap.put("cell",Arrays.asList("csv","et","ett","fods","ods","ots","sxc","xls","xlsb","xlsm","xlsx","xlt","xltm","xltx","xml"));
  7. extensionMap.put("slide",Arrays.asList("dps","dpt","fodp","odp","otp","pot","potm","potx","pps","ppsm","ppsx","ppt","pptm","pptx","sxi"));
  8. extensionMap.put("pdf",Arrays.asList("djvu","oxps","pdf","xps"));}/**
  9. * onlyoffice回调接口,这个接口的内容基本不需要修改,
  10. * 只需要修改 onlyOfficeService.handleCallbackResponse(callBackResponse);
  11. * 及其方法中的业务逻辑即可
  12. */@PostMapping(value ="/callbackToSaveFile")publicvoidcallbackToSaveFile(HttpServletRequest request,HttpServletResponse response)throwsIOException{PrintWriter writer = response.getWriter();Scanner scanner =newScanner(request.getInputStream()).useDelimiter("\\A");String body = scanner.hasNext()? scanner.next():"";CallBackResponse callBackResponse =JSONObject.parseObject(body,CallBackResponse.class);// 只需要修改这行代码及其业务逻辑即可
  13. onlyOfficeService.handleCallbackResponse(callBackResponse);
  14. writer.write("{\"error\":0}");}/**
  15. * 文档下载接口
  16. */@GetMapping("/download")publicvoidofficeDownload(@RequestParam("documentId")@NotNullString documentId,@RequestParam(value ="versionId", required =false)String versionId,HttpServletResponse response){
  17. onlyOfficeService.downloadFile(documentId, versionId, response);}/**
  18. * 获取onlyoffice配置接口
  19. */@GetMapping("/config")publicAjaxResultgetOnlyOfficeConfig(String documentId,String versionId,Boolean isEdit){DevelopDocumentVo developDocumentVo = developDocumentService.selectDevelopDocumentById(documentId);if(StringUtils.isNull(developDocumentVo))returnerror("文件不存在");String fileName = developDocumentVo.getFileName();OnlyOfficeConfig.Config config = onlyOfficeConfig.getConfig();OnlyOfficeConfig.Config.Document document = config.getDocument();OnlyOfficeConfig.Config configuration =newOnlyOfficeConfig.Config();OnlyOfficeConfig.Config.Document documentConfig =newOnlyOfficeConfig.Config.Document();
  20. documentConfig.setKey(documentId);// 编辑模式if(StringUtils.isNotNull(isEdit)&&isEdit){
  21. documentConfig.setTitle(fileName);}else{// 预览模式
  22. documentConfig.setTitle(StringUtils.format("{}({})", fileName,"预览模式"));}
  23. documentConfig.setFileType(this.getExtension(fileName));OnlyOfficeConfig.Config.Document.Permissions permissions = config.getDocument().getPermissions();if(StringUtils.isNotNull(isEdit)){
  24. permissions.setEdit(isEdit);
  25. permissions.setReview(false);}
  26. documentConfig.setPermissions(permissions);String documentUrl =StringUtils.isEmpty(versionId)?StringUtils.format("{}?documentId={}", document.getUrl(), documentId):StringUtils.format("{}?documentId={}&versionId={}", document.getUrl(), documentId, versionId);
  27. documentConfig.setUrl(documentUrl);Long userId =SecurityUtils.getUserId();SysUser sysUser =SecurityUtils.getLoginUser().getSysUser();OnlyOfficeConfig.Config.EditorConfig editorConfig = config.getEditorConfig();OnlyOfficeConfig.Config.EditorConfig.User user = editorConfig.getUser();
  28. user.setId(String.valueOf(userId));
  29. user.setName(sysUser.getNickName());
  30. user.setImage(sysUser.getAvatar());
  31. editorConfig.setUser(user);
  32. configuration.setEditorConfig(editorConfig);
  33. configuration.setDocumentType(this.getDocumentType(fileName));
  34. configuration.setDocument(documentConfig);String secret = onlyOfficeConfig.getSecret();HashMap<String,Object> claims =newHashMap<>();
  35. claims.put("document", documentConfig);
  36. claims.put("editorConfig", editorConfig);
  37. claims.put("documentType",this.getDocumentType(fileName));
  38. claims.put("type", configuration.getType());Signer signer =HMACSigner.newSHA256Signer(secret);JWT jwt =newJWT();for(String key : claims.keySet()){
  39. jwt.addClaim(key, claims.get(key));}String token =JWT.getEncoder().encode(jwt, signer);
  40. configuration.setToken(token);
  41. configuration.setType(config.getType());returnsuccess(configuration);}}
  • (4)CallBackResponse实体类
  1. # CallBackResponse.java内容
  2. /**
  3. * onlyOffice回调响应参数实体
  4. * 数据格式:
  5. * {
  6. * "key": "1797934023043756034",
  7. * "status": 6,
  8. * "url": "http://10.x.xx.42:10020/cache/files/data/179793402xxx6034_5182/output.docx/output.docx?md5=w6_C_mPuu6uWt7jsYURmWg&expires=1717572948&WOPISrc=179793402xxx6034&filename=output.docx",
  9. * "changesurl": "http://10.x.xx.42:10020/cache/files/data/179793xxxx3756034_5182/changes.zip/changes.zip?md5=8lYUI4TD1s2bW-pzs_akgQ&expires=1717572948&WOPISrc=1797934023xxx56034&filename=changes.zip",
  10. * "history": {
  11. * "serverVersion": "8.0.1",
  12. * "changes": [
  13. * {
  14. * "created": "2024-06-05 07:20:01",
  15. * "user": {
  16. * "id": "2",
  17. * "name": "mlzhilu"
  18. * }
  19. * },
  20. * {
  21. * "created": "2024-06-05 07:20:44",
  22. * "user": {
  23. * "id": "1",
  24. * "name": "超级管理员"
  25. * }
  26. * }
  27. * ]
  28. * },
  29. * "users": [
  30. * "1"
  31. * ],
  32. * "actions": [
  33. * {
  34. * "type": 2,
  35. * "userid": "1"
  36. * }
  37. * ],
  38. * "lastsave": "2024-06-05T07:20:45.000Z",
  39. * "forcesavetype": 1,
  40. * "token": "eyJhbGciOiJIU......-53bhhSRg",
  41. * "filetype": "docx"
  42. * }
  43. */@Data@AllArgsConstructor@NoArgsConstructor@Accessors(chain =true)publicclassCallBackResponse{privateString key;privateint status;privateString url;@JsonProperty("changesurl")privateString changesUrl;privateHistory history;privateList<String> users;privateList<Map<String,Object>> actions;@JsonProperty("lastsave")privateDate lastSave;@JsonProperty("forcesavetype")privateint forceSaveType;privateString token;privateString filetype;// History 内部类@Data@AllArgsConstructor@NoArgsConstructor@Accessors(chain =true)publicstaticclassHistory{privateString serverVersion;privateList<Change> changes;// Change 内部类@Data@AllArgsConstructor@NoArgsConstructor@Accessors(chain =true)publicstaticclassChange{privateDate created;privateUser user;// User 内部类@Data@AllArgsConstructor@NoArgsConstructor@Accessors(chain =true)publicstaticclassUser{privateString id;privateString name;}}}}
  • (5)ServiceImpl接口
  1. # OnlyOfficeServiceImpl.java内容
  2. @ServicepublicclassOnlyOfficeServiceImpl{// 文档关闭标志位(2和3均表示文档关闭)// 强制保存文档标志位(6和7均表示强制保存文档)privatefinalstaticList<Integer>DOCUMENT_SAVE_STATUS_LIST=Arrays.asList(2,3,6,7);publicvoidhandleCallbackResponse(CallBackResponse callBackResponse){String documentId = callBackResponse.getKey();int status = callBackResponse.getStatus();String url = callBackResponse.getUrl();List<String> users = callBackResponse.getUsers();//保存文档逻辑if(DOCUMENT_SAVE_STATUS_LIST.contains(status)&&StringUtils.isNotEmpty(url)&&!users.isEmpty()&&StringUtils.isNotEmpty(documentId)){// TODO 这里主要是根据onlyoffice服务器中响应的临时文件下载链接,去下载文件并做一些自己的业务处理}}/*
  3. * 文档下载业务
  4. * 这个接口中文档需要通过HttpServletResponse返回文件
  5. */publicvoiddownloadFile(String id,String versionId,HttpServletResponse response){// TODO 这里主要是根据文档ID和文档版本ID提供文档下载的功能,并且需要保证下载文档时是以文档流的形式下载的}}

引用

onlyoffice官方文档

标签: spring boot 后端 java

本文转载自: https://blog.csdn.net/asdfg6541/article/details/140300692
版权归原作者 乌拉队长 所有, 如有侵权,请联系我们删除。

“springboot+vue2整合onlyoffice实现文档在线协同编辑”的评论:

还没有评论