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()反射,謹以此文,僅供參考!