JAVA-poi导出excel到http响应流

JAVA-poi导出excel到http响应流,第1张

导出结果为excel是相对常见的业务需求,大部分情况下只需要导出简单的格式即可,所以有许多可以采用的方案。有些方案还是很容易实现的。

一、可用的解决方案

目前可以有几类解决方案:

  1. 字处理企业提供的解决方案 -- 目前这个还没有看到,大概这些企业不屑于与民争利或者因为某些理由没有直接参与。例如微软,金山等都没有提供。如果有,就会压死一批三方产商。不过估计原厂看不上,或者是故意让利
  2. 三方产商提供的符合规范的解决方案
    1. APACHE poi,这是最著名的解决方案,已经存在20来年了。最早版本Version 0.1 (2001-08-28)。
    2. DOCX4J,也可以用,不过用得相对少。https://www.docx4java.org/ 。有提供收费的,企业级别的解决方案
    3. easyExcel,这是阿里的,本质是对POI的封装。让酷爱注解人士方便了不少。excel编程好像可以简化为对几个注解的背诵。
    4. 其它,暂时没有收集
  3. 各个公司自己的简易解决方案
    1. 输出csv的,这个是取巧的
    2. 输出xml的,这个也是取巧
    3. 其它,暂无收集

如果想简单一点处理,那么可以考虑easyExcel。写了这么多年java代码,本人已经对于注解感到深深的厌倦,什么垃圾、鬼怪都往注解上套。

所以,如果你厌恶注解,那么不要用easyExcel。

我个人倾向于直接使用poi,自己项目和团队中编写一些公用的工具。其它的方案暂时对我没有吸引力。

二、poi直接导出结果到http响应流

这里不讨论极限编程的事情,主要考虑导出一些不算太大的excel,例如10万行之类的。如果更大,一般不这么做了。

由于项目的需要,生成的excel无需缓存到服务器本地,而是可以直接输出到http响应流。

以下是例子代码。

环境:windows11,jdk1.8,springboot 2.6.7,poi-5.2.2,jquery 3.6.0,edge

2.1后端

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);


        Cell cell = null;
        String[] headerTitleArr = headerTitle.split(",");
        for (int i = 0; i < headerTitleArr.length; i  ) {
            cell = row.createCell((short) i);
            cell.setCellValue(headerTitleArr[i]);
            cell.setCellStyle(style);
        }

        // 第五步,写入实体数据 实际应用中这些数据从数据库得到
        // 如果有需要,可以为内容设置一个字体,此处略
        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文档。 更多介绍略(咱暂时也不用)。

2.2前端

原生js方案

/**
 * 导出所有满足条件的内容为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方案

也可以用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,实现得很完美。

文章来源:https://www.cnblogs.com/lzfhope/p/16417870.html

本文经用户投稿或网站收集转载,如有侵权请联系本站。

发表评论

0条回复