EasyExcel(FastExcel)Excel导出功能 技术文档

EasyExcel(FastExcel)Excel导出功能 技术文档

文档说明

本文档基于项目中实际开发的 cn.idev.excel(fastexcel) 导出功能编写,包含依赖配置、核心工具类、自定义处理器、数据转换器、导出实体配置、业务接口实现等全量技术细节,文档适用于开发维护与功能迭代参考。

注:本项目使用的fastexcel 1.3.0是EasyExcel的增强版(EasyExcel-Plus),API与阿里官方EasyExcel兼容,底层基于POI封装,核心优势为低内存占用、高性能、轻量化

一、技术依赖配置

1.1 版本管理

xml 复制代码
<fastexcel.version>1.3.0</fastexcel.version>

1.2 Maven核心依赖

xml 复制代码
<!-- Excel导出核心依赖 EasyExcel-Plus(fastexcel) -->
<dependency>
    <groupId>cn.idev.excel</groupId>
    <artifactId>fastexcel</artifactId>
    <version>${fastexcel.version}</version>
</dependency>

无其他冗余依赖,组件本身已内置POI核心包,无需额外引入。

二、核心功能概述

本次实现的Excel导出为Web端HTTP流式导出,直接将Excel文件写入HttpServletResponse输出流,浏览器自动下载,核心能力包含:

  1. 支持任意实体类的结构化数据导出,泛型化封装,一行代码调用;

  2. 自动适配列宽,根据单元格内容长度动态计算最优列宽;

  3. 固定Excel表头(冻结第一行),滚动表格时表头常驻,提升体验;

  4. 自定义数据类型转换:布尔值转中文描述、时间戳转格式化时间字符串;

  5. 完善的异常处理与响应重置,导出失败时返回友好提示;

  6. 高性能大数据量导出:10万条数据导出耗时5-10秒,内存占用极低(流式写入,不加载全量数据到内存);

  7. 注解式配置导出样式:表头高度、内容行高度、指定列宽、忽略指定字段。

三、核心代码模块说明

3.1 包结构总览

plain 复制代码
com.cn.datacollectionplatform.data
├── util        # Excel导出核心工具类 EasyExcelUtil
├── handler     # Excel写入处理器 FreezeHeaderWriteHandler(冻结表头)
├── converter   # 自定义数据转换器(2个)
├── entity.response # Excel导出实体类 DataExcelDTO
└── controller  # 业务导出接口(POST /api/v1/data/excel/sync)

3.2 核心工具类:EasyExcelUtil (com.xxx.data.util)

3.2.1 核心方法说明
java 复制代码
public static <T> void exportWeb(HttpServletResponse response, String fileName, String sheetName, List<T> data, Class<T> clazz) throws IOException

方法作用 :Web环境下通用的Excel导出入口方法,一站式完成响应头配置+Excel写入+流式下载,泛型化支持所有实体类

入参说明

  • response:HttpServletResponse 响应对象,用于写入Excel流和配置响应头;

  • fileName:导出文件的名称(无需后缀,工具类自动拼接.xlsx);

  • sheetName:Excel工作表名称;

  • data:待导出的业务数据集合 List;

  • clazz:导出对应的实体类字节码对象(如DataExcelDTO.class)。

3.2.2 核心能力实现
  1. 响应头标准化配置 :私有方法setResponseHeader完成浏览器下载的核心配置

    • 设置响应类型:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet 标准xlsx文件类型;

    • 统一编码:UTF-8,避免中文乱码;

    • 中文文件名处理:URLEncoder.encode编码+替换+%20,解决浏览器兼容性问题;

    • 响应头:attachment;filename*=utf-8''xxx.xlsx 触发浏览器下载动作。

  2. Excel写入核心配置链 EasyExcel.write(response.getOutputStream(), clazz) .sheet(sheetName) .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 自动适配列宽 .registerWriteHandler(new FreezeHeaderWriteHandler()) // 冻结表头第一行 .doWrite(data);

  3. 异常处理机制

    • 捕获IOException(流异常、文件写入异常等);

    • 导出失败时重置响应对象,清空原有响应内容;

    • 响应类型改为JSON,返回导出失败: 异常信息的友好提示;

    • 保证异常时不会返回损坏的Excel文件。

3.3 自定义Sheet处理器:FreezeHeaderWriteHandler (com.xxx.data.handler)

3.3.1 功能说明

实现cn.idev.excel.write.handler.SheetWriteHandler接口,在Excel工作表创建完成后,冻结第一行表头,滚动表格时表头不随内容滚动,提升大表格的阅读体验。

3.3.2 核心API说明
java 复制代码
sheet.createFreezePane(0, 1, 0, 1);

POI原生API参数详解(核心必知):

  • 第1个参数 colSplit:需要冻结的列数,0=不冻结列;

  • 第2个参数 rowSplit:需要冻结的行数,1=冻结第一行(表头行);

  • 第3个参数 leftmostColumn:冻结区域右侧的首列索引,固定传0即可;

  • 第4个参数 topRow:冻结区域下方的首行索引,固定传1即可。

3.4 自定义数据转换器 (com.xxx.data.converter)

核心规范:所有转换器均实现 cn.idev.excel.converters.Converter 接口,用于Java数据类型 → Excel单元格展示数据 的自定义转换,本项目仅做导出,因此convertToJavaData方法返回null即可,无需实现读Excel的转换逻辑。
转换器生效方式:在导出实体类的字段上通过@ExcelProperty(converter=xxx.class)指定绑定。

3.4.1 布尔值转换器:BooleanCheckConverter

功能 :将Java中的Boolean类型值,转换为Excel中友好的中文描述

  • null → 空字符串

  • true → 必须

  • false → 非必须

3.4.2 时间戳转换器:TimestampConverter

功能 :将Java中的Long类型毫秒级时间戳 ,转换为Excel中标准的时间字符串格式yyyy-MM-dd HH:mm:ss

  • null → 空字符串

  • 合法时间戳 → 格式化的日期时间字符串

  • 核心API:基于Java8时间工具类Instant+LocalDateTime+ZoneId实现,线程安全无时区问题。

3.5 导出实体类:DataExcelDTO (com.xxx.data.entity.response)

3.5.1 核心定位

该类是Java业务数据 → Excel表格列的核心映射载体,所有导出的列名、列顺序、列宽、数据转换、样式配置均通过该类的注解+字段配置实现,是Excel导出的核心配置类。

3.5.2 核心注解全量说明(优先级:局部注解 > 全局策略)
✅ 类级别的样式注解(作用于整个工作表)
  1. @HeadRowHeight(20):设置Excel表头行的高度为20;

  2. @ContentRowHeight(18):设置Excel所有数据行的高度为18;

✅ 字段级别的核心注解
  1. @ExcelIgnore:导出时忽略当前字段,该字段不会出现在Excel中;

  2. @ExcelProperty:核心注解,配置字段与Excel列的映射关系,必配参数:

    • value:Excel中的列名(表头文字);

    • index:Excel中的列索引,从0开始,必须连续且唯一,决定列的展示顺序;

    • converter:可选,指定当前字段的自定义转换器,实现数据类型转换;

  3. @ColumnWidth(xx):指定当前列的固定宽度,优先级高于全局的自动列宽策略。

3.5.3 字段配置规范
  • 所有需要导出的字段,必须指定@ExcelPropertyindex属性,避免列错位;

  • 无需导出的字段,添加@ExcelIgnore注解;

  • 需要特殊转换的字段,通过converter绑定对应的转换器;

  • 字段类型支持基础类型、包装类,null值会被转换器处理为空字符串,保证Excel美观。

四、业务导出接口实现

4.1 接口地址与请求方式

java 复制代码
@PostMapping("/api/v1/data/excel/sync")
  • 请求方式:POST

  • 请求体:JSON格式的查询参数 DataQueryVO(已做参数校验@Valid

  • 返回值:无返回值(void),直接将Excel流写入HttpServletResponse,浏览器自动触发下载

4.2 核心业务流程

plain 复制代码
1. 记录导出开始时间 → 用于统计耗时
2. 调用业务服务查询待导出数据:motorTestDataService.exportData(request) → 返回List<DataExcelDTO>
3. 生成唯一文件名:数据导出_+时间戳,避免文件重名
4. 调用EasyExcelUtil.exportWeb完成导出核心逻辑
5. 打印导出日志:文件名、数据量、耗时(毫秒)
6. 全局异常捕获:捕获所有异常,打印错误堆栈,避免接口报错导致前端无响应

4.3 性能指标

plain 复制代码
日志打印:EasyExcel 导出成功,文件名:数据导出_1705xxxxxx,数据量:100000,耗时:7800 ms

核心性能优势 :fastexcel采用流式写入机制,数据逐条写入Excel流,不会将全量数据加载到JVM内存中,因此即使导出10万+数据,内存占用依然极低,不会出现OOM内存溢出问题。

五、核心开发规范与注意事项

✅ 必须遵循的开发规范

  1. 导出实体类的@ExcelProperty(index)必须从0开始,连续且无重复,否则会导致Excel列错位、空列;

  2. 自定义转换器必须实现Converter接口,且在实体类字段上通过converter参数绑定,否则转换器不生效;

  3. 时间戳字段必须是毫秒级TimestampConverter仅支持毫秒级Long类型,秒级时间戳会导致时间展示异常;

  4. 中文文件名必须通过URLEncoder.encode编码,否则会出现文件名乱码(如:文件名变成下划线/问号);

  5. 导出接口必须返回void,且不能在接口上添加@ResponseBody注解,否则会导致Excel流被覆盖,文件损坏无法打开;

✅ 优化与扩展建议

  1. 如需冻结Excel的列(如冻结前2列),只需修改FreezeHeaderWriteHandlercreateFreezePane参数:sheet.createFreezePane(2, 1, 2, 1)

  2. 如需自定义更多数据类型转换(如枚举值转中文),可参照现有转换器的写法,实现Converter接口即可;

  3. 如需批量导出多个Sheet,可在EasyExcel.write()后多次调用.sheet().doWrite()

  4. 大数据量查询时,建议业务层采用分页查询+流式拼接,避免一次性查询10万条数据导致查询阶段内存溢出;

  5. 如需自定义表头样式(如背景色、字体加粗),可新增CellStyleWriteHandler处理器,注册到registerWriteHandler即可。

✅ 常见问题排查

  1. Excel文件下载后打不开/提示损坏

    • 检查响应头是否配置正确的xlsx文件类型;

    • 检查接口是否返回void,是否有多余的返回值;

    • 检查异常时是否执行了response.reset()重置响应。

  2. 中文列名/文件名乱码

    • 检查是否设置了response.setCharacterEncoding("utf-8")

    • 检查文件名是否通过URLEncoder.encode编码并替换+%20

  3. 转换器不生效

    • 检查转换器是否正确实现Converter接口;

    • 检查@ExcelProperty是否指定了converter参数;

    • 检查转换器的supportJavaTypeKey是否与字段类型一致。

  4. 列宽适配异常

    • 局部字段通过@ColumnWidth指定了固定宽度,优先级高于全局的自动列宽策略;

    • 自动列宽策略会根据字段内容的最长长度适配,无需额外配置。

六、核心API/注解速查表

6.1 核心工具类API

java 复制代码
// 导出核心调用
EasyExcelUtil.exportWeb(response, fileName, sheetName, dataList, DataExcelDTO.class);

6.2 核心注解速查表

注解 作用域 核心作用
@ExcelIgnore 字段 忽略当前字段,不导出
@ExcelProperty 字段 配置列名、列索引、绑定转换器
@ColumnWidth 字段 指定列的固定宽度
@HeadRowHeight 设置表头行高度
@ContentRowHeight 设置数据行高度

6.3 核心处理器

  1. LongestMatchColumnWidthStyleStrategy:全局自动适配列宽策略;

  2. FreezeHeaderWriteHandler:自定义冻结表头策略。

七、代码附录

本章节整理所有核心代码文件,按包结构顺序排列,可直接复制到项目中使用。

7.1 Maven依赖配置

xml 复制代码
<fastexcel.version>1.3.0&lt;/fastexcel.version&gt;
<!-- Excel 工具(EasyExcel-PLus )  -->
<dependency>
    <groupId>cn.idev.excel</groupId>
    <artifactId>fastexcel</artifactId>
    <version>${fastexcel.version}</version>
</dependency>

7.2 Excel工具类:EasyExcelUtil

java 复制代码
package com.cn.datacollectionplatform.data.util;

import cn.idev.excel.EasyExcel;
import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.cn.datacollectionplatform.data.handler.FreezeHeaderWriteHandler;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class EasyExcelUtil {

    /**
     * 导出 Excel (Web)
     */
    public static <T> void exportWeb(HttpServletResponse response, String fileName, String sheetName, List<T> data, Class<T> clazz) throws IOException {
        try {
            // 1. 设置响应头
            setResponseHeader(response, fileName);

            // 2. 写入 Excel
            EasyExcel.write(response.getOutputStream(), clazz)
                    .sheet(sheetName)
                    // 注册自动列宽策略
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    // 【新增】注册冻结表头策略
                    .registerWriteHandler(new FreezeHeaderWriteHandler())
                    .doWrite(data);

        } catch (IOException e) {
            // 重置响应
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().println("导出失败: " + e.getMessage());
        }
    }

    private static void setResponseHeader(HttpServletResponse response, String fileName) throws IOException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName + ".xlsx");
    }
}

7.3 表头冻结处理器:FreezeHeaderWriteHandler

java 复制代码
package com.cn.datacollectionplatform.data.handler;

import cn.idev.excel.write.handler.SheetWriteHandler;
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
import org.apache.poi.ss.usermodel.Sheet;

/**
 * 冻结第一行(表头)的处理器
 */
public class FreezeHeaderWriteHandler implements SheetWriteHandler {

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        // 获取底层的 POI Sheet 对象
        Sheet sheet = writeSheetHolder.getSheet();

        /**
         * createFreezePane 参数说明:
         * int colSplit: 冻结的列数 (0 表示不冻结列)
         * int rowSplit: 冻结的行数 (1 表示冻结第 1 行,即表头)
         * int leftmostColumn: 冻结区域右侧第一列的索引 (通常为 0)
         * int topRow: 冻结区域下方第一行的索引 (通常为 1)
         */
        sheet.createFreezePane(0, 1, 0, 1);
    }
}

7.4 数据转换器:BooleanCheckConverter

java 复制代码
package com.cn.datacollectionplatform.data.converter;

import cn.idev.excel.converters.Converter;
import cn.idev.excel.enums.CellDataTypeEnum;
import cn.idev.excel.metadata.GlobalConfiguration;
import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.data.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty;

public class BooleanCheckConverter implements Converter<Boolean> {

    @Override
    public Class<?> supportJavaTypeKey() {
        return Boolean.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    @Override
    public Boolean convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        return null;
    }

    @Override
    public WriteCellData<?> convertToExcelData(Boolean value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        if (value == null) {
            return new WriteCellData<>("");
        }
        return new WriteCellData<>(value ? "必须" : "非必须");
    }
}

7.5 数据转换器:TimestampConverter

java 复制代码
package com.cn.datacollectionplatform.data.converter;

import cn.idev.excel.converters.Converter;
import cn.idev.excel.enums.CellDataTypeEnum;
import cn.idev.excel.metadata.GlobalConfiguration;
import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.data.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

public class TimestampConverter implements Converter<Long> {

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public Class<?> supportJavaTypeKey() {
        return Long.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    @Override
    public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        return null;
    }

    @Override
    public WriteCellData<?> convertToExcelData(Long value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        if (value == null) {
            return new WriteCellData<>("");
        }
        LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneId.systemDefault());
        return new WriteCellData<>(FORMATTER.format(dateTime));
    }
}

7.6 导出实体类:DataExcelDTO

java 复制代码
package com.cn.datacollectionplatform.data.entity.response;

import cn.idev.excel.annotation.ExcelIgnore;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.write.style.ColumnWidth;
import cn.idev.excel.annotation.write.style.ContentRowHeight;
import cn.idev.excel.annotation.write.style.HeadRowHeight;
import com.cn.datacollectionplatform.data.converter.BooleanCheckConverter;
import com.cn.datacollectionplatform.data.converter.TimestampConverter;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * Excel导出实体类
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@HeadRowHeight(20)
@ContentRowHeight(18)
public class DataExcelDTO {


    // 加上这个注解,导出时会自动忽略该字段
    @ExcelIgnore
    private Long time;

    @ExcelProperty(value = "上传批次号", index = 0)
    @ColumnWidth(25)
    private String uploadId;

    @ExcelProperty(value = "订单号", index = 1)
    @ColumnWidth(20)
    private String orderNum;

    @ExcelProperty(value = "产品序列号", index = 2)
    @ColumnWidth(20)
    private String productSn;

    @ExcelProperty(value = "设备编码", index = 3)
    @ColumnWidth(20)
    private String equipmentSn;

    @ExcelProperty(value = "设备名称", index = 4)
    @ColumnWidth(15)
    private String equipmentName;

    @ExcelProperty(value = "工序说明", index = 5)
    @ColumnWidth(15)
    private String process;

    @ExcelProperty(value = "线别", index = 6)
    @ColumnWidth(10)
    private String lineNo;

    @ExcelProperty(value = "测试结果", index = 7)
    @ColumnWidth(10)
    private String testResult;

    // 注意:Converter 类必须是 cn.idev.excel 包下的
    @ExcelProperty(value = "测试时间", index = 8, converter = TimestampConverter.class)
    @ColumnWidth(20)
    private Long testTime;

    @ExcelProperty(value = "所属公司", index = 9)
    @ColumnWidth(15)
    private String belongCompany;

    @ExcelProperty(value = "是否必须", index = 10, converter = BooleanCheckConverter.class)
    @ColumnWidth(15)
    private Boolean check;

    @ExcelProperty(value = "测试项描述", index = 11)
    @ColumnWidth(15)
    private String desc;

    //@ExcelProperty(value = "数据类型", index = 12)
    @ExcelIgnore
    private String type;

    @ExcelProperty(value = "单位", index = 12)
    private String unit;

    @ExcelProperty(value = "数值", index = 13)
    @ColumnWidth(15)
    private String value;

    @ExcelProperty(value = "上限", index = 14)
    private String upperLimit;

    @ExcelProperty(value = "下限", index = 15)
    private String lowerLimit;
}

7.7 业务导出接口

java 复制代码
/**
 * 同步导出Excel (基于 EasyExcel 改造)
 * 性能优化:预计 10万条数据在 5-10秒内完成,内存占用极低。
 */
@PostMapping("/api/v1/data/excel/sync")
public void exportExcelSync(
        @Valid @RequestBody DataQueryVO request,
        HttpServletResponse response) {

    long startTime = System.currentTimeMillis();
    try {
        // 1. 查询数据
        // 注意:确保 motorTestDataService.exportData 返回的 List 已经是扁平化的 DataExcelDTO 列表
        List<DataExcelDTO> dataList = motorTestDataService.exportData(request);
        String fileName = "数据导出_" + System.currentTimeMillis();
        // 3. 调用工具类导出 (一行代码搞定)
        EasyExcelUtil.exportWeb(response, fileName, "Sheet1", dataList, DataExcelDTO.class);
        long costTime = System.currentTimeMillis() - startTime;
        log.info("EasyExcel 导出成功,文件名:{},数据量:{},耗时:{} ms", fileName, dataList.size(), costTime);

    } catch (Exception e) {
        log.error("同步导出失败", e);
    }
}

文档编写完成时间:2026-01-17

文档适用版本:fastexcel 1.3.0

相关推荐
破z晓17 分钟前
javascript 导出excel表
开发语言·javascript·excel
Am-Chestnuts5 小时前
豆包多轮回答中的表格导出 Excel:编号、日期与长数字检查
excel
hPw0eKIqD18 小时前
使用二次封装的Excel COM 组件操作Excel\WPS ET中的区域、行和列
excel·wps
冰心孤城1 天前
Excel: xls与xlsx格式转换排坑指南
java·前端·excel
金豆呀1 天前
WPS批量提取Word文档内容生成固定格式Excel表格
word·excel·wps
Access开发易登软件1 天前
Access 怎么做前后端分离?用 Web API 读写 SQL Server
前端·数据库·人工智能·microsoft·excel·access
红莲凪是2 天前
使用二次封装的Excel COM 组件操作Excel\WPS ET IExcelRange 高级应用
python·excel·wps
用户298698530142 天前
如何使用免费工具在线将 Excel 转换为 XML 格式
人工智能·后端·excel
开开心心就好2 天前
内存清理工具定时自动清理开机自启动
java·开发语言·elasticsearch·ocr·excel·音视频·big data
开开心心就好2 天前
电脑内存优化工具一键释放内存超简单
java·开发语言·macos·arcgis·ocr·excel·音视频