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

文章目录

  • 前言
  • 正文
    • 一、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.mfbz.cn/a/203472.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Node.js】笔记整理 5 - Express框架

写在最前&#xff1a;跟着视频学习只是为了在新手期快速入门。想要学习全面、进阶的知识&#xff0c;需要格外注重实战和官方技术文档&#xff0c;文档建议作为手册使用 系列文章 【Node.js】笔记整理 1 - 基础知识【Node.js】笔记整理 2 - 常用模块【Node.js】笔记整理 3 - n…

阿里云崩溃了,为什么你没有收到补偿?【补偿领取方式放文末】

事情经过 北京时间11月27日&#xff0c;阿里云部分地域云数据库控制台访问出现异常。据悉&#xff0c;从当日09:16起&#xff0c;阿里云监控发现北京、上海、杭州、深圳、青岛、香港以及美东、美西地域的数据库产品(RDS、PolarDB、Redis等)的控制台和OpenAPI访问出现异常&…

从源代码出发,Jenkins 任务排队时间过长问题的解决过程

最近开发了一个部署相关的工具&#xff0c;使用 Jenkins 来构建应用。Jenkins 的任务从模板中创建而来。每次部署时&#xff0c;通过 Jenkins API 来触发构建任务。在线上运行时发现&#xff0c;通过 API 触发的 Jenkins 任务总是会时不时在队列中等待较长的时间。某些情况下的…

hash_hmac函数讲解

hash_hmac函数的概述 PHP中的hash_hmac函数是一种基于加密哈希算法的函数&#xff0c;用于计算消息的哈希值。它返回一个哈希值字符串&#xff0c;并且可以用于验证消息的完整性和认证。 哈希是一种将任意长度的消息映射到固定长度的值的算法。哈希函数可以将任意大小的数据转…

我若拿出这个,阁下该如何应对,整理常用的Python库!

Requests Requests是一个常用的Python第三方库&#xff0c;用于发送HTTP请求。它提供了简洁而直观的API&#xff0c;使得发送HTTP请求变得非常方便。 使用Requests库可以实现以下功能&#xff1a; 发送GET请求&#xff1a;使用requests.get(url, paramsNone, **kwargs)方法发…

Python基础语法之学习占位符

Python基础语法之学习占位符 一、代码二、效果 一、代码 name "张三" sex "男" age 10 money 12.5# 通过占位符完成拼接 print("姓名&#xff1a;%s" % name) print("姓名&#xff1a;%s,性别&#xff1a;%s" % (name, sex))text…

maven 常用命令解析

目录 maven 是什么 Maven 目录结构 maven 常用命令解析 mvn clean mvn validate mvn compile mvn test mvn package mvn verify mvn install mvn site mvn deploy maven 是什么 Maven 是一个流行的项目管理和构建工具&#xff0c;用于帮助开发人员管理 Java 项目的…

arcgis导出某个属性的栅格

选中栅格特定属性想要导出时&#xff0c;无法选中“所选图形” 【方法】spatial analyst 工具——提取分析——按属性提取

一套后台管理系统的入门级的增删改查(vue3组合式api+elemment-plus)

一、页面示意&#xff1a; 图一 图二 二、组件结构 列表组件 &#xff1a;index.vue,对应图一添加组件&#xff1a;add.vue&#xff0c;对应图二&#xff0c;用抽屉效果编辑组件&#xff1a;edit.vue&#xff0c;和添加组件的效果一个。 三、代码 1、列表组件: index.vue …

【程序员的自我修养02】初识ELF文件格式

绪论 大家好&#xff0c;欢迎来到【程序员的自我修养】专栏。正如其专栏名&#xff0c;本专栏主要分享学习《程序员的自我修养——链接、装载与库》的知识点以及结合自己的工作经验以及思考。编译原理相关知识本身就比较有难度&#xff0c;我会尽自己最大的努力&#xff0c;争取…

卷积的理解,卷积与通道的关系

神经网络中的卷积操作卷积在图像领域的功能单通道卷积多通道卷积&#xff08;1个卷积核&#xff09;多通道卷积&#xff08;多个卷积核&#xff09;总结扩展 图像处理中的卷积核恒等&#xff08;Identity&#xff09;边缘检测&#xff08;Edge detection&#xff09;锐化&#…

SAP ABAP ALV Tree 的使用

在 SAP 业务系统中&#xff0c;大量地使用到了ALV Tree 对象&#xff0c;该对象在表格基础上对同类数据 进行归类&#xff0c;并对各分类能进行数据汇总&#xff0c;如图8-10 所示。 以航班表&#xff08;SPFLI&#xff09;为例&#xff1a; &#xff08;1&#xff09;按国家…

主流数据库类型总结

前言&#xff1a;随着互联网的高速发展&#xff0c;为了满足不同的应用场景&#xff0c;数据库的种类越来越多容易混淆&#xff0c;所以有必要在此总结一下。数据库根据数据结构可分为关系型数据库和非关系型数据库。非关系型数据库中根据应用场景又可分为键值&#xff08;Key-…

深度学习之十二(图像翻译AI算法--UNIT(Unified Neural Translation))

概念 UNIT(Unified Neural Translation)是一种用于图像翻译的 AI 模型。它是一种基于生成对抗网络(GAN)的框架,用于将图像从一个域转换到另一个域。在图像翻译中,这意味着将一个风格或内容的图像转换为另一个风格或内容的图像,而不改变图像的内容或语义。 UNIT 的核心…

微服务--06--Sentinel 限流、熔断

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.微服务保护雪崩问题服务保护方案1.1.请求限流1.2.线程隔离1.3.服务熔断 2.Sentinel2.1.介绍和安装官方网站&#xff1a;[https://sentinelguard.io/zh-cn/](https…

Windows安装EMQX(搭建MQTT服务)

1、EMQX介绍 EMQ X是云原生分布式物联网接入平台。 EMQ X (Erlang/Enterprise/Elastic MQTT Broker) 是基于 Erlang/OTP 平台开发的开源物联网 MQTT 消息服务器。 Erlang/OTP是出色的软实时 (Soft-Realtime)、低延时 (Low-Latency)、分布式 (Distributed)的语言平台。 MQTT 是…

自学MySql(一)

1.安装下载 下载网址 2、将mysql的bin目录添加到环境变量&#xff08;可选&#xff09; 3、使用一下命令测试

最新消息:滴滴 P0 事故原因,原因出来了

最新消息滴滴P0故障原因&#xff0c;是由于k8s集群升级导致的&#xff0c;后面又进行版本回退&#xff0c;由于现在大型互联网公司基本都是基于K8s进行部署的&#xff0c;如果K8s集群一出问题&#xff0c;上面运行的业务Pod和运维系统全部都得宕机&#xff0c;导致没法回滚。 …

深入理解网络阻塞 I/O:BIO

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

源码安装mysql

使用源码安装mysql&#xff0c;这里选择的版本是mysql5.7.35 ,系统是Centos7.6 官网下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 下载源码压缩包 [rootlocalhost ~]# cd /opt[rootlocalhost opt]# wget https://downloads.mysql.com/archives/get/…
最新文章