EasyExcel导出导入excel工具类

news2025/4/4 5:49:33

接上一篇EasyExcel导出导入excel的文章,附上一份完整的工具类代码。对于字体颜色名称,请参考这篇文章。
POI字体颜色

小技巧

  • 类转换用属性拷贝
  • 不同类如果有相同属性,则使用反射验证,减少代码量
    private List<Person> validateAndSetTypeThenReturn(List<Long> list, List<?> objects, String key) {
        Consumer<Object> validator = item -> {
            try {
                Method setType = item.getClass().getMethod("setType", String.class);
                Method getId = item.getClass().getMethod("getId");
                // 设置类型
                setType.invoke(item, key);
                // 校验ID必填
                if (!list.contains((Long) getId.invoke(item))) {
                    throw new ServiceException("无效的ID" + getId.invoke(item));
                }
            } catch (ServiceException ex) {
                throw new ServiceException(ex.getMessage());
            } catch (Exception e) {
                throw new ServiceException("数据验证失败");
            }
        };
        objects.forEach(validator);
        return BeanUtil.copyToList(objects, Person.class);
    }
  • 遍历List不如遍历枚举
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.handler.WorkbookWriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.yfld.common.core.convert.ExcelBigNumberConvert;
import com.yfld.common.core.exception.ServiceException;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @ClassName : ExcelUtilAdvance
 * @Description : excel高级工具
 */
@Slf4j
public class ExcelUtilAdvance {
    /**
     * 导出多 Sheet 版本的 Excel 文件(动态生成,无需模板)
     *
     * @param sheetDataMap Sheet 数据映射,key 为 Sheet 名称,value 为数据列表(列表元素需为同一 DTO 类型)
     * @param filename     导出的 Excel 文件名
     * @param response     HttpServletResponse
     * @throws IOException 文件操作异常
     */
    public static void exportDynamicMultiSheet(LinkedHashMap<String, List<?>> sheetDataMap, String filename, HttpServletResponse response) {
        try {
            // 1. 设置 HTTP 响应头(文件类型和编码)
            setResponse(filename, response);

            // 2. 创建写入处理器-指定样式
            HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(createHeaderStyle(), createContentStyle());

            // 3. 创建 ExcelWriter 对象(自动关闭流)
            try (ExcelWriter excelWriter = EasyExcel
                    .write(response.getOutputStream())
                    .autoCloseStream(true)
                    .registerConverter(new ExcelBigNumberConvert())
                    .registerWriteHandler(styleStrategy)  // 样式处理器
                    .registerWriteHandler(new AutoColumnWidthStyleStrategy())  // 自动列宽适配
                    .registerWriteHandler(new ZoomScaleHandler(150))  // 缩放比例
//                    .registerWriteHandler(new FirstRedStarHeaderHandler())  // 表头着色器,将*号标记为红色
                    .build()) {

                // 4. 遍历所有 Sheet 数据
                for (Map.Entry<String, List<?>> entry : sheetDataMap.entrySet()) {
                    String sheetName = entry.getKey();
                    List<?> dataList = entry.getValue();

                    // 4.1 动态推断 DTO 类型(通过列表第一个元素)
                    if (CollUtil.isEmpty(dataList)) {
                        throw new IllegalArgumentException("Sheet [" + sheetName + "] 数据列表不能为空");
                    }
                    Class<?> dtoClass = dataList.get(0).getClass();

                    // 4.2 创建 WriteSheet(动态绑定表头和样式)
                    WriteSheet writeSheet = EasyExcel.writerSheet(sheetName)
                            .head(dtoClass)          // 根据 DTO 类自动生成表头
                            .needHead(Boolean.TRUE)  // 强制生成表头
                            .build();

                    // 4.3 写入数据(自动跳过空列表)
                    excelWriter.write(dataList, writeSheet);
                }
            }
        } catch (IOException e) {
            throw new ServiceException("导出 Excel 失败:" + e.getMessage(), e);
        }
    }

    /**
     * 导入多 Sheet 版本的 Excel 文件,自动将每个 Sheet 映射到指定实体类
     *
     * @param file         上传的 Excel 文件
     * @param sheetNameMap Sheet 配置信息,key 为 Sheet 名称,value 为对应实体类的 Class 对象
     * @return 解析后的数据 Map,key 为 Sheet 名称,value 为对应实体对象列表
     */
    public static Map<String, List<?>> importDynamicMultiSheet(MultipartFile file, Map<String, Class<?>> sheetNameMap) {
        try {
            // 1. 创建 ExcelReader 对象(自动关闭流)
            try (ExcelReader excelReader = EasyExcel.read(file.getInputStream())
                    .registerConverter(new ExcelBigNumberConvert())
                    .autoCloseStream(true) // 确保流自动关闭
                    .build()) {

                // 2. 初始化数据容器,存储每个 Sheet 的解析结果
                Map<String, List<?>> dataMap = new LinkedHashMap<>(); // 保持插入顺序

                // 3. 遍历 Sheet 配置信息
                for (Map.Entry<String, Class<?>> entry : sheetNameMap.entrySet()) {
                    String sheetName = entry.getKey();
                    Class<?> clazz = entry.getValue();

                    // 4. 创建数据监听器(泛型安全)
                    GenericSheetListener<Object> listener = new GenericSheetListener<>();

                    // 5. 构建 ReadSheet(指定 Sheet 名称和表头类)
                    ReadSheet readSheet = EasyExcel.readSheet(sheetName)
                            .head(clazz)
                            .registerReadListener(listener)
                            .headRowNumber(1) // 表头行数
                            .build();

                    try {
                        // 6. 读取当前 Sheet 数据
                        excelReader.read(readSheet);
                    } catch (Exception e) {
                        throw new RuntimeException("解析 Sheet [" + sheetName + "] 失败: " + e.getMessage(), e);
                    }

                    // 7. 将解析结果存入 Map(类型安全转换)
                    dataMap.put(sheetName, convertToTypedList(listener.getDataList(), clazz));
                }
                return dataMap;
            }
        } catch (Exception e) {
            throw new RuntimeException("导入 Excel 失败:" + e.getMessage(), e);
        }
    }

    private static <T> List<T> convertToTypedList(List<Object> dataList, Class<T> clazz) {
        return dataList.stream()
                .map(clazz::cast)
                .collect(Collectors.toList());
    }

    /**
     * 创建统一的表头样式(灰色背景、居中、加粗)
     *
     * @return WriteCellStyle
     */
    private static WriteCellStyle createHeaderStyle() {
        WriteCellStyle style = new WriteCellStyle();
        // 表头背景色-青绿色
        style.setFillForegroundColor(IndexedColors.AQUA.getIndex());
        style.setWrapped(false);
        // 水平剧中
        style.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 自动调整列宽
        // 字体
        WriteFont font = new WriteFont();
        font.setBold(true);
        font.setFontName("宋体");
        font.setFontHeightInPoints((short) 11);
//        font.setColor(IndexedColors.BLACK.getIndex());
        style.setWriteFont(font);
        return style;
    }

    /**
     * 创建内容样式(中对齐、黑色字体)
     */
    private static WriteCellStyle createContentStyle() {
        WriteCellStyle style = new WriteCellStyle();
        style.setHorizontalAlignment(HorizontalAlignment.CENTER);
        WriteFont font = new WriteFont();
        font.setFontName("宋体");
        font.setFontHeightInPoints((short) 10);
        style.setWriteFont(font);
        return style;
    }

    private static void setResponse(String filename, HttpServletResponse response) throws UnsupportedEncodingException {
        // 设置响应头-excel文件类型和文件名
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
    }

    /**
     * 通用 Sheet 数据监听器(泛型类)
     *
     * @param <T> 实体类型
     */
    @Getter
    private static class GenericSheetListener<T> implements ReadListener<T> {
        private final List<T> dataList = new ArrayList<>();

        @Override
        public void invoke(T data, AnalysisContext context) {
            if (StrUtil.isEmpty(data.toString())) {
                throw new ExcelAnalysisException("第 " + context.readRowHolder().getRowIndex() + " 行数据错误:不能为空");
            }
            dataList.add(data); // 收集每行数据
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            // 可在此添加后处理逻辑(如校验、日志)
            log.info("解析完成,共处理 {} 行数据", dataList.size());
        }
    }

    /**
     * 自动调整列宽,用于在写入完成后动态设置列宽
     */
    public static class AutoColumnWidthStyleStrategy implements WorkbookWriteHandler {
        @Override
        public void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) {
            SXSSFWorkbook workbook = (SXSSFWorkbook) writeWorkbookHolder.getWorkbook();
            // 遍历所有Sheet
            for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
                SXSSFSheet sheet = workbook.getSheetAt(i);
                adjustColumnWidth(sheet);
            }
        }

        private void adjustColumnWidth(SXSSFSheet sheet) {
            sheet.trackAllColumnsForAutoSizing();
            // 获取第一行(表头)
            Row headerRow = sheet.getRow(0);
            if (headerRow == null) {
                log.error("表头行不存在,无法调整列宽");
                return;
            }
            int columnCount = headerRow.getLastCellNum();
            for (int i = 0; i < columnCount; i++) {
                // 自动调整列宽
                sheet.autoSizeColumn(i);
                // 解决自动设置列宽中文失效的问题
//                sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 17 / 10);
                // 设置列宽范围(示例:10到50字符)
                int currentWidth = sheet.getColumnWidth(i);
                int minWidth = 10 * 256;  // 10字符
                int maxWidth = 50 * 256;  // 50字符
                if (currentWidth < minWidth) {
                    sheet.setColumnWidth(i, minWidth);
                } else if (currentWidth > maxWidth) {
                    sheet.setColumnWidth(i, maxWidth);
                }
            }
        }
    }

    /**
     * 自定义缩放比例处理器,用于在写入完成后设置 Sheet 的显示比例
     */
    @Data
    public static class ZoomScaleHandler implements SheetWriteHandler {

        private final int zoomScale; // 缩放比例(如 150 表示 150%)

        @Override
        public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
            Sheet sheet = writeSheetHolder.getSheet();
            // 设置缩放比例(POI 的 setZoom 方法直接接受百分比值)
            sheet.setZoom(zoomScale); // 例如 150 表示 150%
        }
    }

    /**
     * 表头着色器
     */
    public static class FirstRedStarHeaderHandler implements CellWriteHandler {
        @Override
        public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList,
                                     Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
            if (isHead && cell.getRow().getRowNum() == 0) { // 仅处理第一行表头
                // 1. 检查单元格类型和内容
                if (cell.getCellType() != CellType.STRING || cell.getStringCellValue() == null) {
                    return;
                }
                String cellValue = cell.getStringCellValue();
                if (!cellValue.startsWith("*")) {
                    return;
                }

                // 2. 获取工作簿和原始样式
                Workbook workbook = cell.getSheet().getWorkbook();
                CellStyle originalStyle = cell.getCellStyle();

                // 3. 创建红色字体(继承默认属性)
                Font defaultFont = workbook.getFontAt(originalStyle.getFontIndexAsInt());
                Font redFont = workbook.createFont();
                redFont.setFontName(defaultFont.getFontName());
                redFont.setFontHeightInPoints(defaultFont.getFontHeightInPoints());
                redFont.setBold(defaultFont.getBold());
                redFont.setColor(IndexedColors.RED.getIndex()); // 红色

                // 4. 构建富文本
                XSSFRichTextString richText = new XSSFRichTextString(cellValue);
                richText.applyFont(0, 1, redFont); // 第一个字符(*)为红色
                if (cellValue.length() > 1) {
                    // 剩余字符保留默认样式
                    richText.applyFont(1, cellValue.length(), defaultFont);
                }

                // 5. 应用富文本到单元格
                cell.setCellValue(richText);
            }
        }
    }
}

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

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

相关文章

终端SSH连接工具SecureCRT安装和连接Linux

SecureCRT 9.5是一款集终端仿真与加密功能于一身的专业软件&#xff0c;其坚如磐石的安全性、高效的信息传输能力以及高度可定制的会话管理&#xff0c;使得它成为众多用户的首选。该软件不仅支持SSH2、SSH1、Telnet等多种协议&#xff0c;还提供了Relogin、Serial、TAPI、RAW等…

赛逸展2025“创新引擎”启动:限量席位,点亮科技绿色新征程

当今时代&#xff0c;科技革新与绿色发展已然成为推动社会进步的双引擎。2025第七届亚洲消费电子技术贸易展&#xff08;赛逸展&#xff09;敏锐捕捉这一趋势&#xff0c;重磅打造“科技创新专区”&#xff0c;并面向科技、绿色企业吹响限量招募号角。 这个独具特色的专区紧扣…

FPGA实现数码管显示分秒时间

目录 一. verilog实现 二. 烧录验证 三. 结果验证 使用开发板&#xff1a;DE2-115开发板 一. verilog实现 要实现分和秒&#xff0c;需要知道定时器的频率&#xff0c;通过查手册可知&#xff0c;我使用的开发板时钟为50hz&#xff0c;也就是时钟一个周期是2微秒。 5000000…

可视化开发:用Qt实现Excel级动态柱状图

Qt柱状图 QtChart 首先我们介绍一下 图表建立的基础&#xff1a;Qt Charts QtChart 是Qt框架的一个模块&#xff0c;专注与提供交互式数据可视化功能 俗话就是 用于用户轻松创建各种类型的图表和图形界面 它包含的图表类型有很多&#xff1a;折线图&#xff0c;饼图&#x…

从零实现Json-Rpc框架】- 项目实现 - 基于Dispatcher模块的RPC框架

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

kubekey -实现懒人一键部署K8S集群

kubekey -实现懒人一键部署K8S集群 操作步骤 官网&#xff1a; https://kubesphere.io/zh/ 一、执行以下命令快速创建一个 Kubernetes 集群。 Master节点 如果您访问 GitHub/Googleapis 受限&#xff0c;请登录 Linux 主机&#xff0c;执行以下命令设置下载区域。 [roottest ~]…

李宏毅机器学习笔记(1)—机器学习基本概念+深度学习基本概念

机器学习基本概念 1、获取模型 步骤 1.1、假定未知函数 带未知参数的函数 1.2、定义损失函数 真实值&#xff1a;label MAE MSE 几率分布&#xff0c;cross-entropy? 1.3、优化 单独考虑一个参数 让损失函数最小&#xff0c;找导数为零的点 单独考虑w&#xff0c;w…

数字IC后端项目常见问题之streamOut layermap和innovus drc violation

Q1&#xff1a;我需要将Innovus设计GDS导出到Virtuoso&#xff0c;但发现写出GDS的过程会报如下所示的警告。这里写出GDS使用的是Virtuoso (DFII) streamOut mapping文件&#xff01; Clock Gen模块Routing DRC&#xff0c;Timing分析及解决 streamOut tease.gds2 -mapFile cd…

短剧系统开发动漫短剧系统源码开发上线小程序app教程

一、市场规模与用户增长&#xff1a;突破677亿&#xff0c;Z世代成主力 整体扩张 2025年短剧市场预计同比增长15%&#xff0c;规模达677.9亿元&#xff0c;用户规模6.62亿&#xff08;占网民59.7%&#xff09;。动漫短剧作为细分领域&#xff0c;增速显著受益于二次元文化渗透&…

太阳能高杆路灯:照亮未来的新光

在全球能源转型进程加速以及可持续发展理念日益深入人心的背景下&#xff0c;太阳能高杆路灯作为融合新能源技术、智能控制技术与多功能集成特性的创新产品&#xff0c;正逐步革新传统路灯的格局。其不仅有效解决了传统路灯对电网供电的依赖问题&#xff0c;更为城市及乡村的照…

《C++Linux编程进阶:从0实现muduo 》-第8讲.C++面试如何高效获取线程ID

章节重点 在C面试时&#xff0c;经常被问到如果高效获取线程ID&#xff0c;但不少同学都不知道如何回答。 重点是通过__thread关键字。 重点内容 视频讲解&#xff1a;《CLinux编程进阶&#xff1a;从0实现muduo C网络框架系列》-第8讲. C面试如何高效获取线程ID 测试获取线…

【Tauri2】011——菜单menu(2)

前言 前面简单地创建了菜单&#xff0c;接下来就来试试菜单中的action Rust中菜单项注册action AppHandle in tauri - Rusthttps://docs.rs/tauri/2.4.0/tauri/struct.AppHandle.html#method.on_menu_event这就需要用到App或者AppHandle中的方法on_menu_event #[must_use] …

架构设计基础系列:面向对象设计的原则

引言 面向对象设计&#xff08;Object-Oriented Design&#xff0c;OOD&#xff09;是软件开发中的重要概念&#xff0c;其核心在于通过对象、类、继承、封装和多态等机制&#xff0c;实现对现实世界问题的抽象和建模。OOD不仅有助于提高代码的可重用性、可维护性和可扩展性&a…

UE5学习笔记 FPS游戏制作35 使用.csv配置文件

文章目录 导入.csv要求首先创建一个结构体导入配置文件读取配置 导入 .csv要求 第一行必须包含标题 第一列的内容必须不能重复&#xff0c;因为第一列会被当成行的名字&#xff0c;在数据处理中发挥类似于字典的key的作用 当前的配置文件内容如下 首先创建一个结构体 结构…

嵌入式单片机ADC数模转换的基本方法

第一:模数转换的概述 1:模数转换的概念 一般在电路中,信号分为两种,一种是模拟信号,一种是数字信号,绝大多数传感器采集的都是模拟信号,如温度、湿度、烟雾浓度、亮度.......,但是对于计算机需要处理的数字信号,那就需要利用电路把模拟信号转换为数字信号,这个转换的…

01-Docker 安装

1、安装环境介绍 安装环境&#xff1a;Linux CentOS 7 本安装教程参考Docker官方文档&#xff0c;地址如下&#xff1a;https://docs.docker.com/engine/install/centos/ 2、卸载旧版docker 首先如果系统中已经存在旧的Docker&#xff0c;则先卸载&#xff1a; yum remove do…

Redis 的缓存雪崩、击穿、穿透及其解决办法

文章目录 Redis 的缓存雪崩、击穿、穿透及其解决办法缓存雪崩解决办法 缓存击穿解决方案 缓存穿透解决方案 Redis 的缓存雪崩、击穿、穿透及其解决办法 本篇文章回顾 Redis 当中缓存崩溃、击穿、穿透现象以及相应的解决办法&#xff0c;主要的参考资料是&#xff1a;https://w…

性能比拼: Pingora vs Nginx (My NEW Favorite Proxy)

本内容是对知名性能评测博主 Anton Putra Pingora vs Nginx Performance Benchmark: My NEW Favorite Proxy! 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准 介绍 在本视频中&#xff0c;我们将对比 Nginx 和 Pingora&#xff08;一个用于构建网络服务的 Rust 框架…

Ranger一分钟

简介 Ranger Admin&#xff1a;Web UIPolicy Admin Tool&#xff1a;定义和管理策略的模块Ranger Plugins&#xff1a;HDFS、Hive、HBase、Kafka、Storm、YARNRanger UserSync&#xff1a; LDAP、Active DirectoryRanger KMS&#xff1a;管理和保护数据加密的密钥 加密密钥管理…

STM32单片机入门学习——第5节: [3-1]GPIO输出

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.04.01 STM32开发板学习——第5节&#xff1a; [3-1]GPIO输出 前言开发板说明引用解答和…