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

相关推荐
wtsolutions5 小时前
Understanding JSON Formats - What JSON to Excel Supports
json·excel
wtsolutions5 小时前
Advanced Features - Unlocking the Power of JSON to Excel Pro
linux·json·excel
fs哆哆7 小时前
VB.NET和VBA教程-如何查找Excel数据区域边界
excel
小矮强7 小时前
Excel中根据年月日提取月日,并按月日进行排序
excel
开开心心_Every7 小时前
图片批量压缩工具:支持有损无损两种模式
python·游戏·微信·django·pdf·excel·语音识别
wtsolutions8 小时前
Real-World Use Cases - How Organizations Use JSON to Excel
json·excel
wtsolutions8 小时前
Introduction to JSON to Excel - The Ultimate Conversion Tool
json·excel
骆驼爱记录9 小时前
Python打包命令全攻略
自动化·word·excel·新人首发
wtsolutions9 小时前
JSON to Excel WPS Add-in - Perfect for WPS Office Users
json·excel·wps