最近研究了一下onlyoffice的开发一个在线协同编辑的小功能,在网上也找了很多博客,非常感谢一个老哥的知道,这里贴出他博客的地址:如何在 Windows 上 使用 ONLYOFFICE 协作编辑文档这个博客的作者已经开源了一套比较成熟的onlyoffice二次开发的系统,环境安装完毕后可直接运行使用。因为我这边的需求是要自己使用java后台集成一些所以,在开发过程中也请教了好多前边那个大神的很多问题,也都一一耐心的指导。好了话不多说了。
        我的小Demo已经上传到码云:https://gitee.com/lei223/onlyofficeDemo,欢迎有需要的同学下载。里边包含了前后台的源码,数据库使用的sqlite 前端用的vue基础项目。欢迎下载。下面开始我的集成步骤了。安装过程看上述大神的教程即可。
        1》Controller类
 

@Slf4j
@Controller
public class FileController {
    @Value("${files.savePath}")
    private String filePath;
    @Value("${files.docservice.url.site}")
    private String officeUrl;
    @Value("${files.docservice.url.command}")
    private String officeCommand;
    @Autowired
    private DocumentService documentService;
    @Autowired
    private FileUploadService uploadService;
    @Autowired
    RestTemplate restTemplate;
    @ResponseBody
    @PostMapping(value = "upload")
    public ResponseEntity<Object> upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception {

        if (file.isEmpty()) {
            throw new Exception("上传文件不能为空");
        }
        FileUpload upload = new FileUpload();
        String fileName = file.getOriginalFilename();
//        if (!fileName.endsWith("xls") && !fileName.endsWith("xlsx")) {
//            throw new Exception("请上传Excel文件");
//        }
        //更新保存文件信息到数据库
        FileUtil.saveFile(file.getInputStream(), filePath + file.getOriginalFilename());
//        System.out.println(fileName);
        upload.setUpload_date(new Date());
        System.out.println(".".indexOf(fileName));
        System.out.println(fileName.length());
        upload.setFile_type(fileName.substring(fileName.indexOf(".")));
        upload.setFile_path(filePath);
        upload.setFile_name(file.getOriginalFilename());
        upload.setFile_size(file.getSize());
        uploadService.save(upload);
        //操作人
//        String operator=request.getAttribute(StrUtil.USER_WORKNUMBER).toString();
//        xxxService.saveUploadCkdExecl(file,operator);

        return new ResponseEntity<Object>("上传成功", HttpStatus.OK);

    }

    /**
     * \
     * 查询所有上传文档信息接口
     *
     * @return
     */
    @GetMapping("/filelist")
    public ResponseEntity<Object> listFile() {

        return new ResponseEntity<Object>(uploadService.list(), HttpStatus.OK);
    }

//    public ResponseEntity<Object>  rview(){
//
//    }

    /**
     * 下载文档接口
     * @param name
     * @param response
     */
    @GetMapping("/download")
    public void download(String name, HttpServletResponse response) {
        try {
            FileUtil.downLoadFile(name,filePath,response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @GetMapping("/edit")
    public String editDocFile(@RequestParam String name,String userName,String userId,Model model) {
        String path = filePath+name;
//        String name = "cc.docx";
        Document document = documentService.getDocument(documentService.buildDocument(path, name));
        model.addAttribute("document", document);
        // 如果该格式不支持编辑,则返回预览页面
        if (!documentService.canEdit(document)) {
            return "/demo";
        }
        model.addAttribute("documentEditParam", documentService.buildDocumentEditParam(userId, userName,name));
        return "/editor";
    }

    /**
     * 编辑文档时回调接口
     * @param request
     * @param response
     * @throws IOException
     */
    @RequestMapping("/callback")
    public void saveDocumentFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //处理编辑回调逻辑
        callBack(request, response);

    }

    /**
     *
     * @return
     */
    @GetMapping("/editStatus")
    public  ResponseEntity<Object> getDoucmentEditStatus(String name) throws ParseException {
        String url = officeUrl+officeCommand;
        Map<String,String>  map = new HashMap<String,String>();
        map.put("c", "forcesave");
        String docFileMd5 = Md5Utils.getFileMd5(new File(filePath+name));
        if (StringUtils.isBlank(docFileMd5)) {
            throw new DocumentException(ErrorCodeEnum.DOC_FILE_MD5_ERROR);
        }
        String pathShortMd5 = Md5Utils.md5(filePath + name);
        String nameShortMd5 = Md5Utils.md5(name);
        Hashids hashids = new Hashids(DocumentConstants.HASH_KEY);
        // (将路径字符串短md5值 + 名称字符串短md5值) ==> 再转成短id形式 ==> 作为文档的key(暂且认为是不会重复的)
        String key = hashids.encodeHex(String.format("%s%s%s", docFileMd5,pathShortMd5, nameShortMd5));
        map.put("key", key);
        map.put("userdata", "sample userdata");
        JSONObject obj = (JSONObject) new JSONParser().parse(FileUtil.editStatus(url, JSON.toJSONString(map)));
        return new ResponseEntity<Object>(obj, HttpStatus.OK);

    }
    /**
     * 处理在线编辑文档的逻辑
     * @param request
     * @param response
     * @throws IOException
     */
    private void callBack(HttpServletRequest request, HttpServletResponse response) throws IOException {
        PrintWriter writer = null;
        JSONObject jsonObj = null;
        System.out.println("===saveeditedfile------------");

        try {
            writer = response.getWriter();
            Scanner scanner = new Scanner(request.getInputStream()).useDelimiter("\\A");
            String body = scanner.hasNext() ? scanner.next() : "";
            jsonObj = (JSONObject) new JSONParser().parse(body);
            System.out.println(jsonObj);
            System.out.println("===saveeditedfile:" + jsonObj.get("status"));
                /*
                    0 - no document with the key identifier could be found,
                    1 - document is being edited,
                    2 - document is ready for saving,
                    3 - document saving error has occurred,
                    4 - document is closed with no changes,
                    6 - document is being edited, but the current document state is saved,
                    7 - error has occurred while force saving the document.
                 * */
            if ((long) jsonObj.get("status") == 2) {
                FileUtil.callBackSaveDocument(jsonObj,filePath,request, response);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        /*
         * status = 1,我们给onlyoffice的服务返回{"error":"0"}的信息,这样onlyoffice会认为回调接口是没问题的,这样就可以在线编辑文档了,否则的话会弹出窗口说明
         * 在线编辑还没有关闭,前端有人下载文档时,强制保存最新内容  当status 是6时说明有人在编辑时下载文档
         * */
        System.out.println(jsonObj.get("status"));
        if ((long) jsonObj.get("status") == 6) {
            //处理当文档正在编辑为关闭时,下载文档
            if (((String)jsonObj.get("userdata")).equals("sample userdata")){
                FileUtil.callBackSaveDocument(jsonObj,filePath,request, response);
            }

            System.out.println("====保存失败:");
            writer.write("{\"error\":1}");
        } else {
            //执行删除编辑时下载保存的文件:
            FileUtil.deleteTempFile(filePath,request.getParameter("fileName"));
            writer.write("{\"error\":0}");
        }
    }

}

此类的主要接口有四个集成onlyoffice的在线协同编辑功能:

        1》 /edit 此接口是返回给前端onlyoffice的编辑界面并打开要编辑的文件

        2》 /download 下载文件接口,onlyoffice需要的接口之一,当将文档参数传入到onlyoffice服务器时需要一个download 的url 来让onlyoffice将文件下载到服务器配置参数如下:

        

         3》/callback 接口 此接口是onlyoffice服务器对于你这服务的回调接口,当打开文档时,编辑完成后保存文档,或是正在编辑时,下载文档调用onlyoffice的强制保存命令之后都会回调这个接口。

          4》  /editStatus 获取文档的编辑状态,用于编辑时下载文档,如果用户下载的文档正在被编辑,就可以先调用此接口,向onlyoffice服务器发送强制保存的命令,然后会调用callback接口进行保存。

        以上的四个接口是,demo对于onlyoffice服务器的大部分的集成逻辑都在这几个接口的实现细节里。当然还有另外的,上传文件接口和提供文件列表查询的接口。

2。详细介绍一下协作编辑接口的实现逻辑/edit

         1》 首先需要了解到如果想要打开一个编辑界面必须遵循onlyoffice文档中的前端文档要求

                

config = {
    "document": {
        "fileType": "docx",
        "key": "Khirz6zTPdfd7",
        "title": "Example Document Title.docx",
        "url": "https://example.com/url-to-example-document.docx"
    },
    "documentType": "word",
    "editorConfig": {
        "callbackUrl": "https://example.com/url-to-callback.ashx"
    }
};

var docEditor = new DocsAPI.DocEditor("placeholder", config);

         上述的config 对象就是 打开文本编辑器最基本的配置。/edit 接口的内容就是构建了上述config配置的过程。其中key参数,是一个文档能否多人协同编辑的一个关键点,也就是说多人同事操作的同一个文档时,此时文档的key值肯定是同一个,demo使用算法保持同一个文件key值的唯一。 document下的url 参数就是下载接口的路径   在demo中的接口地址是 /download  config下的editorConfig 是相关回调的配置,其中callbackUrl 代表编辑完成以后 文档将要保存,并将最后结果返回给onlyOffice服务器,onlyoffice服务器需要我们的服务器给它响应为 {"error":0}的响应结果来通知服务器此文档没有问题,否则刚开始无法正常编辑文档。

         2》 /edit 的就是做了上述相关配置的构建。具体的实现细节看代码便知。

3.   /download接口的实现:

 

上图是下载接口的实现逻辑。

4. /callback实现逻辑:

@RequestMapping("/callback")
public void saveDocumentFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
    PrintWriter writer = null;
    JSONObject jsonObj = null;
    System.out.println("===saveeditedfile------------");

    try {
        writer = response.getWriter();
        Scanner scanner = new Scanner(request.getInputStream()).useDelimiter("\\A");
        String body = scanner.hasNext() ? scanner.next() : "";
        jsonObj = (JSONObject) new JSONParser().parse(body);
        System.out.println(jsonObj);
        System.out.println("===saveeditedfile:" + jsonObj.get("status"));
         /*
             0 - no document with the key identifier could be found,
             1 - document is being edited,
             2 - document is ready for saving,
             3 - document saving error has occurred,
             4 - document is closed with no changes,
             6 - document is being edited, but the current document state is saved,
             7 - error has occurred while force saving the document.
          * */
        if ((long) jsonObj.get("status") == 2) {
            FileUtil.callBackSaveDocument(jsonObj,filePath,request, response);
        }
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (ParseException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    /*
     * status = 1,我们给onlyoffice的服务返回{"error":"0"}的信息,这样onlyoffice会认为回调接口是没问题的,这样就可以在线编辑文档了,否则的话会弹出窗口说明
     * 在线编辑还没有关闭,前端有人下载文档时,强制保存最新内容  当status 是6时说明有人在编辑时下载文档
     * */
    System.out.println(jsonObj.get("status"));
    if ((long) jsonObj.get("status") == 6) {
        //处理当文档正在编辑为关闭时,下载文档
        if (((String)jsonObj.get("userdata")).equals("sample userdata")){
            FileUtil.callBackSaveDocument(jsonObj,filePath,request, response);
        }

        System.out.println("====保存失败:");
        writer.write("{\"error\":1}");
    } else {
        //执行删除编辑时下载保存的文件:
        FileUtil.deleteTempFile(filePath,request.getParameter("fileName"));
        writer.write("{\"error\":0}");
    }

}

因为有点长一张截图放不下,此代码是当编辑回调后保存文件。此代码是摘自onlyoffice官方文档的中javasample中的代码.官方地址是sample 加入了自己保存逻辑。实现在编辑时,有人下载也能保存文档。保存文档操作的逻辑如下:

/**
 * 编辑以后保存文件
 * @param jsonObj
 * @param filePath
 * @param request
 * @param response
 * @throws IOException
 */
public static void callBackSaveDocument(JSONObject jsonObj, String filePath, HttpServletRequest request, HttpServletResponse response) throws IOException {
    /*
     * 当我们关闭编辑窗口后,十秒钟左右onlyoffice会将它存储的我们的编辑后的文件,,此时status = 2,通过request发给我们,我们需要做的就是接收到文件然后回写该文件。
     * */
    /*
     * 定义要与文档存储服务保存的编辑文档的链接。当状态值仅等于2或3时,存在链路。
     * */
    String downloadUri = (String) jsonObj.get("url");
    System.out.println("====文档编辑完成,现在开始保存编辑后的文档,其下载地址为:" + downloadUri);
    //解析得出文件名
    //String fileName = downloadUri.substring(downloadUri.lastIndexOf('/')+1);
    String fileName = request.getParameter("fileName");
    System.out.println("====下载的文件名:" + fileName);

    URL url = new URL(downloadUri);
    java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
    InputStream stream = connection.getInputStream();
    File savedFile = new File(filePath + fileName);
    //判断是否包含userdata字段信息,如果有证明前端在编辑文档没有关闭时下载正在在线编辑的文档,
    // 此时将文件保存为带一个前缀 v1的文档供前端下载。  如 v1xxx.docx
    if (null!=((String) jsonObj.get("userdata"))&&((String) jsonObj.get("userdata")).equals("sample userdata")) {
        savedFile = new File(filePath + "v1" + fileName);
    }


    try (FileOutputStream out = new FileOutputStream(savedFile)) {
        int read;
        final byte[] bytes = new byte[1024];
        while ((read = stream.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }
        out.flush();
    }
    connection.disconnect();
}

5. /editStatus 接口的实现

 FileUtil.editStatus方法具体实现

  /**
     * 发送网路请求查看是否正在编辑
     * @param path
     * @param params
     * @return
     */
    public static String editStatus(String path, String params) {
        OutputStreamWriter out = null;
        BufferedReader in = null;
        StringBuilder result = new StringBuilder();
        HttpURLConnection conn = null;
        try {
            URL url = new URL(path);
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            //发送POST请求必须设置为true
            conn.setDoOutput(true);
            conn.setDoInput(true);
            //设置连接超时时间和读取超时时间
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(10000);
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setRequestProperty("Accept", "application/json");
            //获取输出流
            out = new OutputStreamWriter(conn.getOutputStream());
//            String jsonStr = "{\"c\":\"forcesave\", \"key\":\"WpP7m85eNQSEOoepp31oIYVG2oJyJJcvkLdoywgvs1k3ywm3Omuxk4\",\"userdata\":\"sample userdata\"}";
            out.write(params);
            out.flush();
            out.close();
            //取得输入流,并使用Reader读取
            if (200 == conn.getResponseCode()) {

                return IOUtils.toString(conn.getInputStream());


            } else {
                System.out.println("ResponseCode is an error code:" + conn.getResponseCode());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }

        return "";
    }

发送完请求以后,onlyoffice就会调用回调接口 进行文件强制保存就会触发callback接口中的这一步逻辑:

此demo具体实现逻辑就是上述。

下边说下修改配置 然后运行起来。

先说前端配置。将请求url中的ip加端口都替换成你本地服务的ip+端口

 修改 java工程中的url配置:url的配置全部在application.yml中:

如果确保上树的修改都已经做了,就可以运行前后台的项目了

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐