Bootstrap

Java实现下载文件到本地(压缩+多级目录结构+response)

实现的功能:
下载照片到缓存文件,分类存放照片,压缩成zip(保留多级目录结构)通过response返回后自定义下载路径。

【用于此功能练习的代码(详细注释)可参考:https://blog.csdn.net/FFJ_Olivia/article/details/121101226】

package com.epoch.customproject.goer.bill.controller;


import com.alibaba.fastjson.JSON;
import com.epoch.bdp.imageflow.utils.ImageUtils;
import com.epoch.customproject.goer.bill.model.ImageResult;
import com.epoch.ifp.expenseclaim.model.vo.ExpenseImageResult;
import com.epoch.infrastructure.util.service.DateUtils;
import com.epoch.infrastructure.util.service.HttpClientUtils;
import com.epoch.infrastructure.util.service.JsonUtils;
import com.epoch.infrastructure.util.service.fdfs.FastDFSClientUtil;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.util.UriUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 1. @author olivia
 */
public class MyController {
    /**
     * 根据凭证号批量下载影像
     *
     * @param jsonData 数据集合
     */
    @PostMapping("/batchDownloadImg")
    public void batchDownloadImg(@RequestBody Map<String, List<String>> jsonData, HttpServletResponse response) throws IOException {
        Map<String, Object> param = new HashMap<>();
        List<String> voucherCodeData = jsonData.get("voucherCodeData");
        List<String> billCodeData = jsonData.get("billCodeData");
        if (voucherCodeData != null) {
            // 这里的一步是从数据库获取单据号
            List<Map<String, Object>> billCodeDataList = billRequestMachineAccountDAO.getMainBillCodeByBillCode(voucherCodeData);
            if (!billCodeDataList.isEmpty()) {
                Map<String, Object> codeParam = new HashMap<>();
                for (Map<String, Object> billCode : billCodeDataList) {
                    // key:凭证号,value:单据号
                    codeParam.put(billCode.get("BILL_CODE").toString(), billCode.get("MAIN_BILL_CODE").toString());
                }
                param.put("voucherCode", codeParam);
            }
        }
        if (billCodeData != null) {
            Map<String, Object> codeParam = new HashMap<>();
            for (String billCode : billCodeData) {
                // key:单据号,value:单据号
                codeParam.put(billCode, billCode);
            }
            param.put("billCode", codeParam);
        }
        if (param != null) {
            batchDownloadImg(param, response);
        }
    }

	/**
     * 批量下载影像
     * @param paramDate key:单据类型,value:单号集合(key:单据号/凭据号,value:单据号)
     */
    public void batchDownloadImg(Map<String, Object> paramData, HttpServletResponse response) throws IOException {
        long time = System.currentTimeMillis();
        // 影像接口地址
        String url = this.serverIp + ":" + this.serverPort + "/" + this.serverName + "/rest/api/image/list";
        // 请求报文参数
        List<String> param = new ArrayList<>();
        param.add(this.appCode);
        param.add(this.docCode);
        String sign = ImageUtils.getSignString(time, param, this.secretKey);
        Map<String, Object> paramMap = new HashMap<>(4);
        paramMap.put("sign", sign);
        paramMap.put("timestamp", time);
        paramMap.put("appCode", this.appCode);
        Map<String, Map<String, Object>> imgsDataMap = new HashMap<>();
        // 不同单据类型
        for (Map.Entry<String, Object> docTypeMap : paramData.entrySet()) {
            Map<String, Map<String, Object>> dataMap = new HashMap<>();
            Map<String, Object> codeDataMap = JSON.parseObject(JSON.toJSONString(docTypeMap.getValue()));
            // 遍历单据号
            for (Map.Entry<String, Object> codeMap : codeDataMap.entrySet()) {
                // 获取单据号
                paramMap.put("documentCode", codeMap.getValue().toString());
                // 获取单据的所有影像的信息
                ExpenseImageResult allImageResult = HttpClientUtils.postForJsonNoToken(url, paramMap, ExpenseImageResult.class);
                List<ImageResult> imageResultList = JsonUtils.fromJSONList(allImageResult.getObj(), ImageResult.class);
                // 获取影像地址集合
                if (imageResultList != null) {
                    dataMap.put(codeMap.getKey(), new HashMap<>());
                    for (ImageResult imageResult : imageResultList) {
                        // dataMap的key:凭证号/单据号,value:影像信息集合(key:影像名,value:影像地址)
                        dataMap.get(codeMap.getKey()).put(imageResult.getImageName(), imageResult.getDfsId());
                    }
                }
            }
            imgsDataMap.put(docTypeMap.getKey(), new HashMap<>());
            imgsDataMap.get(docTypeMap.getKey()).putAll(dataMap);
        }
        // 下载zip文件
        batchImgsDownload(imgsDataMap, response);
    }


    /**
     * 下载zip文件
     * @param dataMap key:单据类型,value:单号信息集合[key:凭证号/单据号,value:影像信息集合(key:影像名,value:影像地址)]
     */
    public void batchImgsDownload(Map<String, Map<String, Object>> dataMap, HttpServletResponse response) throws IOException {
        File rootFile = new File(System.getProperty("java.io.tmpdir") + File.separator + "imgsTemp" + DateUtils.longToDateString(System.currentTimeMillis(), "yyyyMMddHHmmss"));
        rootFile.mkdirs();
        for (Map.Entry<String, Map<String, Object>> typeDataMap : dataMap.entrySet()) {
            String secondLevelFileName;
            if ("voucherCode".equals(typeDataMap.getKey())) {
                secondLevelFileName = "凭证号";
            } else {
                secondLevelFileName = "单据号";
            }

            for (Map.Entry<String, Object> billCodeDataMap : typeDataMap.getValue().entrySet()) {
                File imgsFile = new File(rootFile, secondLevelFileName + File.separator + billCodeDataMap.getKey());
                imgsFile.mkdirs();
                Map<String, Object> imgsDataMap = JSON.parseObject(JSON.toJSONString(billCodeDataMap.getValue()));
                // 下载影像到文件夹
                for (Map.Entry<String, Object> imgsData : imgsDataMap.entrySet()) {
                    try (FileOutputStream fos = new FileOutputStream(new File(imgsFile, imgsData.getKey()))) {
                        // 获取影像数据
                        byte[] data = FastDFSClientUtil.downLoadFile(imgsData.getValue().toString());
                        fos.write(data);
                    } catch (Exception e) {
                        e.printStackTrace();
                        throw new IOException(e);
                    }
                }
            }
        }
        try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
        	// 未解决问题:zip包的名称中文会乱码,zip包内的中文文件目录不会乱码
            response.setHeader("Content-Disposition", "attachment;filename=" + UriUtils.encode(rootFile.getName() + ".zip", "utf-8"));
            response.setContentType("application/zip");
            // 压缩下载
            compress(rootFile, rootFile.getName(), zos);
        } catch (Exception e) {
            e.printStackTrace();
            throw new IOException(e);
        }
    }

    /**
     * 递归压缩法
     *
     * @param sourceFile       源文件
     * @param fileName         源文件的文件名称
     * @param zos              压缩输出流
     */
    public void compress(File sourceFile, String fileName, ZipOutputStream zos) throws IOException {
        byte[] bytes = new byte[10240];
        if (sourceFile.isFile()) {
            // 源文件是文件
            try (FileInputStream fis = new FileInputStream(sourceFile)) {
                zos.putNextEntry(new ZipEntry(fileName));
                int len;
                while ((len = fis.read(bytes, 0, bytes.length)) != -1) {
                    zos.write(bytes, 0, len);
                }
                zos.closeEntry();
                zos.flush();
            } catch (IOException e) {
                throw new IOException(e);
            }
        } else {
            // 源文件是目录
            File[] fileList = sourceFile.listFiles();
            if (fileList == null || fileList.length == 0) {
                // 空目录
                try {
                    zos.putNextEntry(new ZipEntry(fileName));
                    zos.closeEntry();
                    zos.flush();
                } catch (IOException e) {
                    throw new IOException(e);
                }
            } else {
                // 非空目录
                for (File file : fileList) {
                    compress(file, fileName + File.separator + file.getName(), zos);
                }
            }
        }
    }
}

遗留的问题:
用postman调用接口下载后的zip包的包名中文会乱码,包内的文件目录中文不会乱码。
尝试方法:

  1. response.setHeader(“Content-Disposition”, “attachment;filename=” + UriUtils.encode(“批量下载.zip”, “UTF-8”));
  2. response.setHeader(“Content-Disposition”, “attachment;filename=” + UriUtils.encode(“批量下载.zip”, “GBK”));
  3. response.setContentType(“application/zip;charset=UTF-8”);
  4. ZipOutputStream.setEncoding(“GBK”);
  5. ZipOutputStream.setEncoding(“UTF-8”);

以上几种方法皆没效果。暂不确定其他请求方式是否会复现,因为只用postman调用过。

============
有任何疑问或者建议欢迎随时评论交流哈~~

;