POI导出Excel
前言
数据报表是许多项目都有的模块,一般都是导出Excel或者PDF,这里记录下我在项目里用POI导出Excel。项目中,我需要根据页面jqgrid的机架查询条件导出对应的机架数据,jqgrid是分页的,但导出是要导出所有。
POI
Apache POI - the Java API for Microsoft Documents,官网:http://poi.apache.org/
代码编写
maven引如POI
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> </dependency>
或者
<dependency><!--Excel工具类(Easy POI)--> <groupId>cn.afterturn</groupId> <artifactId>easypoi-base</artifactId> <version>3.2.0</version> </dependency> <dependency><!--Excel工具类(Easy POI)--> <groupId>cn.afterturn</groupId> <artifactId>easypoi-web</artifactId> <version>3.2.0</version> </dependency> <dependency><!--Excel工具类(Easy POI)--> <groupId>cn.afterturn</groupId> <artifactId>easypoi-annotation</artifactId> <version>3.2.0</version> </dependency>
html、js调用
<button type="button" class="btn btn-default fa table-download float-right" onclick="exportRackExcel();">导出</button>
//导出excel function exportRackExcel() { //获取当前jqGrid分页参数 var postData = $("#rack").jqGrid("getGridParam", "postData"); postData.page = 1; postData.rows = 999999999;//设置每页9亿条记录(相当于无穷大,查询所有) //ajax不支持Excel类型,使用location.href或者表单提交 //window.location.href,get提交,数据会暴露在URL,相对不安全 //创建临时的、隐藏的form表单,post提交,数据在请求体里,相对安全 var $form = $(document.createElement('form')).css({display: 'none'}).attr("method", "POST").attr("action", ctx + "/excel"); for (var key in postData) { var $input = $(document.createElement('input')).attr('name', key).val(postData[key]); $form.append($input); } $("body").append($form); $form.submit(); //过河拆桥,提交完成后remove掉 $form.remove(); }
纯js写法
//其他操作,同上 let $form = document.createElement('form'); $form.style.display="none"; $form.method="POST"; $form.action=ctx + "/excel"; for (let key in postData) { if(postData[key]){ let $input = document.createElement('input'); $input.name=key; $input.value=postData[key]; $form.appendChild($input); } } document.body.appendChild($form); $form.submit(); //过河拆桥,提交完成后remove掉 $form.remove();
controller
/** * 根据当前jqGrid分页情况,创建并导出Excel文件 * * @param entity 机架实体,用来接收查询条件 * @return ResponseEntity */ @PostMapping("/excel") public ResponseEntity createExcel(RackVo entity) { //Excel对应的columnNames列名集合 { key,label } String[][] excelMap = { {"no", "Rack Code"}, {"rackName", "Rack Name"}, {"roomName", "Room"}, {"idc", "IDC Center"}, {"clientName", "Customer"}, {"rackTypeName", "Type"}, {"existentialMode", "Existential Mode"}, {"maxPower", "Maximum Power(KVA)"}, {"status", "Status"}, {"administrate", "Administrate"}, }; return DownloadUtil.download(ExportExcelUtil.createExcel("Rack Management", excelMap, rackService.createExcel(entity).getData()).getData(), "机架数据报表"); }
两个工具类:导出Excel工具类 ExportExcelUtil,下载工具类 DownloadUtil
/** * java POI 导出Excel表工具类 */ public class ExportExcelUtil { //禁止实例化 private ExportExcelUtil() { } /** * 只支持一级表头 * * @param titleName 表标题 * @param columnNames 列名集合,key是用来设置填充数据时对应单元格的值,label就是对应的列名,生成Excel表时, * 第一维数组下标0对应值为Excel表最左边的列的列名 例:{ { key,label },{ key,label } } * @param dataLists 数据集合,key对应的是列名集合的key,value是要填充到单元格的值 例:ArrayList<HashMap<String key, String vaule>> * @return ResultModel<Workbook> */ public static ResultModel<Workbook> createExcel(String titleName, String[][] columnNames, ArrayList<HashMap<String, String>> dataLists) { //创建HSSFWorkbook对象(excel的文档对象) HSSFWorkbook wb = new HSSFWorkbook(); //建立新的sheet对象(excel的表单) HSSFSheet sheet = wb.createSheet(titleName);//设置表单名 //1、标题名 //创建标题行,参数为行索引(excel的行),可以是0~65535之间的任何一个 HSSFRow row1 = sheet.createRow(0); //标题的字体 HSSFFont font1 = wb.createFont(); font1.setFontHeightInPoints((short) 12); font1.setFontName("黑体"); //标题的样式 HSSFCellStyle style1 = wb.createCellStyle(); style1.setAlignment(HSSFCellStyle.ALIGN_CENTER);//水平居中 style1.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中 // 把字体 应用到当前样式 style1.setFont(font1); //自动换行 style1.setWrapText(true); //自定义填充颜色(天空蓝) style1.setFillPattern(FillPatternType.SOLID_FOREGROUND); style1.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex()); // 设置边框 style1.setBorderBottom(HSSFCellStyle.BORDER_THIN); style1.setBorderLeft(HSSFCellStyle.BORDER_THIN); style1.setBorderRight(HSSFCellStyle.BORDER_THIN); style1.setBorderTop(HSSFCellStyle.BORDER_THIN); createCell(row1, 0, style1, titleName); //合并单元格CellRangeAddress构造参数依次表示起始行,截至行,起始列, 截至列 sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, columnNames.length - 1)); //2、列名 //创建列名行 //列名的字体 HSSFFont font2 = wb.createFont(); font2.setFontHeightInPoints((short) 12); font2.setFontName("新宋体"); //列名的样式 HSSFCellStyle style2 = wb.createCellStyle(); style2.setAlignment(HSSFCellStyle.ALIGN_CENTER);//水平居中 style2.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中 // 把字体 应用到当前样式 style2.setFont(font2); //自动换行 style2.setWrapText(true); //自定义填充颜色(浅蓝色) style2.setFillPattern(FillPatternType.SOLID_FOREGROUND); style2.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex()); // 设置边框 style2.setBorderBottom(HSSFCellStyle.BORDER_THIN); style2.setBorderLeft(HSSFCellStyle.BORDER_THIN); style2.setBorderRight(HSSFCellStyle.BORDER_THIN); style2.setBorderTop(HSSFCellStyle.BORDER_THIN); HSSFRow row2 = sheet.createRow(1); for (int i = 0; i < columnNames.length; i++) { //单元格宽度 sheet.setColumnWidth(i, 20 * 256); createCell(row2, i, style2, columnNames[i][1]);//例:[[key,label],[key,label]] 取label } //3、填充数据 //内容的字体 HSSFFont font3 = wb.createFont(); font3.setFontHeightInPoints((short) 12); font3.setFontName("新宋体"); //内容的样式 HSSFCellStyle style3 = wb.createCellStyle(); style3.setAlignment(HSSFCellStyle.ALIGN_CENTER);//水平居中 style3.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中 // 把字体 应用到当前样式 style3.setFont(font3); //自动换行 style3.setWrapText(true); //默认无填充 style3.setFillPattern(FillPatternType.NO_FILL); style3.setFillForegroundColor(IndexedColors.RED.getIndex()); // 设置边框 style3.setBorderBottom(HSSFCellStyle.BORDER_THIN); style3.setBorderLeft(HSSFCellStyle.BORDER_THIN); style3.setBorderRight(HSSFCellStyle.BORDER_THIN); style3.setBorderTop(HSSFCellStyle.BORDER_THIN); int index = 2;//标题行、列名行,所以数据行默认从第三行开始 for (HashMap<String, String> map : dataLists) { //创建内容行 HSSFRow row3 = sheet.createRow(index); for (int i = 0; i < columnNames.length; i++) { String val = map.get(columnNames[i][0]); createCell(row3, i, style3, val == null ? "" : val);//例:[[key,label],[key,label]] 取key } index++; } return ResultModel.of(wb); } /** * 创建一个单元格 * * @param row 行 * @param column 列 * @param cellStyle 单元格样式 * @param text 值 */ private static void createCell(Row row, int column, CellStyle cellStyle, String text) { Cell cell = row.createCell(column); // 创建单元格 cell.setCellValue(text); // 设置值 cell.setCellStyle(cellStyle); // 设置单元格样式 } }
import org.apache.poi.ss.usermodel.Workbook; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import java.io.*; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Date; /** * 文件下载工具类 */ public class DownloadUtil{ /** * 快速下载 */ public static ResponseEntity download(byte[] fileBytes, String fileName) { //设置文件 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.setContentDispositionFormData("attachment", new String(fileName.getBytes(StandardCharsets.UTF_8),StandardCharsets.ISO_8859_1)); //下载文件 return new ResponseEntity<>(fileBytes, headers, HttpStatus.CREATED); } /** * 快速下载 */ public static ResponseEntity download(File file) { return download(getByteArray(file), file.getName()); } /** * 快速下载 */ public static ResponseEntity download(Workbook workbook, String fileName) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { fileName = fileName + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".xls"; workbook.write(outputStream); } catch (IOException e) { throw new RuntimeException(e); } return download(outputStream.toByteArray(), fileName); } //获取文件的字节数组 private static byte[] getByteArray(File file) { if (!file.exists()) { throw new RuntimeException("File Not Found:" + file.getPath()); } ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length()); BufferedInputStream in = null; try { in = new BufferedInputStream(new FileInputStream(file)); int buf_size = 1024; byte[] buffer = new byte[buf_size]; int len; while (-1 != (len = in.read(buffer, 0, buf_size))) { bos.write(buffer, 0, len); } return bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } finally { try { assert in != null; in.close(); bos.close(); } catch (IOException e) { e.printStackTrace(); } } } //获取文件名后缀 private static String getSuffix(String fileName) { int lastPointIndex = fileName.lastIndexOf("."); if (StringUtils.isEmpty(fileName) || lastPointIndex == -1) { return null; } return fileName.substring(lastPointIndex + 1); } }
获取封装数据的service层 createExcel,直接到取page分页方法,遍历机架数据集合,设置Map<key,value>,add到list<Map>中,最后将封装好的数据return回controller,传入工具类,最后下载。
/** * 根据当前jqGrid分页情况,创建并导出Excel文件 * * @param entity 查询条件 * @return 封装好的数据集合 */ @Override public ResultModel<ArrayList<HashMap<String, String>>> createExcel(RackVo entity) { ArrayList<HashMap<String, String>> dataLists = new ArrayList<HashMap<String, String>>(); //直接调page分页方法,获取当前jqGrid分页条件对应的数据集合, ResultModel<PageInfo<RackVo>> rm = page(entity); if (rm.isFlag()) { List<RackVo> rackVoList = rm.getData().getRows(); for (RackVo rackVo : rackVoList) { HashMap<String, String> map = new HashMap<String, String>(16); map.put("no", rackVo.getNo() != null ? rackVo.getNo() : ""); map.put("rackName", rackVo.getName() != null ? rackVo.getName() : ""); map.put("roomName", rackVo.getRoom() != null ? rackVo.getRoom().getRoomname() : ""); map.put("idc", rackVo.getOrg() != null ? rackVo.getOrg().getOrgName() : ""); map.put("clientName", rackVo.getCustomer() != null ? rackVo.getCustomer().getClientname() : ""); map.put("rackTypeName", rackVo.getRacktype() != null ? rackVo.getRacktype().getName() : ""); map.put("existentialMode", "1".equals(rackVo.getExistentialMode()) ? "Physical" : "Virtual"); map.put("maxPower", rackVo.getMaxpower() != null ? rackVo.getMaxpower() : ""); String status = rackVo.getServiceStatus(); switch (status != null ? status : "") { case "1": status = "Idle"; break; case "2": status = "Reserved"; break; case "3": status = "Occupied"; break; default: status = ""; break; } map.put("status", status); String administrate = rackVo.getAdministrate(); switch (administrate != null ? administrate : "") { case "R": administrate = "Cust Own"; break; case "U": administrate = "CTG Own"; break; default: administrate = ""; break; } map.put("administrate", administrate); dataLists.add(map); } } return ResultModel.of(dataLists); }
效果
从开发阶段到测试阶段,导了无数次,没毛病
小升级
excelMap,Excel对应的columnNames列名集合 { key,label },可以不用再controller设置了,直接从页面jqgrid抓取,传入controller就行(滑稽脸~)
//获取jqgrid头部标题tr,有多少个tr就有多少级标题 var thead_tr = $(".ui-jqgrid-htable").find("tr.ui-jqgrid-labels"); //遍历thead_tr找出每一个标题,并保存到对象中 var titles = []; thead_tr.each(function(index_tr,element_tr){ titles.push([]); $(element_tr).find("th").each(function(index_th,element_th){ //内容 var label = $(element_th).text(); //所占行 rowspan 默认1 var rowspan = $(element_th).attr("rowspan") || 1; //所占列 colspan 默认1 var colspan = $(element_th).attr("colspan") || 1; //键 var key = $(element_th).attr("id"); key = key.substring(key.lastIndexOf("_")+1,key.length); if(label){ titles[index_tr].push({ label:label, key:key, rowspan:rowspan, colspan:colspan, }); } }); }); //JSON.stringify(titles) console.log(titles);
更新
2020-10-20更新
直接构造form表单提交,我们不能设置请求头信息,有些需求不能满足(例如在前后端分离的项目中,需要在请求头传递token令牌),当我们导出Excel功能需要设置请求头信息时应该如何操作呢?封装原生Ajax,利用responseType: 'blob'属性,接收二进制数据,构建Blob对象,将二进制数据转成文件,利用a标签下载文件
//封装原生Ajax var Ajax={ get: function(options) { let xhr = new XMLHttpRequest(); xhr.open('GET', options.url, true); //设置请求头 xhr.setRequestHeader("Authorization", 'Bearer ' + store.getters.token); xhr.onload = function() { let response = null; // responseType="" / "text"时,响应的结果从xhr.responseText获取 if(xhr.responseType === "" || xhr.responseType === "text"){ response = xhr.responseText; } //200 请求成功 if (xhr.status === 200) { options.success.call(response); } //其他情况,请求失败 if(options.error){ options.error.call(xhr.error); } }; xhr.send(); }, post: function (options) { let xhr = new XMLHttpRequest(); xhr.open("POST", options.url, true); //设置请求头 xhr.setRequestHeader("Content-Type", "application/json"); xhr.setRequestHeader("Authorization", 'Bearer ' + store.getters.token); //设置响应内容类型、超时时间 options.responseType ? xhr.responseType = options.responseType : xhr.responseType = "text"; options.timeout ? xhr.timeout = options.timeout : xhr.timeout = 30000; xhr.onload = function() { let response = null; // responseType="" / "text"时,响应的结果从xhr.responseText获取 if(xhr.responseType === "" || xhr.responseType === "text"){ response = xhr.responseText; } //200 请求成功 if (xhr.status === 200) { options.success.call(response); } // responseType = "blob"时,响应的是Blob二进制数据,直接调用下载 if(xhr.status === 201){ download(xhr,options.success) } //其他情况,请求失败 if(options.error){ options.error.call(xhr.error); } }; xhr.send(JSON.stringify(options.data)); } }; //Blob响应,转成文件下载 function download(response,callback) { //创建一个隐藏的下载a标签 let url = window.URL.createObjectURL(new Blob([response.response])); let link = document.createElement("a"); link.style.display = "none"; link.href = url; //设置文件名,文件名从响应头中获取(PS:可能会存在中文乱码、文件后缀多个下划线等问题) let fileName = response.getAllResponseHeaders().split("\n")[4].split(":")[1].split(";")[2].split("=")[1].replace(/"/g,""); fileName = decodeURIComponent(escape(fileName)); console.log("文件名:" + fileName); link.setAttribute("download", fileName); document.body.appendChild(link); link.click(); //过河拆桥 link.remove(); if(callback){ callback(); } }
使用
//获取当前分页参数 let postData = vue.getPageParameter(); postData.page = 1; postData.pageSize = 999999999;//设置每页9亿条记录(相当于无穷大,查询所有) console.log("开始导出..."); Ajax.post({ url:vue.excelUrl, data:postData, timeout: 30000, responseType: 'blob', success:function () { console.log("导出完成,请您注意浏览器的下载管理器!"); } });
效果
后缀多了个下划线,很奇怪...,删除下划线文件能正常打开,数据、单元格背景等正常
补充
2021-09-08更新
利用Excel对html的table标签的兼容,可以不依赖POI生成、导出Excel表格(说白了,其实就是html文件改后缀名.xls)
新的ExcelUtil工具类
package cn.huanzi.qch.util; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileWriter; import java.io.OutputStream; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.*; /** * Excel工具类 */ public class ExcelUtil { /** * 导出 * 无需依赖POI */ public static void exportByResponse(HttpServletResponse response, String fileName, LinkedHashMap<String,String> columns, List<Map<String,Object>> datas) throws Exception { response.addHeader("Content-disposition","attachment; filename=" + new String(fileName.getBytes("GBK"), "ISO8859-1") + ".xls"); response.setContentType("application/ms-excel"); StringBuilder sb = exportOfData(columns, datas); OutputStream out = response.getOutputStream(); out.write(sb.toString().getBytes("UTF-8")); out.flush(); out.close(); } public static void exportByFile(File file, LinkedHashMap<String,String> columns, List<Map<String,Object>> datas) { StringBuilder sb = exportOfData(columns, datas); try(FileWriter resultFile = new FileWriter(file, false);PrintWriter myFile = new PrintWriter(resultFile);) { myFile.println(sb.toString()); } catch (Exception e) { System.err.println("exportByFile(),操作出错..."); e.printStackTrace(); } System.out.println(file.getName()+",操作完成!"); } private static StringBuilder exportOfData(LinkedHashMap<String,String> columns, List<Map<String,Object>> datas){ StringBuilder sb = new StringBuilder("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">"); sb.append("<table border=\"1\">"); //列名 sb.append("<tr style=\"text-align: center;\">"); for (Map.Entry<String, String> entry : columns.entrySet()) { sb.append("<td style=\"background-color:#bad5fd\">" + entry.getValue() + "</td>"); } sb.append("</tr>"); //数据 for (Map<String, Object> data : datas) { sb.append("<tr style=\"text-align: center;\">"); for (Map.Entry<String, String> entry : columns.entrySet()) { Object dataValue = data.get(entry.getKey()); //如果是日期类型 if(dataValue instanceof java.util.Date){ dataValue = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(dataValue); } sb.append("<td>" + dataValue.toString() + "</td>"); } sb.append("</tr>"); } sb.append("</table>"); return sb; } }
测试
public static void main(String[] args) { //列名 LinkedHashMap<String, String> columns = new LinkedHashMap<>(4); columns.put("id","编号"); columns.put("name","名字"); columns.put("age","年龄"); columns.put("time","参加工作时间"); //数据 List<Map<String, Object>> datas = new ArrayList<>(3); HashMap<String, Object> hashMap = new HashMap<>(); hashMap.put("id","A001"); hashMap.put("name","张三"); hashMap.put("age",18); hashMap.put("time",new Date()); datas.add(hashMap); //带换行符: HashMap<String, Object> hashMap2 = new HashMap<>(); hashMap2.put("id","A002"); hashMap2.put("name","李四 李四1 李四2"); hashMap2.put("age",20); hashMap2.put("time",new Date()); datas.add(hashMap2); HashMap<String, Object> hashMap3 = new HashMap<>(); hashMap3.put("id","A003"); hashMap3.put("name","王五"); hashMap3.put("age",25); hashMap3.put("time",new Date()); datas.add(hashMap3); //导出 ExcelUtil.exportByFile(new File("D:\\XFT User\\Downloads\\Excel导出测试.xls"),columns,datas); }
效果
版权声明
捐献、打赏
支付宝
微信