Java导出写入固定Excel模板数据

目录

介绍

引入easyExcel

sheet页属性单值写入

sheet页列表数据写入

数据占位写入

非占位写入


介绍

有时数据导入导出时,有些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

即可

相关推荐
FQNmxDG4S13 小时前
Java多线程编程:Thread与Runnable的并发控制
java·开发语言
虹科网络安全14 小时前
艾体宝干货|数据复制详解:类型、原理与适用场景
java·开发语言·数据库
axng pmje14 小时前
Java语法进阶
java·开发语言·jvm
rKWP8gKv715 小时前
Java微服务性能监控:Prometheus与Grafana集成方案
java·微服务·prometheus
老前端的功夫15 小时前
【Java从入门到入土】28:Stream API:告别for循环的新时代
java·开发语言·python
qq_4352879215 小时前
第9章 夸父逐日与后羿射日:死循环与进程终止?十个太阳同时值班的并行冲突
java·开发语言·git·死循环·进程终止·并行冲突·夸父逐日
小江的记录本15 小时前
【Kafka核心】架构模型:Producer、Broker、Consumer、Consumer Group、Topic、Partition、Replica
java·数据库·分布式·后端·搜索引擎·架构·kafka
yaoxin52112315 小时前
397. Java 文件操作基础 - 创建常规文件与临时文件
java·开发语言·python
极客先躯17 小时前
高级java每日一道面试题-2025年11月24日-容器与虚拟化题[Dockerj]-runc 的作用是什么?
java·oci 的命令行工具·最小可用·无守护进程·完全标准·创建容器的核心流程·runc 核心职责思维导图
用户606487671889617 小时前
AI 抢不走的技能:用 Claude API 构建自动化工作流实战
java