poi 下载接口demo

1. 场景

最后的效果

2. 接口设计

uri:

bash 复制代码
http://localhost:8080/admin-api/external/indicator/query/exportExcel

POST请求

入参:

bash 复制代码
{
    "type":"日",
    "indicators":["中国煤炭指数5500大卡","中国煤炭指数5000大卡","中国煤炭指数4500大卡","CCI3800进口"],
    "beginTime":"2026-03-30",
    "endTime":"2026-04-30"
}

3. controller

controller 代码

java 复制代码
    @Operation(summary = "指标列表-导出")
    @PostMapping("/exportExcel")
    public void exportExcel(@Validated HttpServletResponse response, @RequestBody IndicatorDataQuery query) throws IOException {
        externalIndicatorQueryService.exportExcel(response, query);
    }

4. service

数据查询逻辑 ExternalIndicatorQueryListUtil ,略。

java 复制代码
@Override
    public void exportExcel(HttpServletResponse response, IndicatorDataQuery query) {
        ExternalIndicatorQueryListUtil util = new ExternalIndicatorQueryListUtil(externalIndicatorListMapper, query);
        List<IndicatorDataDTO> list = util.handle();
        ExternalIndicatorQueryListExcelUtil excelUtil = new ExternalIndicatorQueryListExcelUtil(query,list,response);
        excelUtil.exportExcel();
    }
ExternalIndicatorQueryListExcelUtil

excel 数据处理逻辑

java 复制代码
import com.xxx.ics.module.system.controller.externaldata.onpage.query.IndicatorDataQuery;
import com.xxx.ics.module.system.controller.externaldata.onpage.vo.IndicatorDTO;
import com.xxx.ics.module.system.controller.externaldata.onpage.vo.IndicatorDataDTO;
import org.apache.poi.ss.util.CellRangeAddress;

import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author leiming5
 * @description ExternalIndicatorQueryListExcelUtil
 * @date 2026/4/29-14:03
 * Copyright 2026 xxx SSG West Region Service Solution Group
 */
public class ExternalIndicatorQueryListExcelUtil {
    private IndicatorDataQuery query;
    private List<IndicatorDataDTO> list;
    private HttpServletResponse response;

    private static final BigDecimal HUNDRED = new BigDecimal("100");
    private static final int SCALE_TWO = 2;
    private static final String PERCENT_SYMBOL = "%";

    public ExternalIndicatorQueryListExcelUtil(IndicatorDataQuery query, List<IndicatorDataDTO> list, HttpServletResponse response) {
        this.query = query;
        this.list = list;
        this.response = response;
    }

    public void exportExcel() {
        String fileName = "考核指标查询列表";
        List<List<String>> excelHead = getExcelHead();
        List<CellRangeAddress> mergeList = getMergeList();
        List<List<Object>> excelData = getExcelData();
        ExcelUtil.download(response, fileName, excelHead, excelData, mergeList);
    }

    private List<List<String>> getExcelHead() {
        List<List<String>> headList = new ArrayList<>();
        List<String> head1 = generateHeader1();
        headList.add(head1);
        List<String> head2 = generateHeader2();
        headList.add(head2);
        return headList;
    }

    private List<String> generateHeader1() {
        List<String> header1 = new ArrayList<>();
        header1.add("日期");
        for (int i = 0; i < query.getIndicators().size(); i++) {
            header1.add(query.getIndicators().get(i));
            header1.add("");
            header1.add("");
        }
        header1.add("更新时间");
        return header1;
    }

    private List<String> generateHeader2() {
        List<String> header1 = new ArrayList<>();
        header1.add("日期");
        for (int i = 0; i < query.getIndicators().size(); i++) {
            header1.add("当期");
            header1.add("同期");
            header1.add("同比");
        }
        header1.add("更新时间");
        return header1;
    }

    private List<CellRangeAddress> getMergeList() {
        List<CellRangeAddress> mergeList = new ArrayList<>();
        CellRangeAddress merge1 = new CellRangeAddress(0, 1, 0, 0);
        mergeList.add(merge1);
        for (int i = 1; i <= query.getIndicators().size(); i++) {
            CellRangeAddress merge2 = new CellRangeAddress(0, 0, i * 3 - 2, i * 3);
            mergeList.add(merge2);
        }
        Integer lastIndex = query.getIndicators().size() * 3 + 1;
        CellRangeAddress merge3 = new CellRangeAddress(0, 1, lastIndex, lastIndex);
        mergeList.add(merge3);
        return mergeList;
    }

    private List<List<Object>> getExcelData() {
        List<List<Object>> dataList = new ArrayList<>();
        for (IndicatorDataDTO data : list) {
            dataList.add(generateRowData(data));
        }
        return dataList;
    }

    private List<Object> generateRowData(IndicatorDataDTO data) {
        List<Object> rowData = new ArrayList<>();
        rowData.add(data.getDateTime());
        for (IndicatorDTO indicatorDTO : data.getIndicators()) {
            Double value = indicatorDTO.getValue();
            rowData.add(value);

            Double lastYearValue = indicatorDTO.getLastYearValue();
            rowData.add(lastYearValue);

            if (Objects.nonNull(value) && Objects.nonNull(lastYearValue)) {
                BigDecimal percent = indicatorDTO.getYearOnYear().multiply(HUNDRED).setScale(SCALE_TWO, RoundingMode.HALF_UP);
                rowData.add(percent + PERCENT_SYMBOL);
            } else {
                rowData.add("");
            }
        }
        return rowData;
    }

}
ExcelUtil

excel sheet 文件生成,单元格合并

java 复制代码
import com.xxx.ics.module.system.controller.cockpit.leader.util.DateUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * @author leiming5
 * @description ExcelUtil
 * @date 2026/4/29-13:58
 * Copyright 2026 xxx SSG West Region Service Solution Group
 */

@Component
public class ExcelUtil {

    /**
     * 解析动态表头,使用此方法
     *
     * @param response  响应
     * @param sheetName 名称
     * @param excelHead 表头
     * @param excelData 数据
     * @author: leiming5
     */
    public static void download(HttpServletResponse response, String sheetName, List<List<String>> excelHead, List<List<Object>> excelData, List<CellRangeAddress> mergeList) {

        // 1. 定义原始中文文件名(你的业务文件名)
        String originalFileName = String.format("%s-%s.xlsx", sheetName, DateUtil.formatDate2(new Date()));

        // 2. 核心:对文件名进行 URL 编码(解决中文乱码/非法字符问题)
        String encodeFileName = null;
        try {
            encodeFileName = URLEncoder.encode(originalFileName, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }

        // 3. 设置下载响应头(编码后的文件名)
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 关键:使用编码后的文件名设置 Content-Disposition
        response.setHeader("Content-Disposition", "attachment;filename=" + encodeFileName);

        try (OutputStream out = response.getOutputStream()) {

            Workbook workbook = new XSSFWorkbook();
            Sheet sheet = workbook.createSheet(sheetName);
            ExcelGenerator generator = new ExcelGenerator(sheet, workbook, excelHead, excelData);
            generator.generate();
            mergeListBy(mergeList, sheet);
            workbook.write(out);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void mergeListBy(List<CellRangeAddress> mergeList, Sheet sheet) {
        if (CollectionUtils.isEmpty(mergeList)) {
            return;
        }
        for (CellRangeAddress cellRangeAddress : mergeList) {
            sheet.addMergedRegion(cellRangeAddress);
        }
    }

    private List<String> getProductType() {
        return Arrays.asList("A", "B", "C");
    }

    public static void exportExcel(HttpServletResponse response,
                                   List<List<String>> excelData,
                                   String sheetName,
                                   String fileName,
                                   int columnWidth) throws IOException {

        //声明一个工作簿
        SXSSFWorkbook workbook = new SXSSFWorkbook();

        //生成一个表格,设置表格名称
        Sheet sheet = workbook.createSheet(sheetName);

        //设置表格列宽度
        sheet.setDefaultColumnWidth(columnWidth);

        //写入List<List<String>>中的数据
        int rowIndex = 0;
        for (List<String> data : excelData) {
            //创建一个row行,然后自增1
            Row row = sheet.createRow(rowIndex++);

            //遍历添加本行数据
            for (int i = 0; i < data.size(); i++) {
                //创建一个单元格
                Cell cell = row.createCell(i);

                //创建一个内容对象
                XSSFRichTextString text = new XSSFRichTextString(String.valueOf(data.get(i)));

                //将内容对象的文字内容写入到单元格中
                cell.setCellValue(text);
            }
        }

        //准备将Excel的输出流通过response输出到页面下载
        //八进制输出流
        response.setContentType("application/octet-stream");

        //设置导出Excel的名称
        response.setHeader("Content-disposition", "attachment;filename=" + fileName);

        //刷新缓冲
        response.flushBuffer();

        //workbook将Excel写入到response的输出流中,供页面下载该Excel文件
        workbook.write(response.getOutputStream());

        //关闭workbook
        workbook.close();
    }

    public static int lineNumberByIndex(int i) {
        return i + 2;
    }
}
ExcelGenerator:

向 excel 中写入数据

java 复制代码
import com.xxx.ics.framework.common.util.date.DateUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;

import java.util.List;
import java.util.Objects;

/**
 * @author leiming5
 * @description ExcelGenerator
 * @date 2026/4/29-13:59
 * Copyright 2026 xxx SSG West Region Service Solution Group
 */

public class ExcelGenerator {

    // import
    private Workbook workbook;
    private Sheet sheet;
    private List<List<String>> excelHead;
    private List<List<Object>> excelData;

    // local
    private CellStyle bodyStyleLockAndFormat;
    private CellStyle bodyStyleUnLockAndFormat;
    private Font bodyBlackFont;


    public ExcelGenerator(Sheet sheet, Workbook workbook, List<List<String>> excelHead, List<List<Object>> excelData) {
        this.sheet = sheet;
        this.workbook = workbook;

        this.excelHead = excelHead;
        this.excelData = excelData;

    }

    public void generate() {
        // 生成表格
        createExcel();
    }

    /**
     * 1. 生产表头
     * 2. 生产数据
     * 3. 生产下拉框
     * 4. 设置样式
     *
     * @author: leiming5
     */
    public void createExcel() {
        setCommonStyle();
        createHeader();
        createBody();
    }

    private void createHeader() {
        for (int i = 0; i < excelHead.size(); i++) {
            Row row = sheet.createRow(i);
            List<String> list = excelHead.get(i);
            for (int j = 0; j < list.size(); j++) {
                Cell cell = row.createCell(j);
                cell.setCellValue(list.get(j));
                cell.setCellStyle(getHeadCellStyle(workbook, list.get(j), i));

            }
        }
    }

    private void setCommonStyle() {
        setSheetColumnWidth();
        createDefaultBodyCellStyle();
    }

    private void setSheetColumnWidth() {
        List<String> header = excelHead.get(0);
        for (int i = 0; i < header.size(); i++) {
            sheet.setColumnWidth(i, 12 * 256);
        }
    }


    private void createDefaultBodyCellStyle() {
        bodyBlackFont = getBodyBlackFont();
        bodyStyleLockAndFormat = getCellStyle(bodyBlackFont);
        bodyStyleLockAndFormat.setDataFormat(workbook.createDataFormat().getFormat("0.00%"));
        bodyStyleUnLockAndFormat = getCellStyle(bodyBlackFont);
    }

    private Font getBodyBlackFont() {
        Font font = workbook.createFont();
        font.setBold(false);
        short colorIndex = HSSFColor.HSSFColorPredefined.BLACK.getIndex();
        font.setColor(colorIndex);
        font.setFontName("Calibri");
        font.setFontHeightInPoints((short) 12);
        return font;
    }

    private Font getBodyRedFont() {
        Font font = workbook.createFont();
        font.setBold(false);
        short colorIndex = HSSFColor.HSSFColorPredefined.RED.getIndex();
        font.setColor(colorIndex);
        font.setFontName("Calibri");
        font.setFontHeightInPoints((short) 12);
        return font;
    }

    private void createBody() {
        for (int i = 0; i < excelData.size(); i++) {

            int rowNumber = i + excelHead.size();

            Row row = sheet.createRow(rowNumber);
            List<Object> list = excelData.get(i);

            setRowDataOfList(row, list);
        }
    }

    private void setRowDataOfList(Row row, List<Object> list) {
        for (int columnIndex = 0; columnIndex < list.size(); columnIndex++) {

            Cell cell = row.createCell(columnIndex);
            Object o = list.get(columnIndex);

            String str = Objects.isNull(o) ? "" : String.valueOf(o);

            if (isNumeric(str)) {
                cell.setCellValue(Double.parseDouble(str));
            } else {
                cell.setCellValue(str);
            }
            boolean isShowPercent = false;
            cell.setCellStyle(getBodyCellStyle(isShowPercent, str));
        }
    }

    /**
     * 获取日期格式单元格的样式
     *
     * @return 单元格样式
     * @author leiming5
     */
    private CellStyle getDateCellStyle(Workbook workbook) {
        CellStyle CellStyleDate = workbook.createCellStyle();
        DataFormat df = workbook.createDataFormat();
        CellStyleDate.setDataFormat(df.getFormat(DateUtils.FORMAT_YEAR_MONTH_DAY));
        // 背景色的设定 自动换行
        CellStyleDate.setWrapText(true);
        // 设置字体
        Font font = workbook.createFont();
        font.setBold(true);
        font.setColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex());
        CellStyleDate.setFont(font);
        // 水平居中,垂直居中
        CellStyleDate.setAlignment(HorizontalAlignment.CENTER);
        CellStyleDate.setVerticalAlignment(VerticalAlignment.CENTER);
        CellStyleDate.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex());
        CellStyleDate.setFillPattern(FillPatternType.SOLID_FOREGROUND);

        return CellStyleDate;
    }

    /**
     * 判断表格数据类型
     *
     * @param str 原始数据
     * @return true:数字类型,false:字符类型
     * @author: leiming5
     */
    public static boolean isNumeric(String str) {

        if (StringUtils.isEmpty(str)) {
            return false;
        }

        try {
            Double.valueOf(str);
        } catch (NumberFormatException e) {
            return false;
        }

        return true;
    }

    /**
     * 设置单元格样式
     *
     * @return 单元格样式
     * @author: leiming5
     */
    private CellStyle getBodyCellStyle(boolean isShowPercent, String value) {
        if (isShowPercent) {
            return bodyStyleLockAndFormat;
        }
        return bodyStyleUnLockAndFormat;
    }

    private CellStyle getCellStyle(Font font) {
        CellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setWrapText(true);
        cellStyle.setFont(font);

        // 水平居中,垂直居中
        cellStyle.setAlignment(HorizontalAlignment.LEFT);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        return cellStyle;
    }

    public static CellStyle getHeadCellStyle(Workbook workbook, String value, int rowNumber) {
        // Sheet样式
        CellStyle cellStyle = workbook.createCellStyle();
        // 背景色的设定 自动换行
        cellStyle.setWrapText(true);

        // 设置字体
        Font font = workbook.createFont();
        font.setFontName("Calibri");
        font.setFontHeightInPoints((short) 12);
        font.setBold(true);
        font.setColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex());
        cellStyle.setFont(font);

        // 水平居中,垂直居中
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        cellStyle.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex());
        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

        drawBorderColor(cellStyle, IndexedColors.BLACK.getIndex());
        return cellStyle;
    }

    public static void drawBorderColor(CellStyle cellStyle, short borderColor) {
        //边框
        cellStyle.setBorderBottom(BorderStyle.THIN);
        cellStyle.setBottomBorderColor(borderColor);
        cellStyle.setBorderLeft(BorderStyle.THIN);
        cellStyle.setLeftBorderColor(borderColor);
        cellStyle.setBorderRight(BorderStyle.THIN);
        cellStyle.setRightBorderColor(borderColor);
        cellStyle.setBorderTop(BorderStyle.THIN);
        cellStyle.setTopBorderColor(borderColor);
    }
}