当前位置: 首页 > wzjs >正文

西宁网站设计高端seo北京公司

西宁网站设计高端,seo北京公司,竞价开户推广,担路网络科技有限公司的证书EasyExcel实现Excel复杂格式导出:合并单元格与样式设置实战 背景介绍 项目技术栈 在一次需求的开发过程中,我们需要实现一个具有复杂格式要求的Excel导出功能。项目采用了以下技术栈: Spring Boot: 3.2.0JDK: 21EasyExcel: 3.3.4 (阿里巴…

EasyExcel实现Excel复杂格式导出:合并单元格与样式设置实战

背景介绍

项目技术栈

在一次需求的开发过程中,我们需要实现一个具有复杂格式要求的Excel导出功能。项目采用了以下技术栈:

  • Spring Boot: 3.2.0
  • JDK: 21
  • EasyExcel: 3.3.4 (阿里巴巴开源的Excel处理工具)
  • Apache POI: 5.2.4 (EasyExcel底层依赖)
  • MyBatis-Plus: 3.5.5
  • Maven: 3.9.x

业务需求

需要导出的Excel文件具有特定的格式要求:

  1. 第1行:标题行"XXXX台账",需要合并A1:O1单元格
  2. 第2行:15个字段的表头行
  3. 第3行:详细的填写说明
  4. 第4行及以后:实际数据内容

每一行都需要不同的样式设置:

  • 标题行:Calibri字体,16号,加粗,居中对齐
  • 表头行:Calibri字体,11号,加粗,居中对齐
  • 说明行:Calibri字体,10号,斜体,左对齐,自动换行
  • 数据行:Calibri字体,10号,普通,左对齐

技术实现方案

1. 基础架构设计

首先,我们需要禁用EasyExcel的默认表头机制,采用手动控制的方式来精确控制每一行的内容和样式。

@Override
public void exportMarketingPhoneData(MarketingPhoneQueryDTO queryDTO, HttpServletResponse response) throws IOException {log.info("Export marketing phone data with params: {}", queryDTO);// 设置响应头setExcelExportResponseHeaders(response, EXPORT_FILE_NAME_PREFIX);// 初始化Excel写入器,关键:不使用模板类,直接操作工作表ExcelWriter excelWriter = null;try {excelWriter = EasyExcelFactory.write(response.getOutputStream()).excelType(ExcelTypeEnum.XLSX).registerWriteHandler(new TitleMergeStrategy()) // 注册自定义处理器.build();WriteSheet writeSheet = EasyExcelFactory.writerSheet(EXPORT_SHEET_NAME).needHead(false) // 关键:禁用默认表头.build();// 分层写入数据writeTitleRow(excelWriter, writeSheet);      // 写入标题行writeHeaderRow(excelWriter, writeSheet);     // 写入表头行  writeDescriptionRow(excelWriter, writeSheet); // 写入说明行writeDataRows(excelWriter, writeSheet, queryDTO); // 写入数据行log.info("Export marketing phone data completed");} finally {if (excelWriter != null) {excelWriter.finish();}}
}

2. 数据写入方法实现

/*** 写入标题行(第1行)*/
private void writeTitleRow(ExcelWriter excelWriter, WriteSheet writeSheet) {List<List<String>> titleData = new ArrayList<>();List<String> titleRow = new ArrayList<>();titleRow.add("台账");// 为其他14列填充空字符串,确保合并单元格正确for (int i = 1; i < 15; i++) {titleRow.add("");}titleData.add(titleRow);excelWriter.write(titleData, writeSheet);
}/*** 写入表头行(第2行)*/
private void writeHeaderRow(ExcelWriter excelWriter, WriteSheet writeSheet) {List<List<String>> headerData = new ArrayList<>();List<String> headerRow = Arrays.asList("序号", "省份", "地市", "号码类型", "客户名称", "客户编码", "接入时间","客户资质", "使用期限", "线路资源类型", "实际使用客户", "实际使用客户的接入时间", "场景用途", "号码", "办理渠道");headerData.add(headerRow);excelWriter.write(headerData, writeSheet);
}

核心技术难点与解决方案

问题1:单元格合并冲突

遇到的问题

Cannot add merged region A1:O1 to sheet because it overlaps with an existing merged region

问题分析
最初尝试使用AbstractMergeStrategy抽象类时,EasyExcel在某些情况下会重复尝试合并同一区域的单元格,导致冲突。

解决方案
改用SheetWriteHandler接口,在afterSheetCreate方法中执行一次性合并:

private static class TitleMergeStrategy implements SheetWriteHandler, RowWriteHandler {@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {Sheet sheet = writeSheetHolder.getSheet();// 合并第一行的A1到O1(行索引0,列索引0-14)CellRangeAddress cellRangeAddress = new CellRangeAddress(0, 0, 0, 14);sheet.addMergedRegion(cellRangeAddress);log.info("Successfully merged title row: A1:O1");}
}

问题2:autoSizeColumn列宽自适应失败

遇到的问题

IllegalStateException: Could not auto-size column. Make sure the column was tracked prior to auto-sizing the column.

问题分析
EasyExcel基于Apache POI的SXSSFSheet实现,为了内存效率,默认不跟踪所有列的内容,因此无法自动计算列宽。

解决方案
放弃autoSizeColumn,在sheet创建时预设固定列宽:

@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {Sheet sheet = writeSheetHolder.getSheet();// 合并标题行CellRangeAddress cellRangeAddress = new CellRangeAddress(0, 0, 0, 14);sheet.addMergedRegion(cellRangeAddress);// 预设列宽,避免autoSizeColumn的追踪问题int[] columnWidths = {2500,  // 序号2500,  // 省份  3000,  // 地市3500,  // 号码类型6000,  // 客户名称4000,  // 客户编码3500,  // 接入时间8000,  // 客户资质3500,  // 使用期限4000,  // 线路资源类型6000,  // 实际使用客户4000,  // 实际使用客户的接入时间8000,  // 场景用途4000,  // 号码3000   // 办理渠道};for (int i = 0; i < 15; i++) {sheet.setColumnWidth(i, columnWidths[i]);}
}

问题3:样式设置时机和作用域

遇到的问题
样式设置不生效,特别是合并单元格的样式无法正确应用。

解决方案
afterRowDispose方法中设置样式,确保在行数据完全写入后再应用样式:

@Override
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {if (row != null) {Workbook workbook = writeSheetHolder.getParentWriteWorkbookHolder().getWorkbook();int rowIndex = row.getRowNum();if (rowIndex == 0) {// 标题行样式setTitleRowStyle(row, workbook);} else if (rowIndex == 1) {// 表头行样式setHeaderRowStyle(row, workbook);} else if (rowIndex == 2) {// 说明行样式setDescriptionRowStyle(row, workbook);} else {// 数据行样式setDataRowStyle(row, workbook);}}
}

样式设置的最佳实践

1. 样式创建方法

为了确保合并单元格的样式正确应用,需要对所有相关单元格都设置样式:

/*** 设置标题行样式*/
private void setTitleRowStyle(Row row, Workbook workbook) {CellStyle titleStyle = workbook.createCellStyle();Font titleFont = workbook.createFont();// 设置字体titleFont.setFontName("Calibri");titleFont.setFontHeightInPoints((short) 16);titleFont.setBold(true);// 设置样式titleStyle.setFont(titleFont);titleStyle.setAlignment(HorizontalAlignment.CENTER);titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 关键:对合并区域的所有单元格都设置样式for (int i = 0; i < 15; i++) {Cell cell = row.getCell(i);if (cell == null) {cell = row.createCell(i);}cell.setCellStyle(titleStyle);}
}/*** 设置说明行样式(支持自动换行)*/
private void setDescriptionRowStyle(Row row, Workbook workbook) {CellStyle descStyle = workbook.createCellStyle();Font descFont = workbook.createFont();// 设置字体descFont.setFontName("Calibri");descFont.setFontHeightInPoints((short) 10);descFont.setItalic(true);// 设置样式descStyle.setFont(descFont);descStyle.setAlignment(HorizontalAlignment.LEFT);descStyle.setVerticalAlignment(VerticalAlignment.CENTER);descStyle.setWrapText(true); // 启用自动换行// 应用到所有说明单元格for (int i = 0; i < 15; i++) {Cell cell = row.getCell(i);if (cell == null) {cell = row.createCell(i);}cell.setCellStyle(descStyle);}
}

2. 行高设置

afterRowCreate方法中设置不同行的高度:

@Override
public void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {if (row != null) {int rowIndex = row.getRowNum();if (rowIndex == 0) {row.setHeightInPoints(23.2f); // 标题行高度} else if (rowIndex == 1) {row.setHeightInPoints(16.8f); // 表头行高度} else if (rowIndex == 2) {row.setHeightInPoints(107.0f); // 说明行高度(因为内容较多)} else {row.setHeightInPoints(20); // 数据行高度}}
}

代码优化与最佳实践

1. 常量提取,消除魔法值

// Excel样式常量
private static final String EXCEL_FONT_NAME = "Calibri";
private static final short TITLE_FONT_SIZE = 16;
private static final short HEADER_FONT_SIZE = 11;
private static final short DESCRIPTION_FONT_SIZE = 10;
private static final short DATA_FONT_SIZE = 10;// 行高常量
private static final float TITLE_ROW_HEIGHT = 23.2f;
private static final float HEADER_ROW_HEIGHT = 16.8f;
private static final float DESCRIPTION_ROW_HEIGHT = 107.0f;
private static final float DATA_ROW_HEIGHT = 20f;// Excel结构常量
private static final int EXCEL_COLUMN_COUNT = 15;
private static final int TITLE_START_COLUMN = 0;
private static final int TITLE_END_COLUMN = 14;// 行索引常量
private static final class RowIndex {static final int TITLE = 0;static final int HEADER = 1;static final int DESCRIPTION = 2;static final int DATA_START = 3;
}

2. 方法职责单一化

/*** 创建标题行单元格样式*/
private CellStyle createTitleCellStyle(Workbook workbook) {CellStyle titleStyle = workbook.createCellStyle();Font titleFont = workbook.createFont();titleFont.setFontName(EXCEL_FONT_NAME);titleFont.setFontHeightInPoints(TITLE_FONT_SIZE);titleFont.setBold(true);titleStyle.setFont(titleFont);titleStyle.setAlignment(HorizontalAlignment.CENTER);titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);return titleStyle;
}/*** 获取或创建单元格*/
private Cell getOrCreateCell(Row row, int columnIndex) {Cell cell = row.getCell(columnIndex);if (cell == null) {cell = row.createCell(columnIndex);}return cell;
}

性能优化策略

1. 分批查询避免内存溢出

private void writeDataRows(ExcelWriter excelWriter, WriteSheet writeSheet, MarketingPhoneQueryDTO queryDTO) {final int PAGE_SIZE = 1000; // 每批处理1000条记录int currentPage = 1;while (true) {Page<MarketingPhone> page = new Page<>(currentPage, PAGE_SIZE);LambdaQueryWrapper<MarketingPhone> queryWrapper = buildCountWrapper(queryDTO);IPage<MarketingPhone> pageResult = marketingPhoneMapper.selectPage(page, queryWrapper);if (pageResult.getRecords().isEmpty()) {break;}// 转换为导出数据格式List<List<String>> dataRows = convertToDataRows(pageResult.getRecords(), currentPage, PAGE_SIZE);excelWriter.write(dataRows, writeSheet);currentPage++;// 如果当前页记录数小于页大小,说明已经是最后一页if (pageResult.getRecords().size() < PAGE_SIZE) {break;}}
}

2. 使用流式处理转换数据

private List<List<String>> convertToDataRows(List<MarketingPhone> records, int currentPageNum, int pageSize) {if (records.isEmpty()) {return List.of();}final int startIndex = (currentPageNum - 1) * pageSize;return IntStream.range(0, records.size()).mapToObj(i -> {MarketingPhone record = records.get(i);List<String> row = new ArrayList<>(15);row.add(String.valueOf(startIndex + i + 1)); // 序号row.add(MarketingPhoneUtil.getProvinceName()); // 省份row.add(MarketingPhoneUtil.getCityNameById(record.getCity())); // 地市// ... 其他字段return row;}).toList();
}

总结

通过本次Excel复杂格式导出功能的实现,我们掌握了以下关键技术点:

成功要素

  1. 正确的Handler选择:使用SheetWriteHandler而非AbstractMergeStrategy避免合并冲突
  2. 样式应用时机:在afterRowDispose中设置样式确保数据写入完成
  3. 列宽管理策略:预设固定宽度替代autoSizeColumn避免追踪问题
  4. 分批处理机制:避免大数据量导致的内存溢出

技术收益

  1. 专业的Excel输出:实现了完全符合业务要求的复杂格式
  2. 良好的性能表现:支持大数据量导出而不会内存溢出
  3. 高可维护性:通过常量提取和方法拆分提高代码质量
  4. 可扩展性:架构设计支持未来的格式变更需求

注意事项

  1. 版本兼容性:EasyExcel 3.3.4与Apache POI 5.2.4的兼容性良好
  2. 内存管理:大文件导出时建议使用SXSSFWorkbook的流式写入
  3. 样式复用:避免重复创建相同的CellStyle对象,可以提高性能
  4. 异常处理:确保ExcelWriter在finally块中正确关闭

这个实现方案在生产环境中稳定运行,成功解决了复杂Excel格式导出的技术难题,为类似需求提供了可靠的技术参考。

http://www.dtcms.com/wzjs/121388.html

相关文章:

  • 19楼网站模板实时热搜榜榜单
  • 重庆高端网站设计网络营销推广的
  • 海南万宁市q群排名优化软件
  • 企业运营网站建设社群营销
  • 苏州市住房建设局网站首页网络营销型网站
  • 找网络公司做网站网上电商怎么做
  • 龙岗做网站公司哪家好网络营销的作用和意义
  • 海南房产金昌网站seo
  • 如何做网站拓扑结构图做seo必须有网站吗
  • 做的好的电商网站项目百度seo优化系统
  • 技术难度高的网站开发百度推广好不好做
  • 建设银行交学费网站没经验怎么开广告公司
  • 网站用户黏度表现在外贸网站平台有哪些
  • 点网站建设网站seo推广优化
  • 宁夏做网站天津seo结算
  • 政府网站建设和管理总结百度快照怎么弄
  • 网站设计要求网站app开发公司
  • 中山市企业网站seo营销工具专注网站建设服务机构
  • wordpress美容主题石家庄seo网络优化的公司
  • 找个人做网站一键建站免费
  • 厦门专业网站建设建站安顺seo
  • 网站域名备案查询百度关键词优化软件如何
  • 可以自己做网站服务器不电子商务
  • 无锡网站开发平台怎么在网上做广告宣传
  • 顺德网站建设要多少钱网络营销主要是什么
  • 新网站如何做免费推广如何开发一个软件平台
  • 中国最好的网站器域名统一宁波网站推广方案
  • 一家只做家纺的网站搜索引擎优化报告
  • 做网站包括图片设计吗舆情网站直接打开的软件
  • 外贸网站制作方案裤子seo关键词