基于hutool实现.Excel导出多级表头数据
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import org.apache.poi.ss.usermodel.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class ExcelUtils {
public static void downloadExcelMoreHeaders(List<String> topLine,List<ExcelHeader> headers,
List<HashMap<String, Object>> dataList,
HttpServletResponse response,
String fileName) throws IOException {
ExcelWriter writer = null;
try {
writer = ExcelUtil.getWriter(true);
Sheet sheet = writer.getSheet();
int startRow = 0;
if (topLine != null && !topLine.isEmpty()) {
startRow = 1;
Row topRow = sheet.createRow(0);
for (int i = 0; i < topLine.size(); i++) {
Cell cell = topRow.createCell(i);
cell.setCellValue(topLine.get(i));
CellStyle style = writer.getWorkbook().createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
cell.setCellStyle(style);
}
}
int maxLevel = getMaxHeaderLevel(headers);
java.util.concurrent.atomic.AtomicInteger colIndex = new java.util.concurrent.atomic.AtomicInteger(0);
buildHeaderRecursive(writer, sheet, headers, startRow, colIndex, startRow + maxLevel);
List<String> fieldOrder = collectAllLeafFields(headers);
List<List<Object>> dataRows = new ArrayList<>();
for (HashMap<String, Object> data : dataList) {
dataRows.add(fieldOrder.stream()
.map(field -> data.getOrDefault(field, ""))
.collect(Collectors.toList()));
}
writer.passRows(startRow + maxLevel);
writer.write(dataRows, false);
CellStyle dataStyle = createDataCellStyle(writer.getWorkbook());
for (int rowNum = startRow + maxLevel; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row row = sheet.getRow(rowNum);
if (row != null) {
for (Cell cell : row) {
if (cell != null) {
cell.setCellStyle(dataStyle);
}
}
}
}
int colCount = 0;
for (int rowNum = 0; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row row = sheet.getRow(rowNum);
if (row != null) {
colCount = Math.max(colCount, row.getLastCellNum());
}
}
colCount = colCount == 0 ? 100 : colCount;
float chineseRatio = 2.0f;
float bufferWidth = 3.0f;
int maxWidth = 50;
int[] maxColWidths = new int[colCount];
int checkRows = Math.min(sheet.getLastRowNum(), 1000);
for (int rowNum = 0; rowNum <= checkRows; rowNum++) {
Row row = sheet.getRow(rowNum);
if (row == null) continue;
int cellNum = row.getLastCellNum();
for (int j = 0; j < cellNum; j++) {
Cell cell = row.getCell(j);
if (cell == null) continue;
String text = getCellStringValue(cell);
int width = getMaxLineWidth(text, chineseRatio);
if (width > maxColWidths[j]) {
maxColWidths[j] = width;
}
}
}
for (int j = 0; j < colCount; j++) {
sheet.setColumnWidth(j, Math.min((int) (maxColWidths[j] + bufferWidth) * 256, maxWidth * 256));
}
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
response.setHeader("Content-Disposition",
"attachment;filename*=UTF-8''" +
URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20") +
".xlsx");
writer.flush(response.getOutputStream(), true);
} finally {
if (writer != null) writer.close();
try {
if (response.getOutputStream() != null) response.getOutputStream().close();
} catch (Exception ignored) {}
}
}
private static void buildHeaderRecursive(ExcelWriter writer, Sheet sheet,
List<ExcelHeader> headers,
int row, AtomicInteger colIndex, int maxLevel) {
for (ExcelHeader header : headers) {
int currentCol = colIndex.get();
List<ExcelHeader> children = header.getChildren();
if (children == null || children.isEmpty()) {
int rowSpan = maxLevel - row;
if (rowSpan > 1) {
writer.merge(row, row + rowSpan - 1, currentCol, currentCol, header.getTitle(), true);
} else {
writer.writeCellValue(currentCol, row, header.getTitle());
}
applyHeaderStyle(writer, currentCol, row);
colIndex.incrementAndGet();
} else {
int span = countLeafColumns(children);
writer.merge(row, row, currentCol, currentCol + span - 1, header.getTitle(), true);
applyHeaderStyle(writer, currentCol, row);
buildHeaderRecursive(writer, sheet, children, row + 1, colIndex, maxLevel);
}
}
}
private static int countLeafColumns(List<ExcelHeader> headers) {
int count = 0;
for (ExcelHeader h : headers) {
if (h.getChildren() == null || h.getChildren().isEmpty()) count++;
else count += countLeafColumns(h.getChildren());
}
return count;
}
private static int getMaxHeaderLevel(List<ExcelHeader> headers) {
int max = 1;
for (ExcelHeader h : headers) {
if (h.getChildren() != null && !h.getChildren().isEmpty()) {
int level = getMaxHeaderLevel(h.getChildren()) + 1;
max = Math.max(max, level);
}
}
return max;
}
private static List<String> collectAllLeafFields(List<ExcelHeader> headers) {
List<String> list = new ArrayList<>();
for (ExcelHeader h : headers) {
if (h.getChildren() == null || h.getChildren().isEmpty()) list.add(h.getFieldName());
else list.addAll(collectAllLeafFields(h.getChildren()));
}
return list;
}
private static int getRealColumnCount(Sheet sheet) {
int count = 0;
for (int i = 0; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row != null) count = Math.max(count, row.getLastCellNum());
}
return Math.max(count, 100);
}
private static int[] calculateColumnWidths(Sheet sheet, int colCount, float chineseRatio) {
int[] widths = new int[colCount];
int checkRows = Math.min(sheet.getLastRowNum(), 1000);
for (int i = 0; i <= checkRows; i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
for (int j = 0; j < colCount; j++) {
Cell cell = row.getCell(j);
if (cell == null) continue;
String text = getCellStringValue(cell);
int w = getMaxLineWidth(text, chineseRatio);
if (w > widths[j]) widths[j] = w;
}
}
return widths;
}
private static CellStyle createDataCellStyle(Workbook wb) {
CellStyle style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setWrapText(true);
return style;
}
private static void applyHeaderStyle(ExcelWriter writer, int col, int row) {
CellStyle style = writer.getWorkbook().createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
writer.setStyle(style, col, row);
}
private static String getCellStringValue(Cell cell) {
if (cell == null) return "";
switch (cell.getCellType()) {
case STRING: return cell.getStringCellValue();
case NUMERIC: return String.valueOf(cell.getNumericCellValue());
case BOOLEAN: return String.valueOf(cell.getBooleanCellValue());
default: return "";
}
}
private static int getMaxLineWidth(String text, float chineseRatio) {
if (text == null || text.isEmpty()) return 0;
int max = 0;
for (String line : text.split("\n")) {
int w = 0;
for (char c : line.toCharArray()) {
w += (Character.UnicodeBlock.of(c) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS) ? chineseRatio : 1;
}
max = Math.max(max, w);
}
return max;
}
}
配套 ExcelHeader.java
import lombok.Data;
import java.util.List;
@Data
public class ExcelHeader {
private String title;
private String fieldName;
private List<ExcelHeader> children;
}
必须依赖(pom.xml)
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.10</version>
</dependency>
调用案例
public static void export(HttpServletResponse response) throws Exception {
List<ExcelHeader> headers = new ArrayList<>();
ExcelHeader title = new ExcelHeader();
title.setTitle("学生成绩统计表");
title.setChildren(new ArrayList<>());
headers.add(title);
ExcelHeader group1 = new ExcelHeader();
group1.setTitle("学生信息");
group1.setChildren(new ArrayList<>());
title.getChildren().add(group1);
ExcelHeader group2 = new ExcelHeader();
group2.setTitle("成绩信息");
group2.setChildren(new ArrayList<>());
title.getChildren().add(group2);
group1.getChildren().add(createHeader("姓名", "name"));
group1.getChildren().add(createHeader("班级", "className"));
group2.getChildren().add(createHeader("语文", "chinese"));
group2.getChildren().add(createHeader("数学", "math"));
group2.getChildren().add(createHeader("英语", "english"));
List<HashMap<String, Object>> dataList = new ArrayList<>();
HashMap<String, Object> row1 = new HashMap<>();
row1.put("name", "张三");
row1.put("className", "高三1班");
row1.put("chinese", 125);
row1.put("math", 143);
row1.put("english", 130);
dataList.add(row1);
HashMap<String, Object> row2 = new HashMap<>();
row2.put("name", "李四");
row2.put("className", "高三1班");
row2.put("chinese", 118);
row2.put("math", 136);
row2.put("english", 127);
dataList.add(row2);
ExcelUtils.downloadExcelMoreHeaders(null,headers, dataList, response, "学生成绩表");
}
private static ExcelHeader createHeader(String title, String fieldName) {
ExcelHeader h = new ExcelHeader();
h.setTitle(title);
h.setFieldName(fieldName);
return h;
}
实现导出多sheet 合并的数据文档
一、基于downloadExcelMoreHeaders方法,创建一个包含单个Sheet的Workbook的方法
public static Workbook createSingleSheetBook(
List<String> topLine,
List<ExcelHeader> headers,
List<HashMap> dataList,
String sheetName
) {
Workbook workbook = new XSSFWorkbook();
ExcelWriter writer = new ExcelWriter(workbook, sheetName);
Sheet sheet = writer.getSheet();
int startRow = 0;
if (topLine != null && !topLine.isEmpty()) {
startRow = 1;
Row topRow = sheet.createRow(0);
for (int i = 0; i < topLine.size(); i++) {
Cell cell = topRow.createCell(i);
cell.setCellValue(topLine.get(i));
CellStyle style = writer.getWorkbook().createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
cell.setCellStyle(style);
}
}
int maxLevel = getMaxHeaderLevel(headers);
java.util.concurrent.atomic.AtomicInteger colIndex = new java.util.concurrent.atomic.AtomicInteger(0);
buildHeaderRecursive(writer, sheet, headers, startRow, colIndex, startRow + maxLevel);
List<String> fieldOrder = collectAllLeafFields(headers);
List<List<Object>> dataRows = new ArrayList<>();
for (HashMap<String, Object> data : dataList) {
dataRows.add(fieldOrder.stream()
.map(field -> data.getOrDefault(field, ""))
.collect(Collectors.toList()));
}
writer.passRows(startRow + maxLevel);
writer.write(dataRows, false);
CellStyle dataStyle = createDataCellStyle(writer.getWorkbook());
for (int rowNum = startRow + maxLevel; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row row = sheet.getRow(rowNum);
if (row != null) {
for (Cell cell : row) {
if (cell != null) {
cell.setCellStyle(dataStyle);
}
}
}
}
int colCount = 0;
for (int rowNum = 0; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row row = sheet.getRow(rowNum);
if (row != null) {
colCount = Math.max(colCount, row.getLastCellNum());
}
}
colCount = colCount == 0 ? 100 : colCount;
float chineseRatio = 2.0f;
float bufferWidth = 3.0f;
int maxWidth = 50;
int[] maxColWidths = new int[colCount];
int checkRows = Math.min(sheet.getLastRowNum(), 1000);
for (int rowNum = 0; rowNum <= checkRows; rowNum++) {
Row row = sheet.getRow(rowNum);
if (row == null) continue;
int cellNum = row.getLastCellNum();
for (int j = 0; j < cellNum; j++) {
Cell cell = row.getCell(j);
if (cell == null) continue;
String text = getCellStringValue(cell);
int width = getMaxLineWidth(text, chineseRatio);
if (width > maxColWidths[j]) {
maxColWidths[j] = width;
}
}
}
for (int j = 0; j < colCount; j++) {
sheet.setColumnWidth(j, Math.min((int) (maxColWidths[j] + bufferWidth) * 256, maxWidth * 256));
}
return writer.getWorkbook();
}
二、合并多个workbook并下载
public static void mergeAndDownload(
HttpServletResponse response,
String fileName,
Workbook... workbooks
) throws IOException {
Workbook finalWb = new XSSFWorkbook();
for (Workbook wb : workbooks) {
if (wb == null) continue;
Sheet sourceSheet = wb.getSheetAt(0);
String sheetName = sourceSheet.getSheetName();
Sheet targetSheet = finalWb.createSheet(sheetName);
for (int rowNum = 0; rowNum <= sourceSheet.getLastRowNum(); rowNum++) {
Row sourceRow = sourceSheet.getRow(rowNum);
Row targetRow = targetSheet.createRow(rowNum);
if (sourceRow == null) continue;
for (int cellNum = 0; cellNum < sourceRow.getLastCellNum(); cellNum++) {
Cell sourceCell = sourceRow.getCell(cellNum);
Cell targetCell = targetRow.createCell(cellNum);
if (sourceCell == null) continue;
switch (sourceCell.getCellType()) {
case STRING: targetCell.setCellValue(sourceCell.getStringCellValue()); break;
case NUMERIC: targetCell.setCellValue(sourceCell.getNumericCellValue()); break;
case BOOLEAN: targetCell.setCellValue(sourceCell.getBooleanCellValue()); break;
default: targetCell.setCellValue(sourceCell.toString());
}
CellStyle newStyle = finalWb.createCellStyle();
newStyle.cloneStyleFrom(sourceCell.getCellStyle());
targetCell.setCellStyle(newStyle);
}
}
for (int i = 0; i < sourceSheet.getRow(0).getLastCellNum(); i++) {
targetSheet.setColumnWidth(i, sourceSheet.getColumnWidth(i));
}
for (int i = 0; i < sourceSheet.getNumMergedRegions(); i++) {
CellRangeAddress region = sourceSheet.getMergedRegion(i);
targetSheet.addMergedRegion(region);
}
}
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
response.setHeader("Content-Disposition",
"attachment;filename*=UTF-8''" +
URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20") + ".xlsx");
finalWb.write(response.getOutputStream());
finalWb.close();
response.getOutputStream().close();
}
三、调用方式
Workbook wb1 = ExcelUtils.createSingleSheetBook(
null, headers1,
data1,
"表1"
);
Workbook wb2 = ExcelUtils.createSingleSheetBook(
null, headers2,
data2,
"表2"
);
ExcelUtils.mergeAndDownload(
response,
"多表合并",
wb1, wb2
);