Spring Boot 与 Apache POI 实现复杂嵌套结构 Excel 导出

1.业务场景分析

实际业务中,我们常遇到一对多甚至多对多 的数据关系。例如,一个主实体包含多个一级子项,每个一级子项又包含多个二级子项。传统平面表格难以直观展示这种层次关系,需要合并单元格多级表头来优化可读性。

2.实现结果

3.依赖

复制代码
<dependencies>
    <!-- Apache POI 核心库 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>4.1.2</version>
    </dependency>
    <!-- 用于处理 .xlsx 格式的Excel文件(Office Open XML) -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>4.1.2</version>
    </dependency>
</dependencies>

4.代码

复制代码
package com.example.study.controller.easypoi;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.List;

/**
 * 嵌套列表导出控制器 - 增强空集合处理版本
 * 使用Spring Boot的@RestController注解标识这是一个RESTful控制器[6,7](@ref)
 * 使用@RequestMapping注解定义控制器的基础路径[6](@ref)
 */
@RestController
@RequestMapping("/api/export/two")
public class NestedListExportTwoController {

    /**
     * 主实体数据类 - 代表最外层的数据结构
     * 使用Java Bean规范,包含私有字段和公共getter/setter方法
     */
    public static class MainEntity {
        private String mainId;           // 主实体ID
        private String mainName;         // 主实体名称
        private LocalDate createTime;    // 创建时间,使用Java 8的LocalDate类型
        private List<FirstLevelItem> firstLevelItems; // 一级子项列表,可能为null或空

        // 默认构造器,Spring框架反射创建对象时需要[6](@ref)
        public MainEntity() {}

        // 全参数构造器,便于快速创建测试数据
        public MainEntity(String mainId, String mainName, LocalDate createTime, List<FirstLevelItem> firstLevelItems) {
            this.mainId = mainId;
            this.mainName = mainName;
            this.createTime = createTime;
            this.firstLevelItems = firstLevelItems;
        }

        // Getters and Setters - 遵循Java Bean规范,便于框架访问属性[6](@ref)
        public String getMainId() { return mainId; }
        public void setMainId(String mainId) { this.mainId = mainId; }
        public String getMainName() { return mainName; }
        public void setMainName(String mainName) { this.mainName = mainName; }
        public LocalDate getCreateTime() { return createTime; }
        public void setCreateTime(LocalDate createTime) { this.createTime = createTime; }
        public List<FirstLevelItem> getFirstLevelItems() { return firstLevelItems; }
        public void setFirstLevelItems(List<FirstLevelItem> firstLevelItems) { this.firstLevelItems = firstLevelItems; }
    }

    /**
     * 一级子项数据类 - 代表嵌套的第一层数据结构
     */
    public static class FirstLevelItem {
        private String firstId;              // 一级子项ID
        private String firstName;            // 一级子项名称
        private List<SecondLevelItem> secondLevelItems; // 二级子项列表
        private Integer firstLevelCount;     // 一级子项数量,可自动计算

        public FirstLevelItem() {}

        public FirstLevelItem(String firstId, String firstName, List<SecondLevelItem> secondLevelItems, Integer firstLevelCount) {
            this.firstId = firstId;
            this.firstName = firstName;
            this.secondLevelItems = secondLevelItems;
            this.firstLevelCount = firstLevelCount;
        }

        // Getters and Setters
        public String getFirstId() { return firstId; }
        public void setFirstId(String firstId) { this.firstId = firstId; }
        public String getFirstName() { return firstName; }
        public void setFirstName(String firstName) { this.firstName = firstName; }
        public List<SecondLevelItem> getSecondLevelItems() { return secondLevelItems; }
        public void setSecondLevelItems(List<SecondLevelItem> secondLevelItems) { this.secondLevelItems = secondLevelItems; }

        /**
         * 智能获取一级子项数量:优先使用显式设置的值,否则计算二级子项列表大小
         * 使用Optional类安全处理null值,避免空指针异常[1](@ref)
         */
        public Integer getFirstLevelCount() {
            return firstLevelCount != null ? firstLevelCount :
                    (secondLevelItems != null ? secondLevelItems.size() : 0);
        }
        public void setFirstLevelCount(Integer firstLevelCount) { this.firstLevelCount = firstLevelCount; }
    }

    /**
     * 二级子项数据类 - 代表嵌套的第二层数据结构
     */
    public static class SecondLevelItem {
        private String secondId;     // 二级子项ID
        private String secondName;   // 二级子项名称
        private Integer secondCount; // 二级子项数量
        private Integer secondYear;  // 二级子项年份

        public SecondLevelItem() {}

        public SecondLevelItem(String secondId, String secondName, Integer secondCount, Integer secondYear) {
            this.secondId = secondId;
            this.secondName = secondName;
            this.secondCount = secondCount;
            this.secondYear = secondYear;
        }

        // Getters and Setters
        public String getSecondId() { return secondId; }
        public void setSecondId(String secondId) { this.secondId = secondId; }
        public String getSecondName() { return secondName; }
        public void setSecondName(String secondName) { this.secondName = secondName; }
        public Integer getSecondCount() { return secondCount; }
        public void setSecondCount(Integer secondCount) { this.secondCount = secondCount; }
        public Integer getSecondYear() { return secondYear; }
        public void setSecondYear(Integer secondYear) { this.secondYear = secondYear; }
    }

    /**
     * 生成包含各种空集合情况的测试数据
     * 模拟真实业务场景中可能遇到的各种数据边界情况
     * 使用@return注解明确返回值类型和含义[1](@ref)
     */
    private List<MainEntity> generateTestData() {
        List<MainEntity> mainEntities = new ArrayList<>();

        // 主实体1:正常数据(有完整的一级和二级子项)- 典型业务场景
        List<FirstLevelItem> firstLevelItems1 = new ArrayList<>();
        List<SecondLevelItem> secondItems1 = Arrays.asList(
                new SecondLevelItem("S12-001", "二级子项1", 10, null),
                new SecondLevelItem("S22-001", "二级子项2", 14, 2025)
        );
        firstLevelItems1.add(new FirstLevelItem("S1-001", "一级子项1", secondItems1, 2));

        List<SecondLevelItem> secondItems2 = Arrays.asList(
                new SecondLevelItem("S12-002", "二级子项1", 14, 2025)
        );
        firstLevelItems1.add(new FirstLevelItem("S2-002", null, secondItems2, 1));

        MainEntity mainEntity1 = new MainEntity("M001", "主实体1", LocalDate.of(2024, 1, 15), firstLevelItems1);

        // 主实体2:正常数据 - 另一个典型业务场景
        List<FirstLevelItem> firstLevelItems2 = new ArrayList<>();
        List<SecondLevelItem> secondItems3 = Arrays.asList(
                new SecondLevelItem("S12-002", "二级子项2", 5, 2023),
                new SecondLevelItem("S22-002", "二级子项2", 8, 2024),
                new SecondLevelItem("S32-002", "二级子项3", 12, 2025)
        );
        firstLevelItems2.add(new FirstLevelItem("S1-003", "一级子项3", secondItems3, 3));
        MainEntity mainEntity2 = new MainEntity("M002", "主实体2", LocalDate.of(2024, 3, 20), firstLevelItems2);

        // 主实体3:一级子项列表为null(测试空指针处理)- 边界情况测试
        MainEntity mainEntity3 = new MainEntity("M003", "主实体3", null, null);

        // 主实体4:一级子项列表不为空但二级子项列表为null - 边界情况测试
        List<FirstLevelItem> firstLevelItems4 = new ArrayList<>();
        FirstLevelItem firstItem4 = new FirstLevelItem("S1-004", "一级子项4", null, 0); // 二级子项为null
        firstLevelItems4.add(firstItem4);
        MainEntity mainEntity4 = new MainEntity("M004", "主实体4", null, firstLevelItems4);

        // 主实体5:一级子项列表为空列表(非null)- 边界情况测试
        MainEntity mainEntity5 = new MainEntity("M005", "主实体5", LocalDate.of(2024, 5, 1), Collections.emptyList());

        // 主实体6:一级子项有数据但二级子项为空列表 - 边界情况测试
        List<FirstLevelItem> firstLevelItems6 = new ArrayList<>();
        FirstLevelItem firstItem6 = new FirstLevelItem("S1-006", "一级子项6", Collections.emptyList(), 0);
        firstLevelItems6.add(firstItem6);
        MainEntity mainEntity6 = new MainEntity("M006", "主实体6", null, firstLevelItems6);

        // 将所有测试数据添加到返回列表
        mainEntities.add(mainEntity1);
        mainEntities.add(mainEntity2);
        mainEntities.add(mainEntity3);
        mainEntities.add(mainEntity4);
        mainEntities.add(mainEntity5);
        mainEntities.add(mainEntity6);

        return mainEntities;
    }

    /**
     * 增强的嵌套列表导出接口 - 支持各种空集合情况
     * 使用@GetMapping注解映射HTTP GET请求[6,7](@ref)
     * 使用ResponseEntity<byte[]>返回文件下载响应,支持精确控制HTTP响应头[6](@ref)
     *
     * @param response HttpServletResponse对象,用于设置响应参数
     * @return ResponseEntity包含Excel文件的字节数组和下载头信息
     * @throws IOException 当文件操作出现IO异常时抛出
     */
    @GetMapping("/nested-list")
    public ResponseEntity<byte[]> exportNestedList(HttpServletResponse response) throws IOException {
        // 生成测试数据
        List<MainEntity> dataList = generateTestData();

        // 创建SXSSFWorkbook,使用流式处理避免内存溢出(100行缓存)
        SXSSFWorkbook workbook = new SXSSFWorkbook(100);
        Sheet sheet = workbook.createSheet("嵌套列表");

        // 创建单元格样式
        CellStyle headerStyle = createHeaderStyle(workbook);
        CellStyle dataStyle = createDataStyle(workbook);

        // 创建表格标题和表头
        createTableHeader(sheet, headerStyle);

        // 存储合并区域信息
        List<CellRangeAddress> mergeRegions = new ArrayList<>();
        int rowIndex = 3; // 数据从第4行开始(前3行被表头占用)

        // 遍历所有主实体数据
        for (MainEntity mainEntity : dataList) {
            int mainEntityStartRow = rowIndex; // 记录当前主实体的起始行
            int mainEntityRowCount = 0; // 记录当前主实体占用的行数

            // 关键修复:安全获取一级子项列表,使用Optional避免空指针异常[1](@ref)
            List<FirstLevelItem> firstLevelItems = Optional.ofNullable(mainEntity.getFirstLevelItems())
                    .orElse(Collections.emptyList());

            // 处理空一级子项列表的情况 - 为主实体创建一行基础数据
            if (firstLevelItems.isEmpty()) {
                Row row = sheet.createRow(rowIndex);
                createMainEntityRow(row, mainEntity, dataStyle);
                // 为其他列创建空单元格,保持表格结构完整
                for (int col = 2; col <= 8; col++) {
                    Cell emptyCell = row.createCell(col);
                    emptyCell.setCellValue("");
                    emptyCell.setCellStyle(dataStyle);
                }
                mainEntityRowCount = 1;
                rowIndex++;
            } else {
                // 处理有一级子项的情况
                for (FirstLevelItem firstItem : firstLevelItems) {
                    // 关键修复:安全获取二级子项列表,处理null情况
                    List<SecondLevelItem> secondItems = Optional.ofNullable(firstItem.getSecondLevelItems())
                            .orElse(Collections.emptyList());

                    // 计算当前一级子项需要的行数(至少1行)
                    int firstLevelRows = Math.max(1, secondItems.size());
                    int firstLevelStartRow = rowIndex;

                    // 创建当前一级子项的所有数据行
                    for (int j = 0; j < firstLevelRows; j++) {
                        Row row = sheet.createRow(rowIndex);

                        // 处理主实体列(只在每组的首行填充)
                        if (j == 0) {
                            createMainEntityCells(row, mainEntity, dataStyle);
                        }

                        // 处理一级子项列(只在每组的首行填充)
                        if (j == 0) {
                            createFirstLevelCells(row, firstItem, dataStyle);
                        }

                        // 处理二级子项列
                        if (j < secondItems.size()) {
                            SecondLevelItem secondItem = secondItems.get(j);
                            createSecondLevelCells(row, secondItem, dataStyle);
                        } else {
                            // 如果没有二级子项数据,创建空单元格保持表格对齐
                            for (int k = 4; k <= 7; k++) {
                                Cell emptyCell = row.createCell(k);
                                emptyCell.setCellValue("");
                                emptyCell.setCellStyle(dataStyle);
                            }
                        }

                        // I列:一级子项数量(每行都填充)
                        Cell firstLevelCountCell = row.createCell(8);
                        firstLevelCountCell.setCellValue(firstItem.getFirstLevelCount() != null ?
                                firstItem.getFirstLevelCount() : 0);
                        firstLevelCountCell.setCellStyle(dataStyle);

                        rowIndex++; // 移动到下一行
                    }

                    // 如果当前一级子项有多行数据,添加合并区域信息
                    if (firstLevelRows > 1) {
                        addFirstLevelMergeRegions(mergeRegions, firstLevelStartRow, firstLevelRows);
                    }

                    mainEntityRowCount += firstLevelRows; // 累计主实体行数
                }
            }

            // 如果主实体占用多行,添加合并区域信息
            if (mainEntityRowCount > 1) {
                addMainEntityMergeRegions(mergeRegions, mainEntityStartRow, mainEntityRowCount);
            }
        }

        // 应用所有合并区域到工作表
        for (CellRangeAddress region : mergeRegions) {
            sheet.addMergedRegion(region);
        }

        // 自动调整列宽
        autoSizeColumnsWithTracking(sheet, 10);

        // 将工作簿写入字节数组输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        workbook.write(baos);
        workbook.dispose(); // 清理临时文件

        // 设置HTTP响应头,触发文件下载[6](@ref)
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attachment; filename=nested_list_export.xlsx");
        headers.add("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

        // 返回包含Excel文件的响应实体
        return ResponseEntity.ok()
                .headers(headers)
                .body(baos.toByteArray());
    }

    /**
     * 创建主实体单元格(A列、B列、J列)
     * 这些单元格在合并区域中会被垂直合并
     *
     * @param row 当前行对象
     * @param mainEntity 主实体数据
     * @param dataStyle 数据单元格样式
     */
    private void createMainEntityCells(Row row, MainEntity mainEntity, CellStyle dataStyle) {
        // A列:主实体ID
        Cell mainIdCell = row.createCell(0);
        mainIdCell.setCellValue(mainEntity.getMainId() != null ? mainEntity.getMainId() : "");
        mainIdCell.setCellStyle(dataStyle);

        // B列:实体名
        Cell mainNameCell = row.createCell(1);
        mainNameCell.setCellValue(mainEntity.getMainName() != null ? mainEntity.getMainName() : "");
        mainNameCell.setCellStyle(dataStyle);

        // J列:主实体创建时间,格式化为yyyy-MM-dd字符串
        Cell createTimeCell = row.createCell(9);
        if (mainEntity.getCreateTime() != null) {
            createTimeCell.setCellValue(mainEntity.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        } else {
            createTimeCell.setCellValue("");
        }
        createTimeCell.setCellStyle(dataStyle);
    }

    /**
     * 创建一级子项单元格(C列、D列)
     * 这些单元格在合并区域中会被垂直合并
     *
     * @param row 当前行对象
     * @param firstItem 一级子项数据
     * @param dataStyle 数据单元格样式
     */
    private void createFirstLevelCells(Row row, FirstLevelItem firstItem, CellStyle dataStyle) {
        // C列:一级子项ID
        Cell firstIdCell = row.createCell(2);
        firstIdCell.setCellValue(firstItem.getFirstId() != null ? firstItem.getFirstId() : "");
        firstIdCell.setCellStyle(dataStyle);

        // D列:一级子项名称
        Cell firstNameCell = row.createCell(3);
        firstNameCell.setCellValue(firstItem.getFirstName() != null ? firstItem.getFirstName() : "");
        firstNameCell.setCellStyle(dataStyle);
    }

    /**
     * 创建二级子项单元格(E列、F列、G列、H列)
     * 这些单元格不会被合并,每行都有独立数据
     *
     * @param row 当前行对象
     * @param secondItem 二级子项数据
     * @param dataStyle 数据单元格样式
     */
    private void createSecondLevelCells(Row row, SecondLevelItem secondItem, CellStyle dataStyle) {
        // E列:二级子项ID
        Cell secondIdCell = row.createCell(4);
        secondIdCell.setCellValue(secondItem.getSecondId() != null ? secondItem.getSecondId() : "");
        secondIdCell.setCellStyle(dataStyle);

        // F列:二级子项名称
        Cell secondNameCell = row.createCell(5);
        secondNameCell.setCellValue(secondItem.getSecondName() != null ? secondItem.getSecondName() : "");
        secondNameCell.setCellStyle(dataStyle);

        // G列:二级子项数量,处理null值
        Cell secondCountCell = row.createCell(6);
        if (secondItem.getSecondCount() != null) {
            secondCountCell.setCellValue(secondItem.getSecondCount());
        } else {
            secondCountCell.setCellValue(0);
        }
        secondCountCell.setCellStyle(dataStyle);

        // H列:二级子项年份,处理null值
        Cell secondYearCell = row.createCell(7);
        if (secondItem.getSecondYear() != null) {
            secondYearCell.setCellValue(secondItem.getSecondYear());
        } else {
            secondYearCell.setCellValue("");
        }
        secondYearCell.setCellStyle(dataStyle);
    }

    /**
     * 创建完整的主实体行(用于空一级子项的情况)
     * 当一级子项为空时,只为最外层主实体创建一行数据
     *
     * @param row 当前行对象
     * @param mainEntity 主实体数据
     * @param dataStyle 数据单元格样式
     */
    private void createMainEntityRow(Row row, MainEntity mainEntity, CellStyle dataStyle) {
        createMainEntityCells(row, mainEntity, dataStyle);
    }

    /**
     * 添加一级子项合并区域
     * 当一级子项对应多个二级子项时,需要合并一级子项的单元格
     *
     * @param mergeRegions 合并区域列表
     * @param startRow 起始行索引
     * @param rowCount 需要合并的行数
     */
    private void addFirstLevelMergeRegions(List<CellRangeAddress> mergeRegions, int startRow, int rowCount) {
        if (rowCount > 1) {
            // 合并C列:一级子项ID
            mergeRegions.add(new CellRangeAddress(startRow, startRow + rowCount - 1, 2, 2));
            // 合并D列:一级子项名称
            mergeRegions.add(new CellRangeAddress(startRow, startRow + rowCount - 1, 3, 3));
            // 合并I列:一级子项数量
            mergeRegions.add(new CellRangeAddress(startRow, startRow + rowCount - 1, 8, 8));
        }
    }

    /**
     * 添加主实体合并区域
     * 当主实体有多个一级子项时,需要合并主实体的单元格
     *
     * @param mergeRegions 合并区域列表
     * @param startRow 起始行索引
     * @param rowCount 需要合并的行数
     */
    private void addMainEntityMergeRegions(List<CellRangeAddress> mergeRegions, int startRow, int rowCount) {
        if (rowCount > 1) {
            // 合并A列:主实体ID
            mergeRegions.add(new CellRangeAddress(startRow, startRow + rowCount - 1, 0, 0));
            // 合并B列:实体名
            mergeRegions.add(new CellRangeAddress(startRow, startRow + rowCount - 1, 1, 1));
            // 合并J列:主实体创建时间
            mergeRegions.add(new CellRangeAddress(startRow, startRow + rowCount - 1, 9, 9));
        }
    }

    /**
     * 创建表头单元格样式
     * 使用粗体、居中显示,增强可读性
     *
     * @param workbook 工作簿对象
     * @return 配置好的单元格样式
     */
    private CellStyle createHeaderStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setBold(true); // 设置粗体
        font.setFontHeightInPoints((short) 12); // 设置字体大小
        style.setFont(font);
        style.setAlignment(HorizontalAlignment.CENTER); // 水平居中
        style.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
        return style;
    }

    /**
     * 创建数据单元格样式
     * 使用居中对齐,保持数据整洁
     *
     * @param workbook 工作簿对象
     * @return 配置好的单元格样式
     */
    private CellStyle createDataStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        style.setAlignment(HorizontalAlignment.CENTER); // 水平居中
        style.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
        return style;
    }

    /**
     * 创建表格标题和表头
     * 使用三级表头结构,清晰展示数据层级关系
     *
     * @param sheet 工作表对象
     * @param headerStyle 表头单元格样式
     */
    private void createTableHeader(Sheet sheet, CellStyle headerStyle) {
        // 第一行表头:大标题(合并A-J列)
        Row row1 = sheet.createRow(0);
        Cell titleCell = row1.createCell(0);
        titleCell.setCellValue("嵌套列表导出");
        titleCell.setCellStyle(headerStyle);
        sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 9));

        // 第二行表头:分组标题(展示数据层级关系)
        Row row2 = sheet.createRow(1);

        // 主实体分组(A-B列)
        Cell mainEntityHeader1 = row2.createCell(0);
        mainEntityHeader1.setCellValue("主实体");
        mainEntityHeader1.setCellStyle(headerStyle);
        sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 1));

        // 一级子项分组(C-D列)
        Cell firstLevelHeader1 = row2.createCell(2);
        firstLevelHeader1.setCellValue("一级子项列表");
        firstLevelHeader1.setCellStyle(headerStyle);
        sheet.addMergedRegion(new CellRangeAddress(1, 1, 2, 3));

        // 二级子项分组(E-H列)
        Cell secondLevelHeader = row2.createCell(4);
        secondLevelHeader.setCellValue("二级子项列表");
        secondLevelHeader.setCellStyle(headerStyle);
        sheet.addMergedRegion(new CellRangeAddress(1, 1, 4, 7));

        // 一级子项数量(I列)
        Cell firstLevelHeader2 = row2.createCell(8);
        firstLevelHeader2.setCellValue("一级子项列表");
        firstLevelHeader2.setCellStyle(headerStyle);

        // 主实体创建时间(J列)
        Cell mainEntityHeader2 = row2.createCell(9);
        mainEntityHeader2.setCellValue("主实体");
        mainEntityHeader2.setCellStyle(headerStyle);

        // 第三行表头:具体列名
        Row row3 = sheet.createRow(2);
        String[] columnHeaders = {
                "主实体ID", "实体名", "一级子项", "一级子项名称",
                "二级子项ID", "二级子项名称", "二级子项数量", "二级子项年份",
                "一级子项数量", "主实体创建时间"
        };

        // 为每个列创建表头单元格
        for (int i = 0; i < columnHeaders.length; i++) {
            Cell headerCell = row3.createCell(i);
            headerCell.setCellValue(columnHeaders[i]);
            headerCell.setCellStyle(headerStyle);
        }
    }

    /**
     * 自动调整列宽(增强版)
     * 针对SXSSFWorkbook的特殊处理,避免自动调整列宽时的性能问题
     *
     * @param sheet 工作表对象
     * @param columnCount 需要调整的列数
     */
    private void autoSizeColumnsWithTracking(Sheet sheet, int columnCount) {
        // 针对SXSSFSheet的特殊处理:跟踪列以支持自动调整列宽
        if (sheet instanceof SXSSFSheet) {
            SXSSFSheet sxsSheet = (SXSSFSheet) sheet;
            for (int i = 0; i < columnCount; i++) {
                sxsSheet.trackColumnForAutoSizing(i);
            }
        }

        // 调整每一列的宽度
        for (int i = 0; i < columnCount; i++) {
            int minWidth = 2000; // 设置最小列宽(约2个字符)
            sheet.setColumnWidth(i, minWidth);
            sheet.autoSizeColumn(i); // 自动调整列宽

            int currentWidth = sheet.getColumnWidth(i);
            // 确保列宽不小于最小值
            if (currentWidth < minWidth) {
                sheet.setColumnWidth(i, minWidth);
                currentWidth = minWidth;
            }

            // 增加10%的边距,避免内容过于拥挤
            int newWidth = (int)(currentWidth * 1.1);
            if (newWidth < 255 * 256) { // Excel最大列宽限制
                sheet.setColumnWidth(i, newWidth);
            }
        }
    }

    /**
     * 测试数据接口 - 返回增强的测试数据信息
     * 使用@GetMapping注解映射HTTP GET请求[7](@ref)
     *
     * @return 包含测试数据和统计信息的Map对象
     */
    @GetMapping("/test-data")
    public Map<String, Object> getTestData() {
        List<MainEntity> data = generateTestData();
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("message", "成功 - 增强的空集合处理版本");
        result.put("data", data);
        result.put("count", data.size());

        // 统计各种边界情况的行数,用于验证导出逻辑
        int totalRows = 0;
        int nullFirstLevelCount = 0;
        int emptyFirstLevelCount = 0;
        int nullSecondLevelCount = 0;
        int emptySecondLevelCount = 0;

        // 遍历数据统计各种边界情况
        for (MainEntity entity : data) {
            if (entity.getFirstLevelItems() == null) {
                nullFirstLevelCount++;
                totalRows += 1; // 即使一级子项为null,也会创建一行
            } else if (entity.getFirstLevelItems().isEmpty()) {
                emptyFirstLevelCount++;
                totalRows += 1; // 即使一级子项为空列表,也会创建一行
            } else {
                for (FirstLevelItem firstItem : entity.getFirstLevelItems()) {
                    if (firstItem.getSecondLevelItems() == null) {
                        nullSecondLevelCount++;
                        totalRows += 1;
                    } else if (firstItem.getSecondLevelItems().isEmpty()) {
                        emptySecondLevelCount++;
                        totalRows += 1;
                    } else {
                        totalRows += firstItem.getSecondLevelItems().size();
                    }
                }
            }
        }

        // 添加统计信息到返回结果
        result.put("totalRows", totalRows);
        result.put("excelColumns", 10);
        result.put("nullFirstLevelEntities", nullFirstLevelCount);
        result.put("emptyFirstLevelEntities", emptyFirstLevelCount);
        result.put("nullSecondLevelItems", nullSecondLevelCount);
        result.put("emptySecondLevelItems", emptySecondLevelCount);
        result.put("features", "支持所有空集合情况:一级为null、一级为空列表、二级为null、二级为空列表");

        return result;
    }
}
相关推荐
SadSunset2 小时前
(35)使用Spring的AOP
java·数据库·spring
better_liang2 小时前
每日Java面试场景题知识点之-Spring AI企业级AI应用开发
java·面试题·智能客服·ai应用·spring ai·企业级开发
申城异乡人2 小时前
使用Java Stream,将集合转换为一对一Map
java
廋到被风吹走2 小时前
【Spring】Spring ORM 深度解析
java·后端·spring
嘻哈baby2 小时前
数据库连接池原理与HikariCP调优实战
后端
自由生长20242 小时前
系统的雪崩-反脆弱设计
后端
高级盘丝洞2 小时前
Spring Boot 集成 InfluxDB 3.x
spring boot
bbq粉刷匠2 小时前
Java--二叉树概念及其基础应用
java·数据结构·算法