需求

前端发送请求数组,后端返回请求集合的压缩文件。

步骤梳理

前端请求查询文章对应附件列表生成文件名-文件K-V构造压缩文件输出流构造返回值

实现

首先封装一个公共的构造ZipFile的类

public class ZipEntityUtil {

    public static void downloadZipFiles(Map<String,File> fileMap, HttpServletResponse response) throws IOException {

        //设置response的Header
        response.setContentType("application/zip");
        response.setHeader("Content-Disposition", "attachment; filename=\"download.zip\"");

        //创建输出流
        ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream());

        //循环将每个文件写入压缩包
        for (Map.Entry<String,File> entry : fileMap.entrySet()) {
            FileInputStream fis = new FileInputStream(entry.getValue());
            ZipEntry zipEntry = new ZipEntry(entry.getKey());
            zipOut.putNextEntry(zipEntry);

            byte[] bytes = new byte[1024];
            int length;
            while ((length = fis.read(bytes)) >= 0) {
                zipOut.write(bytes, 0, length);
            }
            fis.close();
        }

        //关闭输出流
        zipOut.close();
    }

}

在此构造的方法中,我希望传入文章的名字和对应的文件,还有response,让函数帮我把文件写入response

然后再在controller层写一个API

    @RequestMapping(value = "exportPDFbyIds",method = RequestMethod.GET)
    @ApiOperation(value = "根据文献ID导出PDF",notes = "根据文献ID导出PDF")
    public void exportPDFbyIds(@RequestParam(required = true)String[] LiteratureIds,HttpServletResponse response) throws IOException {
        //从文献ID数组中取到每篇文献对应的附件数组
        List<Literature> literatureList = literatureMapper.exportLiteratureById(LiteratureIds);
        System.out.println(literatureList);
        if (literatureList.isEmpty()) {
            //构造response返回json
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(500);
            PrintWriter writer = response.getWriter();
            writer.write("{\"resultCode\":500,\"message\":\"抱歉,所选文献不存在附件!\"}");
            writer.flush();
            writer.close();
            return;
        }
        // 构建附件ID列表
        List<Integer> AnnexIds = literatureList.stream().map(Literature::getAnnexId).collect(Collectors.toList());
        List<LiteratureAnnex> literatureAnnexList = literatureAnnexService.getLiteratureAnnexByIds(AnnexIds);

        Map<String,File> fileMap = literatureAnnexList.stream().collect(Collectors.toMap(LiteratureAnnex::getTitle,literatureAnnex -> new File(literatureAnnex.getFilePath())));



        try{
        ZipEntityUtil.downloadZipFiles(fileMap,response);
        }catch (IOException e){
            e.printStackTrace();
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(500);
            PrintWriter writer = response.getWriter();
            writer.write("{\"resultCode\":500,\"message\":\"抱歉,获取文件失败!!\"}");
            writer.flush();
            writer.close();
        }
    }

前端代码将按钮绑定在axios请求上,点击后开始下载

        axios({
          method:"GET",
          url:"/api/exportPDFbyIds",
          responseType:'blob',
          headers:{
            'Content-Type': 'application/zip'
          },
          params:{
            LiteratureIds:selectedIds + ''
          },
        }).then(
            res =>{
              if (res.status === 200){
                // 具体思路是模拟一个dom节点,把下载事件绑定在该节点并模拟点击事件。
                const url = window.URL.createObjectURL(new Blob([res.data]))
                const link = document.createElement("a")
                link.href = url
                link.setAttribute('download','download.zip')
                document.body.appendChild(link)
                link.click()
              }
            }
        )

收获

  1. Controller层在返回ZIP类型的非JSON响应的时候,可以自己构造HttpServletResponse 来进行个性化的响应。

  2. 从List中的某个字段构造键值对的时候,可以使用Java8的Lamda来快速构建,无需使用for循环这种麻烦的用法。

Map<String,File> fileMap = literatureAnnexList.stream().collect(Collectors.toMap(LiteratureAnnex::getTitle,literatureAnnex -> new File(literatureAnnex.getFilePath())));
  1. 实现返回前端的方案不止一种,也可以使用打包在本地的方法并且返回给前端绝对路径,并定时清除生成目录。个人权衡之后认为本文的方法更好。