0


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

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

目录

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

1.docker部署onlyoffice

# 使用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
# onlyoffice/index.vue文件内容
<template>
  <div style="overflow: scroll;height: calc(100vh - 84px);">
    <div :id="onlineEditorId"></div>
  </div>
</template>
<script>
import {getOnlyOfficeConfig} from "@/api/documents/menu";

export default {
  name: "OnlineEditor",
  data() {
    return {
      // 文档ID
      documentId: '',
      // 文档版本号
      versionId: '',
      // 打开文件的方式,true-编辑模式,false-预览模式
      isEdit: true,
      docEditor: null,
      onlineEditorId: 'onlineEditor',
    }
  },
  watch: {
    documentId: {
      handler: function () {
        this.loadScript();
        this.initEditor();
      },
      deep: true,
    },
  },
  activated() {
    if (this.documentId) {
      this.loadScript();
      this.initEditor();
    }
  },
  created() {
      // 从路由中获取参数
    const documentId = this.$route.query.documentId;
    const versionId = this.$route.query.versionId;
    const isEdit = this.$route.query.isEdit;
    if (versionId) this.versionId = versionId;
    if (isEdit) this.isEdit = isEdit;
    if (documentId) {
        this.documentId = documentId;
        this.onlineEditorId += this.documentId;
        this.loadScript();
        this.initEditor();
    }
  },
  methods: {
      // 动态加载onlyoffice api脚本
    async loadScript(){
      const scriptId = "script_"+this.documentId;
      if (document.getElementById(scriptId)===null){
        const script = document.createElement('script')
        script.id = scriptId;
        script.src = "http://10.49.47.24:10086/web-apps/apps/api/documents/api.js"
        script.type = "text/javascript"
        document.head.appendChild(script);
      }
    },
    // 初始化onlyoffice编辑器
    async initEditor(){
      const scriptId = "script_"+this.documentId;
      if (document.getElementById(scriptId)===null){
        await this.loadScript();
      }
      // 保证每次刷新页面时重新加载onlyoffice对象,避免缓存问题
      if (this.docEditor){
        this.docEditor.destroyEditor();
        this.docEditor = null;
      }
      const param = {
        documentId: this.documentId,
        versionId: this.versionId,
        isEdit: this.isEdit
      }
      // 从后端获取onlyoffice配置,避免配置被修改
      await getOnlyOfficeConfig(param).then(res=>{
        let data = res.data;
        this.docEditor = new window.DocsAPI.DocEditor(this.onlineEditorId, data);
      })
    },
  },
  // 关闭页面销毁onlyoffice对象
  beforeDestroy() {
    if (this.docEditor){
      this.docEditor.destroyEditor();
      this.docEditor = null;
    }
  }
}
</script>
<style scoped lang="scss">

</style>
  • (2)父组件页面路由调用
# 通过点击时间出发路由跳转,并传递参数
handleEdit(item){const route =this.$router.resolve({path:"/components/edit/office",query:{documentId: item.id,isEdit:true},});// 在新窗口打开页面
    window.open(route.href,"_blank");},

3.SpringBoot回调接口配置

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

  • (1) 在配置文件(如:application.yml或application.properties)中添加如下配置
# onlyoffice配置only-office:secret: devops_20240521
  config:document:# 文档下载接口,这个接口需要在springboot后端中实现url: http://10.49.47.24:10010/dev-api/documents/only/office/download
      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
      lang: zh-CN
      coEditing:mode: fast,change:true# 定制化配置customization:forcesave:trueautosave:falsecomments:truecompactHeader:falsecompactToolbar:falsecompatibleFeatures:falsecustomer:address: 中国北京市海淀区
          info: xxxxx文档在线写作平台
          logo: https://example.com/logo-big.png
          logoDark: https://example.com/dark-logo-big.png
          mail: [email protected]
          name: xxxxx平台
          phone:123456789www: www.example.com
        features:# 是否开启拼写检查spellcheck:mode:truechange:trueregion: zh-CN
    type: desktop
  • (2)OnlyOfficeConfig配置类
# OnlyOfficeConfig.java内容

/**
 * onlyOffice配置
 * 这里的配置会从 application.yml或application.properties 中读取
 */@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加密时需要用到一个依赖

prime-jwt

,坐标如下:

<dependency><groupId>com.inversoft</groupId><artifactId>prime-jwt</artifactId><version>1.3.1</version></dependency>
# OnlyOfficeController.java内容

/**
* onlyoffice接口类
*/@RestController@RequestMapping("/only/office")publicclassOnlyOfficeController{@ResourceprivateOnlyOfficeConfig onlyOfficeConfig;@ResourceprivateOnlyOfficeServiceImpl onlyOfficeService;privatestaticfinalHashMap<String,List<String>> extensionMap =newHashMap<>();// 初始化扩展名映射static{
        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"));
        extensionMap.put("cell",Arrays.asList("csv","et","ett","fods","ods","ots","sxc","xls","xlsb","xlsm","xlsx","xlt","xltm","xltx","xml"));
        extensionMap.put("slide",Arrays.asList("dps","dpt","fodp","odp","otp","pot","potm","potx","pps","ppsm","ppsx","ppt","pptm","pptx","sxi"));
        extensionMap.put("pdf",Arrays.asList("djvu","oxps","pdf","xps"));}/**
    * onlyoffice回调接口,这个接口的内容基本不需要修改,
    * 只需要修改 onlyOfficeService.handleCallbackResponse(callBackResponse);
    * 及其方法中的业务逻辑即可
    */@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);// 只需要修改这行代码及其业务逻辑即可
        onlyOfficeService.handleCallbackResponse(callBackResponse);
        writer.write("{\"error\":0}");}/**
    * 文档下载接口
    */@GetMapping("/download")publicvoidofficeDownload(@RequestParam("documentId")@NotNullString documentId,@RequestParam(value ="versionId", required =false)String versionId,HttpServletResponse response){
        onlyOfficeService.downloadFile(documentId, versionId, response);}/**
    * 获取onlyoffice配置接口
    */@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();
        documentConfig.setKey(documentId);// 编辑模式if(StringUtils.isNotNull(isEdit)&&isEdit){
            documentConfig.setTitle(fileName);}else{// 预览模式
            documentConfig.setTitle(StringUtils.format("{}({})", fileName,"预览模式"));}
        documentConfig.setFileType(this.getExtension(fileName));OnlyOfficeConfig.Config.Document.Permissions permissions = config.getDocument().getPermissions();if(StringUtils.isNotNull(isEdit)){
            permissions.setEdit(isEdit);
            permissions.setReview(false);}
        documentConfig.setPermissions(permissions);String documentUrl =StringUtils.isEmpty(versionId)?StringUtils.format("{}?documentId={}", document.getUrl(), documentId):StringUtils.format("{}?documentId={}&versionId={}", document.getUrl(), documentId, versionId);
        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();
        user.setId(String.valueOf(userId));
        user.setName(sysUser.getNickName());
        user.setImage(sysUser.getAvatar());
        editorConfig.setUser(user);
        configuration.setEditorConfig(editorConfig);
        configuration.setDocumentType(this.getDocumentType(fileName));
        configuration.setDocument(documentConfig);String secret = onlyOfficeConfig.getSecret();HashMap<String,Object> claims =newHashMap<>();
        claims.put("document", documentConfig);
        claims.put("editorConfig", editorConfig);
        claims.put("documentType",this.getDocumentType(fileName));
        claims.put("type", configuration.getType());Signer signer =HMACSigner.newSHA256Signer(secret);JWT jwt =newJWT();for(String key : claims.keySet()){
            jwt.addClaim(key, claims.get(key));}String token =JWT.getEncoder().encode(jwt, signer);
        configuration.setToken(token);
        configuration.setType(config.getType());returnsuccess(configuration);}}
  • (4)CallBackResponse实体类
# CallBackResponse.java内容

/**
 * onlyOffice回调响应参数实体
 * 数据格式:
 * {
 *   "key": "1797934023043756034",
 *   "status": 6,
 *   "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",
 *   "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",
 *   "history": {
 *     "serverVersion": "8.0.1",
 *     "changes": [
 *       {
 *         "created": "2024-06-05 07:20:01",
 *         "user": {
 *           "id": "2",
 *           "name": "mlzhilu"
 *         }
 *       },
 *       {
 *         "created": "2024-06-05 07:20:44",
 *         "user": {
 *           "id": "1",
 *           "name": "超级管理员"
 *         }
 *       }
 *     ]
 *   },
 *   "users": [
 *     "1"
 *   ],
 *   "actions": [
 *     {
 *       "type": 2,
 *       "userid": "1"
 *     }
 *   ],
 *   "lastsave": "2024-06-05T07:20:45.000Z",
 *   "forcesavetype": 1,
 *   "token": "eyJhbGciOiJIU......-53bhhSRg",
 *   "filetype": "docx"
 * }
 */@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接口
# OnlyOfficeServiceImpl.java内容

@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服务器中响应的临时文件下载链接,去下载文件并做一些自己的业务处理}}/*
    * 文档下载业务
    * 这个接口中文档需要通过HttpServletResponse返回文件
    */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实现文档在线协同编辑”的评论:

还没有评论