我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!
1 前言
事情是这样的,俺们项目中有一个数据汇总的表,需求突发奇想,说他想把这下载下来在excel中看。你给咱实现一下,我想着这简单啊。以前搞过PDF的,excel的也一样呗。搜一下搜一下,看来这个easypoi的excel导出就不错。来来来本渣带哥哥们搞一遍。也算有个记录。
2 实现
假如,我们需要的效果就如下(这其实就是最终的效果,穿越过来的)
2.1 依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>3.3.0</version>
</dependency>
2.2 创建对象
我模拟上边的数据来搞,首先有一个用户表。
@Data
@AllArgsConstructor
public class UserInfo {
@Excel(name = "用户id", needMerge = true)
private Integer id;
@Excel(name = "姓名", needMerge = true)
private String name;
@Excel(name = "电话", needMerge = true)
private String phoneNum;
@Excel(name = "性别", needMerge = true)
private String sex;
@ExcelCollection(name = "地址")
private List<UserAddress> addressList;
}
这里解释一下,注解@Excel里的name就是列名,needMerge = true则是行合并,在有private List<UserAddress> addressList;的时候需要添加合并,并且private List<UserAddress> addressList;的注解为@ExcelCollection(name = "地址"),表示这是个多行。
再来一个地址表
@Data
@AllArgsConstructor
public class UserAddress {
private Integer userId;
@Excel(name = "国家")
private String country;
@Excel(name = "省")
private String province;
@Excel(name = "城市")
private String city;
@Excel(name = "详细地址", width = 25)
private String detail;
@Excel(name = "是否默认", replace = {"是_1", "否_0"})
private Integer isDefault;
}
这里的两个地址对应一个用户,模拟合并单元格的效果。我们也可以对内容做映射,这里的是否默认行上的replace = {"是_1", "否_0"}可以帮我们把1映射到是,把0映射到否
2.3 mock数据
我们造一些数据用来填充表格,这个没啥难度。注意的是我给地址的是否默认添加的是0和1,为了展示上边的映射数据的效果。
private List<UserInfo> generateList() {
List<UserInfo> userInfoList = new ArrayList<>();
UserAddress address1_1 = new UserAddress(1, "中国", "陕西省", "西安", "西安的详细地址",1);
UserAddress address1_2 = new UserAddress(1, "中国", "陕西省", "铜川", "铜川的详细地址",0);
UserAddress address2_1 = new UserAddress(2, "中国", "陕西省", "咸阳", "咸阳的详细地址",1);
UserAddress address2_2 = new UserAddress(2, "中国", "陕西省", "渭南", "渭南的详细地址",0);
UserAddress address3_1 = new UserAddress(3, "中国", "陕西省", "西安", "西安的详细地址",1);
UserAddress address3_2 = new UserAddress(3, "中国", "陕西省", "西安", "西安的详细地址",0);
UserInfo user1 = new UserInfo(1, "花儿它", "123456", "男", Stream.of(address1_1, address1_2).collect(Collectors.toList()));
UserInfo user2 = new UserInfo(2, "为什么", "789456", "女", Stream.of(address2_1, address2_2).collect(Collectors.toList()));
UserInfo user3 = new UserInfo(3, "这么红", "1234789", "男", Stream.of(address3_1, address3_2).collect(Collectors.toList()));
//塞值
userInfoList.add(user1);
userInfoList.add(user2);
userInfoList.add(user3);
return userInfoList;
}
2.4 导出
下来就是重点的导出了。这里可以命名excel的title和sheet还有文件名,文件名我使用now做了个时间戳,完整的文件名是用户列表_20220718230115.xls。
@GetMapping(value = "excel")
public void exportList(HttpServletResponse response) {
//搞一个对象出来
List<UserInfo> userInfoList = generateList();
//生成Excel
try {
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("用户列表", "用户列表"), UserInfo.class, userInfoList);
String now = LocalDateTime.now(ZoneId.of("Asia/Shanghai")).format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
response.reset();
String filename = "用户列表" + "_" + now + ".xls";
filename = new String(filename.getBytes(), StandardCharsets.ISO_8859_1);
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Disposition", "attachment;filename=" + filename);
ServletOutputStream outputStream = response.getOutputStream();
workbook.write(outputStream);
outputStream.flush();
outputStream.close();
workbook.close();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
3 我踩过的坑
来来来,这才是真正的重点好不好。
3.1 needMerge = true必须每个字段都添加
这个我开始没有添加完全,表就是不那么完美,来瞅瞅是啥样子的,找了很久才找到问题。
这里,我去掉id的needMerge = true做演示。
@Excel(name = "用户id")
private Integer id;
这里我将id的属性去掉了,但是仔细看,性别变成了两行,且只显示在上边的一行,经过多番调查研究,发现,当有属性的类型为一个类的时候,只要少了任何一个needMerge,都是这个样子的。所以,所有的属性都要添加。
3.2 可以加属性字段变换属性值
没看懂题目是个啥?来来来,我给咱解释下。 比如,电话字段,想做一个脱敏处理,但是又不想修改源数据。我们可以这样做。
@Data
@AllArgsConstructor
public class UserInfo {
@Excel(name = "用户id", needMerge = true)
private Integer id;
@Excel(name = "姓名", needMerge = true)
private String name;
private String phoneNum;
@Excel(name = "电话", needMerge = true)
private String phoneNum2;
@Excel(name = "性别", needMerge = true)
private String sex;
@ExcelCollection(name = "地址")
private List<UserAddress> addressList;
public String getPhoneNum2() {
return DigestUtils.md5DigestAsHex(phoneNum.getBytes(StandardCharsets.UTF_8));
}
}
主要是去掉了phoneNum的注解,加了一个属性,并且添加了这个属性的get方法,在get方法里做了脱敏的操作。只是作为例子展示哈,其实不想要直接不展示就行了呗,我们项目中实际是做了时间的转换。这样的话,导出来的结果就如下所示
3.3 适当添加行宽
上边的表是不是有点丑。性别列永远就是一个字,男或者女,地址一行有可能就很长。给定width = 5.
@Excel(name = "性别", width = 5 ,needMerge = true)
private String sex;
当性别字段给定宽度之后,就变窄喽,其实主要是加长用的,就像加密过的电话字段,会显示多行,当列变宽的时候,就会在一行显示,也就更清晰。