/**
* 导出 Excel 数据
*
* @param sheetName 表格名称
* @param headers 表头列表
* @param fieldNames 字段名称映射
* @param data 数据列表
* @param <T> 数据类型
* @return Excel 数据字节流
* @throws IOException 异常
*/
public static <T> byte[] exportToExcel(String sheetName, List<String> headers, Map<String, String> fieldNames, List<T> data) throws IOException {
// 创建一个新的 Excel 工作簿对象(XSSFWorkbook 支持生成 .xlsx 格式的 Excel 文件)
Workbook workbook = new XSSFWorkbook();
// 创建一个新的 Sheet,指定表格名称
Sheet sheet = workbook.createSheet(sheetName);
// 创建表头行,第一行作为表头
Row headerRow = sheet.createRow(0); // 第 0 行是表头
// 将每个列头的内容放入第一行对应的单元格
for (int i = 0; i < headers.size(); i++) {
headerRow.createCell(i).setCellValue(headers.get(i)); // 创建单元格并设置列名(标题)
}
// 填充数据部分
int rowNum = 1; // 从第 1 行开始填充数据,跳过表头
for (T record : data) {
// 对于每一条记录,创建一行数据
Row row = sheet.createRow(rowNum++); // 为每一条记录创建一行
int cellNum = 0; // 每一行的单元格索引
// 遍历字段映射,将每个字段的值填充到对应的单元格
for (Map.Entry<String, String> entry : fieldNames.entrySet()) {
try {
// 获取字段名称(Map 中的 key)对应的字段对象
Field field = record.getClass().getDeclaredField(entry.getKey());
field.setAccessible(true); // 设置字段可访问,即使是私有字段也能访问
// 获取当前字段的值
Object value = field.get(record);
// 如果字段值不为空,则写入该值,否则写入空字符串
row.createCell(cellNum++).setCellValue(value != null ? value.toString() : "");
} catch (NoSuchFieldException | IllegalAccessException e) {
// 如果找不到字段或访问字段时出错,打印错误并写空白
e.printStackTrace();
row.createCell(cellNum++).setCellValue(""); // 如果字段没有找到或者获取失败,写空白
}
}
}
// 将工作簿写入字节流并返回
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
// 将工作簿内容写入字节流
workbook.write(bos);
// 返回字节数组,表示生成的 Excel 文件内容
return bos.toByteArray();
} finally {
// 确保工作簿在结束时关闭,释放资源
workbook.close();
}
}
代码注释说明:
1. 创建 Workbook
和 Sheet
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet(sheetName);
XSSFWorkbook
用于创建.xlsx
格式的工作簿,支持 Excel 2007 及以后的版本。createSheet(sheetName)
创建一个新的工作表,表格名称由sheetName
参数指定。
2. 创建表头行并填充表头数据
Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.size(); i++) {
headerRow.createCell(i).setCellValue(headers.get(i));
}
- 使用
createRow(0)
创建表头行,行号为 0。 - 遍历
headers
列表,将表头列名写入第一行的对应单元格。
3. 填充数据部分
int rowNum = 1;
for (T record : data) {
Row row = sheet.createRow(rowNum++);
int cellNum = 0;
for (Map.Entry<String, String> entry : fieldNames.entrySet()) {
Field field = record.getClass().getDeclaredField(entry.getKey());
field.setAccessible(true);
Object value = field.get(record);
row.createCell(cellNum++).setCellValue(value != null ? value.toString() : "");
}
}
- 从第 1 行开始填充数据(
rowNum = 1
),跳过表头行。 - 对每一条
record
数据:- 使用反射获取记录对象中对应字段的值。
- 如果字段值存在,写入单元格;如果为空,则写空字符串。
4. 处理异常情况
catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
row.createCell(cellNum++).setCellValue(""); // 如果字段没有找到或者获取失败,写空白
}
- 如果反射过程发生错误(如字段不存在或无权限访问),捕获异常并写入空白。
5. 写入字节流并返回
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
workbook.write(bos);
return bos.toByteArray();
}
- 使用
ByteArrayOutputStream
将工作簿内容写入内存字节流。 - 返回生成的 Excel 文件字节流。
6. 关闭资源
finally {
workbook.close(); // 确保工作簿在结束时关闭,释放资源
}
- 确保
workbook
关闭,释放资源,避免内存泄漏。
7. 如何使用这个工具类?
假设我们有一个类 IncomeBillHeaderVo
,它有不同的字段,我们可以通过工具类进行 Excel 导出。
示例使用代码:
public class IncomeBillHeaderVo {
private Long id;
private String documentCode;
private Date documentDate;
private String contactUnit;
private Integer businessType;
private String remark;
// Getters and Setters
}
// 控制层或者服务层使用
public byte[] exportIncomeBillHeaders(List<IncomeBillHeaderVo> data) throws IOException {
// 定义表头
List<String> headers = List.of("序号", "单据编号", "单据日期", "往来单位", "业务类型", "备注");
// 定义字段映射
Map<String, String> fieldNames = Map.of(
"id", "序号",
"documentCode", "单据编号",
"documentDate", "单据日期",
"contactUnit", "往来单位",
"businessType", "业务类型",
"remark", "备注"
);
// 调用工具类生成 Excel 文件的字节流
return ExcelExportUtil.exportToExcel("收款单数据", headers, fieldNames, data);
}
8. 功能总结:
- 通用性强:通过传递不同的字段和表头,能够导出各种类型的数据。
- 反射机制:使得工具类支持不同的数据类型,无需为每个不同的数据类型编写单独的导出方法。
- 字段与表头分离:字段与表头分开定义,易于维护和调整。
9. 可扩展性:
- 可以进一步扩展支持更多的 Excel 格式,如
.xls
,通过增加对HSSFWorkbook
的支持。 - 可以增加对 Excel 样式的支持(如字体、颜色、单元格格式等)。