Java导出写入固定Excel模板数据
目录
介绍
引入easyExcel
sheet页属性单值写入
sheet页列表数据写入
数据占位写入
非占位写入
介绍
有时数据导入导出时,有些excel是固定好的标题数据,也就是固定的excel模板数据,此时让我们进行数据的写出,按照固定配置的标题数据进行导出excel
比如下面的样式,治理信息页面的
可以看到治理信息sheet页是固定好的表头模板,其他sheet页是集合列表形式,也是固定好表头的;此时可以使用阿里的easyExcel来简单实现excel固定模板数据的导出
引入easyExcel
引入maven依赖
<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目录下
书写方法查询数据,然后进行数据写入
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 和 sheetExcelWriter writer = writerBuilder.build();WriteSheet sheet = EasyExcel.writerSheet("治理信息").build();// ✅ EasyExcel 4.x 正确填充方式:直接传 dto 对象// 前提:Excel 模板中写的是 {orgFullName},而不是 {data.orgFullName}writer.fill(dto, sheet);writer.finish();// 3. 输出到 responsebyte[] 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.属性值}
下面上代码
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 和 sheetExcelWriter 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. 输出到 responsebyte[] 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列名")
代码调整:
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 和 sheetExcelWriter 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. 输出到 responsebyte[] 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
即可