Java生成Excel保姆级教程

2,642 阅读3分钟

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!

1 前言

事情是这样的,俺们项目中有一个数据汇总的表,需求突发奇想,说他想把这下载下来在excel中看。你给咱实现一下,我想着这简单啊。以前搞过PDF的,excel的也一样呗。搜一下搜一下,看来这个easypoi的excel导出就不错。来来来本渣带哥哥们搞一遍。也算有个记录。

2 实现

假如,我们需要的效果就如下(这其实就是最终的效果,穿越过来的)

image.png

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;

image.png 这里我将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方法里做了脱敏的操作。只是作为例子展示哈,其实不想要直接不展示就行了呗,我们项目中实际是做了时间的转换。这样的话,导出来的结果就如下所示

image.png

3.3 适当添加行宽

上边的表是不是有点丑。性别列永远就是一个字,男或者女,地址一行有可能就很长。给定width = 5.

@Excel(name = "性别",  width = 5 ,needMerge = true)
private String sex;

当性别字段给定宽度之后,就变窄喽,其实主要是加长用的,就像加密过的电话字段,会显示多行,当列变宽的时候,就会在一行显示,也就更清晰。 image.png