目录
介绍
有时数据导入导出时,有些excel是固定好的标题数据,也就是固定的excel模板数据,此时让我们进行数据的写出,按照固定配置的标题数据进行导出excel
比如下面的样式,治理信息页面的



可以看到治理信息sheet页是固定好的表头模板,其他sheet页是集合列表形式,也是固定好表头的;此时可以使用阿里的easyExcel来简单实现excel固定模板数据的导出
引入easyExcel
引入maven依赖
java
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>
sheet页属性单值写入
数据库表和java实体类需要和excel表sheet数据进行建模对应,这里简单介绍下实体类的对应,数据库建表,service,mapper层不再介绍,博主使用的是mybatis-plus来和数据库进行查询交互

sheet页属性单值,可以看到博主这里列举的例子该sheet页的每个属性值都是单一数据,和java中的实体属性一一对应,此时使用数据写入时,excel的对应位置需要直接写入对应实体属性值进行占位,并且使用 {} 进行包裹,:

模板excel写好占位属性值后,将对应excel放入java工程目录的rescourse目录下

书写方法查询数据,然后进行数据写入
java
public void exportGovernanceReport(String orgId, HttpServletResponse response) {
QyxxStandardGovernanceReportDto dto = queryExportGovernanceReport(orgId);
if(dto == null){
log.warn("导出数据或列不存在");
return;
}
// 方法1: 使用 Spring 的 ClassPathResource 来定位文件
ClassPathResource resource = new ClassPathResource("excelTemp/governanceReport.xlsx");
try (
// 1. 读取模板到内存
ByteArrayOutputStream templateOut = new ByteArrayOutputStream();
) {
// 先加载模板
EasyExcel.write(templateOut)
.withTemplate(resource.getInputStream())
.build()
.finish();
byte[] templateBytes = templateOut.toByteArray();
try (
// 2. 基于模板进行填充
ByteArrayInputStream templateInput = new ByteArrayInputStream(templateBytes);
ByteArrayOutputStream output = new ByteArrayOutputStream()
) {
ExcelWriterBuilder writerBuilder = EasyExcel.write(output).withTemplate(templateInput);
// 构建 writer 和 sheet
ExcelWriter writer = writerBuilder.build();
WriteSheet sheet = EasyExcel.writerSheet("治理信息").build();
// ✅ EasyExcel 4.x 正确填充方式:直接传 dto 对象
// 前提:Excel 模板中写的是 {orgFullName},而不是 {data.orgFullName}
writer.fill(dto, sheet);
writer.finish();
// 3. 输出到 response
byte[] finalBytes = output.toByteArray();
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("治理报告", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
response.setContentLength(finalBytes.length);
try (ServletOutputStream servletOutputStream = response.getOutputStream()) {
servletOutputStream.write(finalBytes);
servletOutputStream.flush();
}
} catch (IOException e) {
log.error("导出失败:写入响应流异常", e);
throw new ApiException("导出失败:请联系管理员");
}
} catch (Exception e) {
log.error("导出治理报告失败: {}", CommonUtil.analysisExceptionMessage(e), e);
throw new ApiException("导出失败:请联系管理员");
}
}
注意这里的方法,这里是博主的一个查询方法,这里可以忽略,因为无论什么样的场景这里都是会有查询数据的地方,这里在用时可以灵活实现自己的逻辑,反正就是到这一步时,dto里就已经查询到数据了,查询到数据后,下面的步骤才进入到写入的逻辑

打个断点看下这里查询的数据


可以看到dto这里已经查询到数据了
后续进行写入
先拿取放在rescourse目录下的excle模板文件


使用apipost测试运行


可以看到都出的excel文件,指定占位的地方已被替换为对应数据的值
sheet页列表数据写入
前面介绍的是sheet页的单值属性写入,在有的数据里是以列表形式的数据绑定,一行就是一条数据,
比如下面的示例


此时的数据绑定有两种方法可以进行数据写入
数据占位写入
第一种写法还是按照{} 数据占位的写法,但是这里的占位写法需要调整下
还是dto实体里,要定义好列表sheet页的实体属性集合


高管信息的就不再展示,同理,excel里占位 需要加上 前缀进行占位 {xx.属性值}

下面上代码
java
QyxxStandardGovernanceReportDto dto = queryExportGovernanceReport(orgId);
if(dto == null){
log.warn("导出数据或列不存在");
return;
}
// 方法1: 使用 Spring 的 ClassPathResource 来定位文件
ClassPathResource resource = new ClassPathResource("excelTemp/governanceReport.xlsx");
try (
// 1. 读取模板到内存
ByteArrayOutputStream templateOut = new ByteArrayOutputStream();
) {
// 先加载模板
EasyExcel.write(templateOut)
.withTemplate(resource.getInputStream())
.build()
.finish();
byte[] templateBytes = templateOut.toByteArray();
try (
// 2. 基于模板进行填充
ByteArrayInputStream templateInput = new ByteArrayInputStream(templateBytes);
ByteArrayOutputStream output = new ByteArrayOutputStream()
) {
ExcelWriterBuilder writerBuilder = EasyExcel.write(output).withTemplate(templateInput);
// 构建 writer 和 sheet
ExcelWriter writer = writerBuilder.build();
WriteSheet sheet = EasyExcel.writerSheet("治理信息").build();
// ✅ EasyExcel 4.x 正确填充方式:直接传 dto 对象
// 前提:Excel 模板中写的是 {orgFullName},而不是 {data.orgFullName}
writer.fill(dto, sheet);
Map<String, List<?>> dataMap = new HashMap<>();
dataMap.put("席位归属分布", dto.getBoardSeatDistributions());
dataMap.put("董事信息", dto.getDirectorInfos());
// 后续还有其他多个sheet页,还可以接着添加
// ========== 循环填充每个 sheet ==========
FillConfig verticalConfig = FillConfig.builder().direction(WriteDirectionEnum.VERTICAL).build();
for (Map.Entry<String, List<?>> entry : dataMap.entrySet()) {
String sheetName = entry.getKey();
List<?> dataList = entry.getValue();
if (CollectionUtils.isEmpty(dataList)) {
log.warn("Sheet [{}] 数据为空,跳过填充", sheetName);
continue;
}
WriteSheet sheetNameSheet = EasyExcel.writerSheet(sheetName).build();
// 使用 FillWrapper,统一用 "data" 作为占位符前缀
writer.fill(new FillWrapper("data", dataList), verticalConfig, sheetNameSheet);
}
// 3. 输出到 response
byte[] finalBytes = output.toByteArray();
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("治理报告", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
response.setContentLength(finalBytes.length);
try (ServletOutputStream servletOutputStream = response.getOutputStream()) {
servletOutputStream.write(finalBytes);
servletOutputStream.flush();
}
} catch (IOException e) {
log.error("导出失败:写入响应流异常", e);
throw new ApiException("导出失败:请联系管理员");
}
} catch (Exception e) {
log.error("导出治理报告失败: {}", CommonUtil.analysisExceptionMessage(e), e);
throw new ApiException("导出失败:请联系管理员");
}
}
这里由于是拿取对应名字的sheet页,所以将sheet页名字作为map的key,对应sheet页要传入的集合数据作为map的value,循环map进行写入excel数据

测试接口查看文件:


可以看到数据已经渲染上
非占位写入
excel模板里如果不想写入占位符进行占位绑定,需要在java对应实体类中使用
@ExcelProperty("对应excel列名")

代码调整:
java
public void exportGovernanceReport(String orgId, HttpServletResponse response) {
QyxxStandardGovernanceReportDto dto = queryExportGovernanceReport(orgId);
if(dto == null){
log.warn("导出数据或列不存在");
return;
}
// 方法1: 使用 Spring 的 ClassPathResource 来定位文件
ClassPathResource resource = new ClassPathResource("excelTemp/governanceReports.xlsx");
System.out.println(resource.getFilename());
System.out.println(resource.exists());
System.out.println(resource.getPath());
try (
// 1. 读取模板到内存
ByteArrayOutputStream templateOut = new ByteArrayOutputStream();
) {
// 先加载模板
EasyExcel.write(templateOut)
.withTemplate(resource.getInputStream())
.build()
.finish();
byte[] templateBytes = templateOut.toByteArray();
try (
// 2. 基于模板进行填充
ByteArrayInputStream templateInput = new ByteArrayInputStream(templateBytes);
ByteArrayOutputStream output = new ByteArrayOutputStream()
) {
ExcelWriterBuilder writerBuilder = EasyExcel.write(output).withTemplate(templateInput);
// 构建 writer 和 sheet
ExcelWriter writer = writerBuilder.build();
WriteSheet sheet = EasyExcel.writerSheet("治理信息").build();
// ✅ EasyExcel 4.x 正确填充方式:直接传 dto 对象
// 前提:Excel 模板中写的是 {orgFullName},而不是 {data.orgFullName}
writer.fill(dto, sheet);
//// ========== ✅ Sheet2: 席位归属分布(集合数据)==========
if (CollectionUtils.isNotEmpty(dto.getBoardSeatDistributions())) {
WriteSheet sheet2 = EasyExcel.writerSheet("席位归属分布")
.build();
writer.write(dto.getBoardSeatDistributions(), sheet2);
} else {
System.out.println("【警告】boardSeatDistributions 为空");
}
// ========== ✅ Sheet2: 席位归属分布(集合数据)==========
writer.finish();
// 3. 输出到 response
byte[] finalBytes = output.toByteArray();
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("治理报告", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
response.setContentLength(finalBytes.length);
try (ServletOutputStream servletOutputStream = response.getOutputStream()) {
servletOutputStream.write(finalBytes);
servletOutputStream.flush();
}
} catch (IOException e) {
log.error("导出失败:写入响应流异常", e);
throw new ApiException("导出失败:请联系管理员");
}
} catch (Exception e) {
log.error("导出治理报告失败: {}", CommonUtil.analysisExceptionMessage(e), e);
throw new ApiException("导出失败:请联系管理员");
}
}

这里传入对应的实体数据集合
查看结果

可以看到数据正确渲染,但是有一个问题,就是渲染了多余的数据,实体字段里没有配置@ExcelProperty注解的字段数据也被渲染了,此时如果哪些字段不想被渲染需要加上@ExcelIgnore
即可
