1. 程式人生 > >java批量檔案打包成壓縮成zip下載和大量資料匯出excel時的處理方法

java批量檔案打包成壓縮成zip下載和大量資料匯出excel時的處理方法

對於我們來說,java匯出資料成excel或其他資料檔案,或者下載資源是開發中的家常便飯, 但是在匯出的時候,如果點選一個按鈕匯出幾百萬條資料,如果不作處理的話很可能會出現一系列的問題. 這裡介紹打包成zip壓縮包下載

針對大量資料匯出excel, 這裡有幾種辦法:
1. 每到一定數量就分成一個sheet
2. 每到一定數量分成一個excel,壓縮成zip包打包下載
3. 控制匯出的資料量,或者分頁查詢
4. 加快取,用POI官方的api
SXSSFWorkbook workbook = new SXSSFWorkbook(rowCache);

我這邊用的是2和4 , 整體的思路就是,如果是小數目的資料,直接下載excel,如果是大量資料,先轉換成一個個excle放在一個臨時資料夾中, 然後打包臨時資料夾成zip包返回給瀏覽器,然後刪除臨時資料夾

[下載檔案的工具類]

先準備好自己定義的工具類,之後的方法裡面都需要用到這個工具類, 包括設定兩頭一流, 壓縮檔案,刪除資料夾等


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Encoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import
java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @SuppressWarnings("restriction") public class FileDownloadUtils { private static final Logger logger = LoggerFactory.getLogger(FileDownloadUtils.class); /** * 編譯下載的檔名 * @param filename * @param agent * @return
* @throws IOException */
public static String encodeDownloadFilename(String filename, String agent)throws IOException { if (agent.contains("Firefox")) { // 火狐瀏覽器 filename = "=?UTF-8?B?" + new BASE64Encoder().encode(filename.getBytes("utf-8")) + "?="; filename = filename.replaceAll("\r\n", ""); } else { // IE及其他瀏覽器 filename = URLEncoder.encode(filename, "utf-8"); filename = filename.replace("+"," "); } return filename; } /** * 建立資料夾; * @param path */ public static void createFile(String path) { File file = new File(path); //判斷檔案是否存在; if (!file.exists()) { //建立檔案; file.mkdirs(); } } /** * 生成.zip檔案; * @param path * @throws IOException */ public static ZipOutputStream craeteZipPath(String path) throws IOException{ ZipOutputStream zipOutputStream = null; File file = new File(path+DateUtils.getDateWx()+".zip"); zipOutputStream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(file))); File[] files = new File(path).listFiles(); FileInputStream fileInputStream = null; byte[] buf = new byte[1024]; int len = 0; if(files!=null && files.length > 0){ for(File excelFile:files){ String fileName = excelFile.getName(); fileInputStream = new FileInputStream(excelFile); //放入壓縮zip包中; zipOutputStream.putNextEntry(new ZipEntry(path + "/"+fileName)); //讀取檔案; while((len=fileInputStream.read(buf)) >0){ zipOutputStream.write(buf, 0, len); } //關閉; zipOutputStream.closeEntry(); if(fileInputStream != null){ fileInputStream.close(); } } } /*if(zipOutputStream !=null){ zipOutputStream.close(); }*/ return zipOutputStream; } /** * //壓縮檔案 * @param srcfile 要壓縮的檔案陣列 * @param zipfile 生成的zip檔案物件 */ public static void ZipFiles(java.io.File[] srcfile, File zipfile) throws Exception { byte[] buf = new byte[1024]; FileOutputStream fos = new FileOutputStream(zipfile); ZipOutputStream out = new ZipOutputStream(fos); for (int i = 0; i < srcfile.length; i++) { FileInputStream in = new FileInputStream(srcfile[i]); out.putNextEntry(new ZipEntry(srcfile[i].getName())); int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } out.closeEntry(); in.close(); } out.close(); fos.flush(); fos.close(); } /** * 刪除資料夾及資料夾下所有檔案 * @param dir * @return */ public static boolean deleteDir(File dir) { if (dir == null || !dir.exists()){ return true; } if (dir.isDirectory()) { String[] children = dir.list(); //遞迴刪除目錄中的子目錄下 for (int i=0; i<children.length; i++) { boolean success = deleteDir(new File(dir, children[i])); if (!success) { return false; } } } // 目錄此時為空,可以刪除 return dir.delete(); } /** * 生成html * @param msg * @return * @author zgd * @time 2018年6月25日11:47:07 */ public static String getErrorHtml(String msg) { StringBuffer sb = new StringBuffer(); sb.append("<html>"); sb.append("<head>"); sb.append("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>"); sb.append("</head>"); sb.append("<body>"); sb.append("<div id='errorInfo'> "); sb.append("</div>"); sb.append("<script>alert('"+msg+"')</script>"); sb.append("</body>"); sb.append("</html>"); return sb.toString(); } /** * 設定下載excel的響應頭資訊 * @param response * @param request * @param agent * @param fileName * @throws IOException * @author zgd * @time 2018年6月25日11:47:07 */ public static void setExcelHeadInfo(HttpServletResponse response, HttpServletRequest request, String fileName) { try { // 獲取客戶端瀏覽器的型別 String agent = request.getHeader("User-Agent"); // 對檔名重新編碼 String encodingFileName = FileDownloadUtils.encodeDownloadFilename(fileName, agent); // 告訴客戶端允許斷點續傳多執行緒連線下載 response.setHeader("Accept-Ranges", "bytes"); //檔案字尾 response.setContentType("application/vnd.ms-excel;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment; filename=" + encodingFileName); } catch (IOException e) { logger.error(Thread.currentThread().getStackTrace()[1].getMethodName() +"發生的異常是: ",e); throw new RuntimeException(e); } } /** * 設定下載zip的響應頭資訊 * @param response * @param fileName 檔名 * @param request * @throws IOException * @author zgd * @time 2018年6月25日11:47:07 */ public static void setZipDownLoadHeadInfo(HttpServletResponse response, HttpServletRequest request, String fileName) throws IOException { // 獲取客戶端瀏覽器的型別 String agent = request.getHeader("User-Agent"); response.setContentType("application/octet-stream "); // 表示不能用瀏覽器直接開啟 response.setHeader("Connection", "close"); // 告訴客戶端允許斷點續傳多執行緒連線下載 response.setHeader("Accept-Ranges", "bytes"); // 對檔名重新編碼 String encodingFileName = FileDownloadUtils.encodeDownloadFilename(fileName, agent); response.setHeader("Content-Disposition", "attachment; filename=" + encodingFileName); } }

1.控制層接收引數

/**
     * 匯出商家訂單資料列表的excel檔案
     *
     * @param request
     * @author zgd
     * @time 2018年6月5日14:47:21
     */
    @RequestMapping(value = "/exportStoreOrderList")
    public void exportStoreOrderList(HttpServletRequest request, HttpServletResponse response) {
       /*
       * 從service層獲取List的資料,此處省略
       */
        List<Map<String, Object>> list = data;

        //匯出excel
        // 建立Excel檔案,每個excel限制10000條資料,超過則打包zip
        int size = 10000;
        //每次快取1000條到記憶體,其餘寫到磁碟
        int rowCache = 1000;
        String fileName = "商家訂單資料-" + DateUtils.getDateWx();
        // 建立Excel檔案,每個excel限制10000條資料,超過則打包zip
        int size = 10000;
        //每次快取1000條到記憶體,其餘寫到磁碟
        int rowCache = 1000;
        String fileName = "商家訂單資料-" + DateUtils.getDateWx();
        //匯出excel
        try {
            //excel檔案個數
            int n = list.size() / size + 1;
            if (list != null) {
                SXSSFWorkbook workbook = getStoreOrderExcel(list, rowCache);
                if (n == 1) {
                    //下載單個excle
                    fileName = fileName + ".xls";
                    FileDownloadUtils.downloadExcel(request, response, fileName, workbook);
                } else {
                    fileName = "批量" + fileName + ".zip";
                    String realPath = request.getSession().getServletContext().getRealPath("WEB-INF");
                    //建立臨時資料夾儲存excel
                    String tempDir = realPath + "/tempDir/" + DateUtils.getDateWx();
                    List<File> files = getStoreOrderExcels(tempDir, size, list, rowCache);
                    String zipPath = tempDir + "\\" + fileName;
                    //下載zip
                    FileDownloadUtils.downloadZip(request, response, fileName, files, zipPath);
                    //刪除tempDir資料夾和其中的excel和zip檔案
                    boolean b = FileDownloadUtils.deleteDir(new File(realPath + "\\tempDir"));
                    if (!b) {
                        throw new RuntimeException("tempDir資料夾及其中的臨時Excel和zip檔案刪除失敗");
                    }
                }
            }
        }catch (Exception e) {
            try {
                if (!response.isCommitted()) {
                    response.setContentType("text/html;charset=utf-8");
                    response.setHeader("Content-Disposition", "");
                    String html = FileDownloadUtils.getErrorHtml("下載失敗");
                    response.getOutputStream().write(html.getBytes("UTF-8"));
                }
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

2. getStoreOrderExcel方法解析

 /**
     * 將資料庫查出來的商家訂單資料,建立成excel
     * @param list  商家訂單資料
     * @param rowCache  緩衝的行數
     * @return
     */
    private SXSSFWorkbook getStoreOrderExcel(List<Map<String, Object>> list, int rowCache) {
        //如果rowCache是1000,就是每次讀取1000條資料到快取中
         SXSSFWorkbook workbook = new SXSSFWorkbook(rowCache);
         /**
            ....設定行,列,塞入資料
         **/
         return workbook;
    }

2.1 這裡分成兩條線: 下載excel和下載zip

2.2 下載excel支線

2.2.1 downloadExcel方法解析
/**
     * 下載excel
     *
     * @param request
     * @param response
     * @param fileName
     * @param workbook
     * @throws Exception
     * @author zgd
     * @time 2018年6月25日11:47:07
     */
    private void downloadExcel(HttpServletRequest request, HttpServletResponse response, String fileName, SXSSFWorkbook workbook) {
        //一個流兩個頭
        //設定下載excel的頭資訊
        FileDownloadUtils.setExcelHeadInfo(response, request, fileName);

        // 寫出檔案
        ServletOutputStream os = null;
        try {
            os = response.getOutputStream();
            workbook.write(os);
        } catch (IOException e) {
            logger.error(Thread.currentThread().getStackTrace()[1].getMethodName() + "發生的異常是: ", e);
            throw new RuntimeException(e);
        } finally {
            try {
                if (os != null) {
                    os.flush();
                    os.close();
                }
                if (workbook != null) {
                    workbook.close();
                }
            } catch (Exception e1) {
                logger.error(Thread.currentThread().getStackTrace()[1].getMethodName() + "發生的異常是: ", e1);
                throw new RuntimeException(e1);
            }
        }
    }

下載excel支線完畢

2.3 下載zip支線

2.3.1 getStoreOrderExcels方法解析,先將大量資料轉化成批量的excel臨時檔案
 /**
     * 將資料轉成多個excel檔案放在專案中
     *
     * @param tempDir
     * @param size     每個excel的資料的行數
     * @param list     資料
     * @param rowCache 下載時快取的行數
     * @throws Exception
     * @author zgd
     * @time 2018年6月25日11:47:07
     */
    private List<File> getStoreOrderExcels( String tempDir, int size, List<Map<String, Object>> list, int rowCache) throws Exception {
        //excel檔案個數
        int n = list.size() / size + 1;
        FileDownloadUtils.createFile(tempDir);
        List<File> files = new ArrayList<File>();  //宣告一個集合,用來存放多個Excel檔案路徑及名稱

        for (int i = 0; i < n; i++) {
            int max = Math.min((i + 1) * size, list.size());
            //避免將需要合併的單元格拆分成兩個表 , 如果最後的一條主訂單資料等於下一條資料,max+1
            while (max < list.size() - 1 && list.get(max).get("orderNo").equals(list.get(max + 1).get("orderNo"))) {
                max++;
            }
            List<Map<String, Object>> partList = list.subList(i * size, max);
            SXSSFWorkbook wb = getStoreOrderExcel(partList, rowCache);
            //生成一個excel
            String path = tempDir + "\\商家訂單資料-" + (i + 1) + ".xlsx";
            generateExcelToPath(wb, path);
            //excel新增到files中
            files.add(new File(path));
        }
        return files;
    }
2.3.1.1 generateExcelToPath方法,將生成excel到指定路徑
/**
     * 生成excel到指定路徑
     * @param wb
     * @param path
     * @throws Exception
     */
    private void generateExcelToPath(SXSSFWorkbook wb, String path) throws Exception {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(path);
            wb.write(fos);
        } finally {
            if (fos != null) {
                fos.flush();
                fos.close();
            }
            if (wb != null) {
                wb.close();
            }
        }
    }
2.3.2 downloadZip方法解析,打包下載zip
/**
     * 將批量檔案打包下載成zip
     * @param request
     * @param response
     * @param zipName     下載的zip名
     * @param files       要打包的批量檔案
     * @param zipPath     生成的zip路徑
     * @throws Exception
     */
    private void downloadZip(HttpServletRequest request, HttpServletResponse response, String zipName, List<File> files, String zipPath)throws Exception {
        File srcfile[] = new File[files.size()];
        File zip = new File(zipPath);
        for (int i = 0; i < files.size(); i++) {
            srcfile[i] = files.get(i);
        }
        //生成.zip檔案;
        FileInputStream inStream = null;
        ServletOutputStream os = null;
        try {
            //設定下載zip的頭資訊
            FileDownloadUtils.setZipDownLoadHeadInfo(response, request, zipName);
            os = response.getOutputStream();
            FileDownloadUtils.ZipFiles(srcfile, zip);
            inStream = new FileInputStream(zip);
            byte[] buf = new byte[4096];
            int readLength;
            while (((readLength = inStream.read(buf)) != -1)) {
                os.write(buf, 0, readLength);
            }
        }  finally {
            if (inStream != null) {
                inStream.close();
            }
            if (os != null) {
                os.flush();
                os.close();
            }
        }
    }

zip下載支線完畢

3.附 打成zip包

直接用工具類

/**
     * //壓縮檔案
     * @param srcfile   要壓縮的檔案陣列
     * @param zipfile  生成的zip檔案物件
     */
    public static void ZipFiles(java.io.File[] srcfile, File zipfile) throws Exception {
        byte[] buf = new byte[1024];
        FileOutputStream fos = new FileOutputStream(zipfile);
        ZipOutputStream out = new ZipOutputStream(fos);
        for (int i = 0; i < srcfile.length; i++) {
            FileInputStream in = new FileInputStream(srcfile[i]);
            out.putNextEntry(new ZipEntry(srcfile[i].getName()));
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
            out.closeEntry();
            in.close();
        }
        out.close();
        fos.flush();
        fos.close();
    }

4. 刪除臨時資料夾

/**
     * 刪除資料夾及資料夾下所有檔案
     * @param dir
     * @return
     */
    public static boolean deleteDir(File dir) {
        if (dir == null || !dir.exists()){
            return true;
        }
        if (dir.isDirectory()) {
            String[] children = dir.list();
            //遞迴刪除目錄中的子目錄下
            for (int i=0; i<children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        // 目錄此時為空,可以刪除
        return dir.delete();
    }