1. 程式人生 > 實用技巧 >Springboot+POI實現excel生成下載進階版(單元格合併,多Sheet,各種樣式處理)

Springboot+POI實現excel生成下載進階版(單元格合併,多Sheet,各種樣式處理)

上週五來了新的需求,基本上我寫的還款那一系列流程不要了(我好悲傷,當時寫了很久的,邏輯複雜的寫的我很驕傲),新的變成如上所示(僅僅一部分),勾選幾筆後生成一個excel表格,不同的融資編號所引發的那堆資料生成的表格放在不同的sheet頁。模板如下:

  哎呦,這個表格不太規則喲~之前寫的比較簡單,中規中矩,一行欄位標題對應幾行資料,連表頭都沒有,但這個是雙重表格,而且有16個以上的合併單元格,表格帶邊框,左對齊,右對齊,居中的資料都有,還有加粗的,大小不一的資料,一看寫出程式碼就少不了。於是最開始的想法是偷個懶,坐等同事的在上一步業務中對每筆單號生成各自的excel,我在這一步拿著所有的融資編號去找到他們在上一步儲存的excel,然後寫套程式碼做一個excel的資料合併,將不同的excel合到一個excel中,放入不同sheet頁(來誇誇我,我是不是很機智),結果我成功了,但我又失敗了。。。我在網上找了套程式碼進行合併的,然後無腦的修改後還是執行,生成本地成功了,但是放在swagger上測試下載時,一直說檔案打不開。。整到下班也一直說格式問題檔案打不開,網上各種方式都試了,,果然,別人的程式碼不要隨意套用,還是自己寫一套比較實用。

  週末的日子比較無聊,望著空蕩蕩的房子,安靜的過分,無聊的我決定找點事情做,開啟電腦開啟IDEA,將週五決定偷懶的工作整一下,於是各種通過程式碼調整合並單元格,樣式諸類。。。基本上把整個思路全寫完了,大面積程式碼,剩下的就是那些資料在哪取我不清楚,只能先造點假資料了。。週一來了後,問了下同事資料取的表在哪,然後把程式碼補充下,再根據生成的excel樣式對比模板表,調了調,大功告成。因為第一次處理生成這種樣式的excel,所以紀念一下,有需要的朋友可以當個參考,註釋我都寫的很明確了,因為我在main方法裡面加個測試資料,所以程式碼粘下來可以直接執行看效果。

程式碼如下:

package cn.exrick.xboot.common.utils;

import org.apache.poi.hssf.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; import javax.servlet.http.HttpServletResponse; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.text.SimpleDateFormat;
import java.util.*; /** * @author zae */ public class DownLoadPayMsgUtil { public static void main(String[] args) { //示例資料: //申請資訊資料 String[] apllyTitlesKey = new String[]{"txn_no","deliver_contact","order_id","order_date","deliver_address","account_period","commodity_name","trade_amount"}; String[] applyTitle = new String[]{"序號","產融平臺融資編號","發貨人","訂單編號","訂單日期","工廠地址","收貨地址","商品名稱","運費金額"}; Map<String, Object> randomMapOne = getRandomMap(apllyTitlesKey); Map<String, Object> randomMapTwo = getRandomMap(apllyTitlesKey); Map<String, Object> randomMapThree = getRandomMap(apllyTitlesKey); Map<String, Object> randomMapFour = getRandomMap(apllyTitlesKey); List<Map<String,Object>> applyContantList = Arrays.asList(randomMapOne,randomMapTwo,randomMapThree,randomMapFour);//實際工作中在資料庫中獲取 String[][] applyContent = DownLoadPayMsgUtil.convertListToArray(apllyTitlesKey, applyContantList);//list轉換二維陣列 //支付資訊資料 String[] payTitlesKey = new String[]{"carry_date","carry_no","carry_contact_user","carry_plate","carry_amount","account_bank","account_no"}; String[] payTitle = new String[]{"承運時間","提單號","承運人","承運車號","運費金額","開戶行","賬號"}; Map<String, Object> mapOne = getRandomMap(payTitlesKey); Map<String, Object> mapTwo = getRandomMap(payTitlesKey); List<Map<String,Object>> payContantList = Arrays.asList(mapOne,mapTwo);//實際工作中在資料庫中獲取 String[][] payContent = DownLoadPayMsgUtil.convertListToArray(payTitlesKey, payContantList);//list轉換二維陣列 //前置內容資料 List<String> beforeTitle = Arrays.asList("申請人:","開戶行:","名稱:","賬號:"); List<String> beforeContent = Arrays.asList("zae","工商銀行","zae的賬戶","274630575");//實際工作在資料庫中取得 Map<String,Object> dataMap = new HashMap<>(); dataMap.put("applyTitle",applyTitle);//申請單表字段組 dataMap.put("applyContent",applyContent);//申請單表內容資料 dataMap.put("payTitle",payTitle);//支付資訊表字段組 dataMap.put("payContent",payContent);//支付資訊表內容資料 dataMap.put("beforeTitle",beforeTitle);//前置內容欄位組 dataMap.put("beforeContent",beforeContent);//前置內容資料 dataMap.put("applyAmount","2000");//申請金額總和 dataMap.put("payAmount","3000");//運費金額總和 List<Map<String,Object>> dataList = new ArrayList<>(); Map<String,Object> dataMap2 = new HashMap<>(dataMap); dataList.add(dataMap);//多個map情況下會生成多個sheet頁 dataList.add(dataMap2); //根據傳入的資料,生成一個excel物件 HSSFWorkbook wb = DownLoadPayMsgUtil.getHSSFWorkbook(dataList); //匯出下載 //DownLoadPayMsgUtil.exportExcel(wb,"提款申請.xls",null); //儲存在本地 exportExcelToLocally(wb,"D:\\提款申請.xls"); } /** * 用傳入的sheet名稱,標題,內容生成HSSFWorkbook物件。 * @param dataList * @return HSSFWorkbook */ public static HSSFWorkbook getHSSFWorkbook(List<Map<String,Object>> dataList){ // 第一步,建立一個HSSFWorkbook,對應一個Excel檔案 HSSFWorkbook wb = new HSSFWorkbook(); //第二步,建立確定位置的的合併單元格物件 CellRangeAddress callRangeAddress1 = new CellRangeAddress(2,2,0,8);//起始行,結束行,起始列,結束列 CellRangeAddress callRangeAddress2 = new CellRangeAddress(3,3,0,1);//起始行,結束行,起始列,結束列 CellRangeAddress callRangeAddress3 = new CellRangeAddress(3,3,2,5);//起始行,結束行,起始列,結束列 CellRangeAddress callRangeAddress4 = new CellRangeAddress(3,3,7,8);//起始行,結束行,起始列,結束列 CellRangeAddress callRangeAddress5 = new CellRangeAddress(4,4,0,1);//起始行,結束行,起始列,結束列 CellRangeAddress callRangeAddress6 = new CellRangeAddress(4,4,2,7);//起始行,結束行,起始列,結束列 CellRangeAddress callRangeAddress7 = new CellRangeAddress(5,5,0,1);//起始行,結束行,起始列,結束列 CellRangeAddress callRangeAddress8 = new CellRangeAddress(5,5,2,7);//起始行,結束行,起始列,結束列 CellRangeAddress callRangeAddress9 = new CellRangeAddress(6,6,0,1);//起始行,結束行,起始列,結束列 CellRangeAddress callRangeAddress10 = new CellRangeAddress(6,6,2,7);//起始行,結束行,起始列,結束列 CellRangeAddress callRangeAddress11 = new CellRangeAddress(8,8,0,8);//起始行,結束行,起始列,結束列 // 第三步,建立單元格樣式(字型大小,是否加粗,是否加邊框,對齊方式) HSSFCellStyle styleTitle = createCellStyle(wb,(short)11,true,false,"C");//標題頭 HSSFCellStyle styleBeforeTitle = createCellStyle(wb,(short)9,false,false,"R");//前置內容欄位 HSSFCellStyle styleBeforeData = createCellStyle(wb,(short)9,false,false,"F");//前置內容資料 HSSFCellStyle styleTableTitle = createCellStyle(wb,(short)9,true,true,"C");//表頭 HSSFCellStyle styleTableContent = createCellStyle(wb,(short)9,false,true,"C");//表內容 for(int k=1;k<=dataList.size();k++){ Map<String,Object> dataMap = dataList.get(k-1); String []applyTitle = (String[]) dataMap.get("applyTitle");//申請資訊表的欄位組 String [][]applyContent = (String[][]) dataMap.get("applyContent");//申請資訊表的內容組 String []payTitle = (String[]) dataMap.get("payTitle");//支付資訊表的欄位組 String [][]payContent = (String[][]) dataMap.get("payContent");//支付資訊表的內容組 //第四步:建立未確定位置的合併單元格物件 CellRangeAddress callRangeAddress12 = new CellRangeAddress(10+applyContent.length,10+applyContent.length,0,4);//起始行,結束行,起始列,結束列 CellRangeAddress callRangeAddress13 = new CellRangeAddress(10+applyContent.length,10+applyContent.length,6,7);//起始行,結束行,起始列,結束列 CellRangeAddress callRangeAddress14 = new CellRangeAddress(11+applyContent.length,11+applyContent.length,0,8);//起始行,結束行,起始列,結束列 CellRangeAddress callRangeAddress15 = new CellRangeAddress(12+applyContent.length,12+applyContent.length,6,8);//起始行,結束行,起始列,結束列 // 第五步,在workbook中新增一個sheet,對應Excel檔案中的sheet HSSFSheet sheet = wb.createSheet("Sheet"+k); //設定列寬 sheet.setColumnWidth(0, 256*15); sheet.setColumnWidth(1, 256*20); sheet.setColumnWidth(2, 256*25); sheet.setColumnWidth(5, 256*30); //第六步,載入合併單元格物件 sheet.addMergedRegion(callRangeAddress1); sheet.addMergedRegion(callRangeAddress2); sheet.addMergedRegion(callRangeAddress3); sheet.addMergedRegion(callRangeAddress4); sheet.addMergedRegion(callRangeAddress5); sheet.addMergedRegion(callRangeAddress6); sheet.addMergedRegion(callRangeAddress7); sheet.addMergedRegion(callRangeAddress8); sheet.addMergedRegion(callRangeAddress9); sheet.addMergedRegion(callRangeAddress10); sheet.addMergedRegion(callRangeAddress11); sheet.addMergedRegion(callRangeAddress12); sheet.addMergedRegion(callRangeAddress13); sheet.addMergedRegion(callRangeAddress14); sheet.addMergedRegion(callRangeAddress15); for(int i = 1;i<=payContent.length+1;i++){//支付資訊賬號部分單元格合併的建立以及載入 CellRangeAddress callRangeAddress16 = new CellRangeAddress(12+applyContent.length+i,12+applyContent.length+i,6,8);//起始行,結束行,起始列,結束列 sheet.addMergedRegion(callRangeAddress16); } // 第七步,根據模板往sheet中新增資料 //7.1 設定標題頭和前置內容 List<String> beforeTitle = (List<String>) dataMap.get("beforeTitle"); List<String> beforeContent = (List<String>) dataMap.get("beforeContent"); //設定總標題 HSSFRow rowTitle = sheet.createRow(2); HSSFCell cellTitle = rowTitle.createCell(0); cellTitle.setCellStyle(styleTitle); cellTitle.setCellValue("提款資訊表"); //設定前置內容資料 for(int i = 3;i<=6;i++){ HSSFRow rowBefore = sheet.createRow(i); for(int j = 0;j<=2;j++){//迴圈賦值前置資訊資料(排除申請日期) if(j == 1){ continue; } HSSFCell cellBefore = rowBefore.createCell(j); if(j == 0){ cellBefore.setCellStyle(styleBeforeTitle); cellBefore.setCellValue(beforeTitle.get(i-3)); }else{ cellBefore.setCellStyle(styleBeforeData); cellBefore.setCellValue(beforeContent.get(i-3)); } } if(i == 3){//申請日期 HSSFCell cellBeforeDate = rowBefore.createCell(7); cellBeforeDate.setCellStyle(styleBeforeData); cellBeforeDate.setCellValue("申請日期:"+new SimpleDateFormat("yyyy-MM-dd").format(new Date())); } } //7.2申請資訊 //設定申請資訊表的表頭 HSSFRow rowApplyTitle = sheet.createRow(8); for(int i = 0;i<applyTitle.length;i++){ HSSFCell cellApplyTitle = rowApplyTitle.createCell(i); cellApplyTitle.setCellStyle(styleTableTitle); cellApplyTitle.setCellValue("申請資訊"); } //設定申請資訊表的內容 for(int row = 9;row <= 9+applyContent.length;row++){ HSSFRow rowApplyContent = sheet.createRow(row); for(int cell = 0;cell<applyTitle.length;cell++){ HSSFCell cellApplyContent = rowApplyContent.createCell(cell); cellApplyContent.setCellStyle(styleTableContent); if(row == 9){//表的欄位名 cellApplyContent.setCellValue(applyTitle[cell]); }else if(row != 9 && cell == 0){//表的序號列 cellApplyContent.setCellValue(row-9); }else{//表的內容 cellApplyContent.setCellValue(applyContent[row-10][cell-1]); } } } //設定申請資訊表的底部 HSSFRow rowApplyBottom = sheet.createRow(9+applyContent.length+1); for(int i = 0;i<=8;i++){ if(i==0 || i==5 || i==6 || i==8){ HSSFCell cellApplyBottom = rowApplyBottom.createCell(i); cellApplyBottom.setCellStyle(styleTableContent); if(i == 8){ cellApplyBottom.setCellValue(dataMap.get("applyAmount").toString()); } } } //7.3支付資訊 //設定支付資訊表的表頭 HSSFRow rowPayTitle = sheet.createRow(11+applyContent.length); for(int i = 0;i<applyTitle.length;i++){ HSSFCell cellPayTitle = rowPayTitle.createCell(i); cellPayTitle.setCellStyle(styleTableTitle); cellPayTitle.setCellValue("支付資訊"); } //設定支付資訊表的內容 for(int row = 12+applyContent.length;row<=12+applyContent.length+payContent.length+1;row++){ HSSFRow rowPayContent = sheet.createRow(row); for(int cell = 0;cell<payTitle.length-1;cell++){ HSSFCell cellPayContent = rowPayContent.createCell(cell); cellPayContent.setCellStyle(styleTableContent); if(row == 12+applyContent.length){//支付資訊表的欄位名 cellPayContent.setCellValue(payTitle[cell]); }else if(row == 12+applyContent.length+payContent.length+1){//支付資訊表的最後一行 if(cell == 4){//運費金額總和 cellPayContent.setCellValue(dataMap.get("payAmount").toString()); }else{//空白 cellPayContent.setCellValue(""); } }else{//支付資訊表的內容 cellPayContent.setCellValue(payContent[row-13-applyContent.length][cell]); } } for(int cell = payTitle.length-1;cell<9;cell++){//關於該表中賬號欄位的特殊處理 HSSFCell cellPayContent = rowPayContent.createCell(cell); cellPayContent.setCellStyle(styleTableContent); if(row == 12+applyContent.length){ cellPayContent.setCellValue(payTitle[payTitle.length-1]); }else if(row == 12+applyContent.length+payContent.length+1){ cellPayContent.setCellValue(""); }else{ cellPayContent.setCellValue(payContent[row-13-applyContent.length][payTitle.length-1]); } } } } return wb; } //把list轉換為String[][] public static String[][] convertListToArray(String[] titles , List<Map<String,Object>> list){ //excel的主體內容 String[][] content = new String[list.size()][]; for (int i = 0; i < list.size(); i++) { Map<String,Object> map = list.get(i); content[i] = new String[titles.length]; for (int j = 0; j < titles.length; j++) { Object obj = map.get(titles[j]); if(obj == null){ obj = ""; } content[i][j] = obj.toString(); } } return content; } /** *字型大小,是否加粗,是否水平居中,是否加邊框,左對齊"L",右對齊"R" * @param workbook * @param fontsize * @return 單元格樣式 */ private static HSSFCellStyle createCellStyle(HSSFWorkbook workbook, short fontsize,boolean flag,boolean flag2,String alignType) { HSSFCellStyle style = workbook.createCellStyle(); //左右對齊 if(alignType!=null){ if("L".equalsIgnoreCase(alignType)){ style.setAlignment(HSSFCellStyle.ALIGN_LEFT);//左對齊 }else if("R".equalsIgnoreCase(alignType)){ style.setAlignment(HSSFCellStyle.ALIGN_RIGHT);//右對齊 }else if("C".equalsIgnoreCase(alignType)){ style.setAlignment(HSSFCellStyle.ALIGN_CENTER);//水平居中 } } //是否加邊框 if(flag2){ style.setBorderBottom(HSSFCellStyle.BORDER_THIN); //下邊框 style.setBorderLeft(HSSFCellStyle.BORDER_THIN);//左邊框 style.setBorderTop(HSSFCellStyle.BORDER_THIN);//上邊框 style.setBorderRight(HSSFCellStyle.BORDER_THIN);//右邊框 } style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中 //建立字型 HSSFFont font = workbook.createFont(); //是否加粗字型 if(flag){ font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD); } font.setFontHeightInPoints(fontsize); //載入字型 style.setFont(font); return style; } /* 把HSSFWorkbook物件寫入到瀏覽器輸出流,完成下載功能 */ public static void exportExcel(HSSFWorkbook hssfWorkbook, String fileName, HttpServletResponse response){ try { //設定響應頭 DownLoadPayMsgUtil.setResponseHeader(response, fileName); //獲取輸出流 OutputStream os = response.getOutputStream(); //寫入 hssfWorkbook.write(os); os.flush(); os.close(); } catch (Exception e) { e.printStackTrace(); } } /* 設定瀏覽器下載響應頭 */ private static void setResponseHeader(HttpServletResponse response, String fileName) { try { try { fileName = new String(fileName.getBytes(),"ISO8859-1"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } response.setContentType("application/octet-stream;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment;filename="+ fileName); response.addHeader("Pargam", "no-cache"); response.addHeader("Cache-Control", "no-cache"); } catch (Exception ex) { ex.printStackTrace(); } } /** * 將生成的excel生成在本地 * @param hssfWorkbook * @param path */ private static void exportExcelToLocally(HSSFWorkbook hssfWorkbook,String path){ try { FileOutputStream fileOut = new FileOutputStream(path); hssfWorkbook.write(fileOut); fileOut.flush(); fileOut.close(); System.out.println("生成成功"); } catch (IOException e) { e.printStackTrace(); } } /** * 隨機生成map資料,測試專用 * @param titleArr * @return */ private static Map<String,Object> getRandomMap(String [] titleArr){ Map<String,Object> rtnMap = new HashMap<>(); for(int i = 0;i<titleArr.length;i++){ rtnMap.put(titleArr[i],"測試資料"+i); } return rtnMap; } }

生成的效果如下:

生成了兩個sheet頁,當然如果勾選更多筆融資編號,就會生成更多的sheet頁。

註釋很清楚,不多解釋了,嘻嘻。