Java生成並下載Excel文件-工具類


1.設計思路

Excel文件內容分為:表頭和數據內容,需要下載Excel文件,先需要查詢數據,將需要導出的數據生成Excel文件,然后才能通過輸出流下載

2.代碼實現

2.1.生成Excel文件

a. 給定fps路徑、Excel文件名,新建一個空文件對象File,調用生成文件方法,調用FileUtil工具類將文件以流的形式寫入response

public void exportTaskDetailFile(ErrHandleTaskReq req, HttpServletResponse response) throws IOException {
    String fileName = String.format("%s_%s.%s", req.getId(), System.currentTimeMillis(), "xlsx");
    String dirPath = String.format("%s/%s", ConfigUtil.getConf("fps.filepath"), LocalDate.now().format(WeDateUtils.FMT_DATE_SHORT));
    File dir = new File(dirPath);
    if (!dir.exists()) {
        dir.mkdirs();
    }
    File excelFile = new File(String.format("%s/%s", dirPath, fileName));
    generateDetailFile(excelFile, req.getId());//將數據寫入Excel文件
    FileUtil.writeResponse(excelFile, response);//打印輸出流
}

b. 將數據寫入Excel文件
查詢並封裝需要的生成的Excel文件數據,調用Excel工具類生成Excel文件

private void generateDetailFile(File excelFile, String taskId) throws IOException {
    String sheetName = "差錯文件需要處理明細";
    String[] headName = new String[EXPORT_TASK_DETAIL_NAME.size()];
    String[] headKey = new String[EXPORT_TASK_DETAIL_KEY.size()];
    int batchSize = 1000;
    int count = depositDailyAdjustRecordDao.selectCountByTaskId(taskId);
    Function<Integer, List<ExportTaskDetailFileBo>> queryFunction = i -> {
        List<DepositDailyAdjustRecordEntityWithBLOBs> list = depositDailyAdjustRecordDao.selectListByTaskId(taskId, i * batchSize, batchSize);
        List<ExportTaskDetailFileBo> returnList = new ArrayList<>();
        list.stream().forEach(entity -> {
            ExportTaskDetailFileBo bo = new ExportTaskDetailFileBo();
            BeanUtils.copyProperties(entity, bo);
            DepositDailyRecordEntity oriEntity = JsonUtil.fromJson(entity.getOriBizData(), DepositDailyRecordEntity.class);
            DepositDailyRecordEntity newEntity = JsonUtil.fromJson(entity.getNewBizData(), DepositDailyRecordEntity.class);
            if (oriEntity != null) {
                bo.setOriPassFlag(YesNoEnum.get(oriEntity.getPassFlag()).getRemark());
                bo.setOriCardLevel(oriEntity.getCardLevel());
                bo.setOriDayEndAmt(oriEntity.getDayEndAmt());
            }
            if (newEntity != null) {
                bo.setPassFlag(YesNoEnum.get(newEntity.getPassFlag()).getRemark());
                bo.setCardLevel(newEntity.getCardLevel());
                bo.setDayEndAmt(newEntity.getDayEndAmt());
            }
            bo.setState(String.format("%s-%s", entity.getState(), TxnStatusEnum.get(entity.getState()).getRemark()));
            bo.setDealStep(String.format("%s-%s", entity.getDealStep(), FileDealStep4PointAdjustEnum.get(entity.getDealStep()).getRemark()));
            bo.setErrType(String.format("%s-%s", ErrType4DepositDailyAdjustRecordEnum.get(entity.getErrType()).getCode(),
                    ErrType4DepositDailyAdjustRecordEnum.get(entity.getErrType()).getRemark()));
            bo.setHandlerType(String.format("%s-%s", HandleType4DepositDailyAdjustRecordEnum.get(entity.getHandlerType()).getCode(),
                    HandleType4DepositDailyAdjustRecordEnum.get(entity.getHandlerType()).getRemark()));
            bo.setCreateTime(DateUtils.localTimeFormat(entity.getCreateTime(), WeDateUtils.DATETIME_LONG));
            bo.setUpdateTime(DateUtils.localTimeFormat(entity.getUpdateTime(), WeDateUtils.DATETIME_LONG));
            returnList.add(bo);
        });
        return returnList;
    };
    // 生成Excel文件
    ExcelUtil.writeExcel(excelFile, sheetName, EXPORT_TASK_DETAIL_NAME.toArray(headName),
            EXPORT_TASK_DETAIL_KEY.toArray(headKey), count, batchSize, queryFunction);
}

c. 生成Excel文件
分批次調用Function查詢結果,並將數據寫入Excel文件

public static <T> boolean writeExcel(File excelFile, String sheetName, String[] headName, String[] headKey, int count,
                                     int batchSize, Function<Integer, List<T>> queryFunction) throws IOException {
    // 生成工作簿並寫入數據
    return ExcelUtil.traverse(excelFile, workbook -> {
        // 生成表頭
        generateExcelTitle(workbook, sheetName, headName, headKey, count);
        // 生成數據
        int times = count / batchSize + 1;// 需要寫入次數
        for (int i = 0; i < times; i++) {
            // 從sheetName頁第batchSize*i+1追加寫入數據
            int startRow = batchSize * i + 1;
            List<T> dataList = queryFunction.apply(i);
            generateExcelData(workbook, sheetName, headKey, dataList, startRow);
        }
    });
}

d. 生成工作簿Workbook並寫入數據
1). 新建工作簿(XLS/XLSX類型)

private static Workbook getWorkbook(File excelFile) {
    String extension = excelFile.getAbsolutePath().substring(excelFile.getAbsolutePath().lastIndexOf(".") + 1);
    switch (extension) {
        case XLS:
            return new HSSFWorkbook();
        case XLSX:
            return new XSSFWorkbook();
        default:
            throw new IllegalArgumentException(String.format("%s|%s", "EXCEL文件擴展名有誤", extension));
    }
}

2). 調用Consumer實現,向工作簿寫入數據,並寫回原始文件excelFile

public static boolean traverse(File excelFile, Consumer<Workbook> consumer) throws IOException {
    Workbook workbook = null;
    FileOutputStream outputStream = null;
    try {
        outputStream = new FileOutputStream(excelFile);
        // 新建工作簿(XLS/XLSX類型)
        workbook = getWorkbook(excelFile);
        // 調用Consumer實現,向工作簿寫入數據
        consumer.accept(workbook);
        // 工作簿寫回原始文件excelFile
        workbook.write(outputStream);
        return true;
    } finally {
        try {
            if (null != workbook) {
                workbook.close();
            }
            if (null != outputStream) {
                outputStream.close();
            }
        } catch (Exception e) {
            OpLogUtil.logOpStepException("關閉流", "異常", e);
        }
    }
}

e. 向工作簿Workbook寫入Excel表頭數據
將定義的Excel表頭名稱寫入工作簿

public static void generateExcelTitle(Workbook workbook, String sheetName, String[] headName, String[] headKey, int count) {
    if (count <= 0) {
        throw new BusinessException("數據為空,無需生成");
    }
    if (headName.length == 0 || headKey.length == 0) {
        throw new BusinessException("表頭為空,無需生成");
    }
    if (headName.length != headKey.length) {
        throw new BusinessException("表頭數與要求生成數不一致");
    }
    // 往sheetName頁寫一行表頭數據
    writeSheetRows(workbook, sheetName, Collections.singletonList(Arrays.asList(headName)), 0);
}

f. 向工作簿Workbook寫入Excel內容數據
轉換需要寫入的每一行數據的類型后,追加寫入工作簿

public static void generateExcelData(Workbook workbook, String sheetName, String[] headKey, List dataList, int startRow) {
    List<List<Object>> dataObjList = new ArrayList<>();
    try {
        Iterator iterator = dataList.listIterator();
        while (iterator.hasNext()) {
            List<Object> data = new ArrayList<>();
            Object obj = iterator.next();
            Field[] fields = obj.getClass().getDeclaredFields();
            for (int j = 0; j < headKey.length; j++) {
                for (int i = 0; i < fields.length; i++) {
                    if (fields[i].getName().equals(headKey[j])) {
                        fields[i].setAccessible(true);
                        if (fields[i].get(obj) == null) {
                            data.add("");
                            break;
                        }
                        data.add(fields[i].get(obj).toString());
                        break;
                    }
                }
            }
            dataObjList.add(data);
        }
    } catch (Exception e) {
        OpLogUtil.logOpStepException("Excel數據轉換", "異常", e);
    }
    // 往sheetName頁寫多行數據
    writeSheetRows(workbook, sheetName, dataObjList, startRow);
}

g. 往工作簿sheetName寫入多行數據(生成Excel文件的核心代碼
1). 遍歷一行數據,往Row創建一行的單元格

private static void writeRow(List<Object> rowObj, Row row) {
    for (int i = 0; i < rowObj.size(); i++) {
        row.createCell((short) i).setCellValue(rowObj.get(i).toString());
    }
}

2). 遍歷多行數據,遞歸生成Sheet頁

/**
 * 由於xls表格頁有最大長度的限制(65535),因此每6w條數據分成一頁
 *
 * 說明:startRow(開始行號)必須大於等於currRows(當前sheet頁已有數據行數),否則,
 * 條件i + currRows >= MAX_ROWS_LENGTH 一直滿足,遞歸方法進入死循環,導致 StackOverflowError
 *
 * @param workbook    工作簿
 * @param sheetName   sheet名稱
 * @param dataObjList 待處理數據
 * @param startRow    開始行號
 */
private static void writeSheetRows(Workbook workbook, String sheetName, List<List<Object>> dataObjList, int startRow) {
    String newSheetName = startRow < MAX_ROWS_LENGTH ? sheetName : sheetName + startRow / MAX_ROWS_LENGTH;
    Sheet sheet = workbook.getSheet(newSheetName);
    if (sheet == null) {
        sheet = workbook.createSheet(newSheetName);
        sheet.setDefaultColumnWidth(20);
        sheet.setDefaultRowHeight((short) (2 * 256));
    }
    // 當前sheet頁已有數據行數
    int currRows = sheet.getPhysicalNumberOfRows();
    for (int i = 0; i < dataObjList.size(); i++) {
        if (i + currRows >= MAX_ROWS_LENGTH) {
            writeSheetRows(workbook, sheetName,
                    new ArrayList(dataObjList.subList(i, dataObjList.size())), i + startRow);
            break;
        }
        List<Object> rowObj = dataObjList.get(i);
        Row row = sheet.createRow(i + currRows);
        ExcelUtil.writeRow(rowObj, row);
    }
}

2.2.下載Excel文件

將文件以輸出流形式寫入response對象

public static void writeResponse(File file, HttpServletResponse response) throws IOException {
    OutputStream out = null;
    try {
        response.reset();
        response.setContentType("text/csv; charset=gbk");
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "gbk"));
        long fileLength = file.length();
        String length = String.valueOf(fileLength);
        response.setHeader("Content_Length", length);
        response.setHeader("code", "0");
        out = response.getOutputStream();
        out.write(FileUtils.readFileToByteArray(file));
        out.flush();
    } catch (IOException e) {
        OpLogUtil.logOpStepException("文件下載,操作結果返回","異常",e);
        throw e;
    } finally {
        if (out != null) {
            try {
                out.close();
            } catch (IOException e) {
                OpLogUtil.logOpStepException("文件下載", "下載異常", e);
                throw e;
            }
        }
    }
}

3.END

發布此文,以便學習、記錄並回顧項目實戰中使用到的新技能或技術知識,如:JDK1.8新特性Function、Consumer、obj.getClass()反射,謹以此文,僅供參考!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM