項目中有兩個不同框架組裝成的Excel,現在要對Excel內容合並,組成多sheet的Excel。
主調用方法:
public ResponseEntity exportExcel(Long id) { List<PcOrganization> list = partnerOrganizationService.getList(id); List<Long> ids = list.stream().map(PcOrganization::getId).collect(Collectors.toList()); List<ExportAgentVo> sheet2 = makeSheetDeviceList(ids); List<ExportAgentAndCustomerOrderVo> sheet3 = makeSheetCustomerList(ids); HashMap<String, Object> map = Maps.newHashMap(); map.put(ExportEnum.DEVICEdETAIL.getSheetName(), sheet2); map.put(ExportEnum.CUSTOMERDETAIL.getSheetName(), sheet3); HashMap<String, Class> classMap = Maps.newHashMap(); classMap.put(ExportEnum.DEVICEdETAIL.getSheetName(), ExportAgentVo.class); classMap.put(ExportEnum.CUSTOMERDETAIL.getSheetName(), ExportAgentAndCustomerOrderVo.class); LocalDate now = LocalDate.now(); File datafile = null; //File datafile2 = null; try { datafile = ExcelTools.createDatafile(location, now.toString()); //datafile2 = ExcelTools.createDatafile(location, "原版"); } catch (Exception e) { throw new BusinessException(ResultCodeEnum.OPERATION_ERR.getCode(), "創建文件失敗"); } Workbook sheets = ExcelTools.exportSheet(map, classMap, datafile); PcOrganization organization = list.get(0); List<OrgStatisticsExcelVo> statisticsExcelVos = makeSheetOrgStatistics(list); //主sheet Workbook workBook = StatisticsExcelUtils.createExcelFile2(statisticsExcelVos, organization); Iterator<Sheet> sheetIterator = sheets.sheetIterator(); int hasSheetIndex = workBook.getNumberOfSheets()-1;//主Excel的最后一個sheet的索引 while(sheetIterator.hasNext()){ Sheet srcSheet = sheetIterator.next(); HSSFSheet newSheet1 = (HSSFSheet) workBook.createSheet(); CopySheetUtil.copySheets(newSheet1, (HSSFSheet) srcSheet, true); hasSheetIndex += 1; workBook.setSheetName(hasSheetIndex, srcSheet.getSheetName()); } try { ExcelTools.writeDatafile(workBook, datafile); } catch (Exception e) { throw new BusinessException(ResultCodeEnum.OPERATION_ERR.getCode(), "導出失敗"); } return new ResponseEntity(ResultEnum.SUCCESS, null); }
變量sheets是由easypoi生成。變量workbook由原生生成。
附easypoi生成Excel的封裝方法:
import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; import org.apache.poi.ss.usermodel.Workbook; import org.springframework.util.Assert; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.*; public class ExcelTools { /** * map 即為要導出的數據。map的key為sheet的名稱,value為excel的list數據 * classMap為easypoi要導出的數據entuty,與第一個參數map要對應。map的長度要與classMap的長度一致。 * ckassMap的key與第一個參數map的key一致,都是sheet的名稱,value是要解析的注解了@ExcelTagret的class對象。 * 目的是講第一個參數map的value的list數據解析成classMap的class對象。 * dataFile為導出的excel的文件全路徑名 * 功能描述:把同一個表格多個sheet測試結果重新輸出,如果后續增加多個List<Map<String, Object>>對象,需要后面繼續追加 * @ExcelEntiry sheet表格映射的實體對象 * @return */ public static Workbook exportSheet(Map map, Map classMap, File datafile){ Assert.isTrue(map.size() == classMap.size(), "導出的數據map長度要與對應的對象map長度一致"); Workbook workBook = null; List<Map<String, Object>> sheetsList = new ArrayList<>(); Iterator<Map.Entry<String, List>> iterator = map.entrySet().iterator(); while(iterator.hasNext()){ Map.Entry<String, List> it = iterator.next(); String key = it.getKey(); List value = it.getValue(); Class t = (Class) classMap.get(key); // 創建參數對象(用來設定excel得sheet得內容等信息) ExportParams exportParams = new ExportParams(); //添加序號 //exportParams.setAddIndex(true); // 設置sheet得名稱 exportParams.setSheetName(key); // 設置sheet表頭名稱 exportParams.setTitle(key); // 創建sheet使用得map Map<String, Object> exportMap = new HashMap<>(); // title的參數為ExportParams類型,目前僅僅在ExportParams中設置了sheetName exportMap.put("title", exportParams); // 模版導出對應得實體類型 exportMap.put("entity", t); // sheet中要填充得數據 exportMap.put("data", value); sheetsList.add(exportMap); } // 執行方法 workBook = ExcelExportUtil.exportExcel(sheetsList, ExcelType.HSSF); return workBook; } /** * 設置文件路徑 && 保證文件對象的正確打開 * */ public static File createDatafile(String path, String fileName) throws Exception{ String resource = path+fileName+".xls"; //URI uri = new URI(resource); File myFile = new File(resource);//創建File對象,參數為String類型,表示目錄名 //判斷文件是否存在,如不存在則調用createNewFile()創建新目錄,否則跳至異常處理代碼 if(!myFile.exists()) { myFile.createNewFile(); } return myFile; } public static void writeDatafile(Workbook workBook, File datafile) throws Exception{ try{ FileOutputStream fos = new FileOutputStream(datafile); workBook.write(fos); fos.close(); }catch (Exception e){ e.printStackTrace(); }finally { if(workBook != null) { try { workBook.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
對easypoi生成的Workbook復制其sheet到主Workbook中。工具類來源於網上copy的,實際運行時發現有bug,改動后可以正常使用:
import lombok.extern.slf4j.Slf4j; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFCellStyle; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.util.CellRangeAddress; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; @Slf4j public class CopySheetUtil { public CopySheetUtil() { } public static void copySheets(HSSFSheet newSheet, HSSFSheet sheet) { copySheets(newSheet, sheet, true); } public static void copySheets(HSSFSheet newSheet, HSSFSheet sheet, boolean copyStyle) { int maxColumnNum = 0; Map<Integer, HSSFCellStyle> styleMap = (copyStyle) ? new HashMap<Integer, HSSFCellStyle>() : null; Set<CellRangeAddressWrapper> mergedRegions = new TreeSet<CellRangeAddressWrapper>(); for (int i = sheet.getFirstRowNum(); i <= sheet.getLastRowNum(); i++) { HSSFRow srcRow = sheet.getRow(i); HSSFRow destRow = newSheet.createRow(i); if (srcRow != null) { CopySheetUtil.copyRow(sheet, newSheet, srcRow, destRow, styleMap, mergedRegions); if (srcRow.getLastCellNum() > maxColumnNum) { maxColumnNum = srcRow.getLastCellNum(); } } } for (int i = 0; i <= maxColumnNum; i++) { //設置列寬 newSheet.setColumnWidth(i, sheet.getColumnWidth(i)); } } /** * 復制並合並單元格 * @param srcSheet * @param destSheet * @param srcRow */ public static void copyRow(HSSFSheet srcSheet, HSSFSheet destSheet, HSSFRow srcRow, HSSFRow destRow, Map<Integer, HSSFCellStyle> styleMap, Set<CellRangeAddressWrapper> mergedRegions) { try{ //Set<CellRangeAddressWrapper> mergedRegions = new TreeSet<CellRangeAddressWrapper>(); destRow.setHeight(srcRow.getHeight()); int deltaRows = destRow.getRowNum() - srcRow.getRowNum(); //如果copy到另一個sheet的起始行數不同 for (int j = srcRow.getFirstCellNum(); j <= srcRow.getLastCellNum(); j++) { HSSFCell oldCell = srcRow.getCell(j); // old cell HSSFCell newCell = destRow.getCell(j); // new cell if (oldCell != null) { if (newCell == null) { newCell = destRow.createCell(j); } copyCell(oldCell, newCell, styleMap); CellRangeAddress mergedRegion = getMergedRegion(srcSheet, srcRow.getRowNum(), (short) oldCell.getColumnIndex()); if (mergedRegion != null) { CellRangeAddress newMergedRegion = new CellRangeAddress( mergedRegion.getFirstRow() + deltaRows, mergedRegion.getLastRow() + deltaRows, mergedRegion .getFirstColumn(), mergedRegion .getLastColumn()); CellRangeAddressWrapper wrapper = new CellRangeAddressWrapper( newMergedRegion); if (isNewMergedRegion(wrapper, mergedRegions)) { boolean i = mergedRegions.add(wrapper); if(i){ destSheet.addMergedRegion(wrapper.range); } } } } } }catch(Exception e){ e.printStackTrace(); } } /** * 把原來的Sheet中cell(列)的樣式和數據類型復制到新的sheet的cell(列)中 * * @param oldCell * @param newCell * @param styleMap */ public static void copyCell(HSSFCell oldCell, HSSFCell newCell, Map<Integer, HSSFCellStyle> styleMap) { if (styleMap != null) { if (oldCell.getSheet().getWorkbook() == newCell.getSheet() .getWorkbook()) { newCell.setCellStyle(oldCell.getCellStyle()); } else { int stHashCode = oldCell.getCellStyle().hashCode(); HSSFCellStyle newCellStyle = styleMap.get(stHashCode); if (newCellStyle == null) { newCellStyle = newCell.getSheet().getWorkbook() .createCellStyle(); newCellStyle.cloneStyleFrom(oldCell.getCellStyle()); styleMap.put(stHashCode, newCellStyle); } newCell.setCellStyle(newCellStyle); } } switch (oldCell.getCellType().getCode()) { //case CellType.STRING: case 1: newCell.setCellValue(oldCell.getStringCellValue()); break; //case CellType.NUMERIC: case 0: newCell.setCellValue(oldCell.getNumericCellValue()); break; //case CellType.BLANK: case 3: newCell.setCellType(CellType.BLANK); break; //case CellType.BOOLEAN: case 4: newCell.setCellValue(oldCell.getBooleanCellValue()); break; //case CellType.ERROR: case 5: newCell.setCellErrorValue(oldCell.getErrorCellValue()); break; //case CellType.FORMULA: case 2: newCell.setCellFormula(oldCell.getCellFormula()); break; default: break; } } // 獲取merge對象 public static CellRangeAddress getMergedRegion(HSSFSheet sheet, int rowNum, short cellNum) { for (int i = 0; i < sheet.getNumMergedRegions(); i++) { CellRangeAddress merged = sheet.getMergedRegion(i); if (merged.isInRange(rowNum, cellNum)) { return merged; } } return null; } private static boolean isNewMergedRegion( CellRangeAddressWrapper newMergedRegion, Set<CellRangeAddressWrapper> mergedRegions) { boolean bool = mergedRegions.contains(newMergedRegion); return !bool; } }
其中為了判斷合並單元格是否一樣,自定義的類
CellRangeAddressWrapper:
import org.apache.poi.ss.util.CellRangeAddress; public class CellRangeAddressWrapper implements Comparable<CellRangeAddressWrapper>{ public CellRangeAddress range; public CellRangeAddressWrapper(CellRangeAddress theRange) { this.range = theRange; } @Override public int compareTo(CellRangeAddressWrapper craw) { if (range.getFirstColumn() < craw.range.getFirstColumn() || range.getFirstRow() < craw.range.getFirstRow()) { return -1; } else if (range.getFirstColumn() == craw.range.getFirstColumn() && range.getFirstRow() == craw.range.getFirstRow()) { return 0; } else { return 1; } } }
即可將源對象的sheet拷貝至目標對象中。
需要注意的是 生成 ExcelTools 類中方法 exportSheet 中有注釋掉的 生成文件加 序號 的方法。這個晚上可以找到很多。也貼一下,需要自定義 導出的方法,原生的會報錯。
/** * 重寫easypoi的工具類,原工具類是final類型的 * */ public class ExcelExportUtil { public static int USE_SXSSF_LIMIT = 100000; public static final String SHEET_NAME = "sheetName"; private ExcelExportUtil() { } /** * 大數據量導出 * * @param entity 表格標題屬性 * @param pojoClass Excel對象Class * @param server 查詢數據的接口 * @param queryParams 查詢數據的參數 */ public static Workbook exportBigExcel(ExportParams entity, Class<?> pojoClass, IExcelExportServer server, Object queryParams) { ExcelBatchExportService batchServer = new ExcelBatchExportService(); batchServer.init(entity, pojoClass); return batchServer.exportBigExcel(server, queryParams); } /** * 大數據量導出 * * @param entity * @param excelParams * @param server 查詢數據的接口 * @param queryParams 查詢數據的參數 * @return */ public static Workbook exportBigExcel(ExportParams entity, List<ExcelExportEntity> excelParams, IExcelExportServer server, Object queryParams) { ExcelBatchExportService batchServer = new ExcelBatchExportService(); batchServer.init(entity, excelParams); return batchServer.exportBigExcel(server, queryParams); } /** * @param entity 表格標題屬性 * @param pojoClass Excel對象Class * @param dataSet Excel對象數據List */ public static Workbook exportExcel(ExportParams entity, Class<?> pojoClass, Collection<?> dataSet) { Workbook workbook = getWorkbook(entity.getType(), dataSet.size()); new SubExcelExportService().createSheet(workbook, entity, pojoClass, dataSet); return workbook; } private static Workbook getWorkbook(ExcelType type, int size) { if (ExcelType.HSSF.equals(type)) { return new HSSFWorkbook(); } else if (size < USE_SXSSF_LIMIT) { return new XSSFWorkbook(); } else { return new SXSSFWorkbook(); } } /** * 根據Map創建對應的Excel * * @param entity 表格標題屬性 * @param entityList Map對象列表 * @param dataSet Excel對象數據List */ public static Workbook exportExcel(ExportParams entity, List<ExcelExportEntity> entityList, Collection<?> dataSet) { Workbook workbook = getWorkbook(entity.getType(), dataSet.size()); ; new SubExcelExportService().createSheetForMap(workbook, entity, entityList, dataSet); return workbook; } /** * 根據Map創建對應的Excel(一個excel 創建多個sheet) * * @param list 多個Map key title 對應表格Title key entity 對應表格對應實體 key data * Collection 數據 * @return */ public static Workbook exportExcel(List<Map<String, Object>> list, ExcelType type) { Workbook workbook = getWorkbook(type, 0); for (Map<String, Object> map : list) { //ExcelExportService service = new ExcelExportService(); new SubExcelExportService().createSheet(workbook, (ExportParams) map.get("title"), (Class<?>) map.get("entity"), (Collection<?>) map.get("data")); } return workbook; } /** * 導出文件通過模板解析,不推薦這個了,推薦全部通過模板來執行處理 * * @param params 導出參數類 * @param pojoClass 對應實體 * @param dataSet 實體集合 * @param map 模板集合 * @return */ @Deprecated public static Workbook exportExcel(TemplateExportParams params, Class<?> pojoClass, Collection<?> dataSet, Map<String, Object> map) { return new ExcelExportOfTemplateUtil().createExcelByTemplate(params, pojoClass, dataSet, map); } /** * 導出文件通過模板解析只有模板,沒有集合 * * @param params 導出參數類 * @param map 模板集合 * @return */ public static Workbook exportExcel(TemplateExportParams params, Map<String, Object> map) { return new ExcelExportOfTemplateUtil().createExcelByTemplate(params, null, null, map); } /** * 導出文件通過模板解析只有模板,沒有集合 * 每個sheet對應一個map,導出到處,key是sheet的NUM * * @param params 導出參數類 * @param map 模板集合 * @return */ public static Workbook exportExcel(Map<Integer, Map<String, Object>> map, TemplateExportParams params) { return new ExcelExportOfTemplateUtil().createExcelByTemplate(params, map); } /** * 導出文件通過模板解析只有模板,沒有集合 * 每個sheet對應一個list,按照數量進行導出排序,key是sheet的NUM * * @param params 導出參數類 * @param map 模板集合 * @return */ public static Workbook exportExcelClone(Map<Integer, List<Map<String, Object>>> map, TemplateExportParams params) { return new ExcelExportOfTemplateUtil().createExcelCloneByTemplate(params, map); } }
其中的自定義 SubExcelExportService 類:
import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import cn.afterturn.easypoi.excel.export.ExcelExportService; import cn.afterturn.easypoi.excel.export.styler.IExcelExportStyler; import cn.afterturn.easypoi.exception.excel.ExcelExportException; import cn.afterturn.easypoi.exception.excel.enums.ExcelExportEnum; import cn.afterturn.easypoi.util.PoiExcelGraphDataUtil; import org.apache.poi.ss.usermodel.Drawing; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import java.util.*; public class SubExcelExportService extends ExcelExportService { private static int MAX_NUM = 60000; @Override protected void insertDataToSheet(Workbook workbook, ExportParams entity, List<ExcelExportEntity> entityList, Collection<?> dataSet, Sheet sheet) { try { dataHandler = entity.getDataHandler(); if (dataHandler != null && dataHandler.getNeedHandlerFields() != null) { needHandlerList = Arrays.asList(dataHandler.getNeedHandlerFields()); } dictHandler = entity.getDictHandler(); i18nHandler = entity.getI18nHandler(); // 創建表格樣式 setExcelExportStyler( (IExcelExportStyler) entity.getStyle().getConstructor(Workbook.class).newInstance(workbook)); Drawing patriarch = PoiExcelGraphDataUtil.getDrawingPatriarch(sheet); List<ExcelExportEntity> excelParams = new ArrayList<ExcelExportEntity>(); if (entity.isAddIndex()) { excelParams.add(indexExcelEntity(entity)); } excelParams.addAll(entityList); // sortAllParams(excelParams); int index = entity.isCreateHeadRows() ? createHeaderAndTitle(entity, sheet, workbook, excelParams) : 0; int titleHeight = index; setCellWith(excelParams, sheet); setColumnHidden(excelParams, sheet); short rowHeight = entity.getHeight() != 0 ? entity.getHeight() : getRowHeight(excelParams); setCurrentIndex(1); Iterator<?> its = dataSet.iterator(); List<Object> tempList = new ArrayList<Object>(); while (its.hasNext()) { Object t = its.next(); index += createCells(patriarch, index, t, excelParams, sheet, workbook, rowHeight, 0)[0]; tempList.add(t); if (index >= MAX_NUM) { break; } } if (entity.getFreezeCol() != 0) { sheet.createFreezePane(entity.getFreezeCol(), 0, entity.getFreezeCol(), 0); } mergeCells(sheet, excelParams, titleHeight); its = dataSet.iterator(); for (int i = 0, le = tempList.size(); i < le; i++) { its.next(); its.remove(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("List data more than max ,data size is {}", dataSet.size()); } // 發現還有剩余list 繼續循環創建Sheet if (dataSet.size() > 0) { createSheetForMap(workbook, entity, entityList, dataSet); } else { // 創建合計信息 addStatisticsRow(getExcelExportStyler().getStyles(true, null), sheet); } } catch (Exception e) { LOGGER.error(e.getMessage(), e); throw new ExcelExportException(ExcelExportEnum.EXPORT_ERROR, e.getCause()); } } }
需要注意的是,加入依賴的poi版本。3.幾的我忘了,會報錯,找不到貌似叫一個CellType的錯,忘了截圖了。需要更換至 4 以上的版本即可。
附帶序號的excel示例圖