最近接手了一个需求,开启了一段苦逼的加班之路,也收获了不少,对于Excel导出的功能也更加熟悉了,接下来我就介绍一下我遇到的问题跟解决的方式。
问题一:
EasyExcel在Linux环境导出失败。
这是我在开发这个需求其中一个报表发现的问题——当我使用EasyExcel进行导出的时候本地导出没有问题,但是在测试环境与生产环境导出的时候就很不稳定,时常导出一个只有表头的空表,改了很多遍,想了很多种原因。
例如:
1.可能是实体类字段类型无法识别,尤其像LocalDateTime这种类型的字段
2.可能是pom依赖问题
3.可能是接口超时问题
4.可能是响应对象类型没有改成excel对应类型的问题
5.可能是数据量过大问题
如上类似问题全部都是有异常显示的,但是我的导出没有异常报错,这就令我的问题查找十分困难,并且我在另一个项目中完美导出,说明可能是服务器环境有一些配置导致EasyExcel导出出现了问题,公司的服务器确实不敢动呀,改起来也相对比较麻烦。
这时候我们就需要改变方式,使用另外一种方式导出,这种方式就是POI,虽然POI被诟病容易OOM内存溢出,但是确实是排除超大数据量导出之外的一种比较好的选择了(EasyExcel不好用且找不到问题的备用选择,毕竟工作没人会等你研究明白了再让你开发O(∩_∩)O),所以我找了几个POI灵活导出的例子,供大家选择,但是最好我推荐还是用EasyExcel,是又省事又好用。
正常使用POI导出,需要区分workbook对象。
- HSSFWorkbook 用于Excel2003版及更早版本(扩展名为.xls)的导出。
- XSSFWorkbook 用于Excel2007版(扩展名为.xlsx)的导出。
- SXSSFWorkbook相对前面两种,会在导出数据达到万以上的数据,会报内存不足,导致失败的问题,所以用sxssf,简约sf来导出较大数据量
像上面这种区别的情况下,我们最好优先选择使用SXSSFWorkbook对象来进行Excel的导出,避免出现内存不足的情况。
首先我们还是要导入POI的依赖。
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<artifactId>commons-compress</artifactId>
<groupId>org.apache.commons</groupId>
<version>1.19</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
默认导出方式:
/**
* list 入参查询条件
* service 业务层对象
*
*/
@PostMapping("/poi/excel")
public void getExcelByPoi(@RequestBody @ApiParam list inDto, HttpServletResponse response) {
InputStream inputStream = null;
try{
List<Object> excelList = service.queryToExcel(inDto);
// 判断返回集合内容不为空
if (CollectionUtils.isEmpty(excelList)) {
return;
}
String fileType = ".xlsx";
String fileName = "fileName";
// keep 100 rows in memory, exceeding rows will be flushed to disk
try (SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(100);
OutputStream os = response.getOutputStream();
ByteArrayOutputStream byteStream = new ByteArrayOutputStream()) {
//创建一个Sheet页
SXSSFSheet sheet1 = sxssfWorkbook.createSheet("Sheet1");
CellStyle cellStyle = service.headFont(sheet1.getWorkbook());
//第一行是标题行
Row titleRow = sheet1.createRow(0);
// 写表头
service.writerHeaderOne(titleRow);
// 写表体
for (int i = 1; i < excelList.size() + 1; i++) {
SXSSFRow contentRow = sheet1.createRow(i);
contentRow.createCell(0).setCellValue(i);
service.writerContent(contentRow, excelList.get(i - 1));
}
response.setContentType("application/vnd.ms-excel"); // 设置下载类型
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + fileType); // 设置文件的名称
sheet1.setForceFormulaRecalculation(true);
//创建临时文件 文件输出流
sxssfWorkbook.write(byteStream);
byteStream.flush();
sxssfWorkbook.dispose();// 释放workbook所占用的所有windows资源*/
byte[] b = new byte[4096];
// 必须在这里创建,否则获取不到行数
inputStream = new ByteArrayInputStream(byteStream.toByteArray());
int size = inputStream.read(b);
while (size > 0) {
os.write(b, 0, size);
size = inputStream.read(b);
}
}
} catch (Exception e) {
e.printStackTrace();
log.info("报表导出失败", e);
}finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error(e.getLocalizedMessage());
}
}
}
}
写表头方法
/**
* 写标题
*
* @param titleRow 表头行对象
*/
public void writerHeaderOne(Row titleRow) {
titleRow.createCell(0).setCellValue("id");
titleRow.createCell(1).setCellValue("姓名");
titleRow.createCell(2).setCellValue("年龄");
}
写表体方法
/**
* 导出写入数据
*
* @param contentRow 表体行
* @param result 结果对象
*/
public void writerContent(SXSSFRow contentRow, Object result) {
contentRow.createCell(0).setCellValue(result.getId());
contentRow.createCell(1).setCellValue(result.getName());
contentRow.createCell(2).setCellValue(result.getAge());
}
这种方法确实可以导出,但是缺少了表头的样式,看起来很不舒服,但是SXSSFWorkbook对象的表头样式修改我简单尝试了一下没有实现,所以又出现了如下方式。
DIY特殊表头方式:
这种方式可以随心所欲的设置表头样式,关键是还很简单,我们只需要创建一个只有表头的Excel,设置好我们想要的样式。如下图:
创建好的Excel保存到项目中的resources ——> template 目录下
然后我们创建导出方法
/**
* service为业务类对象
* @param inDto 导出查询条件
* @param response
*/
@PostMapping("/excel")
public void getExcelByPoi(@RequestBody @ApiParam list inDto, HttpServletResponse response) {
try {
List<Object> excelList = service.queryToExcel(inDto);
// 判断返回列表不为空
if (CollectionUtils.isEmpty(excelList)) {
return;
}
//导出数据列表
List<Map<String, Object>> dataList = new ArrayList<>();
SXSSFWorkbook workbook = null;
InputStream inputStream = null;
XSSFWorkbook workbookBuff = null;
//处理
try {
//获取流
inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("template/Excel.xlsx");
//读表格
workbookBuff = new XSSFWorkbook(inputStream);
workbook = new SXSSFWorkbook(workbookBuff, 100);
//获取sheet
SXSSFSheet sheet = workbook.getSheetAt(0);
//sheet页,从第几行开始写数据,数据内容
service.putData(sheet, 1, excelList);
//设置格式
response.setContentType("application/vnd.ms-excel");
//编码
response.setCharacterEncoding("utf-8");
//设置文件格式
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + "Excel" + ExcelTypeEnum.XLSX.getValue());
workbook.write(response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
log.info("生产单报表查询失败", e);
}finally {
//判断
if(workbook != null){
try {
//关闭
workbook.close();
}catch (Exception e){
//异常
e.printStackTrace();
}
}
//判断
if(inputStream != null){
try {
//关闭
inputStream.close();
}catch (Exception e){
//异常
e.printStackTrace();
}
}
//判断
if(response.getOutputStream() != null){
try {
//关闭
response.getOutputStream().close();
}catch (Exception e){
//异常
e.printStackTrace();
}
}
}
}catch (Exception e) {
e.printStackTrace();
log.error("导出查询失败 {}", e.getMessage());
}
}
写表体数据方法
public void putData(SXSSFSheet sheet, int start, List<Object> dataList) {
// 循环处理
for (int i = 0; i < dataList.size(); i++) {
// 获取当前数据
Object obj = dataList.get(i);
// 创建
Row row = sheet.createRow(i + start);
// 列坐标
int col = 0;
// id
Cell getIdCell = row.createCell(col++, CellType.STRING);
// 数据
getIdCell .setCellValue(obj.getId());
// 名称
Cell getNameCell = row.createCell(col++, CellType.STRING);
// 数据
getNameCell .setCellValue(obj.getName());
// 年龄
Cell getAgeCell = row.createCell(col++, CellType.STRING);
// 数据
getAgeCell.setCellValue(obj.getAge());
}
}
这样也可以导出。
下面给大家放上POI根据RGB来修改单元格颜色的代码:
// 创建Excel
SXSSFWorkbook wb = new SXSSFWorkbook(100);
// 创建Sheet页
Sheet sheet = wb.createSheet();
// 获取单元格的对象,去设置他的格式
XSSFCellStyle groupFieldStyle =(XSSFCellStyle) wb.createCellStyle();
groupFieldStyle.setBorderBottom(BorderStyle.THIN);//下边框样式
groupFieldStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());//下边框颜色
groupFieldStyle.setBorderLeft(BorderStyle.THIN);//左边框
groupFieldStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());//左边框颜色
groupFieldStyle.setBorderTop(BorderStyle.THIN);//上边框
groupFieldStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());//上边框颜色
groupFieldStyle.setBorderRight(BorderStyle.THIN);//右边框
groupFieldStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());//右边框颜色
groupFieldStyle.setAlignment(HorizontalAlignment.CENTER); // 设置单元格水平方向对其方式为居中
groupFieldStyle.setVerticalAlignment(VerticalAlignment.CENTER);//设置单元格垂直方向对其方式为居中
// 设置单元格RGB颜色
groupFieldStyle.setFillForegroundColor(new XSSFColor(new java.awt.Color(248, 203, 173), new DefaultIndexedColorMap()));
groupFieldStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 创建行,i表示第几行
Row row = sheet.createRow(i);
// 获取该行中的第几个单元格
Cell cell = row.createCell(j);
// 给单元格添加样式
cell.setCellStyle(groupFieldStyle);
最后放上EasyExcel的文章连接,希望能够解决小伙伴们导出的问题。如果还有小伙伴们有问题 可以私信问我,日常在线哦