当前位置: 首页 > 工具软件 > ONLYOFFICE > 使用案例 >

onlyoffice+vue集成

韦宣
2023-12-01

1、Document Server

53522可以改成你的端口(云服务器记得开权限),注意/var/lib/onlyoffice/documentserver/App_Data/cache/files

  • 注意上面的cache/files,是通过docker exec -it 3a3afa942911(这个是docker ps看到的container的id) bash
  • 然后用find / -name *.docx 这样搜索所有的docx格式的文件,找到的cache files,这里存的是修改后还未保存的file
    进入项目目录后,先创建docker volume:
mkdir -p app/onlyoffice/DocumentServer/logs
mkdir -p app/onlyoffice/DocumentServer/data
mkdir -p app/onlyoffice/DocumentServer/lib
mkdir -p app/onlyoffice/DocumentServer/cache/files
mkdir -p app/onlyoffice/DocumentServer/db

运行docker:

sudo docker run -i -t -d -p 53522:80 --restart=always \
    -v `pwd`/app/onlyoffice/DocumentServer/logs:/var/log/onlyoffice  \
    -v `pwd`/app/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data  \
    -v `pwd`/app/onlyoffice/DocumentServer/lib:/var/lib/onlyoffice \
    -v `pwd`/app/onlyoffice/DocumentServer/cache/files:/var/lib/onlyoffice/documentserver/App_Data/cache/files \
    -v `pwd`/app/onlyoffice/DocumentServer/db:/var/lib/postgresql  onlyoffice/documentserver

2、maven 配置阿里云镜像

3、拉实例项目:

git clone https://github.com/ONLYOFFICE/document-server-integration.git
cd document-server-integration/web/documentserver-example/java-spring

4. 配置application.properties文件:

# server.address=127.0.0.1
# 8181防火墙开放是的
server.port=8181 
files.docservice.url.site=http://xxx.xxx.xxx.xxx/

5. 运行命令:

mvn clean
mvn spring-boot:run

集成:

vue-editor

<!--onlyoffice 编辑器-->
<template>
  <div id="editorDiv"></div>
</template>

<script>

import {handleDocType} from '../../../utils'

export default {
  name: 'editor',
  props: {
    option: {
      type: Object,
      default: () => {
        return {}
      }
    }
  },
  data() {
    return {
      doctype: '',
    }
  },

  created() {
    let script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = window.SITE_CONFIG.onlyOfficeUrl + '/web-apps/apps/api/documents/api.js';
    document.getElementsByTagName('head')[0].appendChild(script);
    console.log(document.getElementsByTagName('head')[0])
  },
  mounted() {
    if (this.option.url) {
      this.setEditor(this.option)
    }
  },
  methods: {
    setEditor(option) {
      this.doctype = 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: 'en',
          customization: {
            commentAuthorOnly: false,
            comments: true,
            compactHeader:false,
            compactToolbar:true,
            feedback:false,
            plugins:true
          },
          user:{
            id:option.user.id,
            name:option.user.name
          },
          mode:option.model ? option.model : 'edit',

        },
        width: '100%',
        height: '100%',
        token:option.token
      }
      console.log(`setEditor ==> option:`, option, 'config:', config)
      this.$nextTick(() => {
        setTimeout(function () {
          let docEditor = new window.DocsAPI.DocEditor('editorDiv', config)
        }, 1500);
      })
    },
  },
  watch: {
    option: {
      handler: function (n, o) {
        this.setEditor(n)
        this.doctype = handleDocType(n.fileType)
      },
      deep: true,
    }
  }
}
</script>

<style scoped>

</style>

vue-fView

使用前面的editor

<template>
  <el-container direction="vertical">
    <el-row>
      <editor ref="fileEditor" :option="option"></editor>
    </el-row>
  </el-container>
</template>

<script>
import editor from './editor'
import Vue from 'vue'
import {getStorageLocation} from '../../../service/fileService'

export default {
  name: 'fView',
  components: { editor },
  data() {
    return {
      fileList: [],
      headers: {
        token: Vue.cookie.get("token")
      },
      uploadUrl: '',
      serverPath: '',
      fileName: '',
      storagePath: '',
      id: 0,
      option: {
        url: '',
        isEdit: true,
        fileType: '',
        title: '',
        token: Vue.cookie.get("token"),// 看实际的请求,没有也可以Vue.ls.get(ACCESS_TOKEN)
        user: {
          id: '',
          name: ''
        },
        mode: 'view',
        editUrl: '',
        key: ''
      },
    };
  },
  created() {
    this.id = this.$route.params.id;
    this.fileName = this.$route.query.fileName;
    let userInfo = {} //Vue.ls.get(USER_INFO)
    this.option.user.id = '1'; //userInfo.id
    this.option.user.name = 'John Smith'; //userInfo.realname

    let that = this;
    setTimeout(function () {
      that.getFile()
    }, 1500);

  },
  methods: {
    updateOption () {
      this.option.fileType = this.fileName.split(".")[1]
      this.option.url = this.serverPath + '/download?fileName=' + this.fileName + '&userAddress=' + this.storagePath
      this.option.title = this.fileName
      console.log(`this.option:`, this.option)
    },

    getFile() {
      let storagePath;
      getStorageLocation().then(res => {
        storagePath = res.data;
      })

      this.option.url = window.SITE_CONFIG.onlyOfficeServerPath + '/previewFile?filePath=' + this.fileName
      this.option.title = this.fileName
      this.option.fileType = this.fileName.split(".")[1]
      this.option.model = 'edit' //docx txt excel编辑测试ok
      this.option.editUrl = window.SITE_CONFIG.onlyOfficeCallBackUrl + '?token=' + Vue.cookie.get("token") + '&fileName=' + this.fileName

      setTimeout(function () {
        if(document.getElementsByName('frameEditor')[0]){
          document.getElementsByName('frameEditor')[0].setAttribute('width', '100%')
          document.getElementsByName('frameEditor')[0].setAttribute('height', screen.availHeight * 0.9+'px')
        }
      }, 3000);
    }
  },
  watch: {
  }
}
</script>

<style scoped>

</style>

后端

注意1:

如果用的是shiro,要放开权限
filterMap.put(“/onlyOffice/**”, “anon”);//从docker中请求后端没有带token,这里不放行会报错
filterMap.put(“/plan/previewFile”, “anon”);//从docker中请求后端没有带token,这里不放行会报错
filterMap.put(“/plan/saveOnlyOfficeFile”, “anon”);//从docker中请求后端没有带token,这里不放行会报错

注意2

在保存时,我遇到了403错误,访问docker中的cache/files失败,所以前面把cache/files暴露出来,访问docker中的cache/files转为访问本地的cache/files中对应的文件。

FileController

整理了一半有点累了…直接放controller。。。
后面再说完整的弄到一个项目吧。。。

本质上就是,从本地文件系统中下载文件(download接口)
保存接口则是:普通修改,status是1,socket一直建立连接。文档关闭后10s,会产生status为2的回调,走下面的track接口,从cache中获取output.docx,覆盖到之前的路径,也就是保存成功!
其它都是文件系统的操作
更多见官方的示例 https://github.com/ONLYOFFICE/document-server-integration.git

package com.ironpan.integration.modules.office.controller;

import com.alibaba.fastjson.JSONObject;
import com.ironpan.integration.modules.office.utils.DefaultFileUtility;
import com.ironpan.integration.modules.office.utils.FileStorageMutator;
import com.ironpan.integration.modules.office.utils.LocalFileStorage;
import com.ironpan.integration.modules.office.entity.R;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Scanner;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

@CrossOrigin("*")
@Controller
public class FileController {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private DefaultFileUtility fileUtility;
    @Autowired
    private FileStorageMutator storageMutator;

    @Autowired
    private LocalFileStorage localFileStorage;

    @Value("${cache.file.path}")
    private String cacheFilePath;

    @GetMapping(path = "/download")
    public ResponseEntity<Resource> download(HttpServletRequest request, @RequestParam("fileName") String fileName){
        try{
            logger.info("in /download");

            return downloadFile(fileName);
        } catch(Exception e){
            return null;
        }
    }

    @PostMapping("/upload")
    @ResponseBody
    public R upload(@RequestParam("file") MultipartFile file){
        try {
            if (file.isEmpty()) {
                return R.error("文件不能为空");
            }
            logger.info("开始文件上传");
            String fullFileName = file.getOriginalFilename();  // get file name
            String fileExtension = fileUtility.getFileExtension(fullFileName);  // get file extension
            long fileSize = file.getSize();  // get file size
            byte[] bytes = file.getBytes();  // get file in bytes

            // check if the file size exceeds the maximum file size or is less than 0
            if(fileUtility.getMaxFileSize() < fileSize || fileSize <= 0){
                return R.error("File size is incorrect");
            }

            // check if file extension is supported by the editor
            if(!fileUtility.getFileExts().contains(fileExtension)){
                return R.error("File type is not supported");
            }

            String fileNamePath = storageMutator.updateFile(fullFileName, bytes);  // update a file
            logger.info("       fileNamePath:{}, fullFileName:{}", fileNamePath, fullFileName);

            if (StringUtils.isBlank(fileNamePath)){
                throw new IOException("Could not update a file");  // if the file cannot be updated, an error occurs
            }

            fullFileName = fileUtility.getFileNameWithoutExtension(fileNamePath) + fileExtension;  // get full file name

            return R.ok().put("upload", createUserMetadata(fullFileName));  // 第一个参数uid, create user metadata and return it
        } catch (Exception e) {
            e.printStackTrace();
        }
        return R.error("Something went wrong when uploading the file");
    }

    @RequestMapping(value = "/track", method = RequestMethod.POST)
    public void saveOnlyOfficeFile(HttpServletRequest request, HttpServletResponse response,
        @RequestParam("fileName") String fileName) throws IOException, ServletException {
        String planPath = localFileStorage.getStorageLocation();
        PrintWriter writer = response.getWriter();
        String pathForSave = planPath + "/" + fileName;//这里应该是加上文件名,这样才能把修改后的文件覆盖上去
        Scanner scanner = new Scanner(request.getInputStream()).useDelimiter("\\A");
        String body = scanner.hasNext() ? scanner.next() : "{}";
        logger.info("接收的回调body参数为[{}]", body);
//        logger.info("接收的回调ReportProjectPlan参数为[{}]", JSON.toJSON(params));

        JSONObject jsonObj = JSONObject.parseObject(body);

        //普通修改,status是1,socket一直建立连接。文档关闭后10s,会产生status为2的回调,走下面,从cache中获取output.docx,覆盖到之前的路径,也就是保存成功
        if( jsonObj.getInteger("status") != null &&
            (jsonObj.getInteger("status") == 2 || jsonObj.getInteger("status") == 6))
        {
            String downloadUri = jsonObj.getString("url");
            logger.info("downloadUri: " + downloadUri);
//            URL url = new URL(downloadUri);
//            java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
            //java.io.IOException: Server returned HTTP response code: 403 for URL:

            //http://10.10.111.22:53522/ cache/files/ce2a410b5a7fdc8b224d_8816/output.docx/output.docx?md5=TbOcPEu8ZR3yKmljGQSwSA&amp;expires=1650540479&amp;filename=output.docx
            String[] split = downloadUri.split("/");
            int filesIndex = -1;
            for(int i = 0; i < split.length; i++){
                if(split[i].equals("files")) {
                    filesIndex = i;
                }
            }
            //替换成 this.cacheFilePath + ce2a410b5a7fdc8b224d_8816/output.docx
            String newPath = this.cacheFilePath + split[filesIndex + 1] + "/" +split[filesIndex + 2];
            Path filePath = Paths.get(newPath);
            URI uri = filePath.toUri();
            File file = new File(uri);
            logger.info("新的url:{}, File(uri).exists():{}", newPath, file.exists());
            InputStream stream = new FileInputStream(file);

//            InputStream stream = connection.getInputStream();

            File fileToSave = new File(pathForSave);
            if (!fileToSave.exists()) {   //文件不存在则创建文件,先创建目录
                File dir = new File(fileToSave.getParent());
                dir.mkdirs();
                fileToSave.createNewFile();
                logger.info("文件路径:{} 对应的文件不存在,先创建目录", pathForSave);
            }
            try (FileOutputStream out = new FileOutputStream(fileToSave)) {//默认覆盖
                int read;
                final byte[] bytes = new byte[1024];
                while ((read = stream.read(bytes)) != -1) {
                    out.write(bytes, 0, read);
                }

                out.flush();
                logger.info("写入文件成功");
            }

//            connection.disconnect();
        }else{
            logger.info("不支持的status, jsonObj.getInteger(\"status\"): {}", jsonObj.getInteger("status"));
        }
        writer.write("{\"error\":0}");
    }

    @GetMapping(path = "/storageLocation")
    @ResponseBody
    public String getStorageLocation(){
        return localFileStorage.getStorageLocation();
    }

    // create user metadata //第一个参数:String uid,
    private String createUserMetadata(String fullFileName) {//Integer.parseInt(uid)
        String documentType = fileUtility.getDocumentType(fullFileName).toString().toLowerCase();  // get document type
        return "{ \"filename\": \"" + fullFileName + "\", \"documentType\": \"" + documentType + "\" }";
    }

    // download data from the specified file
    private ResponseEntity<Resource> downloadFile(String fileName) throws IOException {

        Resource resource = storageMutator.loadFileAsResource(fileName);  // load the specified file as a resource

        String contentType = "application/octet-stream";

        logger.info("fileName:" + fileName + " MediaType.parseMediaType(contentType):" + MediaType.parseMediaType(contentType)
            + ", HttpHeaders.CONTENT_DISPOSITION:" + HttpHeaders.CONTENT_DISPOSITION
            + ", headerValues:" + "attachment; filename=\"" + resource.getFilename() + "\""
            + ", resource.isFile:" + resource.isFile()
            + ", resource.getURI:" + resource.getURI()
            + ", resource.getURL:" + resource.getURL()
            + ", resource.getFilename:" + resource.getFilename()
        );

        // create a response with the content type, header and body with the file data
        return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType(contentType))
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
            .body(resource);
    }
}

使用

添加路由:

    {path: '/fileEditor/:id',  name: 'fileEditor', component: () => import('@/views/modules/training/fView.vue')},

使用:

<el-button type="primary" size="mini"  @click="viewFile(fileInfo)">预览</el-button>


viewFile (fileInfo) {
      console.log(`文件信息:`, fileInfo);
      let routeUrl = this.$router.resolve({
        path: '/fileEditor/' + fileInfo.attachId,
        query: {
          id : this.fileId, fileName: fileInfo.attachTitle
        }
      });
      window.open(routeUrl.href,'_blank')
    }

参考链接:
先在服务器上用docker配置
How to integrate online editors into your own website on Java Spring
java+vue+onlyoffice的简单集成
Springboot Vue element-ui前后端分离实现onlyoffice在线协同编辑Demo
OnlyOffice - 在webpack项目的页面上展示 PowerPoint
How it works-Opening file

 类似资料: