老码农教你:Solon + EasyExcel 导出工具

关于 "Excel 导出" ------POI API 是比较复杂的,CellStyle 能把人调得眼冒金星,大数据量导出时内存飙到 90% 的恐惧至今难忘。直到发现了 Alibaba 的 EasyExcel,从此打开新世界的大门。今天就把这套 "导出救命锦囊" 分享给大家,顺便穿插点踩坑经验。

一、先整合项目环境

1. 引入依赖

首先在 pom.xml 里加依赖,这里得注意版本兼容性。加完后记得刷新 Maven。

xml 复制代码
<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-web</artifactId>
    <version>3.5.0</version>
</dependency>

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

2. 创建实体类

定义 Excel 里每一列的数据结构,就像给每个字段安排 "座位"。比如导出用户信息:

java 复制代码
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import lombok.Data;

import java.util.Date;

@Data
public class UserExcelVO {
    // 这里是表头名称,宽度设置成20
    @ExcelProperty(value = "用户ID", index = 0)
    @ColumnWidth(20)
    private Long userId;

    // 设定日期为"yyyy-MM-dd"格式
    @ExcelProperty(value = "注册时间", index = 1)
    @DateTimeFormat("yyyy-MM-dd")
    private Date registerTime;

    // 性别要转换为友好描述
    @ExcelProperty(value = "性别", index = 2, converter = SexConverter.class)
    private Integer sex;
}

这里的 @ExcelProperty 就像给数据贴标签,index是列顺序。别标错号,不然数据错位时会怀疑人生 ------ 笔者曾把金额和年龄的位置搞反,会被财务小姐姐骂得狗血淋头。

3. 编写导出工具类:避免重复工作

把通用导出逻辑封装起来,以后每次导出就简单了。创建EasyExcelUtils:

java 复制代码
import com.alibaba.excel.EasyExcel;
import org.noear.solon.core.handle.Context;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;

public class EasyExcelUtils {
    public static <T> void exportExcel(Context ctx,
                                       List<T> dataList,
                                       Class<T> clazz,
                                       String fileName) throws IOException {
        ctx.contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        ctx.charset("utf-8");
        // 文件名得处理中文,不然下载下来是乱码
        fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        ctx.headerSet("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

        // 这里用EasyExcel.write()就像启动一个Excel生成器
        EasyExcel.write(ctx.outputStream(), clazz)
                .sheet("数据报表")
                .doWrite(dataList);
    }
}

二、实战演练:从 "基础导出" 到 "高深玩法"

1. 基础导出

java 复制代码
import org.noear.solon.annotation.*;
import org.noear.solon.core.handle.Context;

import java.io.IOException;
import java.util.List;

@Controller
public class DemoController {
    @Inject
    UserService userService;

    @Get
    @Mapping("/exportUser")
    public void exportUser(Context ctx) throws IOException {
        List<UserExcelVO> dataList = userService.getUserListForExport(); // 假设这是从数据库查的数据
        EasyExcelUtils.exportExcel(ctx, dataList, UserExcelVO.class, "用户信息表");
    }
}

2. 复杂表头,加一层分类

有时候表头需要多级结构,比如 "用户信息" 下分 "基本信息"" 联系方式 "。这时候需要用@ExcelProperty的数组形式:

java 复制代码
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

@Data
public class ComplexHeaderVO {
    @ExcelProperty({"用户信息", "用户ID"})
    private Long userId;

    @ExcelProperty({"用户信息", "姓名"})
    private String userName;

    @ExcelProperty({"联系方式", "手机号"})
    private String phone;

    @ExcelProperty({"联系方式", "邮箱"})
    private String email;
}

3. 合并单元格

比如导出报表时需要合并相同内容的单元格,这时候得自定义CellWriteHandler。举个例子,合并连续相同的部门名称:

java 复制代码
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;

public class MergeCellHandler implements CellWriteHandler {
    @Override
    public void afterCellDispose(CellWriteHandlerContext context) {
        // 这里省略具体实现,核心是通过行号和列号判断是否合并
        // 就像拼拼图,找到相同的部分粘在一起
    }
}

在导出时注册这个处理器:

java 复制代码
EasyExcel.write(...)
    .registerWriteHandler(new MergeCellHandler())
    .doWrite(...);

4. 自定义格式:美化效果

比如金额需要显示成 "¥1,000.00",日期要显示成 "2025 年 5 月 29 日"。除了前面提到的 @DateTimeFormat,数值格式可以用 @NumberFormat:

java 复制代码
@ExcelProperty("金额")
@NumberFormat("#,##0.00")
private Double amount;

5. 大数据量导出,要避免 OOM

当数据量超过 10 万条时,直接导出会OOM的,这时候要用流式处理。EasyExcel 贴心地支持分页导出,分批次写入:

java 复制代码
EasyExcel.write(ctx.outputStream(), UserExcelVO.class)
    .sheet("大数据报表")
    .registerWriteHandler(...) // 可选的样式处理器
    .doWrite(new AnalysisContext() -> {
        // 这里每次调用获取一页数据,直到没有数据为止
        List<UserExcelVO> pageData = userService.getPageData(analysisContext.readRowHolder().getRowIndex());
        return pageData;
    });

三、避坑指南

1. 依赖冲突:当 Maven 开始 "闹别扭"

如果有引入旧版 POI 依赖,可能会和 EasyExcel 的 POI 版本冲突。这时候可用 mvn dependency:tree 命令排查,然后在pom.xml里用排除冲突项。

2. 注解优先级:别让 "标签" 打架

@ExcelProperty 可以写在字段上或方法上,建议统一写在字段上,不然容易混乱。

3. 样式设置:别把 Excel 变成 "花脸猫"

虽然 EasyExcel 支持自定义样式,但别过度使用,比如给每个单元格设置不同颜色,导出的 Excel 可能打不开。样式设置要适度,就像化妆,自然美就好。

相关推荐
yhole5 小时前
Spring Boot整合Redisson的两种方式
java·spring boot·后端
sthnyph6 小时前
Spring Boot 集成 Kettle
java·spring boot·后端
sxhcwgcy6 小时前
Spring.factories
java·数据库·spring
Mike117.6 小时前
GBase 8a 数据同步实践:从 T+1 同步、实时镜像到一写多读的落地思路
java·服务器·数据库
Nyarlathotep01136 小时前
LongAdder为什么那么快?
java·后端
兑生6 小时前
【灵神题单·贪心】2279. 装满石头的背包的最大数量 | 排序贪心 | Java
java·开发语言
毕设源码-邱学长6 小时前
【开题答辩全过程】以 列车信息查询系统为例,包含答辩的问题和答案
java
mygljx6 小时前
Spring Boot从0到1 -day02
java·spring boot·后端
程序员小郭836 小时前
Spring Ai 04 解决 ChatClient 初始化冲突问题
java·后端·spring
y = xⁿ6 小时前
【LeetCodehot100】T114:二叉树展开为链表 T105:从前序与中序遍历构造二叉树
java·算法·链表