Java导出自定义Excel表格,一套组合拳解决

news2024/12/24 9:52:01

🔵 (一) 功能现状

   🍭目前大部分SpringBoot框架中自带了Excel导出功能,但其中并不支持自定义导出效果的可能性很大。比如很多框架中都能直接支持自动生成关于单表的增删改查操作的前后端代码,但是复杂的多表操作就无法做到,因为复杂的业务操作跟着需求走,自定义Excel表格导出也是如此。

🔵 (二) 核心问题

   🍯想要解决这个自定义问题的我们,大部分是卡在合并单元格的这个环节以及如何做到动态处理单元格这个核心问题的两个方面。那么定义需要做合并单元格处理的字段以及动态处理单元格策略成为了我们的解决这个核心问题的组合拳。

🔴 (三) 合并单元格字段

   🍬在我们实际操作Excel中会发现并不是所有的字段都需要做合并处理,所以Java层面需要指定哪些字段要做合并;而且某些字段内的数据量比较大或者字符数据较长,也需要在Java层面需要指明字段的列宽;同时,我们还需要指明合并字段的主键,以此来作为所导Excel行数以及序列计数的依据。

   1️⃣自定义注解CustomMerge,用于判断是否需要合并,以及合并后哪个字段作为主键。

/**
 * 自定义注解,用于判断是否需要合并以及合并的主键
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CustomMerge {

    /**
     * 是否需要合并单元格
     */
    boolean needMerge() default false;

    /**
     * 是否是主键,即该字段相同的行合并
     */
    boolean isPk() default false;
}

   2️⃣在实体类RoadSectionTrafficRep中写明@ExcelProperty (字段属性名称),@ColumnWidth (列宽),@CustomMerge (上面的自定义注解)

@Data
@EqualsAndHashCode(callSuper = false)
public class RoadSectionTrafficRep {

    @ExcelProperty(value = "***")
    @ColumnWidth(20)
    @CustomMerge(needMerge = true,isPk = true)
    @ApiModelProperty("***")
    private String NO;

    @ExcelProperty(value = "***")
    @ColumnWidth(20)
    @CustomMerge(needMerge = true)
    @ApiModelProperty("***")
    private String statisticalPeriod;

    @ExcelProperty(value = "***")
    @ColumnWidth(20)
    @CustomMerge(needMerge = true)
    @ApiModelProperty("***")
    private String roadSectionName;

	@ExcelProperty(value = "***")
    @ColumnWidth(20)
    @ApiModelProperty("***")
    private Integer smallVehicleTraffic;

    @ExcelProperty(value = "***")
    @ColumnWidth(20)
    @ApiModelProperty("***")
    private Integer middleVehicleTraffic;

    @ExcelProperty(value = "***")
    @ColumnWidth(20)
    @ApiModelProperty("***")
    private Integer bigVehicleTraffic;

    @ExcelProperty(value = "***")
    @ColumnWidth(20)
    @ApiModelProperty("***")
    private Integer vehicleCnt;

}

🔴 (四) 单元格合并策略

   🍪标记好需要合并的字段信息后,核心的合并策略参考以下代码,这里不做过多的阐述。

/**
 * 自定义单元格合并策略
 */
public class CustomMergeStrategy implements RowWriteHandler {
    /**
     * 主键下标
     */
    private Integer pkIndex;

    /**
     * 需要合并的列的下标集合
     */
    private List<Integer> needMergeColumnIndex = new ArrayList<>();

    /**
     * DTO数据类型
     */
    private Class<?> elementType;

    public CustomMergeStrategy(Class<?> elementType) {
        this.elementType = elementType;
    }

    @Override
    public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {
        // 如果是标题,则直接返回
        if (isHead) {
            return;
        }

        // 获取当前sheet
        Sheet sheet = writeSheetHolder.getSheet();

        // 获取标题行
        Row titleRow = sheet.getRow(0);

        if (null == pkIndex) {
            this.lazyInit(writeSheetHolder);
        }

        // 判断是否需要和上一行进行合并
        // 不能和标题合并,只能数据行之间合并
        if (row.getRowNum() <= 1) {
            return;
        }
        // 获取上一行数据
        Row lastRow = sheet.getRow(row.getRowNum() - 1);
        // 将本行和上一行是同一类型的数据(通过主键字段进行判断),则需要合并
        if (lastRow.getCell(pkIndex).getStringCellValue().equalsIgnoreCase(row.getCell(pkIndex).getStringCellValue())) {
            for (Integer needMerIndex : needMergeColumnIndex) {
                CellRangeAddress cellRangeAddress = new CellRangeAddress(row.getRowNum() - 1, row.getRowNum(),
                        needMerIndex, needMerIndex);
                sheet.addMergedRegionUnsafe(cellRangeAddress);
            }
        }
    }

    /**
     * 初始化主键下标和需要合并字段的下标
     */
    private void lazyInit(WriteSheetHolder writeSheetHolder) {

        // 获取当前sheet
        Sheet sheet = writeSheetHolder.getSheet();

        // 获取标题行
        Row titleRow = sheet.getRow(0);
        // 获取DTO的类型
        Class<?> eleType = this.elementType;

        // 获取DTO所有的属性
        Field[] fields = eleType.getDeclaredFields();

        // 遍历所有的字段,因为是基于DTO的字段来构建excel,所以字段数 >= excel的列数
        for (Field theField : fields) {
            // 获取@ExcelProperty注解,用于获取该字段对应在excel中的列的下标
            ExcelProperty easyExcelAnno = theField.getAnnotation(ExcelProperty.class);
            // 为空,则表示该字段不需要导入到excel,直接处理下一个字段
            if (null == easyExcelAnno) {
                continue;
            }
            // 获取自定义的注解,用于合并单元格
            CustomMerge customMerge = theField.getAnnotation(CustomMerge.class);

            // 没有@CustomMerge注解的默认不合并
            if (null == customMerge) {
                continue;
            }

            for (int index = 0; index < fields.length; index++) {
                Cell theCell = titleRow.getCell(index);
                // 当配置为不需要导出时,返回的为null,这里作一下判断,防止NPE
                if (null == theCell) {
                    continue;
                }
                // 将字段和excel的表头匹配上
                if (easyExcelAnno.value()[0].equalsIgnoreCase(theCell.getStringCellValue())) {
                    if (customMerge.isPk()) {
                        pkIndex = index;
                    }

                    if (customMerge.needMerge()) {
                        needMergeColumnIndex.add(index);
                    }
                }
            }
        }

        // 没有指定主键,则异常
        if (null == this.pkIndex) {
            throw new IllegalStateException("使用@CustomMerge注解必须指定主键");
        }

    }
}

🔴 (五) Controller层代码

   🍰导出说到底是对于查询到的数据再做一层处理的功能,所以将其代码放在Controller层即可。中间涉及到的包都是Alibaba中的依赖包,能自动导入,大家可以多点进去看看里面内部的实现机制。

   1️⃣导出表格处理,中间自定义的合并处理可以自行设置。

	@ApiOperation("导出*****表")
    @SneakyThrows
    @GetMapping("/exportRoadSectionTrafficReport")
    public void exportRoadSectionTrafficReport(HttpServletResponse response){
        List<RoadSectionTrafficRep> orderList = dataReportService.getRoadSectionTraffic();
        
        //中间自定义的合并处理,自由发挥咯... ...

        setExcelRespProp(response, "******表");
        EasyExcel.write(response.getOutputStream())
                .head(RoadSectionTrafficRep.class)
                .registerWriteHandler(new CustomMergeStrategy(RoadSectionTrafficRep.class))
                .excelType(ExcelTypeEnum.XLSX)
                .sheet("*****表")
                .doWrite(orderList);
    }

   2️⃣设置Excel下载响应头属性

	/**
     * 设置excel下载响应头属性
     */
    private void setExcelRespProp(HttpServletResponse response, String rawFileName) throws UnsupportedEncodingException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode(rawFileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    }

🍆🍆🍆路过的小伙伴,如果本篇博文对你的学习或者工作有所帮助,可以点赞+收藏+关注一波呀~👊👊👊小编后续每过一段时间会整理出相关项目实例的博文,感谢您的支持哦!!!✈️✈️✈️
在这里插入图片描述

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

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

相关文章

c# winform错误大全

c# winform 错误大全为了实现安装包安装完成后&#xff0c;启动程序。System.BadImageFormatException: 未能加载文件或程序集“file:///C:\xxxxxxxxx\xxxxxxx.exe”或它的某一个依赖项。生成此程序集的运行时比当前加载的运行时新&#xff0c;无法加载此程The version of the …

软件功能测试包含了哪些测试项目?功能测试报告收费标准

一、软件功能测试是什么? 软件功能测试是测试人员通过执行功能测试用例逐步验证软件产品各项功能是否达到预期需求的测试过程。也是俗称的“点点点测试”&#xff0c;这是基础性的测试类型&#xff0c;软件产品的功能直接影响到用户体验&#xff0c;所以软件功能测试意义重大…

UDP的详细解析

UDP的详细解析 文章目录UDP的详细解析UDP 概述UDP的首部格式检验和的计算抓包测试参考TCP/IP运输层的两个主要协议都是互联网的正式标准&#xff0c;即&#xff1a;用户数据报协议UDP (User Datagram Protocol)传输控制协议TCP (Transmission Control Protocol) 按照OSI的术语…

【汽车电子】什么是ADAS?

文章目录ADAS——先进驾驶辅助系统ADAS——商用车安全性能提升的利器总结ADAS——先进驾驶辅助系统 ADAS&#xff0c;全称Advanced Driver Assistance Systems &#xff0c;“先进驾驶辅助系统”&#xff0c;adas是汽车上面的一种系统&#xff0c;中文名叫做高级驾驶辅助系统&…

Java集合框架常见面试题

1. 剖析面试最常见问题之 Java 集合框架 1.1. 集合概述 1.1.1. Java 集合概览1.1.2. 说说 List,Set,Map 三者的区别&#xff1f;1.1.3. 集合框架底层数据结构总结 1.1.3.1. List1.1.3.2. Set1.1.3.3. Map 1.1.4. 如何选用集合?1.1.5. 为什么要使用集合&#xff1f; 1.2. Colle…

促进关键软件高层次人才培养:平凯星辰与华东师范大学签订联合博士培养合作协议

2022 年年初&#xff0c;平凯星辰入选首批工信部教育部支持联合培养国家关键软件高层次人才计划。该计划旨在探索关键软件产教融合育人模式&#xff0c;超常规加快培养一批急需高层次人才&#xff0c;以及探索关键软件联合技术攻关新模式。2022 年年底&#xff0c;在该计划下 平…

算法 ——世界 一

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。个人爱好: 编程&#xff0c;打篮球&#xff0c;计算机知识个人名言&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石…

微服务实战--高级篇:RabbitMQ高级

服务异步通信-高级篇 消息队列在使用过程中&#xff0c;面临着很多实际问题需要思考&#xff1a; 1.消息可靠性 消息从发送&#xff0c;到消费者接收&#xff0c;会经理多个过程&#xff1a; 其中的每一步都可能导致消息丢失&#xff0c;常见的丢失原因包括&#xff1a; 发送…

2月14,情人节双语送祝福!

2月14日是圣瓦伦丁节&#xff0c;也叫情人节&#xff0c;是西方传统节日 Ebruary 14th is Saint Valentines Day, a traditional holiday in the west.在基督教传统中&#xff0c;有数个名叫Valentine或是Valentinus的殉难圣徒。根据传说&#xff0c;这些Valentine中其中一名是…

手把手教你解决传说中的NPE空指针异常

1. 前言最近有好几个初学java的小伙伴&#xff0c;甚至是学习到了JavaWeb、框架阶段的小伙伴也跑来问壹哥&#xff0c;该如何解决Java中的NullPointerException空指针异常。因为NPE是初学者特别常见的典型异常&#xff0c;所以壹哥在这里专门写一篇文章&#xff0c;来手把手地教…

企业需要ERP系统的6个必要原因

你的企业在月底核对财务交易的时间是否比应有的时间长&#xff1f;你是否依靠猜测而不是核心事实和数字来预测销售&#xff1f;你是否感觉到客户满意度下降了&#xff1f;如果是的话&#xff0c;那么这些都是你的企业迫切需要一个高效的ERP系统的信号。 这里有一些原因表明&am…

JSON数据格式【学习记录】

JSON介绍 JSON&#xff08;JavaScript Objet Notation&#xff09;是一种轻量级的数据交换格式。它使得人们很容易的进行阅读和编写。同时也方便了机器进行解析和生成。它采用一种键:值对的文本格式来存储和表示数据&#xff0c;在系统交换数据过程中常常被使用&#xff0c;是…

linux的三权分立设计思路和用户创建(安全管理员、系统管理员和审计管理员)

目录 一、三权分立设计思路 1、什么是三权 2、三员及权限的理解 3、三员之三权 4、权限划分 5、“三员”职责 6、“三员”配置要求 二、linux三权分立的用户创建 1、系统管理员 2、安全管理员 3、审计管理员 一、三权分立设计思路 1、什么是三权 三权指的是配置、…

小微企业真的需要CRM吗?

小型企业真的需要CRM吗&#xff1f;小型企业不需要CRM -它太贵了&#xff0c;不是必需品&#xff0c;对吧&#xff1f;错&#xff01; 在本文中&#xff0c;我们将解释什么是小型企业CRM&#xff0c;小型企业何时应该实施CRM&#xff0c;哪些行业可以从CRM中受益&#xff0c;并…

MySQL数据库12——视图(VIEW)

视图概念 视图是一个虚拟表&#xff0c;称其为虚拟表的原因是&#xff1a;视图内的数据并不属于视图本身&#xff0c;而属于创建视图时用到的基本表。可以认为&#xff0c;视图是一个表中的数据经过某种筛选后的显示方式&#xff1b;或者多个表中的数据经过连接筛选后的显示方…

上手ElasticSearch必须了解的核心概念

ElasticSearch概述ElasticSearch&#xff08;简称 ES&#xff09; 是一个分布式的使用 REST 接口的搜索引擎&#xff0c;属于非关系型数据库。它是在 lucene 的基础上进行研发的&#xff0c;隐藏了 lucene 的复杂性&#xff0c;提供简单易用的 RESTful Api接口。ES 的分片相当于…

HTTP、WebSocket和Socket.IO

一、HTTP协议 HTTP协议是Hyper Text Transfer Protocol&#xff08;超文本传输协议&#xff09;。HTTP 协议和 TCP/IP 协议族内的其他众多的协议相同&#xff0c; 用于客户端和服务器之间的通信。请求访问文本或图像等资源的一端称为客户端&#xff0c; 而提供资源响应的一端称…

七天实现一个go rpc框架

目录rpc协议目的关于RPC和框架服务端与消息编码确保接口的实现消息的序列化与反序列化通信过程服务端的实现main 函数支持并发与异步的客户端Call 的设计实现客户端服务注册(service register)通过反射实现 service集成到服务端超时处理创建连接超时Client.Call 超时服务端处理…

C语言(联合和枚举)

1.联合创建 联合是一种数据类型&#xff0c;它能在同一个内存空间中存储不同的数据类型&#xff08;非同时存储&#xff09; 创建联合和创建结构的方式相同&#xff0c;需要一个联合模板和联合变量。使用关键字union union hold{ int digit; double bigfl; char letter; } 以上…

十、STM32端口复用重映射

目录 1.什么是端口复用&#xff1f; 2.如何配置端口复用&#xff1f; 3.什么是端口重映射 &#xff1f; 4.什么是部分重映射和完全重映射&#xff1f; 5.重映射的配置过程 1.什么是端口复用&#xff1f; STM32有很多外设&#xff0c;外设的外部引脚与GPIO复用。也就是说一…