EasyExcel动态列导出

news2024/11/23 12:41:18

测试代码地址:https://gitee.com/wangtianwen1996/cento-practice/tree/master/src/test/java/com/xiaobai/easyexcel/dynamiccolumn
官方文档:https://easyexcel.opensource.alibaba.com/docs/2.x/quickstart/write

一、实现方式

1、根据需要导出的列找到返回类对象属性的ExcelPropertyColumnWidth注解,最终生成需要显示的列名和每列的列宽;
2、根据需要导出的列获取Excel中的行数据;
3、添加自定义单元格拦截策略(实现com.alibaba.excel.write.handler.WriteHandler接口)和数据类型转换策略(实现com.alibaba.excel.converters.Converter接口);
4、创建Excel的Sheet页,设置第一步获取的列宽;

二、代码实现

(一)添加基础数据类型转换器(LocalDate、LocalDateTime、LocalTime、Integer)

package com.xiaobai.easyexcel.dynamiccolumn;

import cn.hutool.core.date.DateTime;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

/**
 * @author wangtw
 * @ClassName DataConverter
 * @description: 类型转换器
 * @date 2024/2/919:23
 */
public class DataConverter {

    public static class CoreConverter<T> implements Converter<T> {

        private final Class<T> tClass;

        public CoreConverter(Class<T> tClass) {
            this.tClass = tClass;
        }

        /**
         * 导出支持的类型
         * @return
         */
        @Override
        public Class supportJavaTypeKey() {
            return tClass;
        }

        /**
         * 导入支持的Excel类型
         * @return
         */
        @Override
        public CellDataTypeEnum supportExcelTypeKey() {
            return CellDataTypeEnum.STRING;
        }

        /**
         * 导入类型转换
         * @param cellData
         * @param excelContentProperty
         * @param globalConfiguration
         * @return
         * @throws Exception
         */
        @Override
        public T convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
            if (cellData.getData() instanceof LocalDate) {
                return (T) LocalDate.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            }
            if (cellData.getData() instanceof LocalTime) {
                return (T) LocalTime.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("HH:mm:ss"));
            }
            if (cellData.getData() instanceof LocalDateTime) {
                return (T) LocalDateTime.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            }
            if (cellData.getData() instanceof Integer) {
                return (T) Integer.valueOf(cellData.getStringValue());
            }
            return null;
        }

        /**
         * 导出类型转换
         * @param obj
         * @param excelContentProperty
         * @param globalConfiguration
         * @return
         * @throws Exception
         */
        @Override
        public CellData convertToExcelData(T obj, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {

            if (obj instanceof LocalDate) {
                return new CellData(((LocalDate) obj).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            }

            if (obj instanceof LocalDateTime) {
                return new CellData(((LocalDateTime) obj).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            }

            if (obj instanceof LocalTime) {
                return new CellData(((LocalTime) obj).format(DateTimeFormatter.ofPattern("HH:mm:ss")));
            }

            if (obj instanceof Integer) {
                return new CellData(String.valueOf(obj));
            }

            return null;
        }
    }

    /**
     * localDate类型转换
     */
    public static class LocalDateConverter extends CoreConverter<LocalDate> {

        public LocalDateConverter() {
            super(LocalDate.class);
        }
    }

    /**
     * localTime类型转换
     */
    public static class LocalTimeConverter extends CoreConverter<LocalTime> {

        public LocalTimeConverter() {
            super(LocalTime.class);
        }
    }

    /**
     * LocalDateTime类型转换
     */
    public static class LocalDateTimeConverter extends CoreConverter<LocalDateTime> {

        public LocalDateTimeConverter() {
            super(LocalDateTime.class);
        }
    }

    /**
     * Integer
     */
    public static class IntegerConverter extends CoreConverter<Integer> {

        public IntegerConverter() {
            super(Integer.class);
        }
    }
}

(二)导出实现代码

package com.xiaobai.easyexcel.dynamiccolumn;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sun.istack.internal.NotNull;
import jdk.nashorn.internal.runtime.regexp.joni.ast.StringNode;
import org.apache.poi.util.IOUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;

/**
 * @author wangtw
 * @ClassName DynamicColumnExport
 * @description: EasyExcel动态列导出
 * @date 2024/2/918:07
 */
public class DynamicColumnExport {

    /**
     *
     * @param d 查询数据方法参数
     * @param vClass 返回类型
     * @param getDataFun 查询数据函数式接口
     * @param outputStream 输出流
     * @param includeColumns 需要导出的列
     * @param writeHandlerList 自定义拦截器
     * @param converterList 自定义数据格式化转换器
     * @param <D>
     * @param <U>
     * @param <V>
     */
    public static <D, U, V> void export(D d, Class<V> vClass,
                                        @NotNull Function<D, List<V>> getDataFun,
                                        @NotNull OutputStream outputStream,
                                        @NotNull List<String> includeColumns,
                                        @Nullable List<? extends WriteHandler> writeHandlerList,
                                        @Nullable List<? extends Converter> converterList) {


        /**
         * 1、根据需要导出的列获取每列的列名和单元格的列宽
         */
        // 单元格宽度
        int columnIndex = 0;
        Map<Integer, Integer> columnWidthMap = new HashMap<>();
        //  获取表格头
        List<List<String>> headList = new ArrayList<>();
        List<Field> columnList = new ArrayList<>();
        Field[] declaredFields = vClass.getDeclaredFields();
        for (String includeColumn : includeColumns) {
            Optional<Field> includeColumnOptional = Arrays.stream(declaredFields).filter(f -> f.getName().equals(includeColumn))
                    .findFirst();
            if (includeColumnOptional.isPresent()) {
                Field field = includeColumnOptional.get();
                field.setAccessible(true);
                ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
                if (!ObjectUtils.isEmpty(excelProperty)) {
                    // 列名
                    String[] columNameArray = excelProperty.value();
                    headList.add(Arrays.asList(columNameArray));

                    // 可导出的列
                    columnList.add(field);

                    // 保存每列的宽度
                    ColumnWidth columnWidth = field.getAnnotation(ColumnWidth.class);
                    columnWidthMap.put(columnWidth == null ? -1 : columnWidth.value(), columnIndex++);
                }
            }
        }

        /**
         * 2、根据需要导出的列获取需要显示的数据
         */
        List<List<Object>> exportDataList = new ArrayList<>();
        // 执行函数式接口获取需要导出的数据
        List<V> dataCollection = getDataFun.apply(d);

        for (V v : dataCollection) {

            // 拼装每行的数据
            List<Object> dataSubList = new ArrayList<>();
            for (Field field : columnList) {
                try {
                    Object columnValue = field.get(v);
                    dataSubList.add(Optional.ofNullable(columnValue).orElse(""));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            exportDataList.add(dataSubList);
        }

        // 表格处理策略
        ExcelWriterBuilder writerBuilder = EasyExcel.write(outputStream).head(headList);

        if (!ObjectUtils.isEmpty(writeHandlerList)) {
            writeHandlerList.forEach(writerBuilder::registerWriteHandler);
        }

        // 类型转换策略
        if (ObjectUtils.isEmpty(converterList)) {
            writerBuilder.registerConverter(new DataConverter.IntegerConverter());
            writerBuilder.registerConverter(new DataConverter.LocalDateConverter());
            writerBuilder.registerConverter(new DataConverter.LocalTimeConverter());
            writerBuilder.registerConverter(new DataConverter.LocalDateTimeConverter());
        } else {
            converterList.forEach(writerBuilder::registerConverter);
        }

        // 创建Sheet页
        String sheetName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();
        // 设置列宽
        writeSheet.setColumnWidthMap(columnWidthMap);

        // 把数据写入到Sheet页中
        ExcelWriter excelWriter = writerBuilder.build();
        excelWriter.write(exportDataList, writeSheet);

        // 关闭流
        excelWriter.finish();

        // 关闭输出流
        IOUtils.closeQuietly(outputStream);

    }
}

三、测试

(一)设置表头和内容策略

    /**
     * 设置颜色
     * @return
     */
    private WriteHandler setColor() {
        // 头的策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 背景设置为白色
        headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short)18);
        headWriteCellStyle.setWriteFont(headWriteFont);
        // 内容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
        contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        WriteFont contentWriteFont = new WriteFont();
        // 字体大小
        contentWriteFont.setFontHeightInPoints((short)15);
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
        HorizontalCellStyleStrategy horizontalCellStyleStrategy =
                new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
        return horizontalCellStyleStrategy;
    }

(二)设置单元格样式策略

    public static class CellHandler implements CellWriteHandler {

        @Override
        public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
        }

        @Override
        public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

        }

        @Override
        public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

        }

        @Override
        public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> list, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
            CellStyle cellStyle = cell.getCellStyle();
            cellStyle.setBorderBottom(BorderStyle.THIN);
            cellStyle.setBorderTop(BorderStyle.THIN);
            cellStyle.setBorderLeft(BorderStyle.THIN);
            cellStyle.setBorderRight(BorderStyle.THIN);
        }
    }

(四)实体类

可选择某几种属性进行导出

package com.xiaobai.easyexcel.dynamiccolumn;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

/**
 * @author wangtw
 * @ClassName DynamicData
 * @description: EasyExcel动态列导出
 * @date 2024/2/914:59
 */
@AllArgsConstructor
@Builder
@Data
public class DynamicData {

    @ExcelProperty("id")
    private Integer id;

    @ColumnWidth(30)
    @ExcelProperty("姓名")
    private String realName;

    @ColumnWidth(30)
    @ExcelProperty("性别")
    private String sex;

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

    @ColumnWidth(50)
    @ExcelProperty("单位名称")
    private String orgName;

    @ColumnWidth(50)
    @ExcelProperty("部门名称")
    private String deptName;
}

(五)数据准备

    private List<DynamicData> getData(String condition) {
        List<DynamicData> dynamicDataList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            DynamicData data = DynamicData.builder()
                    .id(i)
                    .age(random.nextInt(60))
                    .sex("男")
                    .realName("王" + i)
                    .orgName("单位" + i)
                    .deptName("部门" + i)
                    .build();
            dynamicDataList.add(data);

        }
        return dynamicDataList;
    }

(六)测试代码(选择orgName、deptName、realName、sex进行导出)

    @Test
    public void exportTest() {
        File file = new File("测试.xlsx");
        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        List<String> includeColumns = new ArrayList<>();
        includeColumns.add("orgName");
        includeColumns.add("deptName");
        includeColumns.add("realName");
        includeColumns.add("sex");

        List<WriteHandler> writeHandlers = new ArrayList<>();
        writeHandlers.add(setColor());

        writeHandlers.add(new CellHandler());

        DynamicColumnExport.export(null, DynamicData.class,
                this::getData, outputStream, includeColumns, writeHandlers, null);
    }

(七)效果

在这里插入图片描述

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

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

相关文章

获IROS最佳移动操作论文提名|通研院提出首个实现连续操作任务的空中具身智能机器人CORVUS(渡鸦)

论文导读 本文介绍了通研院机器人实验室发表于2023年国际机器人顶级会议IROS上的论文&#xff0c;题为《Sequential Manipulation Planning for Over-actuated Unmanned Aerial Manipulators》[1]。文章介绍了一种可以实现空中全向平稳飞行的过驱动空间机械臂平台Coordinated …

在计算机/移动设备上恢复已删除视频的 10 个数据恢复工具

视频在网上疯传&#xff0c;我们都观看或创建视频&#xff0c;并将我们最喜欢的视频保存在硬盘上。如果我们丢失了一些重要的视频&#xff0c;那将是非常令人心碎的。但是今天&#xff0c;恢复已删除的视频变得更加容易。删除的视频在被新数据覆盖之前并没有真正从您的存储驱动…

创建你的第一个Vue项目(小白专享版本)

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

算法竞赛进阶指南——基本算法(倍增)

ST表 可以求区间最大、最小、gcd、lcm&#xff0c;符合 f(a, a) a都可以 求区间最值&#xff0c;一个区间划分成两段 f[i][j]: 从i开始&#xff0c;长度为2^j的区间最值 #include<iostream> #include<cmath> using namespace std; const int N 1e6 10; int n,…

【书生·浦语实战营】体系介绍

文章目录 从大模型到应用的路径开放体系内容万卷预训练微调部署智能体 从大模型到应用的路径 开放体系 内容 数据&#xff1a;书生万卷预训练&#xff1a;interLM-Train微调&#xff1a;XTuner&#xff0c;全参数、低成本微调部署&#xff1a;LMDeploy评测&#xff1a;OpenCo…

Windows快捷键大全(包含语音输入、剪切板历史快捷键)

最近发现了微软官网上给出的快捷键大全&#xff0c;并且使用了其中几个新的键盘快捷键&#xff08;语音输入、剪切板历史&#xff09;&#xff0c;确实方便快捷&#xff0c;所以写个博客记录分享一下。 注&#xff1a;windows快捷键大全微软官方已经给出&#xff0c;此处不再赘…

让Python遇上Office--从编程入门到自动化办公实践

最近仔细的学习了这本《让Python遇上Office》的书&#xff0c;同时把我的学习进程与心得录制了同步视频。 到今天终于把全部90集完成&#xff0c;并且上传到下面的视频平台了&#xff0c;欢迎大家观看并指正&#xff01; 西瓜视频&#xff1a;https://www.ixigua.com/7300628…

服务异步通信

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

ClickHouse时区

clickhouse数据库的时间是UTC时间。服务器默认的是上海时间。 sudo vim /etc/clickhouse-server/config.xml clickhouse默认的时区是注释的就是UTC时间 %F 表示日期&#xff0c;格式为 YYYY-MM-DD。%T 表示时间&#xff0c;格式为 HH:MM:SS。 因此&#xff0c;formatDateT…

three.js 细一万倍教程 从入门到精通(一)

目录 一、three.js开发环境搭建 1.1、使用parcel搭建开发环境 1.2、使用three.js渲染第一个场景和物体 1.3、轨道控制器查看物体 二、three.js辅助设置 2.1、添加坐标轴辅助器 2.2、设置物体移动 2.3、物体的缩放与旋转 缩放 旋转 2.4、应用requestAnimationFrame …

windows11 MSYS2下载安装教程

MSYS2 可以理解为在windows平台上模拟linux编程环境的开源工具集 当前环境&#xff1a;windows11 1. 下载 官网地址可下载最新版本&#xff0c;需要科学上网 https://www.msys2.org/ 2. 安装 按照正常安装软件流程一路next就可以 打开 3. 配置环境 网上很多教程提到需…

回归预测模型:MATLAB岭回归和Lasso回归

1. 岭回归和Lasso回归的基本原理 1.1 岭回归&#xff1a; 岭回归&#xff08;Ridge Regression&#xff09; 是一种用于共线性数据分析的技术。共线性指的是自变量之间存在高度相关关系。岭回归通过在损失函数中添加一个L2正则项&#xff08; λ ∑ j 1 n β j 2 \lambda \s…

【维生素C语言】附录:strlen 函数详解

写在前面&#xff1a;本篇将专门为 strlen 函数进行讲解&#xff0c;总结了模拟实现 strlen 函数的三种方法&#xff0c;并对其进行详细的解析。手写库函数是较为常见的面试题&#xff0c;希望通过本篇博客能够加深大家对 strlen 的理解。 0x00 strlen函数介绍 【百度百科】str…

【原创 附源码】Flutter安卓及iOS海外登录--Facebook登录最详细流程

最近接触了几个海外登录的平台&#xff0c;踩了很多坑&#xff0c;也总结了很多东西&#xff0c;决定记录下来给路过的兄弟坐个参考&#xff0c;也留着以后留着回顾。更新时间为2024年2月12日&#xff0c;后续集成方式可能会有变动&#xff0c;所以目前的集成流程仅供参考&…

计算机毕业设计基于的农村蔬菜销售系统SSM

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; vue mybatis Maven mysql5.7或8.0等等组成&#xff0c;B…

力扣题目训练(8)

2024年2月1日力扣题目训练 2024年2月1日力扣题目训练404. 左叶子之和405. 数字转换为十六进制数409. 最长回文串116. 填充每个节点的下一个右侧节点指针120. 三角形最小路径和60. 排列序列 2024年2月1日力扣题目训练 2024年2月1日第八天编程训练&#xff0c;今天主要是进行一些…

c入门第十篇——指针入门

一句话来说: 指针就是存储了内存地址值的变量。 在前面讨论传值和传址的时候&#xff0c;我们就已经开始使用了指针来传递地址。 在正式介绍指针之前&#xff0c;我们先来简单了解一下内存。内存可以简单的理解为一排连续的房子的街道&#xff0c;每个房子都有自己的地址&#…

中国电子学会2019年12月份青少年软件编程Scratch图形化等级考试试卷三级真题(选择题、判断题)

一、单选题(共 25 题&#xff0c;每题 2 分&#xff0c;共 50 分) 1.怎样修改图章的颜色&#xff1f;&#xff08; &#xff09; A. 只需要一个数字来设置颜色 B. 设置 RGB 的值 C. 在画笔中设置颜色、饱和度、亮度 D. 在外观中设置或修改角色颜色特效 2.以下程序的执…

数据分析入门指南:用 Python 开启数据之旅

文章目录 前言发现宝藏为什么选择 Python 进行数据分析&#xff1f;准备工作数据分析基础1. 数据加载2. 数据探索3. 数据清洗4. 数据可视化 探索更多可能性好书推荐总结 前言 为了巩固所学的知识&#xff0c;作者尝试着开始发布一些学习笔记类的博客&#xff0c;方便日后回顾。…

接口测试06 -- pytest接口自动化封装Loggin实战

1. 接口关键字封装 1.1 基本概念 接口关键字封装是指:将接口测试过程中常用的操作、验证封装成可复用的关键字(或称为函数、方法),以提高测试代码的可维护性和可复用性。 1.2 常见的接口关键字封装方式 1. 发送请求:封装一个函数,接受参数如请求方法、URL、请求头、请求…