【EasyExcel实践】万能导出,一个接口导出多张表以及任意字段(可指定字段顺序)

news2024/10/7 11:29:51

文章目录

  • 前言
  • 正文
    • 一、POM依赖
    • 二、核心Java文件
      • 2.1 自定义表头注解 ExcelColumnTitle
      • 2.2 自定义标题头的映射接口
      • 2.3 自定义有序map存储表内数据
      • 2.4 表头工厂
      • 2.5 表flag和表头映射枚举
      • 2.6 测试用的实体
        • 2.6.1 NameAndFactoryDemo
        • 2.6.2 StudentDemo
      • 2.7 启动类
      • 2.8 测试控制器
    • 三、测试
      • 测试1
      • 测试2
      • 测试3
      • 测试4

前言

日前,看到一个比较奇怪的导出功能。

需要根据不同的页面,以及指定不同的字段列表(任意顺序),然后导出对应的表格。

先假设一个场景:
假如你的系统有多个列表展示页,每页中可以依据筛选条件,调整展示的列的个数,顺序等。然后要求导出的时侯,导出一摸一样的格式。也就是“所见即所得”的表格。

那么基于以上场景,我们就来考虑下如何实现?
本文就是对以上场景功能的一个实现。目前仅支持单sheet,不支持数据聚合等。

正文

本文项目环境:
java 8,springboot2.2.0, easyexcel

一、POM依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.2.0.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.2</version>
    </dependency>


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

        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

</dependencies>

二、核心Java文件

此处粘贴全部的java文件
在这里插入图片描述

2.1 自定义表头注解 ExcelColumnTitle

package headbean;

import java.lang.annotation.*;

/**
 * 列名标题注解,标注列的标题
 *
 * @author feng
 */
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelColumnTitle {
    String value();
}

2.2 自定义标题头的映射接口

此接口仅仅是用于规范实体,以及用于辅助实现导出功能。

package headbean;

/**
 * excel头部映射接口,用于规范导出的实体类
 *
 * @author feng
 */
public interface ExcelHeadMapInterface {
}

2.3 自定义有序map存储表内数据

这个是表格导出时,字段数量,顺序的关键。

package headbean;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 表格数据专用的map,带顺序,而且初始化的时候,依据指定的表头变量字段名确定导出数据的顺序
 *
 * @author feng
 */
public class ExcelDataLinkedHashMap<V> extends LinkedHashMap<String, V> {

    private static final long serialVersionUID = -8554095999151235982L;

    /**
     * 头部字段名缓存
     */
    private final Set<String> headColumnNamesCache;

    /**
     * ExcelDataLinkedHashMap构造器
     *
     * @param headColumnNames 表头字段变量名,例如:[name,studentNo,age,className]
     */
    public ExcelDataLinkedHashMap(List<String> headColumnNames) {
        // 字段名去重
        headColumnNames = headColumnNames.stream().distinct().collect(Collectors.toList());
        // 构建字段名缓存
        this.headColumnNamesCache = new HashSet<>(headColumnNames);
        // 指定列数据排列顺序
        for (String headColumnName : headColumnNames) {
            this.put(headColumnName, null);
        }
    }

    @Override
    public V put(String key, V value) {
        // 只保存字段名缓存中的key以及value
        if (headColumnNamesCache.contains(key)) {
            return super.put(key, value);
        }
        return null;
    }
}

2.4 表头工厂

负责实现初始化表头字段名,以及后期使用时,从中获取表头信息。
核心功能是解析自定义的表头注解。

package factory;

import enums.ExcelHeadBeanFlagEnum;
import headbean.ExcelColumnTitle;
import headbean.ExcelHeadMapInterface;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;


public class ExcelHeadMapFactory {
    /**
     * 全局表头名映射,ExcelHeadMapInterface实现类型为key,它内部变量的变量名和中文名映射为value
     */
    private static final Map<Class<? extends ExcelHeadMapInterface>, Map<String, String>> HEAD_NAME_MAP = new HashMap<>();

    public static void addHeadClass(Class<? extends ExcelHeadMapInterface> headClass) {
        HEAD_NAME_MAP.put(headClass, mapToPrepareHead(headClass));
    }

    public static Map<String, String> getHeadMap(Class<? extends ExcelHeadMapInterface> headClass) {
        return HEAD_NAME_MAP.get(headClass);
    }

    public static Map<String, String> getHeadMapByFlag(String flag) {
        return getHeadMap(ExcelHeadBeanFlagEnum.getHeadClass(flag));
    }

    private static Map<String, String> mapToPrepareHead(Class<?> excelHeadClass) {
        Map<String, String> namedMap = new HashMap<>();
        Field[] declaredFields = excelHeadClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            boolean annotationPresent = declaredField.isAnnotationPresent(ExcelColumnTitle.class);
            if(annotationPresent) {
                ExcelColumnTitle excelProperty = declaredField.getAnnotation(ExcelColumnTitle.class);
                String chineseFieldName = excelProperty.value();
                // 保存字段名和中文变量名
                namedMap.put(declaredField.getName(), chineseFieldName);
            }
        }

        return namedMap;
    }
}

2.5 表flag和表头映射枚举

这个枚举,如果你的系统这类功能很多。可以设计为数据库的方式做映射。然后以查字典表的方式,来处理。当然使用枚举大概率是够用了。

package enums;

import headbean.ExcelHeadMapInterface;
import headbean.NameAndFactoryDemo;
import headbean.StudentDemo;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;

/**
 * 表头flag枚举,映射flag与对应的实体类型;主要是可以根据flag找到对应实体类型。
 *
 * @author feng
 */
@Getter
@AllArgsConstructor
public enum ExcelHeadBeanFlagEnum {

    NAME_AND_FACTORY_DEMO("NameAndFactoryDemo", NameAndFactoryDemo.class),
    STUDENT_DEMO("StudentDemo", StudentDemo.class)
    ;

    private final String flag;
    private final Class<? extends ExcelHeadMapInterface> headClass;

    public static Class<? extends ExcelHeadMapInterface> getHeadClass(String flag) {
        return Arrays.stream(values()).
                filter(bean -> bean.getFlag().equals(flag))
                .findFirst()
                .orElseThrow(RuntimeException::new)
                .getHeadClass();
    }
}

2.6 测试用的实体

2.6.1 NameAndFactoryDemo
package headbean;

import lombok.Data;

@Data
public class NameAndFactoryDemo implements ExcelHeadMapInterface {
    @ExcelColumnTitle("名字")
    private String name;
    @ExcelColumnTitle("工厂")
    private String factory;
}

2.6.2 StudentDemo
package headbean;

import lombok.Data;

@Data
public class StudentDemo implements ExcelHeadMapInterface {
    @ExcelColumnTitle("姓名")
    private String name;
    @ExcelColumnTitle("年龄")
    private Integer age;
    @ExcelColumnTitle("学号")
    private String studentNo;
    @ExcelColumnTitle("班级")
    private String className;
}

2.7 启动类

主要是项目启动后,注册表头数据到内存。

package org.feng;

import factory.ExcelHeadMapFactory;
import headbean.ExcelHeadMapInterface;
import headbean.NameAndFactoryDemo;
import headbean.StudentDemo;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

@SpringBootApplication
public class ExcelDemoApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(ExcelDemoApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        List<Class<? extends ExcelHeadMapInterface>> needRegisterExcelHeadClassList = new ArrayList<>();
        needRegisterExcelHeadClassList.add(NameAndFactoryDemo.class);
        needRegisterExcelHeadClassList.add(StudentDemo.class);
        needRegisterExcelHeadClassList.forEach(ExcelHeadMapFactory::addHeadClass);
    }
}

2.8 测试控制器

package org.feng;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.metadata.WriteSheet;
import enums.ExcelHeadBeanFlagEnum;
import factory.ExcelHeadMapFactory;
import headbean.ExcelDataLinkedHashMap;
import headbean.ExcelHeadMapInterface;
import headbean.NameAndFactoryDemo;
import headbean.StudentDemo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.function.BiFunction;

@Controller
@RequestMapping("/excel")
public class ExcelDemoController {

    @GetMapping("/exportDy")
    public String exportDy(@RequestParam("flag")String flag,
                           @RequestParam("table")List<String> table, HttpServletResponse response) throws IOException {
        String fileName = System.currentTimeMillis() + ".xlsx";

        Class<? extends ExcelHeadMapInterface> headClass = ExcelHeadBeanFlagEnum.getHeadClass(flag);
        Map<String, String> namedPrepareHeadMap = ExcelHeadMapFactory.getHeadMap(headClass);
        Map<String, String> head = new LinkedHashMap<>();
        for (String fieldName : table) {
            head.put(namedPrepareHeadMap.get(fieldName), fieldName);
        }

        List<Map<String, String>> excelDataList = new ArrayList<>();
        excelDataList.add(head);

        // 制造假数据
        for (BiFunction<Class<? extends ExcelHeadMapInterface>, List<Map<String, String>>, Boolean> biFunction : bizList) {
            Boolean applied = biFunction.apply(headClass, excelDataList);
            if(applied) {
                break;
            }
        }

        byte[] bytes = easyOut(excelDataList);
        response.setHeader("Content-disposition", "attachment;filename=" + fileName);
        response.setContentType("application/x-msdownload");
        response.setCharacterEncoding("utf-8");
        response.getOutputStream().write(bytes);
        response.getOutputStream().flush();

        return "success";
    }

    static final List<BiFunction<Class<? extends ExcelHeadMapInterface>, List<Map<String, String>>, Boolean>> bizList = new ArrayList<>();

    @PostConstruct
    private void init() {
        bizList.add(this::genStudentDemoData);
        bizList.add(this::genNameAndFactoryDemoData);
    }


    private boolean genStudentDemoData(Class<? extends ExcelHeadMapInterface> headClass, List<Map<String, String>> excelDataList) {
        if(headClass == StudentDemo.class) {
            Map<String, String> headMap = excelDataList.get(0);
            for (int i = 0; i < 5; i++) {
                Collection<String> fieldNames = headMap.values();
                Map<String, String> data = new ExcelDataLinkedHashMap<>(new ArrayList<>(fieldNames));
                excelDataList.add(data);
                data.put("name", "张三"+(i+1));
                data.put("age", "年龄"+(i+1));
                data.put("studentNo", "学号"+(i+1));
                data.put("className", "班级"+(i+1));
            }
            return true;
        }
        return false;
    }

    private boolean genNameAndFactoryDemoData(Class<? extends ExcelHeadMapInterface> headClass, List<Map<String, String>> excelDataList) {
        if(headClass == NameAndFactoryDemo.class) {
            Map<String, String> headMap = excelDataList.get(0);
            for (int i = 0; i < 5; i++) {
                Collection<String> fieldNames = headMap.values();
                Map<String, String> data = new ExcelDataLinkedHashMap<>(new ArrayList<>(fieldNames));
                excelDataList.add(data);
                data.put("name", "张三"+(i+1));
                data.put("factory", "工厂"+(i+1));
            }
            return true;
        }
        return false;
    }



    /**
     * 导出数据(单sheet)
     * @param exportData key 是sheet名称,value是每个sheet里面的数据,支持自定义表头
     */
    public static byte[] easyOut(List<Map<String, String>> exportData) {
        return easyOut(Collections.singletonMap("Sheet", exportData));
    }

    /**
     * 导出数据(多sheet)
     * @param exportData key 是sheet名称,value是每个sheet里面的数据,可以自定义
     */
    public static byte[] easyOut(Map<String, List<Map<String, String>>> exportData) {
        // 导出数据
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        com.alibaba.excel.ExcelWriter excelWriter = EasyExcel.write(out).build();
        int i=0;
        for (Map.Entry<String, List<Map<String, String>>> entry: exportData.entrySet()) {
            WriteSheet writeSheet = EasyExcel.writerSheet(i, entry.getKey()).head(head(entry.getValue().get(0))).build();
            i++;
            excelWriter.write(data(entry.getValue(), true), writeSheet);
        }
        excelWriter.finish();

        return out.toByteArray();
    }

    private static List<List<String>> head(Map<String, String> cellData) {
        List<List<String>> head = new ArrayList<>();
        for (String key: cellData.keySet()) {
            head.add(Collections.singletonList(key));
        }
        return head;
    }

    private static List<List<String>> data(List<Map<String, String>> sheetData, boolean skipHead) {
        List<List<String>> data = new ArrayList<>();
        for (int i = 0; i < sheetData.size(); i++) {
            if(i == 0 && skipHead) {
                continue;
            }
            data.add(new ArrayList<>(sheetData.get(i).values()));
        }
        return data;
    }
}

三、测试

测试1

http://localhost:8080/excel/exportDy?flag=StudentDemo&table=name,studentNo,age,className

获得的表格内容为:
在这里插入图片描述

测试2

http://localhost:8080/excel/exportDy?flag=StudentDemo&table=name,studentNo,age

获得的表格内容为:
在这里插入图片描述

测试3

http://localhost:8080/excel/exportDy?flag=NameAndFactoryDemo&table=factory,name
获得的表格内容为:
在这里插入图片描述

测试4

http://localhost:8080/excel/exportDy?flag=StudentDemo&table=className,name,studentNo
获得的表格内容为:
在这里插入图片描述

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

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

相关文章

关于UART、RS232、RS485的区别与理解

1、UART规定了数据的格式&#xff0c;但没有规定电气的特性&#xff0c;多少伏为高电平&#xff0c;多少伏为低电平。通常我们使用单片机与电脑通信时需要一个USB转TTL模块。 2、UART存在抗干扰能力差、通信距离短&#xff08;一般不超过2~3m&#xff09;,由此产生基于串口的R…

2023 BUCT 计算方法实验报告

前言 Textlive版本&#xff1a;2023 textstudio版本&#xff1a;4.6.3 名字和日期在以下地方修改: 图片下载地址; figures.zip LiangCha_Xyy/Source - Gitee.com 如下图&#xff0c;.tex文件和figures文件夹放在同一路径下即可 .tex代码 \documentclass[UTF8]{ctexart} \usep…

Python数据分析从入门到进阶:模型评估和选择(含代码)

引言 之前我们介绍了机器学习的一些基础性工作&#xff0c;介绍了如何对数据进行预处理&#xff0c;接下来我们可以根据这些数据以及我们的研究目标建立模型。那么如何选择合适的模型呢&#xff1f;首先需要对这些模型的效果进行评估。本文介绍如何使用sklearn代码进行模型评估…

常见算法和Lambda表达式

常见算法和Lambda 常见算法 查找算法 基本查找 从0索引开始逐个查找 代码演示: package Search;import java.util.ArrayList;public class BasicSearch {public static void main(String[] args) {int[] arr {1,2,3,4,5,6,3,4,1,3};ArrayList<Integer> resArr ba…

第二证券:机构争分夺秒抢滩 金融大模型落地为时尚早

本年以来&#xff0c;大模型席卷金融业&#xff0c;一夜之间&#xff0c;简直悉数金融场景都在探索适配大模型接口。但是&#xff0c;志向丰满&#xff0c;实践骨感。有大型金融组织IT部分人士比方&#xff0c;金融大模型从战略规划到安顿落地&#xff0c;有着从“卖家秀”走到…

Docker 下载加速

文章目录 方式1&#xff1a;使用 网易数帆容器镜像仓库进行下载。方式2&#xff1a;配置阿里云加速。方式3&#xff1a;方式4&#xff1a;结尾注意 Docker下载加速的原理是&#xff0c;在拉取镜像时使用一个国内的镜像站点&#xff0c;该站点已经缓存了各个版本的官方 Docker 镜…

两台电脑如何快速传输几百G文件,这款文件传输软件真快

当我们需要传输数百GB的文件时&#xff0c;使用传统工具对于大型文件传输来说往往效率低下。这些方法可能需要数小时&#xff0c;甚至数天才能完成传输。然而&#xff0c;现代生活和工作中&#xff0c;我们经常需要以更快速、更高效的方式传输大文件&#xff0c;无论是因为工作…

连锁零售企业如何提高异地组网的稳定性?

随着数字化时代的到来&#xff0c;连锁零售企业面临着日益复杂和多样化的网络挑战。连锁零售企业是在不同地理位置拥有分支机构和零售店&#xff0c;可能同城或异地&#xff0c;需要确保各个地点之间的网络连接稳定和可靠。但由于不同地区的网络基础设施差异、网络延迟和带宽限…

应用密码学期末复习(3)

目录 第三章 现代密码学应用案例 3.1安全电子邮件方案 3.1.1 PGP产生的背景 3.2 PGP提供了一个安全电子邮件解决方案 3.2.1 PGP加密流程 3.2.2 PGP解密流程 3.2.3 PGP整合了对称加密和公钥加密的方案 3.3 PGP数字签名和Hash函数 3.4 公钥分发与认证——去中心化模型 …

【小布_ORACLE笔记】Part11-5 RMAN Backups

【小布_ORACLE笔记】Part11-5 RMAN Backups 文章目录 【小布_ORACLE笔记】Part11-5 RMAN Backups1. 增量备份&#xff08;Incremental Backups)1.1差异增量备份&#xff08;Differential Incremental Backup&#xff09;1.2累积增量备份&#xff08;Cumulative Incremental Bac…

跨界融合,科技耕耘:MTX基金公司与ICG共塑全球农业科技新景观

在全球经济快速发展的当下&#xff0c;农业科技创新成为了社会进步的重要推动力。MTX基金公司对ICG的投资是在这一背景下的战略决策&#xff0c;不仅寻求经济效益&#xff0c;更承载着改善全球农业生产、实现食品安全与环境保护的使命。 1、战略投资&#xff1a;文化情怀与全球…

Python三十个常见的脚本汇总

1、冒泡排序 2、计算x的n次方的方法 3、计算a*a b*b c*c …… 4、计算阶乘 n! 5、列出当前目录下的所有文件和目录名 6、把一个list中所有的字符串变成小写&#xff1a; 7、输出某个路径下的所有文件和文件夹的路径 8、输出某个路径及其子目录下的所有文件路径 9、输出某个路…

【刷题笔记】长度最小的子数组||二分查找||边界||数组

长度最小的子数组 1 题目描述 https://leetcode.cn/problems/minimum-size-subarray-sum/ 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, …, numsr-1, numsr] &#xff0c;并返回…

如何用管理项目的方式管理个人任务

同样一份工作&#xff0c;有的人做起来得心应手&#xff0c;条理清晰&#xff0c;有的人却是手忙脚乱&#xff0c;苦不堪言。在凡事皆项目的时代&#xff0c;用管理项目的方法管理自己的任务&#xff0c;可能会让你的工作事半功倍。 工欲善其事&#xff0c;必先利其器&#xf…

webshell之API免杀

ScriptEngineManager命令执行免杀 ScriptEngineManager执行js代码 利用ScriptEngineManager可以执行js命令&#xff0c;但是由于一般情况下&#xff0c;即便能运行js代码也不一定能执行系统命令。因为一般情况下js执行系统命令主要是依靠两种方式&#xff0c;IE的ActiveX插件…

「幻醒蓝」可视化主题套件|融合天空的清澈与海洋的深邃

现如今&#xff0c;数据可视化已成为信息传递的重要手段之一。在这样一个信息爆炸的时代&#xff0c;向人们传达正确的信息显得尤为重要。为此&#xff0c;可视化主题套件应运而生&#xff0c;提供了一种多样化的、可视化的方式来展示数据。不同的主题套件能够适应不同的信息传…

Springboot如何快速生成分页展示以及统计条数

这是表结构&#xff1a; 前置知识&#xff1a; 分页查询公式&#xff08;&#xff09;&#xff1a; -- 推导一个公式 -- select * from emp -- order by empno -- limit 每页显示记录数 * (第几页-1)&#xff0c;每页显示记录数 统计条数公式&#xff1a; select count…

力扣5.最长回文子串

题目描述 思路 1.能够反复利用已判断好的回文子串 2.当子串s[i1,j-1]是回文子串时&#xff0c;只要s[i]s[j]&#xff0c;那么s[i,j]也会是回文子串 3.用好动态规划&#xff0c;具体解释在代码注释里 代码 class Solution {public String longestPalindrome(String s) {int…

【网络安全】meterpreter攻击实战

1.meterpreter 攻击成功后可以做什么指令&#xff1f; 远程控制命令执行摄像头监控密码获取创建后门用户破坏篡改系统。 2.创建后门用户并开启远程连接&#xff1a; net user zhangsan 123456/add && net localgroup adminstrators zhangsan/add exit run getgul -…

Linux 代码编辑器:vim

vim 编辑器的简介 vi / vim 都是多模式编辑器&#xff0c;不同的是 vim 是 vi 的升级版本&#xff0c;他不仅兼容 vi 的所有指令&#xff0c;而且还有一些新的特性在里面。比如语法高亮&#xff0c;可视化操作不仅可以在终端运行&#xff0c;也可以在 windows&#xff0c;mac …