1. 为什么选择EasyExcel处理Excel模板第一次接触Excel导出需求时我尝试过Apache POI。当时处理一个20MB的Excel文件直接让服务器内存飙到2GB差点引发生产事故。后来发现阿里开源的EasyExcel同样的文件内存占用不到100MB从此成为我的首选工具。EasyExcel特别适合处理模板导出场景它能直接读取现有Excel文件作为模板通过占位符实现动态数据填充保持原模板所有样式和格式支持百万级数据导出不卡顿最近做的消费帮扶系统需要导出带复杂格式的采购清单。产品经理给的模板包含合并单元格、条件格式、多级表头等复杂样式用EasyExcel只用了不到100行代码就完美实现。2. 快速搭建开发环境2.1 Maven依赖配置建议使用3.0版本老版本有些API不兼容。除了核心依赖还需要特别注意模板文件处理dependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.1.1/version /dependency !-- 防止模板文件被过滤 -- plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-resources-plugin/artifactId configuration nonFilteredFileExtensions nonFilteredFileExtensionxlsx/nonFilteredFileExtension nonFilteredFileExtensionxls/nonFilteredFileExtension /nonFilteredFileExtensions /configuration /plugin2.2 模板设计规范在resources/template目录下创建assistance.xlsx模板文件时要注意这些坑占位符用{}包裹如{name}集合数据要加前缀如{.data}表示循环填充合并单元格的样式要完整否则填充后会丢失建议使用英文命名占位符避免编码问题我遇到过最头疼的问题是模板中的合并单元格。后来发现解决方案很简单在Excel里先取消合并设置好所有单元格样式后重新合并这样EasyExcel处理时就不会丢失样式。3. 动态数据填充实战3.1 基础数据绑定先看最简单的单数据填充。比如要导出用户信息public void exportUserInfo(HttpServletResponse response) { MapString, Object data new HashMap(); data.put(userName, 张三); data.put(age, 28); data.put(department, 技术部); InputStream template getClass().getResourceAsStream(/template/user.xlsx); ExcelWriter writer EasyExcel.write(response.getOutputStream()) .withTemplate(template) .build(); writer.fill(data, EasyExcel.writerSheet().build()); writer.finish(); }3.2 集合数据循环填充处理商品列表这种动态行数据时需要用FillWrapper包装ListProduct products productService.list(); MapString, Object data new HashMap(); data.put(reportDate, LocalDate.now()); ExcelWriter writer EasyExcel.write(outputStream) .withTemplate(template) .build(); // 单独填充普通数据 writer.fill(data, writeSheet); // 循环填充集合数据 FillConfig fillConfig FillConfig.builder().forceNewRow(true).build(); writer.fill(new FillWrapper(products, products), fillConfig, writeSheet);踩坑提醒集合占位符{.products}中的点不能省略否则会导致数据无法正常循环填充。我当初调试了2小时才发现这个细节。4. 样式深度定制技巧4.1 预定义样式策略通过RegisterWriteHandler可以全局控制样式// 标题样式 WriteCellStyle headStyle new WriteCellStyle(); headStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); WriteFont headFont new WriteFont(); headFont.setBold(true); headStyle.setWriteFont(headFont); // 内容样式 WriteCellStyle contentStyle new WriteCellStyle(); contentStyle.setWrapped(true); HorizontalCellStyleStrategy strategy new HorizontalCellStyleStrategy(headStyle, contentStyle); ExcelWriter writer EasyExcel.write(outputStream) .registerWriteHandler(strategy) .withTemplate(template) .build();4.2 动态样式调整有时需要根据数据值动态改变样式比如金额负数标红public class MoneyStyleHandler extends AbstractCellWriteHandler { Override public void afterCellDispose(WriteSheetHolder sheetHolder, WriteTableHolder tableHolder, ListCellData cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { if(!isHead amount.equals(head.getFieldName())) { Double value Double.parseDouble(cell.getStringValue()); if(value 0) { CellStyle style cell.getSheet().getWorkbook().createCellStyle(); style.setFillForegroundColor(IndexedColors.RED.getIndex()); cell.setCellStyle(style); } } } }在项目中实现导出功能时产品突然要求表头要有渐变底色。通过研究POI的API最终用这段代码实现WriteCellStyle headerStyle new WriteCellStyle(); GradientFillPattern gradientFill new GradientFillPattern(); gradientFill.setStartColor(IndexedColors.BLUE.getIndex()); gradientFill.setEndColor(IndexedColors.WHITE.getIndex()); headerStyle.setFillPatternType(gradientFill);5. 高级功能与性能优化5.1 图片导出方案导出带照片的员工信息表时需要特殊处理MapString, Object data new HashMap(); data.put(avatar, new WriteCellData(Files.readAllBytes(avatarPath))); // 设置图片位置 ImageData imageData new ImageData(); imageData.setImage(imageBytes); imageData.setRelativeFirstRowIndex(2); imageData.setRelativeFirstColumnIndex(1); imageData.setRelativeLastRowIndex(4); imageData.setRelativeLastColumnIndex(3);5.2 百万级数据导出处理大数据量导出时这三个参数很关键ExcelWriter writer EasyExcel.write(outputStream) .withTemplate(template) .autoCloseStream(false) .useDefaultStyle(false) .build(); // 分批次填充 for(int i0; i100; i) { ListProduct batch queryBatch(i, 10000); writer.fill(new FillWrapper(data, batch), writeSheet); }最近优化过一个导出慢的问题10万行数据导出要3分钟。通过以下调整降到30秒设置useDefaultStyle(false)禁用默认样式检查增加autoCloseStream(false)避免重复开闭流采用分页查询批量填充6. 常见问题排查指南6.1 乱码问题解决遇到导出文件乱码时按这个顺序检查确保模板文件本身无乱码检查HTTP响应头response.setContentType(application/vnd.ms-excel); response.setCharacterEncoding(UTF-8); response.setHeader(Content-Disposition, attachment;filename URLEncoder.encode(fileName, UTF-8));模板中的字体是否支持中文推荐使用宋体或微软雅黑6.2 样式丢失问题样式不生效时我的检查清单模板中的样式是否应用到了每个单元格合并单元格是否完整设置了所有区域的样式是否误用了useDefaultStyle(false)自定义样式处理器是否正确注册上周就遇到合并单元格边框丢失的问题最后发现是模板中只给左上角单元格设置了边框。解决方法是在Excel里全选合并区域统一设置样式。7. 项目实战案例最近完成的扶贫项目需要导出这样的报表带乡镇LOGO的页眉动态合并的农产品分类自动计算的金额汇总条件格式标记异常数据核心代码结构public void exportReport(HttpServletResponse response) { // 1. 准备数据 ReportData data prepareData(); // 2. 加载模板 InputStream template getTemplateStream(); // 3. 配置导出 ExcelWriter writer EasyExcel.write(response.getOutputStream()) .registerWriteHandler(new CustomMergeStrategy()) .registerWriteHandler(new SummaryHandler()) .withTemplate(template) .build(); // 4. 填充数据 writer.fill(data.getHeader(), writeSheet); writer.fill(new FillWrapper(items, data.getItems()), writeSheet); writer.fill(data.getSummary(), writeSheet); // 5. 处理图片 if(data.getLogo() ! null) { writer.fill(new WriteCellData(data.getLogo()), writeSheet); } writer.finish(); }这个案例中最大的挑战是动态合并单元格。最终通过自定义MergeStrategy实现public class CustomMergeStrategy extends AbstractMergeStrategy { Override protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { if(category.equals(head.getFieldName())) { // 实现合并逻辑 } } }实际开发中建议先用简单模板验证核心功能再逐步增加复杂度。我通常的调试流程是先确保基础数据能正常填充然后测试样式是否保留接着验证动态样式和特殊格式最后处理图片等复杂元素