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);
}
}