1. OnlyOffice服务端进行docker部署(http环境,默认80端口,映射出9999端口)
docker run -i -t -d --name onlyoffice  --privileged  -p 9999:80 -p 5432:5432 --restart=always -v /e/publish/onlyoffice/DocumentServer/logs:/var/log/onlyoffice  -v /e/publish/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data  -v /e/publish/onlyoffice/DocumentServer/lib:/var/lib/onlyoffice -v /e/publish/onlyoffice/DocumentServer/db:/var/lib/postgresql -v /e/work/gznew/seal:/var/www/onlyoffice/documentserver/sdkjs-plugins/seal tps.harbor.elextec.com/library/onlyoffice_documentserver:v1.0.0

1.1 在https环境上进行部署
https环境上需要设置证书,测试环境用的是中台自创建的证书,可参考:https://huaweicloud.csdn.net/638db1cedacf622b8df8c703.html,若是部署到生产环境,需要购买证书。

docker run -it -d -p 1443:443 --restart=always \
    -v /zhiku_dev/server/onlyoffice/logs:/var/log/onlyoffice  \
    -v /zhiku_dev/server/onlyoffice/data:/var/www/onlyoffice/Data  \
    -v /zhiku_dev/server/onlyoffice/lib:/var/lib/onlyoffice \
    -v /zhiku_dev/server/onlyoffice/db:/var/lib/postgresql -e JWT_ENABLED=false onlyoffice/documentserver
  1. 前端修改index.html,并引入api.js

http的:

<script type="text/javascript" src="http://172.16.200.244:9999/web-apps/apps/api/documents/api.js"></script>

https的,如果是不能写死的就需要自己获取协议,ip,拼接路径。

<script type="text/javascript" src="https://172.16.200.118:1443/web-apps/apps/api/documents/api.js"></script>
  1. 前端新增公共组件onlyOffice.vue
<!--onlyoffice 编辑器-->
<template>
  <div
    id="editorDiv"
    ref="editorDiv"
  />
</template>

<script>
export default {
  name: 'Editor',
  props: {
    option: {
      type: Object,
      default: () => {
        return {}
      }
    }
  },
  data () {
    return {
      doctype: '',
      docEditor: null
    }
  },
  watch: {
    option: {
      handler: function (n, o) {
        this.setEditor(n)
        this.doctype = this.handleDocType(n.fileType)
      },
      deep: true
    }
  },
  mounted () {
    if (this.option.url) {
      console.log('url:' + this.option.url)
      this.setEditor(this.option)
    }
  },
  methods: {
    handleDocType (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
    },

    // 缓存到onlyOffice数据库里面的回调
    onDocumentStateChange (event) {
      if (event.data) {
        console.log('文档变更了')
        console.log(event)
      } else {
        console.log('文档没有变更,Changes are collected on document editing service')
      }
      this.$emit('fileIsChanged', event.data)
    },
    // 点击保存按钮的回调
    onRequestSaveAs (event) {
      let fileType = event.data.fileType
      let title = event.data.title
      let url = event.data.url
      console.log(fileType)
      console.log(title)
      console.log(url)
    },
    // 下载另存为
    onDownloadAs (event) {
      let url = event.data.url
      console.log('ONLYOFFICE Document Editor create file: ' + url)
    },
    onDocumentReady (event) {
      console.log('onDocumentReady')
    },
    onAppReady (event) {
      console.log('onappready')
    },
    // onRequestCompareFile () {
    //   console.log('this.option.originUrl:  ', this.option.originUrl)
    //   this.docEditor.setRevisedFile({
    //     'fileType': 'docx',
    //     'url': this.option.originUrl || 'http://172.16.200.118:39501/zhiku-files/20230410/0fdf2ebf66124d19b5cbef9c94e49fe9_1.docx?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minio%2F20230425%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230425T102430Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=93d58499e430a6677d5fc3d9268701ddc026374d0d64d8edaac55646fb9eecb1'
    //   })
    // },
    setEditor (option) {
      // 已经初始化过实例就直接更新文档链接 - 没生效
      // if (this.docEditor) this.docEditor.setReferenceData({ 'url': option.url })
      this.doctype = this.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: 'zh-CN',
          customization: {
            hideRightMenu: false, // 是否显示右侧菜单
            forcesave: true,
            autosave: false, // 是否自动保存
            commentAuthorOnly: false,
            comments: true,
            compactHeader: false,
            compactToolbar: true,
            feedback: false,
            plugins: true,
            showReviewChanges: !option.user.isOperation, // 定义在加载编辑器时是否自动显示或隐藏审阅更改面板。默认值为false
            spellcheck: false, // 关闭拼写检查
            review: {
              hideReviewDisplay: true, // 定义显示模式按钮是在协作选项卡上显示还是隐藏
              reviewDisplay: 'view', // 定义打开文档进行查看时将使用的审阅编辑模式
              trackChanges: true, // true为对自己启动,false为对自己关闭。跟踪变化对自己启动,记录自己的修改。其他人也可以查看在什么时间谁修改的文件的哪部分内容,就是协作功能
              hoverMode: true // 定义检查显示模式:通过悬停更改(true)在工具提示中显示检查,或者通过单击更改(false)在气泡中显示检查。默认值为false。
            }
          },
          user: {
            id: option.user.id,
            name: option.user.name
          },
          mode: 'edit' // 文档操作模式 view 视图模式不可编辑  edit 编辑模式可编辑文档

        },
        width: '100%',
        height: '100%',
        token: option.token,
        events: {
          'onAppReady': this.onAppReady,
          'onDocumentStateChange': this.onDocumentStateChange, // 修改文档时调用的函数
          'onRequestSaveAs': this.onRequestSaveAs, // 保存按钮回调 save-copy-as
          'onDownloadAs': this.onDownloadAs, // 下载另存为
          'onDocumentReady': this.onDocumentReady // 文档加载完成
          // 'onRequestCompareFile': this.onRequestCompareFile // 对比文件
        }
      }
      if (this.docEditor) this.docEditor.destroyEditor()
      // eslint-disable-next-line no-undef
      this.docEditor = new DocsAPI.DocEditor('editorDiv', config)
      console.log('编辑器:', this.docEditor)
    }
  }
}
</script>

<style scoped lang="scss">

</style>
  1. 编辑后服务端存储文档
package com.elex.platform.tps.besrss.controller.office;
  
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import sun.net.www.protocol.http.HttpURLConnection;
 
import java.io.*;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
 
@RestController
@Slf4j
@Api(tags = "OnlyOffice")
public class OnlyOfficeController {
    @PostMapping("/save/{fileId}/{fileName}/{fileSuffix}")
    public ResponseEntity<Map> save(@RequestBody Map param
            , @PathVariable String fileId
            , @PathVariable String fileName
            , @PathVariable String fileSuffix
    ) {
        log.info("method[save] fileId:{}", fileId);
        Integer status = (Integer) (param.get("status"));
        //   log.info("method[save] param:{}", JsonUtils.toJson(param));
        //文档主键
//        Long id = MapUtil.getLong(param, "key");
        //    log.info("method[save] id:{}", id);
        //操作状态 1:编辑  2:准备保存  3: 保存错误  4:文档关闭没有修改  6: 正在编辑文档,但保存了当前文档状态 7:强制保存文档时发生错误
//        Integer status = MapUtil.getInteger(param, "status", -1);
        //    if (status == 2) {
        //当前在操作用户id
//            List<Long> usersId = MapUtil.getValueAsList(param, "users");
        //   log.info("method[save] usersId:{}", usersId.toString());
        //    }
        if (status == 6) {
            //TODO 可以利用websocket 推送到页面 产生提示语
            String url = (String) (param.get("url"));
            //  log.info("method[save] 正在编辑文档,但保存了当前文档状态. url:{}", MapUtil.getString(param, "url"));
            saveUrlAs(url,"d:/","GET",fileName,fileSuffix);
            Map result = new HashMap(1);
            result.put("error", "0");
            result.put("msg", "操作成功");
            return ResponseEntity.ok(result);
        }
        Map result = new HashMap(1);
        result.put("error", "0");
        return ResponseEntity.ok(result);
    }
 
    public static File saveUrlAs(String url, String filePath, String method, String fileName
            ,  String fileSuffix
    ) {
        //System.out.println("fileName---->"+filePath);
        //创建不同的文件夹目录
        File file = new File(filePath);
        //判断文件夹是否存在
        if (!file.exists()) {
            //如果文件夹不存在,则创建新的的文件夹
            file.mkdirs();
        }
        FileOutputStream fileOut = null;
        HttpURLConnection conn = null;
        InputStream inputStream = null;
        try {
            // 建立链接
            URL httpUrl = new URL(url);
            conn = (HttpURLConnection) httpUrl.openConnection();
            //以Post方式提交表单,默认get方式
            conn.setRequestMethod(method);
            conn.setDoInput(true);
            conn.setDoOutput(true);
            // post方式不能使用缓存
            conn.setUseCaches(false);
            //连接指定的资源
            conn.connect();
            //获取网络输入流
            inputStream = conn.getInputStream();
            BufferedInputStream bis = new BufferedInputStream(inputStream);
            //判断文件的保存路径后面是否以/结尾
            if (!filePath.endsWith("/")) {
                filePath += "/";
            }
            //写入到文件(注意文件保存路径的后面一定要加上文件的名称)
            fileOut = new FileOutputStream(filePath + fileName + "." + fileSuffix );
            BufferedOutputStream bos = new BufferedOutputStream(fileOut);
 
            byte[] buf = new byte[4096];
            int length = bis.read(buf);
            //保存文件
            while (length != -1) {
                bos.write(buf, 0, length);
                length = bis.read(buf);
            }
            bos.close();
            bis.close();
            conn.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("抛出异常!!");
        }
 
        return file;
 
 
    }
}

5.1、编辑后服务端存储文档 – 另一种存储实例,文件存储在minio

@ApiOperation("前端保存的回调地址,用于保存文件流到minIO")
    @ApiOperationSupport(order = 8)
    @PostMapping("/wordFileCallBack/{recordCode}/{pageNum}/{userId}")
    public ResponseEntity<Map> wordFileCallBack(@RequestBody Map param,
                                                @ApiParam(name = "recordCode", value = "文件code") @PathVariable String recordCode, @ApiParam(name = "pageNum", value = "当前页码") @PathVariable Integer pageNum,@ApiParam(name = "userId", value = "用户ID") @PathVariable Integer userId) throws InterruptedException {
        //1-操作业务 进操作记录表并将zhiku_attachment_assignment任务的状态置进行中3即可(OpinionConstants中有)。
        Integer status = (Integer) (param.get("status"));
        log.info("===>param:"+param);
        if(status!=null&&status==6){
            ZhikuEditLog zhikuEditLog = new ZhikuEditLog();
            zhikuEditLog.setOperatorPage(String.valueOf(pageNum));
            zhikuEditLog.setRecordCode(recordCode);
            collegeFacade.saveEditLogUpdateCheckStatus(zhikuEditLog,userId);
            //2-保存了文件,将office服务器的word文件传到minIO里面
            Map<String, String> filePath = attachmentFacade.getFilePath(recordCode);
            String wordFileName = MapUtil.getStr(filePath,"transFilePath").replaceAll(".pdf",".docx");
            String url = (String) (param.get("url"));
            minioHelper.replaceWordFile(pageNum,url,wordFileName);
        }
        Map result = new HashMap(1);
        result.put("error", "0"); // 比如返回error:0 这种结构,不然onlyoffice编辑器中会出现错误
        return ResponseEntity.ok(result);
    }
 
 
 public void replaceWordFile(Integer pageNum,String url,String wordFileName) throws InterruptedException {
        //1-先找到onlyoffice服务器里面的文件
        //wordFileName 的格式是 20230410/123123121.docx
        wordFileName = wordFileName.replaceAll(".docx","_"+pageNum+".docx");
        //服务器里面有,不用下载
        //File file = saveUrlAs(url, wordFileName);
        String[] split = url.split("/");
        String fileUrl = split[6]+"/"+split[7];
        fileUrl  = "/root/onlyoffice/data/"+fileUrl;
        log.error("===>服务器上的URL是:"+fileUrl);
        File file  = new File(fileUrl);
        long fileSize = 0;
        while(true){
            if (file.exists() && fileSize > 0 && fileSize == FileUtil.size(file)) {
                break;
            }
            Thread.sleep(150);
            fileSize = FileUtil.size(file);
            log.error("===>getCanonicalPath:"+FileUtil.getCanonicalPath(file));
        }
        log.error("===>跳出循环了");
        //将这个文件传到minIO里面,路径要一样,用于替换之前的文件
        if(checkFile(wordFileName)){
            remove(wordFileName);
        }
        uploadFile(file,wordFileName);
 
    }
 
@SneakyThrows
    public String uploadFile(File file,String wordFileName) {
        String contentType = MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(file);
        InputStream inputStream = new FileInputStream(file);
        ObjectWriteResponse objectWriteResponse = minioClient.putObject(PutObjectArgs.builder()
                .bucket(minioConfig.getBucket())
                .object(wordFileName)
                .contentType(contentType)
                .stream(inputStream, inputStream.available(), -1)
                .build());
        inputStream.close();
        return objectWriteResponse.object();
    }
  1. 注意事项
    1、onlyoffice使用docker安装时,默认会安装最新版本,本次安装的是7.2版本,JWT令牌认证默认会打开的,我们没有令牌,需要将这个配置关闭,不然就算成功打开文档也会如下报错,如何关闭, 可以参考:https://helpcenter.onlyoffice.com/installation/docs-configure-jwt.aspx
    这个设置重启onlyoffice服务会被恢复,请注意。
    在这里插入图片描述
    2、遇到过文件下载失败的问题,这个就要确认后端提供的可访问的文件url在浏览器中能否正常下载,若是也不行,就是文件路径本身有问题,应该考虑是否映射成功,或者文件本身权限问题。

3、保存文件的回调路径editUrl一定要设置正确,前后端得对应上,不然打开文档之后就一直会报文件保存失败。
在这里插入图片描述
4、部署到线上环境时,如果是https的环境,一般项目都会有网关设置,我们的保存接口要走网关的话,需要开网关白名单,并且要去掉ssl的校验(修改onlyoffice的默认配置,既容器中的default.json中的配置),这样文件才能正常编辑后保存。
在这里插入图片描述

Logo

前往低代码交流专区

更多推荐