【2024】通过EasyExcel实现不定长Excel表头导出、以及多sheet页和单元格合并效果

news2025/1/17 15:36:30

目录💻

  • 一、介绍
    • 需求背景
  • 二、实现步骤
    • 1、依赖
      • 1.1、Maven
      • 1.2、Gradle
    • 2、实体类
      • 2.1、学生分数表
      • 2.2、Sheet 工作表对象
    • 3、excel工具类
    • 4、Service层
      • 接口
      • 实现类
    • 5、Controller层
  • 三、测试效果

)

一、介绍

EasyExcel是阿里巴巴开源的一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。

需求背景

最近我们公司需要实现报表的导出功能需要实现每个产品根据用户选择的时间导出不同长度的时间效果,所以再导出的时候除了需要固定的每个产品的列,还需要根据选择的时间导出不确定时间的列。下面是我根据公司的产品导出报表模拟的数据导出的效果图!

先看看需要实现的效果:

  • 创建了多个sheet,这个地方我共用的一个header表头
  • header表头实现合并
  • 内容实现合并单元格
  • 根据内容实现不定长Excel表头和内容导出

在这里插入图片描述

  • sheet页面实现和sheet共用一个表头,但内容单独写入

在这里插入图片描述

数据库的内容原始格式(下面有实体类)
在这里插入图片描述

二、实现步骤

代码基本上实现了把功能代码都拆分出来了,只需要根据自己的业务情况实现部分修改即可套用

1、依赖

1.1、Maven

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.1</version>
</dependency>

1.2、Gradle

implementation("com.alibaba:easyexcel:3.1.1")

2、实体类

2.1、学生分数表

项目业务实际用到的表

/**
 * 学生分数表
 */
@Data
public class Score {
    /**姓名*/
    private String name;
    /**期中语文分数*/
    private Integer monthLanguage;
    /**期中数学分数*/
    private Integer monthMathematics;
    /**期中英语分数*/
    private Integer monthEnglish;
    /**期末语文分数*/
    private Integer finalLanguage;
    /**期末数学分数*/
    private Integer finalMathematics;
    /**期末英语分数*/
    private Integer finalEnglish;
    /**学期*/
    private String semester;
}

2.2、Sheet 工作表对象

一个工作表一个该对象,主要用来拆分每个Sheet 的表头数据,名称会不一样,
我们再实现导出的时候表头和内容的长度需要一致,这样才可以去对应上每一个单元格

一个List<List<String>> 里面的一个list代表一行数据,String代表每一个单元格的数据

@Data
public class Sheet {


    /** 文件名称 */
    String name;

    /** 表头列表 */
    List<List<String>> headerList;

    /** 内容列表 */
    List<List<String>> dataList;

    /** 单元格合并行数 */
    Integer eachRow;
}

3、excel工具类

该类主要是把导出的单独拆分出来,并且拆分出两个方法,一个是处理response。 一个处理ExcelWriter
客户端对象,然后通过我们传入的List<Sheet>去单独处理自己的WriteSheet 对象参数,我这里因为不同的sheet只有合并的单元格不同,和名字不同,如果自己有其他的,也可以往Sheet对象里添加属性去用于单独配置

public class ExcelUtil {

    /**
     * 导出Excel
     *
     * @param response
     * @param name
     * @param list
     */
    public static void exportExcel(
            HttpServletResponse response, String name, List<Sheet> list) {

        try {
            setExcelResponseProp(response, name);
            ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
            // 定义列头的样式:居中对齐
            WriteCellStyle headWriteCellStyle = new WriteCellStyle();
            headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
            headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

            // 定义内容的样式:居中对齐
            WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
            contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
            contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

            for (Sheet sheet : list) {

                WriteSheet writeSheet =
                        EasyExcel.writerSheet(sheet.getName())
                                .head(sheet.getHeaderList())
                                .registerWriteHandler(new LoopMergeStrategy(sheet.getEachRow(), 0)) // 设置第一列每3行合并
                                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 自定义行高
                                .registerWriteHandler(new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle)) //居中对其
                                .build();

                excelWriter.write(sheet.getDataList(), writeSheet);
            }
            // 注意关闭excelWriter,释放资源
            excelWriter.finish();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 设置响应结果
     *
     * @param response 响应结果对象
     * @param rawFileName 文件名
     * @throws UnsupportedEncodingException 不支持编码异常
     */
    private static void setExcelResponseProp(HttpServletResponse response, String rawFileName)
            throws UnsupportedEncodingException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = java.net.URLEncoder.encode(rawFileName, "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    }
}

4、Service层

接口

public interface ScoreService {
    List<Sheet> exportExcel();
}

实现类

该实现类是重点,实现怎么把数据库类转为对应的用于写入sheet的集合的

步骤解析

  1. 先转换可变从表头的数据,总共多少行表头: 第1学期…
    在这里插入图片描述
  2. 添加header列表:添加固定的表头和可变长的,如果是单表头,只需要写一个到集合就行,如果是合并的二级表头,则添加多个,内部会按照下标去取,如果相同的会自动合并
    在这里插入图片描述格式会是下面这样 在这里插入图片描述
  3. 添加sheet1内容:取出来的内容会是数据库按照学生每次考试成绩来存放的
  • 得到数据后先按照学生分组放入map

  • 再根据学生去循环取出每次考试的成绩

  • 引用了getSubjects方法,因为我们数据是不同科目存放到一行数据的,现在做展现需要把一行数据的不同学科加到不同的行去。需要需要循环,去取不同的字段,这里通过了转map再通过拼接字段名去取

  • 这个地方需要注意的是第一次循环肯定要是用作放入每行数据的,我们添加数据的时候需要以行来作为一个集合添加数据

    展现的数据在这里插入图片描述
    一条数据长度需要对应表头的长度,因为底层是通过下标去写入到每个单元格在这里插入图片描述

  1. 添加sheet2内容:和1一样的,也是添加数据,最终把数据添加到集合,长度也需要和对应的表头一致,一一对应上。
    在这里插入图片描述
  2. 把两个sheet的数据添加到集合返回,确认自己每个合并的单元格是多少,这个地方合并的需要一致
  • 完整代码

@Service
public class ScoreServiceImpl implements ScoreService {

    @Override
    public List<Sheet> exportExcel() {
        List<Sheet> sheetList = new ArrayList<>();

//        1、先转换可变从表头的数据,总共多少行表头: 第1学期.....
        //        获取统计日期(可变长字段)
        List<String> dateList = new ArrayList<>();
        for (int i = 1; i <= 6; i++) {
            String date = String.format("第%s学期", i);
            dateList.add(date);
        }

//        2、添加header列表

        //        添加表头列表
//        固定表头
        List<List<String>> headerList = new ArrayList<>();
        headerList.add(new ArrayList<>(Arrays.asList("姓名")));
        headerList.add(new ArrayList<>(Arrays.asList("科目")));
//        可变长的表头,通过循环添加到集合中,如果是拆分的二级表头,则需要再集合中添加多个数据 Arrays.asList(date, "期中考试")
        dateList.forEach(date -> {
            headerList.add(new ArrayList<>(Arrays.asList(date, "期中考试")));
            headerList.add(new ArrayList<>(Arrays.asList(date, "期末考试")));
        });


//        3. 添加sheet1内容:取出来的内容会是数据库按照学生每次考试成绩来存放的
        //数据列表
        List<Score> list = getData();

        Map<String, List<Score>> listMap = //以姓名分组
                list.stream().collect(Collectors.groupingBy(Score::getName));

        List<List<String>> sheetDate = new ArrayList<>();
        for (Map.Entry<String, List<Score>> e : listMap.entrySet()) {
            String trolleyName = e.getKey();
            List<Score> v = e.getValue();
            for (Map.Entry<String, String> entry : getSubjects().entrySet()) { //循环科目,看总共有多少个科目
                //                添加姓名和科目
                List<String> dataList = new ArrayList<>();
                dataList.add(trolleyName);
                String key = entry.getKey();
                String value = entry.getValue();
                dataList.add(value);

                //                添加每次学期数据
                for (int i = 0; i < dateList.size(); i++) {
                    String startDate = dateList.get(i);
                    //做排序,以总分成绩做key,数据做value
                    Map<String, Score> dayMap =
                            v.stream()
                                    .collect(
                                            Collectors.toMap(
                                                    k-> dateList.get(Integer.parseInt(k.getSemester())-1),
                                                    p -> p,
                                                    (s, a) -> a));
                    String month = "";
                    String finals = "";

                    if (dayMap.containsKey(startDate)) { //判断,如果没数据,则添加空数据,避免后面的数据写错位
                        Score item = dayMap.get(startDate);
                        Map<String, Object> trolleyMap = BeanUtil.beanToMap(item); //通过名称去循环写入
                        month = trolleyMap.get("month" + key).toString();
                        finals = trolleyMap.get("final" + key).toString();
                    }
                    dataList.add(month);
                    dataList.add(finals);
                }
                sheetDate.add(dataList);
            }
        }



//          4. 添加sheet2内容
        //        取出每个学期的数据去做分组
        Map<String, List<Score>> dayMap =
                list.stream()
                        .collect(
                                Collectors.groupingBy(
                                        k-> dateList.get(Integer.parseInt(k.getSemester())-1)));
        List<List<String>> sheetDate2 = new ArrayList<>(); //一个sheet页面
        for (int i = 1; i <= 10; i++) { //循环10次,取前10名
            List<String> lineList = new ArrayList<>(); //一行数据
            lineList.add("总分排名");
            lineList.add(String.valueOf(i));

            for (String date : dateList) { //每个学期

                String month = "", finals = "";
                if (dayMap.containsKey(date)) { //每次考试
                    List<Score> monthList = dayMap.get(date).stream()
                            .sorted(Comparator.comparing(p -> p.getMonthEnglish() + p.getMonthLanguage() + p.getMonthMathematics()))
                            .toList();
                    if (monthList.size() >= i) {
                        Score score = monthList.get(monthList.size() - i);
                        month = score.getName() + ":" +
                                (score.getMonthEnglish() + score.getMonthLanguage() + score.getMonthMathematics());
                    }


                    List<Score> finalList =
                            dayMap.get(date).stream()
                                    .sorted(Comparator.comparing(p -> p.getFinalEnglish() + p.getFinalLanguage() + p.getFinalMathematics()))
                                    .toList();
                    if (finalList.size() >= i) {
                        Score score = finalList.get(monthList.size() - i);
                        finals = score.getName() + ":"
                                + (score.getFinalEnglish() + score.getFinalLanguage() + score.getFinalMathematics());
                    }

                }
                lineList.add(month);
                lineList.add(finals);

            }
            sheetDate2.add(lineList);
        }

//        5. 把两个sheet的数据添加到list
        Sheet sheet = new Sheet();
        sheet.setName("学生成绩统计");
        sheet.setHeaderList(headerList);
        sheet.setDataList(sheetDate);
        sheet.setEachRow(3); //合并单元格,合并的单元格内容要一样
        sheetList.add(sheet);

        Sheet sheet2 = new Sheet();
        sheet2.setName("成绩总分排名");
        sheet2.setHeaderList(headerList);
        sheet2.setDataList(sheetDate2);
        sheet2.setEachRow(10);
        sheetList.add(sheet2);

        return sheetList;
    }


    /**
     *  科目
     */
    private  Map<String, String> getSubjects() {
        final Map<String, String> map = new HashMap<>();
        map.put("Language", "语文");
        map.put("Mathematics", "数学");
        map.put("English", "英语");
        return map;
    }


    /**
     * 模拟数据库取出数据
     * @return
     */
    private static List<Score> getData(){
        List<Score> list = new ArrayList<>();
        final String[] surnames = {"赵伟", "钱伟", "孙勇", "李芳", "周娜",  "郑敏", "王敏", "冯静",  "南宫伟","孙岚"};

        for (int i = 1; i <= 6; i++) {
            for (int j = 0; j < surnames.length; j++) {
                Score score = new Score();

                score.setName(surnames[j]);
                score.setFinalEnglish(new Random().nextInt(101) );
                score.setFinalLanguage(new Random().nextInt(101) );
                score.setFinalMathematics(new Random().nextInt(101) );

                score.setMonthEnglish(new Random().nextInt(101)  );
                score.setMonthLanguage(new Random().nextInt(101) );
                score.setMonthMathematics(new Random().nextInt(101) );
                score.setSemester(String.valueOf(i));
                list.add(score);
            }
        }
        return list;
    }

}

5、Controller层

@RestController
@RequestMapping("excel")
public class ScoreController {

    @Resource
    private ScoreService scoreService;

    @GetMapping("/exportExcel")
    public void exportExcel(HttpServletResponse response) {

        List<Sheet> sheets = scoreService.exportExcel();
		//传入响应,返回的下载文件名称,以及页面内容
        ExcelUtil.exportExcel(response, "学生成绩汇总", sheets);

    }

}


三、测试效果

  • 最终导出文件效果
    在这里插入图片描述

  • sheet对象内容
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1790660.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

可视化数据科学平台在信贷领域应用系列四:决策树策略挖掘

信贷行业的风控策略挖掘是一个综合过程&#xff0c;需要综合考虑风控规则分析结果、效果评估、线上实时监测和业务管理需求等多个方面&#xff0c;以发现和制定有效的信贷风险管理策略。这些策略可能涉及贷款审批标准的调整、贷款利率的制定、贷款额度的设定等&#xff0c;在贷…

240602-通过命令行实现HuggingFace文件上传

A. 登录显示 A.1 MacOS A.2 Windows B. 操作步骤 B.1 操作细节 要通过命令行将文件上传到 Hugging Face&#xff0c;可以使用 huggingface-cli 工具。以下是详细步骤&#xff1a; 安装 huggingface_hub 包&#xff1a; 首先&#xff0c;确保已经安装了 huggingface_hub 包。可…

mysql表级锁(表锁/元数据锁/意向锁)

文章目录 表级锁的分类1、表锁(分类)1.表共享读锁&#xff08;read lock&#xff09;2.表独占写锁&#xff08;write lock&#xff09;3.语法&#xff1a; 2、元数据锁&#xff08;meta data lock &#xff09;3、意向锁1.意向共享锁&#xff08;IS&#xff09;&#xff1a;由语…

Java基础29(编码算法 哈希算法 MD5 SHA—1 HMac 算法 堆成加密算法)

目录 一、编码算法 1. 常见编码 2. URL编码 3. Base64编码 4. 小结 二、哈希算法 1. 哈希碰撞 2. 常用哈希算法 MD5算法 SHA-1算法 自定义HashTools工具类 3. 哈希算法的用途 校验下载文件 存储用户密码 4. 小结 三、Hmac算法 小结&#xff1a; 四、对称加密…

WEB攻防-Python-PYC 反编译CTF 与 CMS-SSTI 模版注入

反编译pyc字节码文件 pyc文件是py文件编译后生成的字节码文件(byte code)&#xff0c;pyc文件经过python解释器最终会生成机器码运行。因此pyc文件是可以跨平台部署的&#xff0c;类似Java的.class文件&#xff0c;一般py文件改变后&#xff0c;都会重新生成pyc文件。 真题附件…

6月5日 C++day3

#include <iostream>using namespace std;class Per { private:string name;int age;int *high;double *weight; public:Per(){cout << "Per的无参构造" << endl;}Per(string name,int age,int high,double weight):\name(name),age(age),high(new…

React项目目录结构与组件基础结构

在React中开发项目并扩展组件时&#xff0c;一个清晰合理的目录结构是至关重要的。它不仅可以帮助你更好地组织代码&#xff0c;还能提高项目的可维护性和扩展性。下面是一个基本的React项目目录结构大纲&#xff0c;你可以根据自己的项目需求进行调整&#xff1a; my-app/ ├…

MySQL的联合索引及案例分析

1. 联合索引 关于联合索引的详解参考博客【Mysql-----联合索引和最左匹配】&#xff0c;包含讲解 最左匹配 联合索引失效的情况 不遵循最左匹配原则范围查询右边失效原理like索引失效原理 比较关注的点在于&#xff1a; 对A、B、C三个字段创建一个联合索引&#xff08;A, …

go语言linux安装

下载&#xff1a;https://go.dev/dl/ 命令行使用 wget https://dl.google.com/go/go1.19.3.linux-amd64.tar.gz解压下载的压缩包&#xff0c;linux建议放在/opt目录下 我放在/home/ihan/go_sdk下 sudo tar -C /home/ihan/go_sdk -xzf go1.19.3.linux-amd64.tar.gz 这里的参数…

21.Redis之分布式锁

1.什么是分布式锁 在⼀个分布式的系统中, 也会涉及到多个节点访问同⼀个公共资源的情况. 此时就需要通过 锁 来做互斥控制, 避免出现类似于 "线程安全" 的问题. ⽽ java 的 synchronized 或者 C 的 std::mutex, 这样的锁都是只能在当前进程中⽣效, 在分布式的这种多…

Nginx配置详细解释:(3)http模块及server模块,location模块

目录 环境概述&#xff1a; http模块中的全局模块 1. root配置主要是对主web页面的路径访问。 2.server虚拟主机 2.1基于IP&#xff1a; 2.2基于域名&#xff1a; 3.alias别名 4.location匹配 5.access模块&#xff1a; 6.验证模块 7.自定义错误页面 8.日志存放位置…

信不信,马上教会你Purple Pi OH开发板之ADB常用命令

开源鸿蒙硬件方案领跑者 触觉智能 本文适用于在Purple Pi OH开发板进行分区镜像烧录。触觉智能的Purple Pi OH鸿蒙开源主板&#xff0c;是华为Laval官方社区主荐的一款鸿蒙开发主板。 该主板主要针对学生党&#xff0c;极客&#xff0c;工程师&#xff0c;极大降低了开源鸿蒙开…

专属编程笔记

Utils目录作用 在软件开发中&#xff0c;Utils&#xff08;或 Utilities&#xff09;目录通常用于存放一些通用的、不特定于任何模块的工具类或辅助函数。这些工具类或函数为整个应用程序或多个模块提供便利的功能支持&#xff0c;使得代码更加模块化、易于维护和重用。Utils目…

深度学习 --- stanford cs231 编程作业(assignment1,Q2: SVM分类器)

stanford cs231 编程作业之SVM分类器 写在最前面&#xff1a; 深度学习&#xff0c;或者是广义上的任何学习&#xff0c;都是“行千里路”胜过“读万卷书”的学识。这两天光是学了斯坦福cs231n的一些基础理论&#xff0c;越往后学越觉得没什么。但听的云里雾里的地方也越来越多…

pycharm专业版安装保姆级教程

一、官网下载 PyCharm下载地址&#xff1a;http://www.jetbrains.com/pycharm/download/#sectionwindows 选择专业版点击下载 二、进入安装向导 下载完成后&#xff0c;点击.exe文件 点击是 点击下一步 可修改安装目录为自己想安装的位置 或者不修改也可 点击下一步 选择所…

[数据概念]数据要素和智能算力市场关系解析

昨天的AI圈里最炸裂的莫过于OpenAI GPT4o的发布了。 根据官网的介绍&#xff0c;GPT-4o是面向未来人机交互范式的全新大模型&#xff0c;具有文本、语音、图像三种模态的理解力。 而且加量不加价 国内报道也是铺天盖地的“炸裂”。 反倒是外媒&#xff0c;报道倒是没有那么夸张…

亮数据——全球网络数据一站式平台

在我们日常的项目开发和研究中&#xff0c;数据获取总是一个让人既爱又恨的话题。找到一个既高效又安全的工具&#xff0c;简直就像是在茫茫沙漠中找到绿洲。近期&#xff0c;我测评了&#xff0c;数十家数据获取工具&#xff0c;最后锁定了&#xff0c;亮数据&#xff0c;本篇…

网络安全等级保护相关标准及发展

目录 等保标准 等保定级 发展 等保标准 2016年11月发布的《网络安全法》第二十一条提出“国家实行网络安全等级保护制度”。 等级保护标准体系&#xff1a; &#xff08;1&#xff09;安全等级类标准 主要包括GB/T 22240-2008《信息安全技术 信息系统安全保护等级保护定…

从Series到DataFrame:Python数据操作的转换技巧

在数据分析和处理的过程中&#xff0c;我们经常需要在Pandas库中对Series和DataFrame进行操作。本文将介绍如何将Series转换为DataFrame&#xff0c;以及如何提取DataFrame中的某一列。首先&#xff0c;我们将通过使用to_frame()函数将Series转换为DataFrame。然后&#xff0c;…

Linux系统编程(七)网络编程TCP、UDP

本文目录 一、基础知识点1. IP地址2. 端口3. 域名4. 网络协议类型5. IP协议类型6. 字节序7. socket套接字 二、TCP 常用API1. socket套接字描述符2. bind套接字绑定3. listen设置最大排队数4. accept接收客户端请求5. connect连接服务端6. read读取数据7. write发送数据 三、UD…