【代码】10W+ 数据导出excel,根据表头动态导出单个excel,多个excel到处压缩包

pom引用

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.17</version>
        </dependency>




package com.manager.util;

import com.alibaba.excel.support.ExcelTypeEnum;
import com.manager.dto.productnetvalue.excel.ProductNetvalueExcelExportDto;
import com.manager.dto.productnetvalue.req.ProductNetvalueExportReq;
import com.manager.exception.ServiceException;
import com.manager.utils.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author 
 * @date 2025/03/27 15:11
 **/

@Slf4j
public class ProductNetValueExportExcelUtil {

    private static final String PATTERN = "yyyyMMddHHmmss";

    static DateTimeFormatter dtf = DateTimeFormatter.ofPattern(PATTERN);

    public static void exportExcel(List<ProductNetvalueExportReq.exportField> exportFields, HttpServletResponse response
            , List<ProductNetvalueExcelExportDto> results,String name)  throws IOException {
        String format = dtf.format(LocalDateTime.now());
        String fileName = ProductNetvalueExcelExportDto.SHEET_NAME.concat("-").concat(format);
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

        // 导出 Excel 文件
        try (SXSSFWorkbook wb = new SXSSFWorkbook()){
            buildExcel(exportFields,results,wb);
            wb.write(response.getOutputStream());
        }
    }


    public static void buildExcel(List<ProductNetvalueExportReq.exportField> exportFields
            , List<ProductNetvalueExcelExportDto> results, SXSSFWorkbook wb) throws IOException {

        SXSSFSheet sheet = wb.createSheet(ProductNetvalueExcelExportDto.SHEET_NAME);
        //获取要展示的列名
        if (CollectionUtils.isEmpty(exportFields)) {
            throw new ServiceException("没有设置展示的列!");
        }

        // 创建表头样式
        CellStyle headerStyle = createHeaderStyle(wb);
        // 创建表头行
        SXSSFRow headerRow = sheet.createRow(0);
        //列宽
        Map<Integer, Integer> maxWidth = new HashMap<>();

        int colIndex = 0;
        List<String> columns = new ArrayList<>();
        for (ProductNetvalueExportReq.exportField field : exportFields) {
            SXSSFCell cell = headerRow.createCell(colIndex);
            cell.setCellValue(field.getFieldName());
            cell.setCellStyle(headerStyle);
            columns.add(field.getFieldKey());
            // 初始化列宽(表头宽度)
            int headerWidth = field.getFieldName().getBytes().length * 256 + 200;
            maxWidth.put(colIndex, headerWidth);
            colIndex++;
        }

        // 填充表格数据
        for (int rowIndex = 0; rowIndex < results.size(); rowIndex++) {
            SXSSFRow row = sheet.createRow(rowIndex + 1);
            ProductNetvalueExcelExportDto dto = results.get(rowIndex);

            for (int j = 0; j < columns.size(); j++) {
                String fieldKey = columns.get(j);
                Object value = getFieldValue(dto, fieldKey); // 通过反射或Getter方法获取值
                SXSSFCell cell = row.createCell(j);

                if (value != null) {
                    String cellValue = formatValue(fieldKey, value); // 格式化特定字段(如日期)
                    cell.setCellValue(cellValue);

                    // 计算列宽(按字符数估算)
                    int cellWidth = cellValue.length() * 320;
                    if (cellWidth > maxWidth.get(j)) {
                        maxWidth.put(j, cellWidth);
                    }
                }
            }
        }

        for (int j = 0; j < columns.size(); j++) {
            int colWidth = maxWidth.get(j) * 2;
            if (colWidth < 255 * 256) {
                sheet.setColumnWidth(j, (maxWidth.get(j)) < 2000 ? 2000 : colWidth);
            } else {
                sheet.setColumnWidth(j, 4000);
            }
        }
    }


    private static CellStyle createHeaderStyle(SXSSFWorkbook wb) {
        Font titleFont = wb.createFont();
        titleFont.setBold(true);
        titleFont.setFontHeightInPoints((short) 12);
        titleFont.setColor(IndexedColors.WHITE.getIndex());
        titleFont.setFontName("微软雅黑");

        CellStyle headerStyle = wb.createCellStyle();
        headerStyle.setFont(titleFont);
        headerStyle.setAlignment(HorizontalAlignment.CENTER);
        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        headerStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        return headerStyle;
    }

    // 辅助方法:通过反射获取字段值
    private static Object getFieldValue(ProductNetvalueExcelExportDto dto, String fieldKey) {
        try {
            Field field = ProductNetvalueExcelExportDto.class.getDeclaredField(fieldKey);
            field.setAccessible(true);
            return field.get(dto);
        } catch (Exception e) {
            throw new ServiceException("字段访问失败: " + fieldKey, e);
        }
    }

    // 辅助方法:格式化值(如日期)
    private static String formatValue(String fieldKey, Object value) {
        if ("netDate".equals(fieldKey)) {
            return  DateUtils.format(((Date) value), DateUtils.FORMAT_LONG2);
        }
        return value.toString();
    }


    public static void exportExcelZip(List<ProductNetvalueExportReq.exportField> exportFields
            , HttpServletResponse response,Map<String, List<ProductNetvalueExcelExportDto>> map) throws IOException{
        // 用于存放文件路径
        List<File> excelFiles = new ArrayList<>();
        // 设置ZIP响应格式
        response.setContentType("application/zip");
        String format = dtf.format(LocalDateTime.now());
        String fileName = URLEncoder.encode(ProductNetvalueExcelExportDto.SHEET_NAME.concat("-").concat(format), "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + fileName + ".zip");
        try {
            for (String productName : map.keySet()) {
                // 1. 根据产品ID获取数据
                List<ProductNetvalueExcelExportDto> productData = map.get(productName);
                if (CollectionUtils.isEmpty(productData)){
                    return;
                }
                // 生成文件名
                String excelName = productName
                        .concat("-").concat("净值信息")
                        .concat("-").concat(format)
                        .concat(ExcelTypeEnum.XLSX.getValue());
                File excelFile = creatFile(excelName);
                // 保留100行在内存
                try (SXSSFWorkbook wb = new SXSSFWorkbook(100);
                     FileOutputStream fos = new FileOutputStream(excelFile)) {
                    buildExcel(exportFields, productData, wb);
                    wb.write(fos);
                    excelFiles.add(excelFile);
                    // 清理临时文件(SXSSF生成的后备存储)
                    wb.dispose();
                }catch (IOException e){
                    log.error("文件导出打包出错", e);
                    // 异常时清理临时文件
                    cleanupTempFiles(excelFiles);
                }
            }
            try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
                for (File excelFile : excelFiles) {
                    ZipEntry entry = new ZipEntry(excelFile.getName());
                    zipOut.putNextEntry(entry);
                    Files.copy(excelFile.toPath(), zipOut);
                    zipOut.closeEntry();
                }
            }
        }catch (IOException e) {
            log.error("文件下载出错", e);
            // 异常时清理临时文件
            cleanupTempFiles(excelFiles);
        }finally {
            // 确保最终清理
            cleanupTempFiles(excelFiles);
        }
    }

    //创建文件File对象
    private static File creatFile(String filePath) {
        return new File(filePath);
    }

    // 清理方法
    private static void cleanupTempFiles(List<File> files) {
        for (File file : files) {
            if (file.exists() && !file.delete()) {
                log.warn("临时文件删除失败: {}", file.getAbsolutePath());
            }
        }
        // 清空列表引用
        files.clear();
    }

}



这里表述一个生产遇到的问题,最开始使用的时XSSFWorkbook,在生产遇到了当用户导出10W数据时,把服务器打挂了,后来排查发现数据量太大,导致OOM了。

问题:大内存占用风险(非内存泄漏,但可能导致OOM)
XSSFWorkbook会将整个Excel数据加载到内存,若map中的产品数据量极大,生成多个Excel时可能触发OutOfMemoryError。

修复方案:
改用流式Excel库(如SXSSFWorkbook),逐行写入数据,减少内存占用.

扩展知识:

几种Workbook格式
POI中提供了很多种WorkBook API来操作Excel,有的适合大文件读写,有的
不适合。

SSFWorkbook
用于处理 Excel的 .xls格式(即 Excel 97-2003)。

XSSFWorkbook
用于处理 Excel的.xlsx 格式(即 Excel 2007 及以后版本的)。
支持更大的数据集和更多的功能,如更好的样式和公式支持。但是相对于
HSSFWorkbook,它在处理大数据集时可能占用更多内存。

SXSSFWorkbook
用于处理 .xlsx 格式。它是 XSSFWorkbook 的流式版本,专门设计用于
处理大数据集。通过将数据写入临时文件而非全部保留在内存中,显著减
少内存消耗。特别适合用于创建大型数据集的 Excel 文件。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

《小书生》

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值