excel导出

点击查看

import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class LargeDataExcelExportService {

    // 分页查询每页大小
    private static final int PAGE_SIZE = 5000;
    // keep 100 rows in memory, exceeding rows will be flushed to disk
    private static final int SXSSF_WINDOW_SIZE = 100;
    // 每个 Sheet 最多 100 万行(标题行也算在内)
    private static final int MAX_ROWS_PER_SHEET = 100_0000;
    // Sheet名称最大长度
    private static final int MAX_SHEET_NAME_LENGTH = 31;
    // 总条数
    private int totalRecords;

    // 样式缓存
    private final Map<String, CellStyle> STYLE_CACHE = new ConcurrentHashMap<>();
    private String HEADER_STYLE = "HEADER_STYLE";
    private String DATE_STYLE = "DATE_STYLE";
    private String NUMBER_STYLE = "NUMBER_STYLE";
    private String DEFAULT_STYLE = "DEFAULT_STYLE";

    @Resource
    private LargeDataExcelExportMapper largeDataExcelExportMapper;

    public void export(HttpServletResponse response, int totalRows) throws IOException {
        setupResponse(response, "large_data_excel");

        SXSSFWorkbook workbook = new SXSSFWorkbook(SXSSF_WINDOW_SIZE);
        // 初始化共享样式(每个工作簿只创建一次)
        initializeSharedStyles(workbook);

        try (OutputStream out = response.getOutputStream()) {
            int totalSheets = calculateTotalSheets(totalRows);

            System.out.println("开始导出,总数据量: " + totalRows + " 条,需要拆分 " + totalSheets + " 个Sheet");

            int currentPage = 0;
            int globalRowIndex = 0; // 全局行索引
            int currentSheetIndex = 0; // 当前Sheet索引

            String[] headers = {"ID", "姓名", "年龄", "手机号", "邮箱", "创建时间"};
            Sheet currentSheet = this.createNewSheet(workbook, currentSheetIndex, headers);
            int currentSheetRowCount = 1; // 当前Sheet的行数(从1开始,包含标题行)

            while (globalRowIndex < totalRows) {
                // 检查是否需要创建新Sheet
                if (currentSheetRowCount >= MAX_ROWS_PER_SHEET) {
                    currentSheetIndex++;
                    currentSheet = this.createNewSheet(workbook, currentSheetIndex, headers);
                    currentSheetRowCount = 1; // 重置行计数
                    System.out.println("创建新Sheet: " + getSheetName(currentSheetIndex));
                }

                // 获取当前页数据
                List<Map<String, Object>> currentPageData = largeDataExcelExportMapper.fetchPageData(currentPage, PAGE_SIZE, totalRows);

                if (currentPageData.isEmpty()) {
                    break;
                }

                // 处理当前页数据到当前Sheet
                currentSheetRowCount = processPageDataWithSharedStyles(currentSheet, currentPageData, currentSheetRowCount);

                globalRowIndex += currentPageData.size();
                currentPage++;

                // 定期刷新
                if (currentPage % 10 == 0) {
                    ((SXSSFSheet) currentSheet).flushRows(PAGE_SIZE * 3);
                }

                // 进度监控
                if (currentPage % 50 == 0 || currentPage <= 5) {
                    logProgress(currentPage, currentSheetIndex, globalRowIndex, totalRows);
                }
            }

            workbook.write(out);
            out.flush();

            System.out.println("导出完成!总页数: " + currentPage +
                    ", 总Sheet数: " + (currentSheetIndex + 1) +
                    ", 总行数: " + globalRowIndex);

        } finally {
            STYLE_CACHE.clear();
            workbook.dispose();
            workbook.close();
        }
    }

    /**
     * 计算需要的Sheet总数
     */
    private int calculateTotalSheets(int totalRecords) {
        return (int) Math.ceil((double) totalRecords / MAX_ROWS_PER_SHEET);
    }

    /**
     * 创建新Sheet
     */
    private Sheet createNewSheet(SXSSFWorkbook workbook, int sheetIndex, String[] headers) {
        String sheetName = getSheetName(sheetIndex);
        Sheet sheet = workbook.createSheet(sheetName);

        // 设置默认列宽
        for (int i = 0; i < headers.length; i++) {
            sheet.setColumnWidth(i, 4000);
        }

        // 创建标题行
        createHeaderRow(sheet, headers, workbook);

        return sheet;
    }

    /**
     * 生成Sheet名称(处理Excel的名称限制)
     */
    private String getSheetName(int sheetIndex) {
        String baseName = "数据";
        String sheetName;

        if (sheetIndex == 0) {
            sheetName = baseName;
        } else {
            sheetName = baseName + (sheetIndex + 1);
        }

        // 确保名称不超过Excel限制
        if (sheetName.length() > MAX_SHEET_NAME_LENGTH) {
            sheetName = sheetName.substring(0, MAX_SHEET_NAME_LENGTH);
        }

        // 检查并处理无效字符
        sheetName = sheetName.replaceAll("[\\\\/*\\[\\]?:]", "");

        return sheetName;
    }


    private void createHeaderRow(Sheet sheet, String[] headers, Workbook workbook) {
        Row headerRow = sheet.createRow(0);
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
            cell.setCellStyle(STYLE_CACHE.get(HEADER_STYLE));
        }
    }


    /**
     * 初始化共享样式(每个工作簿只调用一次)
     */
    private void initializeSharedStyles(SXSSFWorkbook workbook) {
        // 标题样式
        CellStyle headerStyle = createHeaderStyle(workbook);

        // 日期样式
        CellStyle dateStyle = workbook.createCellStyle();
        dateStyle.setDataFormat(workbook.createDataFormat().getFormat("yyyy-mm-dd hh:mm:ss"));

        // 数字样式
        CellStyle numberStyle = workbook.createCellStyle();
        numberStyle.setDataFormat(workbook.createDataFormat().getFormat("#,##0.00"));

        // 默认样式
        CellStyle defaultStyle = workbook.createCellStyle();

        STYLE_CACHE.put(HEADER_STYLE, headerStyle);
        STYLE_CACHE.put(DATE_STYLE, dateStyle);
        STYLE_CACHE.put(NUMBER_STYLE, numberStyle);
        STYLE_CACHE.put(DEFAULT_STYLE, defaultStyle);

    }

    private CellStyle createHeaderStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setBold(true);
        font.setColor(IndexedColors.WHITE.getIndex());
        style.setFont(font);
        style.setFillForegroundColor(IndexedColors.DARK_BLUE.getIndex());
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setBorderBottom(BorderStyle.THIN);
        return style;
    }

    /**
     * 使用共享样式处理数据
     */
    private int processPageDataWithSharedStyles(Sheet sheet, List<Map<String, Object>> pageData, int startRow) {
        for (int i = 0; i < pageData.size(); i++) {
            if (startRow + i >= MAX_ROWS_PER_SHEET) break;

            Row row = sheet.createRow(startRow + i);
            Map<String, Object> record = pageData.get(i);

            int colIndex = 0;
            for (Object value : record.values()) {
                Cell cell = row.createCell(colIndex++);
                setCellValueWithSharedStyle(cell, value);
            }
        }
        return startRow + Math.min(pageData.size(), MAX_ROWS_PER_SHEET - startRow);
    }

    /**
     * 使用共享样式设置单元格值
     */
    private void setCellValueWithSharedStyle(Cell cell, Object value) {
        CellStyle defaultStyle = STYLE_CACHE.get(DEFAULT_STYLE);
        CellStyle numberStyle = STYLE_CACHE.get(NUMBER_STYLE);
        CellStyle dateStyle = STYLE_CACHE.get(DATE_STYLE);
        if (value == null) {
            cell.setCellValue("");
            cell.setCellStyle(defaultStyle);
        } else if (value instanceof Number) {
            if (value instanceof Integer || value instanceof Long) {
                cell.setCellValue(((Number) value).doubleValue());
                cell.setCellStyle(defaultStyle);
            } else if (value instanceof Double) {
                cell.setCellValue((Double) value);
                cell.setCellStyle(numberStyle); // 复用数字样式
            }
        } else if (value instanceof Date) {
            cell.setCellValue((Date) value);
            cell.setCellStyle(dateStyle); // 复用日期样式
        } else if (value instanceof Boolean) {
            cell.setCellValue((Boolean) value);
            cell.setCellStyle(defaultStyle);
        } else {
            cell.setCellValue(value.toString());
            cell.setCellStyle(defaultStyle);
        }
    }

    private void setupResponse(HttpServletResponse response, String fileName) {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        String encodedFileName;
        try {
            encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        } catch (UnsupportedEncodingException e) {
            encodedFileName = fileName;
        }
        response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName + ".xlsx");
    }

}
posted @ 2025-11-26 23:28  凛冬雪夜  阅读(5)  评论(0)    收藏  举报