导出结果为excel是相对常见的业务需求,大部分情况下只需要导出简单的格式即可,所以有许多可以采用的方案。有些方案还是很容易实现的。
目前可以有几类解决方案:
如果想简单一点处理,那么可以考虑easyExcel。写了这么多年java代码,本人已经对于注解感到深深的厌倦,什么垃圾、鬼怪都往注解上套。
所以,如果你厌恶注解,那么不要用easyExcel。
我个人倾向于直接使用poi,自己项目和团队中编写一些公用的工具。其它的方案暂时对我没有吸引力。
这里不讨论极限编程的事情,主要考虑导出一些不算太大的excel,例如10万行之类的。如果更大,一般不这么做了。
由于项目的需要,生成的excel无需缓存到服务器本地,而是可以直接输出到http响应流。
以下是例子代码。
环境:windows11,jdk1.8,springboot 2.6.7,poi-5.2.2,jquery 3.6.0,edge
pom.xml
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>5.2.2</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.2</version> </dependency>
核心代码(部分代码出处不可考):
import java.net.URLEncoder; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import tools.model.ExportExcelParam; /** * excel导出工具 * * @author lzfto * @since */ public class ExcelTool { /** * 导出excel到http输出流 * * @param param */ public static void exportExcelToHttpResponse(ExportExcelParam param) { outExcel(param.getFileName(), param.getColList(), param.getHeaderTitle(), param.getDataList(), param.getResponse(), param.getUserAgent()); } /** * 导出文件到 http响应流 * @param fileName 必须xls,xlsx之一为后缀 * @param colList key列表,非空-listMap中,map的key列表,多个以逗号分割。 * @param headerTitle 表头-非空。多个以逗号分割 * @param dataList ListMap-非空。map中的key必须和colList对应 * @param response Http响应 * @param userAgent 客户端信息(暂时不支持移动端) * @apiNote 如果dataList的行数大于5000条,会采用XSSFWorkbook,避免内存溢出 */ private static void outExcel(String fileName, String colList, String headerTitle, List<Map<String, Object>> dataList, HttpServletResponse response, String userAgent) { // 第一步,创建一个workbook,对应一个Excel文件 Workbook wb = null; if(dataList.size()<5000) { if (fileName.endsWith(".xls")) { wb = new HSSFWorkbook(); } else if (fileName.endsWith(".xlsx")) { wb = new XSSFWorkbook(); } } else { wb=new SXSSFWorkbook(); } // 第二步,在workbook中添加一个sheet,对应Excel文件中的sheet Sheet sheet = wb.createSheet("sheet1"); // 第三步,在sheet中添加表头第0行,注意老版本poi对Excel的行数列数有限制short Row row = sheet.createRow((int) 0); // 第四步,创建单元格,并设置值表头 设置表头居中 CellStyle style = wb.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); // 创建一个居中格式 style.setFillForegroundColor(HSSFColor.HSSFColorPredefined.GREEN.getIndex()); style.setBorderBottom(BorderStyle.THIN); style.setBorderLeft(BorderStyle.THIN); style.setBorderRight(BorderStyle.THIN); style.setBorderTop(BorderStyle.THIN); // 生成一个字体 Font font = wb.createFont(); font.setColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex()); font.setFontHeightInPoints((short) 12); font.setBold(true); // 把字体应用到当前的样式 style.setFont(font); CellStyle style1 = wb.createCellStyle(); style1.setAlignment(HorizontalAlignment.CENTER); // 创建一个居中格式 style1.setFillForegroundColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex()); style1.setFillPattern(FillPatternType.SOLID_FOREGROUND); style1.setBorderBottom(BorderStyle.THIN); style1.setBorderLeft(BorderStyle.THIN); style1.setBorderRight(BorderStyle.THIN); style1.setBorderTop(BorderStyle.THIN); // 生成一个字体 Font font1 = wb.createFont(); font1.setFontHeightInPoints((short) 12);// 把字体应用到当前的样式 style1.setFont(font1); Cell cell = null; int count = 0; String[] headerTitleArr = headerTitle.split(","); for (int i = 0; i < headerTitleArr.length; i++) { cell = row.createCell((short) count); cell.setCellValue(headerTitleArr[i]); cell.setCellStyle(style); count++; } // 第五步,写入实体数据 实际应用中这些数据从数据库得到, String[] columnList = StringUtils.split(colList, ','); for (int i = 0; i < dataList.size(); i++) { row = sheet.createRow((int) i + 1); Map<String, Object> dataMap = dataList.get(i); for (int j = 0; j < columnList.length; j++) { cell = row.createCell((short) j); String value = ""; if (dataMap.get(columnList[j]) != null) { value = dataMap.get(columnList[j]).toString(); } cell.setCellValue(value); } } for (int i = 0; i < headerTitleArr.length; i++) { sheet.setColumnWidth(i, headerTitleArr[i].getBytes().length * 2 * 256); } // 第六步,将excel对象输出到HTTP响应流 try { String outputFileName = fileName; if (userAgent.toUpperCase().indexOf("MSIE") > 0) { outputFileName = URLEncoder.encode(fileName, "UTF-8"); } else if (userAgent.toUpperCase().indexOf("IPHONE") > 0) { outputFileName = new String(fileName.getBytes(), "ISO-8859-1"); } else { outputFileName = new String(fileName.getBytes("utf-8"), "ISO-8859-1"); } response.setContentType("application/octet-stream"); response.setHeader("Content-disposition", "attachment; filename=\"" + outputFileName + "\""); wb.write(response.getOutputStream()); response.getOutputStream().flush(); } catch (Exception e) { System.out.println(e.getMessage()); } } }
这里需要注意的是:内存溢出问题,这是使用SXSSFWorkbook 完成的,但这个东西目前限制比较多。
关于SXSSFWorkbook ,可以参阅:https://poi.apache.org/components/spreadsheet/
这里摘取一些资料:
Since 3.8-beta3, POI provides a low-memory footprint SXSSF API built on top of XSSF. SXSSF is an API-compatible streaming extension of XSSF to be used when very large spreadsheets have to be produced, and heap space is limited. SXSSF achieves its low memory footprint by limiting access to the rows that are within a sliding window, while XSSF gives access to all rows in the document. Older rows that are no longer in the window become inaccessible, as they are written to the disk. In auto-flush mode the size of the access window can be specified, to hold a certain number of rows in memory. When that value is reached, the creation of an additional row causes the row with the lowest index to to be removed from the access window and written to disk. Or, the window size can be set to grow dynamically; it can be trimmed periodically by an explicit call to flushRows(int keepRows) as needed. Due to the streaming nature of the implementation, there are the following limitations when compared to XSSF: .Only a limited number of rows are accessible at a point in time. .Sheet.clone() is not supported. .Formula evaluation is not supported See more details at SXSSF How-To 原文下面有个图,略。
这个东西怎么用,建议好好阅读 https://poi.apache.org/components/spreadsheet/how-to.html#sxssf 已经api文档。 更多介绍略(咱暂时也不用)。
/** * 导出所有满足条件的内容为excel格式 */ function exportClick() { let _param = getParamValue(); var url = '/log/srv/export'; var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); // 也可以使用POST方式,根据接口 xhr.setRequestHeader('content-type', 'application/json'); xhr.responseType = "blob"; // 返回类型blob // 定义请求完成的处理函数,请求前也可以增加加载框/禁用下载按钮逻辑 xhr.onload = function () { // 请求完成 if (this.status === 200) { // 返回200 var blob = this.response; var reader = new FileReader(); reader.readAsDataURL(blob); reader.onload = function (e) { // 转换完成,创建一个a标签用于下载 var a = document.createElement('a'); a.download = '服务日志.xlsx'; a.href = e.target.result; $("body").append(a); a.click(); $(a).remove(); }; } }; // 发送ajax请求 xhr.send(JSON.stringify(_param)); }
这是非常简单的代码,没有特别的优化,以及异常处理等等。
也可以用jquery处理,例如:
function downAsExcel() {let _param = getParamValue(); $.ajax({ url: '/log/srv/export', type: 'POST', dataType: 'blob', contentType: "application/json", async: true, data: JSON.stringify(_param), success: function (rs, status, xhr) { var blob = rs; var reader = new FileReader(); reader.readAsDataURL(blob); reader.onload = function (e) { // 转换完成,创建一个a标签用于下载 var a = document.createElement('a'); a.download = '服务日志.xlsx'; a.href = e.target.result; $("body").append(a); a.click(); $(a).remove(); }; }, error: function (rs) { showMessgeBox(_MSG_ERR, '网络错误,汇总失败', 1); } }); }
如果厌倦注解,并且想灵活一些,建议直接使用POI来处理EXCEL的导出。
POI的功能还是很强大的,虽然极限情况和复杂情况处理的不够好(毕竟不是原厂),但是大部分情况下,已经足够用了。
如果不是为了导出,那么完全可以使用厂商提供的api来处理文档,例如微软和金山都有提供类似的开放api,实现得很完美。