导出数据是后端经典模块之一,从原有的poi到现在的easyexcel等等都在努力的帮助开发们缩小数据与excel之间的鸿沟。但是在简单的导出也会遇到一些有的没的问题,特地写个文章记录下~

背景介绍

一般导出流程图如下:

组装数据
导出

组装数据: 包括excel中表头、数据及样式
导出: 文件流

easyexcel 表头及数据

官方文档总结的都是经典~

表头: 分为固定表头和动态表头,然后再可以继续划分简单版本和复杂版本,其中复杂版本类似有三四级表头(以前写poi硬编码写到爆炸=_=||)~ 示例如下(图片来源):
在这里插入图片描述
数据: 随表头变更

表头

简单

在这里插入图片描述

固定表头,可声明一个实体类进行定义,如下:

@Data
public class TitleData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
}

动态表头,只能自己手动写代码进行定义,如下:

// 外层数组,一个值代表一列
List<List<String>> headList = new ArrayList<List<String>>();
List<String> head0 = new ArrayList<String>();
head0.add("字符串标题");
headList.add(head0);

List<String> head1 = new ArrayList<String>();
head1.add("日期标题");
headList.add(head1);

List<String> head2 = new ArrayList<String>();
head2.add("数字标题");
headList.add(head2);
合并

在这里插入图片描述

@Data
public class ComplexHeadData {
    @ExcelProperty({"主标题", "字符串标题"})
    private String string;
    @ExcelProperty({"主标题", "日期标题"})
    private Date date;
    @ExcelProperty({"主标题", "数字标题"})
    private Double doubleData;
}

web项目导出方式

web项目一般有两种导出方式:

  • 提供下载链接(异步):先生成excel并上传至oss/文件服务器,返回文件链接给前端,由前端自行下载
  • 文件流(同步):生成excel并塞入response流中

本文主要关注文件流方式,示例代码如下:

// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("测试", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());

实践出真知

简单动态表头案例

本案例比较简单,根据请求参数time来动态定义表头和创建数据,并以文件流方式返回给前端。 效果如下:
效果图
pom中添加依赖:

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>easyexcel</artifactId>
	<version>2.2.7</version>
</dependency>

业务逻辑代码如下:

@RestController
public class ResultController {
    
	@PostMapping(value = "/export")
    public void export(@RequestParam Integer time) {
    
    	try {
			HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
			response.setHeader("Content-Disposition", "attachment; filename=export.xlsx");
	        // 响应类型,编码
	        response.setContentType("application/vnd.ms-excel;charset=utf-8");
	        response.setCharacterEncoding("utf-8");
			EasyExcel.write(response.getOutputStream()).head(getHead(time)).sheet("数据").doWrite(getData(time));
		} catch (IOException e) {
			log.error("导出问卷数据失败!错误信息为:{}", e.getMessage());
			e.printStackTrace();
		}
    }
    
    /**
     * 获取excel标题栏(姓名、手机号、提交时间)
     * @param questionnaireId
     * @return
     */
    private List<List<String>> getHead(Integer time) {
    	
    	// 外层数组,一个值代表一列
    	List<List<String>> headList = new ArrayList<List<String>>();
    	List<String> nameList = new ArrayList<String>();
    	nameList.add("姓名");
    	headList.add(nameList);
    	
    	List<String> telList = new ArrayList<String>();
    	telList.add("手机号");
    	headList.add(telList);
    	
    	List<String> submitDTList = new ArrayList<String>();
    	submitDTList.add("提交时间");
    	headList.add(submitDTList);
    	
    	for (int i = 0 ; i < time ; i ++ ) {
    		List<String> list = new ArrayList<String>();
    		list.add("动态标题" + i);
        	headList.add(list);
    	}
    	
    	return headList;
    }
    
    /**
	 * 获取excel表格数据
	 * @return
	 */
	private List<List<Object>> getData(Integer time) {
		
		// 将填写结果 + 提交时间合并为一行数据
		List<List<Object>> resultList = new ArrayList<List<Object>>();
		for (int i = 0 ; i < 3 ; i ++ ) {
			
			List<Object> list = new ArrayList<Object>();
			list.add("花卷" + i);
			list.add("1347000000" + i);
			list.add("2022-01-18 14:54:00");
			
			for (int j = 0 ; j < time ; j ++ ) {
	    		list.add("动态内容" + j);
	    	}
			
			resultList.add(list);
		}
		return resultList;
	}
  
}

遇到的问题

前端联调时excel无法打开

问题描述: 后台用postman调试都ok,能正常打开excel!但是前端调试时下载的excel提示有破损,无法打开!!!
Tips: 勇敢(不怕死)的质疑前端,你代码有BUG!
解决方案: 前端需在request和response中添加responseType: blob设置

以下伪代码,请重点关注responseType设置即可

  1. 前端request应该设置responseType为arraybuffer或blob
return request({
url: '/platform/export',
method: 'post',
responseType: 'blob',
headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
},
data: qs.stringify(data)

})

  1. 前端以blob格式接收response,并设置type为application/msexcel
handleExport(){
  exportData({id: this.id}).then(res => {
    if(res){
      const fileName = this.name + '.xlsx';
        var blob = new Blob([res], {
        type: "application/msexcel;charset=utf-8",
      });
      const URL = window.URL || window.webkitURL;
      const downloadElement = document.createElement("a");
      const href = URL.createObjectURL(blob); // 创建下载的链接
      downloadElement.href = href;
      downloadElement.download = fileName; // 下载后文件名
      document.body.appendChild(downloadElement);
      downloadElement.click(); // 点击下载
      document.body.removeChild(downloadElement); // 下载完成移除元素
      URL.revokeObjectURL(href); // 释放掉blob对象
    }
  })
},

下载文件接口有时返回文件流有时返回json的情况

测试小姐姐提了个单,token超时后,点击下载按钮还是能正常导出,excel内容是后端认证系统返回的错误码json串 =_=|| ,网上查找一番也找大佬们讨论了下,对于将文件流封装成系统设定的统一返回json方式不可取,最后抱着前端小姐姐的细腿求解决喽
解决方法:前端获取response后根据 content-type进行区分
网上参考文档
在这里插入图片描述



未完待续......
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐