打包下载成.zip文件项目背景

公司使用vue + SpringBoot实现批量下载功能
今天在调试批量下载这个功能。打包成.zip文件时,在返回给前端浏览器出现报错信息:
后端报错:

ERROR c.c.p.c.e.BusinessExceptionHandler – java.io.IOException: 你的主机中的软件中止了一个已建立的连接。
org.apache.catalina.connector.ClientAbortException: java.io.IOException: 你的主机中的软件中止了一个已建立的连接。

找了好久的错,发现浏览器的控制台有报错信息

Access to XMLHttpRequest at ‘http://…:8097/pcsms-admin/batchExport/pdf?vids=1e7ed541bdd6b9f5614cca14623f8681,8dfda9f7151745ad5aa5f0abc2c55d5c’ from origin ‘http://..:8001’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

于是在后端中设置响应体,如 HttpServletResponse response :

response.setHeader("Access-Control-Allow-Origin","*");

紧接着又出现如下的报错信息:

Access to XMLHttpRequest at ‘http://…:8097/batchExport/pdf?vids=1e7ed541bdd6b9f5614cca14623f8681,8dfda9f7151745ad5aa5f0abc2c55d5c’ from origin ‘http://…:8001’ has been blocked by CORS policy: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

在面向百度编程中,找到一个帖子有详细的解释–axios设置withCredentials导致“跨域”的解决方案。检查前端配置果然有:

axios.defaults.withCredentials = true;

(一) 当前端配置withCredentials=true时, 后端配置Access-Control-Allow-Origin不能为*, 必须是相应地址
(二) 当配置withCredentials=true时, 后端需配置Access-Control-Allow-Credentials
(三) 当前端配置请求头时, 后端需要配置Access-Control-Allow-Headers为对应的请求头集合

于是我们可以采取方法一:axios.defaults.withCredentials = false;

在找其他解决方法时,找到一个可以不更改前端配置的方法二–Jquery Ajax设置withCredentials解决跨域请求,在response里加上以下两个header

response.addHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));// response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");response.addHeader("Access-Control-Allow-Credentials","true");

最后,终于问题成功解决。

后端代码:

  1. TestController
@RequestMapping(value = "/batchExport/pdf", method = RequestMethod.POST)public void batchExportPdf(HttpServletRequest request, HttpServletResponse response, @RequestParam("vids") String vids) throws Exception {        //获取uavFileId,不进行xss过滤//        HttpServletRequest orgRequest = XssHttpServletRequestWrapper.getOrgRequest(request);//        String ids = orgRequest.getParameter("vids");        String[] vid = vids.split(",");        byte[] data = sopService.batchExportPdf(vid);        String fileName = URLEncoder.encode("批量下载PDF" + DateUtils.format(new Date(), "yyyyMMddHHmmss") + ".zip", "UTF-8");        response.reset();        // 当前端配置withCredentials=true时, 后端配置Access-Control-Allow-Origin不能为*, 必须是相应地址        response.addHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));        // 这个若不起作用,使用下面的"FileName"的//        response.addHeader("Access-Control-Expose-Headers","*");        response.addHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE");        // 设置在下载框默认显示的文件名        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ";" + "filename*=utf-8''" + fileName);        response.setHeader("FileName",  fileName);        // 让axios放行指定的响应头 https://blog.csdn.net/u012586326/article/details/125790079        response.addHeader("Access-Control-Expose-Headers","FileName");        // 写明文件大小        response.addHeader("Content-Length", "" + data.length);        // 指明response的返回对象是文件流        response.setContentType("application/octet-stream; charset=UTF-8");        response.setCharacterEncoding("UTF-8");        // 当前端配置withCredentials=true时, 后端需配置Access-Control-Allow-Credentials        response.addHeader("Access-Control-Allow-Credentials","true");        IOUtils.write(data, response.getOutputStream());    }
  1. TestService
    /**     * 批量下载pdf     * @param vids     * @return     */    byte[] batchExportPdf(String[] vids) throws Exception;
  1. TestServiceImpl
    @Override    public byte[] batchExportPdf(String[] vids) {        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();        // 用于将数据压缩成Zip文件格式        ZipOutputStream zipOut = new ZipOutputStream(outputStream);        FileInputStream inputStream = null;        List deletePdf = new ArrayList();        List in = new ArrayList();        try {            for (String vid : vids) {                // 下载到本地                // PdfDTO包含:pdfPath(本地PDF路径)、fileName(下载后的文件名(不含文件后缀.pdf))                PdfDTO dto = exportPdfByVid(vid);                if (!ObjectUtils.isEmpty(dto)) {                    zipOut.putNextEntry(new ZipEntry((Objects.requireNonNull(dto.getFileName())) + ".pdf"));                    inputStream = new FileInputStream(new File(dto.getPdfPath()));                    int len;                    byte[] bytes = new byte[1024];                    while ((len = inputStream.read(bytes)) != -1) {                        zipOut.write(bytes, 0, len);                    }                    //关闭该zip文件中打开的项                    zipOut.closeEntry();                    deletePdf.add(dto.getPdfPath());                    in.add(inputStream);                }            }        } catch (Exception e) {            log.error("批量下载时出现错误:" + e.toString());            throw new BusinessException("批量下载时出现错误:" + e.getMessage());        } finally {            try {                for (FileInputStream stream : in) {                    stream.close();                }                // 关流                IOUtils.closeQuietly(zipOut);            } catch (IOException e) {                log.error("批量下载时流关闭异常:" + e.toString());            }            // 删除源文件(我这里是因为从数据服务器下载了文件到本地,所以删掉本地的)            File file = null;            for (String s : deletePdf) {                file = new File(s);                file.delete();            }        }        return outputStream.toByteArray();    }

参考:
https://blog.csdn.net/cckevincyh/article/details/81140443
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
https://blog.csdn.net/baidu_28068985/article/details/105558280
https://blog.csdn.net/HermitSun/article/details/100797223