easyexcel内容追加与单元格合并

news2024/12/26 22:06:45

    这里的需求是,如果表格不存在,则新建表格,并填入数据,如果表格存在,那么就追加内容,并且支持单元格合并。

    内容追加,需要分两种方式插入,第一种就是没有表格,需要生成表头,并且插入内容,第二种就是已经有了表格,这个表格作为模板并,不用设置表头,直接追加内容,这里需要生成一个新的表格,如果有必要,最后还要删除老的表格。

    关于单元格合并,网上有这样的示例,使用easyexcel来实现,我们就是需要定义一个CellWriteHandler的接口实现,在实现类中,覆盖afterCellDispose()方法,本例中就是CustomCellWriteHandler类。

    这里为了验证我们的结果,我们利用springboot构建两个不同的请求,一个是创建excel,一个是追加excel。

    主要代码如下:

    依赖文件: 

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>easyexcel-core</artifactId>
	<version>3.2.1</version>
</dependency>

     控制器类:

import com.example.service.ExcelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/excel")
public class ExcelController {

    @Autowired
    private ExcelService excelService;

    @GetMapping("/create")
    public ResponseEntity create() {
        return ResponseEntity.ok(excelService.create());
    }

    @GetMapping("/append")
    public ResponseEntity append() {
        return ResponseEntity.ok(excelService.append());
    }
}

    服务类:

package com.example.service;

import com.alibaba.excel.EasyExcel;
import com.example.config.CustomCellWriteHandler;
import com.example.dao.ExcelDao;
import com.example.model.ExcelData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;

@Service
public class ExcelService {

    @Autowired
    private ExcelDao excelDao;

    private String fileName = "e:\\test\\student.xlsx";

    private String tempName = "e:\\test\\temp.xlsx";

    private int mergeRowIndex = 0;

    private int[] mergeColumnIndexes = {0, 1, 2};

    public boolean create() {
        File out = new File(fileName);
        EasyExcel.write(out, ExcelData.class)
                .sheet("student")
                .registerWriteHandler(new CustomCellWriteHandler(mergeRowIndex,mergeColumnIndexes))
                .doWrite(excelDao.createData());
        return true;
    }

    public boolean append() {
        File out = new File(fileName);
        File temp = new File(tempName);
        if (out.exists()) {
            // 采用模板方式写入
            EasyExcel.write(out).needHead(false)
                    .withTemplate(out)
                    .file(temp)
                    .sheet("student")
                    .registerWriteHandler(new CustomCellWriteHandler(mergeRowIndex,mergeColumnIndexes))
                    .doWrite(excelDao.appendData());
            // out.delete();
            // temp.renameTo(out);
        }
        return true;
    }


}

    数据接口模拟类:

package com.example.dao;

import com.example.model.ExcelData;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class ExcelDao {
    public List<ExcelData> createData() {
        List<ExcelData> list = new ArrayList<>();
        list.add(new ExcelData("罗芳",18, "female","语文",100));
        list.add(new ExcelData("罗芳",18, "female","数学",99));
        list.add(new ExcelData("王芳",17, "female","语文",80));
        list.add(new ExcelData("王芳",17, "female","数学",60));
        return list;
    }

    public List<ExcelData> appendData() {
        List<ExcelData> list = new ArrayList<>();
        list.add(new ExcelData("张雪琴",18, "female","语文",88));
        list.add(new ExcelData("张雪琴",18, "female","数学",75));
        list.add(new ExcelData("王勇",18, "male","语文",80));
        list.add(new ExcelData("王勇",18, "male","数学",92));
        return list;
    }
}

     表格实体类:

package com.example.model;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class ExcelData {
    @ExcelProperty("学生姓名")
    private String name;

    @ExcelProperty("年龄")
    private int age;

    @ExcelProperty("性别")
    private String gender;

    @ExcelProperty({"课程", "课程名称"})
    private String courseName;

    @ExcelProperty({"课程", "分数"})
    private double score;
}

    表格单元格合并策略类:

package com.example.config;

import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.util.List;


public class CustomCellWriteHandler implements CellWriteHandler {

    private int mergeRowIndex;
    private int[] mergeColumnIndexes;

    public CustomCellWriteHandler(int mergeRowIndex, int[] mergeColumnIndexes) {
        this.mergeRowIndex = mergeRowIndex;
        this.mergeColumnIndexes = mergeColumnIndexes;
    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        int curRowIndex = cell.getRowIndex();
        int curColIndex = cell.getColumnIndex();
        if (curRowIndex > mergeRowIndex) {
            for (int i = 0; i < mergeColumnIndexes.length; i++) {
                if (curColIndex == mergeColumnIndexes[i]) {
                    mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
                    break;
                }
            }
        }
    }

    private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
        Object curData = cell.getCellType() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
        Row prevRow = cell.getSheet().getRow(curRowIndex - 1);
        if (prevRow != null) {
            Cell prevCell = prevRow.getCell(curColIndex);
            Object prevData = prevCell.getCellType() == CellType.STRING ? prevCell.getStringCellValue() : prevCell.getNumericCellValue();
            boolean isDataSame = curData.equals(prevData);
            if (isDataSame) {
                Sheet sheet = writeSheetHolder.getSheet();
                List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
                boolean isMerged = false;
                for (int i = 0; i < mergeRegions.size(); i++) {
                    CellRangeAddress cellRangeAddress = mergeRegions.get(i);
                    if (cellRangeAddress.isInRange(curRowIndex - 1, curColIndex)) {
                        sheet.removeMergedRegion(i);
                        cellRangeAddress.setLastRow(curRowIndex);
                        sheet.addMergedRegionUnsafe(cellRangeAddress);
                        isMerged = true;
                    }
                }
                if (!isMerged) {
                    CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
                    sheet.addMergedRegion(cellRangeAddress);
                }
            }
        }
    }


}

    运行示例,分别访问 http://localhost:8080/excel/create 和 http://localhost:8080/excel/append 。

    最后会生成两个文件,分别是e:\test\student.xlsx  e:\test\temp.xlsx,内容如下所示:

    内容是符合预期的,第一个只有两个学生信息,第二个表格因为是追加的,所以有四个学生信息。但是单元格合并,我们指定的是0,1,2三列,也就是姓名,年龄,性别需要合并,实际中他们确实合并了,但是似乎合并的超出了范围。我们希望合并的是同样学生的年龄和性别,而不是所有数值一样的年龄、性别。

    解决办法就是在合并策略里面增加一个判断,如果合并行的列值(当前属性列age,gender)相同,而且第一列值(唯一属性name)也相同那么合并,而不是列的内容相同就合并。

    变更过的合并策略主体逻辑:

private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
	Object curData = cell.getCellType() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
	Row prevRow = cell.getSheet().getRow(curRowIndex - 1);
	Row curRow = cell.getSheet().getRow(curRowIndex);
	if (prevRow == null) {
		prevRow = writeSheetHolder.getCachedSheet().getRow(curRowIndex - 1);
	}
	Cell prevFirstColCell = prevRow.getCell(0);
	Cell curFirstColCell = curRow.getCell(0);
	Object prevFirstColData = prevFirstColCell.getCellType() == CellType.STRING ? prevFirstColCell.getStringCellValue() : prevFirstColCell.getNumericCellValue();
	Object curFirstColData = curFirstColCell.getCellType() == CellType.STRING ? curFirstColCell.getStringCellValue() : curFirstColCell.getNumericCellValue();
	Cell prevCell = prevRow.getCell(curColIndex);
	Object prevData = prevCell.getCellType() == CellType.STRING ? prevCell.getStringCellValue() : prevCell.getNumericCellValue();
	boolean isDataSame = curData.equals(prevData) && curFirstColData.equals(prevFirstColData);
	if (isDataSame) {
		Sheet sheet = writeSheetHolder.getSheet();
		List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
		boolean isMerged = false;
		for (int i = 0; i < mergeRegions.size(); i++) {
			CellRangeAddress cellRangeAddress = mergeRegions.get(i);
			if (cellRangeAddress.isInRange(curRowIndex - 1, curColIndex)) {
				sheet.removeMergedRegion(i);
				cellRangeAddress.setLastRow(curRowIndex);
				sheet.addMergedRegionUnsafe(cellRangeAddress);
				isMerged = true;
			}
		}
		if (!isMerged) {
			CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
			sheet.addMergedRegion(cellRangeAddress);
		}
	}

}

    生成的表格: 

    单元格合并范围超出问题似乎解决了,完美。 

    有的代码里面,在获取上一行记录某列的时候,代码是这样的:

Cell prevCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);

    在实际中,这里可能会报空指针异常,其实数据在缓存里面,我们需要这样来获取:

Row prevRow = cell.getSheet().getRow(curRowIndex - 1);
if (prevRow == null) {
	prevRow = writeSheetHolder.getCachedSheet().getRow(curRowIndex - 1);
}

    或者,在后面的逻辑里面加上判断 if (prevRow != null) 也是可以的。

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

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

相关文章

内网渗透之横向移动ipc

0x00 内网横向移动介绍 内网横向移动是什么 在内网渗透中&#xff0c;当攻击者获取到内网某台机器的控制权后&#xff0c;会以被攻陷的主机为跳板&#xff0c;通过收集域内凭证等各种方法&#xff0c;访问域内其他机器&#xff0c;进一步扩大资产范围。通过此类手段&#xff0…

SpringCloud_Eureka服务的注册与发现

文章目录 一、微服务的理论1、微服务和分布式的区别2、微服务的拆分规范和原则 二、微服务的注册与发现(Eureka)1、Spring Cloud Eureka的概念2、构建聚合父工程3、搭建Eureka服务4、搭建Eureka服务的提供者5、创建Eureka服务的消费者 三、Eureka的其他功能1、服务的剔除和自保…

DC-6靶机通关详解

信息收集 漏洞发现 发现无法访问web 加个hosts 这题类似那个dc2还是dc3,网站长的一样 wordPress5.1.1 上wpscan扫 enumrate一下user 看看能不能弱口令 测了wp给的那几个用户,都不能弱口令,dirsearch也没扫到什么有价值的路径 尝试ssh弱口令 没爆出来,回官网看了下描述 确实…

基于药效团的药物设计(Pharmacophore Construction)

基于药效团的药物设计&#xff08;Pharmacophore Construction&#xff09; 药效团模型不仅仅利用分子拓扑学相似性而且利用了基团的功能相似性&#xff0c;从而运用了生物电子等排体&#xff08;bioisosterism&#xff09;的概念使得模型更加可靠。基于药效团的虚拟筛选的方法…

华为OD机试真题(Java),最小步骤数(100%通过+复盘思路)

一、题目描述 一个正整数数组 设为nums&#xff0c;最大为100个成员&#xff0c;求从第一个成员开始正好走到数组最后一个成员所使用的最小步骤数。 要求&#xff1a; 第一步 必须从第一元素起 且 1<第一步步长<len/2 (len为数组长度)&#xff1b;从第二步开始只能以所…

Algo C++:课程介绍

目录 课程介绍前言1. 课程特色2. 课程前言3. 具备条件4. 预期的收获 课程介绍 前言 手写AI推出的全新面向AI算法的C课程 Algo C&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考。 本次课程主要是课程介绍 课程大纲可看下面的思维导图 1. 课程特色 案例驱动 讲解…

Kubernetes(k8s)

Kubernetes(k8s) 前言 ​ 在学习过程中&#xff0c;我们经常会遇到遗忘的问题。为了避免忘记&#xff0c;多复习是非常必要的。复习可以帮助我们巩固知识&#xff0c;加深记忆&#xff0c;提高学习效率。因此&#xff0c;我们应该养成良好的复习习惯&#xff0c;定期回顾所学…

ai免费写作在线平台-ai免费伪原创文章生成器软件

ai伪原创能检测出来吗 人工智能技术可以检测伪原创&#xff0c;但是不是所有的伪原创都可以被检测出来。 现在有许多自然语言处理&#xff08;NLP&#xff09;算法和技术可以用来检测伪原创内容&#xff0c;例如文本相似度比较算法&#xff0c;语气分析算法等。这些算法可以检…

iptables移植+内核修改

iptables移植&#xff0c;交叉编译后的文件&#xff0c;如果交叉编译工具一致可以直接使用-嵌入式文档类资源-CSDN文库 移植iptables过程中出现一些问题&#xff0c;这里记录一下 Iptables是用户态提供的更改过滤规则的便捷工具&#xff0c;通过使用这个工具&#xff0c;可以…

openEuler-linux下部署zabbix-超级详细

一、准备工作 下载&#xff1a;zabbix包 地址&#xff1a;下载Zabbix 准备2台openEuler-linux虚拟机&#xff1a; linux-1&#xff1a;当服务器端 IP地址&#xff1a;192.168.100.100 修改hosts文件 [rootzbx ~]# vim /etc/hosts 192.168.100.100 zbx.xx.cn linux-2&…

Spring Boot Web请求响应

在上一讲&#xff0c;学习了Spring Boot Web的快速入门以及Web开发的基础知识&#xff0c;包括HTTP协议以及Web服务器Tomcat等内容。基于SpringBoot的方式开发一个web应用&#xff0c;浏览器发起请求 /hello 后 &#xff0c;给浏览器返回字符串 “Hello World ~”。运行启动类启…

【Spring篇】IOC相关内容

&#x1f353;系列专栏:Spring系列专栏 &#x1f349;个人主页:个人主页 目录 一、bean基础配置 1.bean基础配置(id与class) 2.bean的name属性 3.bean作用范围scope配置 二、bean实例化 1.构造方法实例化 2.分析Spring的错误信息 3.静态工厂实例化 4.实例工厂 5.FactoryBean 三…

Python爬虫基础-如何获取网页源代码

Python爬虫基础-如何获取网页源代码 网络爬虫(Web Crawler)&#xff0c;又称网页蜘蛛(Web Spider)&#xff0c;是一种按照一定的规则&#xff0c;自动地抓取万维网信息的程序或者脚本。爬虫程序根据一组特定的规则自动的访问网站&#xff0c;然后抓取网页上的内容&#xff0c;进…

布隆过滤器详解

介绍 本文全部代码地址 布隆过滤器是一种高效的数据结构,用于判断一个元素是否存在于一个集合中.它的主要优点是速度快,空间占用少,因此在需要快速判断某个元素是否在集合中的场合得到广泛引用. 布隆过滤器就是一个大型的位数组和几个不一样的无偏hash函数.所谓无偏就是能够…

boot-admin整合flowable官方editor-app源码进行BPMN2-0建模(续)

书接上回 项目源码仓库github 项目源码仓库gitee boot-admin 是一款采用前后端分离模式、基于SpringCloud微服务架构的SaaS后台管理框架。系统内置基础管理、权限管理、运行管理、定义管理、代码生成器和办公管理6个功能模块&#xff0c;集成分布式事务Seata、工作流引擎Flow…

ARM学习

计算机硬件基础* 文章目录 知识体系嵌入式系统分层应用开发和底层开发Linux内核五大功能ARM体系结构和接口技术底层知识的学习方法计算机基础知识 计算机的进制计算机的组成总线三级存储结构地址空间CPU原理概述 简述为什么地址总线为32bit的处理器的地址空间为4G简述CPU执行…

Java应用的优雅停机

一. 优雅停机的概念 优雅停机一直是一个非常严谨的话题&#xff0c;但由于其仅仅存在于重启、下线这样的部署阶段&#xff0c;导致很多人忽视了它的重要性&#xff0c;但没有它&#xff0c;你永远不能得到一个完整的应用生命周期&#xff0c;永远会对系统的健壮性持怀疑态度。…

面试被经常问的SQL窗口函数

面试题 有一张“学生成绩表”&#xff0c;包含4个字段&#xff1a;班级id、学生id、课程id、成绩。 问题1&#xff1a; 求出每个学生成绩最高的三条记录 问题2&#xff1a; 找出每门课程都高于班级课程平均分的学生 技术提升 技术要学会分享、交流&#xff0c;不建议闭门造…

图像去模糊:MSSNet 模型详解

本内容主要介绍实现单图像去模糊的 MSSNet 模型。 论文&#xff1a;MSSNet: Multi-Scale-Stage Network for Single Image Deblurring 代码&#xff08;官方&#xff09;&#xff1a;https://github.com/kky7/MSSNet 1. 背景 单图像去模糊旨在从模糊图像&#xff08;由相机抖…

ZLMediaKit在Windows上实现Rtmp流媒体服务器以及模拟rtmp推流和http-flv拉流播放

场景 开源流媒体服务器ZLMediaKit在Windows上运行、配置、按需拉流拉取摄像头rtsp视频流)并使用http-flv网页播放&#xff1a; 开源流媒体服务器ZLMediaKit在Windows上运行、配置、按需拉流拉取摄像头rtsp视频流)并使用http-flv网页播放_霸道流氓气质的博客-CSDN博客 上面讲…