利用EasyExcel实现简易Excel导出

目标

通过注解形式完成对一个方法返回值的通用导出功能

工程搭建

pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.13</version>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>export</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>export</name>
    <description>export</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.7.6</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--    EasyExcel    -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>4.0.3</version>
        </dependency>
        <!--    aspect    -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>
        <!--    util      -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.15.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.4</version>
        </dependency>

    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.example.export.ExportApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

核心代码

CusExport

package com.example.export.annotation;

import java.lang.annotation.*;

/**
 * @author PC
 * 基于EasyExcel实现动态列导出
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CusExport {
    /**
     * 导出数据对象
     */
    Class<?> dataClass() default Void.class;

    /**
     * 是否是动态的
     *
     * @return true 是 false 否
     */
    boolean dynamicFlag() default false;

    /**
     * 文件名,未指定取实体类名,无实体类取 getDefaultFileName()
     *
     * @return 文件名
     */
    String fileName() default "";
}

CusExportAspect

package com.example.export.aspect;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.example.export.annotation.CusExport;
import com.example.export.config.ExportProperties;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.*;

/**
 * @author PC
 * 导出切面
 */
@Aspect
@Component
public class CusExportAspect {
    private final static Logger logger = LoggerFactory.getLogger(CusExportAspect.class);

    private final ExportProperties exportProperties;

    @Autowired
    public CusExportAspect(ExportProperties exportProperties) {
        this.exportProperties = exportProperties;
    }

    @AfterReturning(pointcut = "@annotation(com.example.export.annotation.CusExport)", returning = "result")
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) throws IOException {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        CusExport cusExport = methodSignature.getMethod().getAnnotation(CusExport.class);
        HttpServletResponse response = getHttpServletResponse();
        // 这里URLEncoder.encode可以防止中文乱码
        String generatorFileName = StringUtils.isEmpty(cusExport.fileName()) ? exportProperties.getDefaultFileName() : cusExport.fileName();
        String fileName = URLEncoder.encode(generatorFileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        if (!cusExport.dynamicFlag()) {
            EasyExcel.write(response.getOutputStream(), cusExport.dataClass())
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet(exportProperties.getDefaultSheetName())
                    .doWrite((Collection<?>) result);
        } else {
            this.dealSpecified(cusExport, result, fileName, response);
        }
    }

    private static HttpServletResponse getHttpServletResponse() {
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attr != null;
        HttpServletResponse response = attr.getResponse();
        assert response != null;
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        return response;
    }

    @SuppressWarnings("unchecked")
    private void dealSpecified(CusExport cusExport, Object result, String fileName, HttpServletResponse response) throws IOException {
        if (Objects.isNull(cusExport) || Objects.isNull(result) || StringUtils.isEmpty(fileName)) {
            logger.error("the required field is missing");
        }
        logger.debug("the data type is not specified");
        List<Map<String, Object>> resultList = new ArrayList<>();
        if (result instanceof List) {
            resultList = (List<Map<String, Object>>) result;
        }
        if (CollectionUtils.isEmpty(resultList)) {
            logger.info("data is empty");
        }
        List<List<String>> headList = new ArrayList<>();
        boolean fillHeadFlag = false;
        for (Map<String, Object> resultItem : resultList) {
            if (!fillHeadFlag) {
                resultItem.keySet().forEach(keyCode -> headList.add(Collections.singletonList(keyCode)));
                fillHeadFlag = true;
            }
        }

        EasyExcel.write(response.getOutputStream())
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                // 放入动态头
                .head(headList).sheet(exportProperties.getDefaultSheetName())
                .doWrite(convertMapToList(resultList, headList));
    }

    private List<List<Object>> convertMapToList(List<Map<String, Object>> mapList, List<List<String>> headList) {
        List<List<Object>> list = new ArrayList<>();
        for (Map<String, Object> map : mapList) {
            List<Object> info = new ArrayList<>();
            for (List<String> itemHeadList : headList) {
                info.add(map.get(itemHeadList.get(0)));
            }
            list.add(info);
        }
        return list;
    }
}

测试

测试代码

在对应方法添加@CusExport注解即可,导出实体类需指定dataClass,导出动态数据需指定dynamicFlag = true,如需调整生成的文件名,还可以指定fileName

TestExportServiceImpl

package com.example.export.app.service.impl;

import com.example.export.annotation.CusExport;
import com.example.export.app.service.TestExportService;
import com.example.export.domain.entity.ExcelTest;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author PC
 */
@Component
public class TestExportServiceImpl implements TestExportService {
    @Override
    @CusExport(dataClass = ExcelTest.class)
    public List<ExcelTest> generatorDataForClass() {
        List<ExcelTest> excelTestList = new ArrayList<>();
        for (int j = 0; j < 100; j++) {
            ExcelTest excelTest = new ExcelTest();
            excelTest.setCode("testCode" + j);
            excelTest.setName("testName" + j);
            excelTest.setEnabledFlag(j % 2);
            excelTestList.add(excelTest);
        }
        return excelTestList;
    }

    @Override
    @CusExport(dynamicFlag = true)
    public List<Map<String, Object>> generatorDataForMap() {
        List<Map<String, Object>> excelTestList = new ArrayList<>();
        for (int j = 0; j < 100; j++) {
            Map<String, Object> item = new HashMap<>(3);
            item.put("code", "testCodeMap" + j);
            item.put("name", "testNameMap" + j);
            item.put("enabledFlag", j % 2);
            excelTestList.add(item);
        }
        return excelTestList;
    }
//
}

ExportTestController

package com.example.export.api.controller;

import com.example.export.app.service.TestExportService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author PC
 */
@RestController("v1.exportTestController")
@RequestMapping("/export")
public class ExportTestController {
    private final TestExportService testExportService;

    @Autowired
    public ExportTestController(TestExportService testExportService) {
        this.testExportService = testExportService;
    }

    @GetMapping("/by-class")
    public void exportTest(){
        testExportService.generatorDataForClass();
    }

    @GetMapping("/by-map")
    public void exportTestMap(){
        testExportService.generatorDataForMap();
    }
}

测试

访问127.0.0.1:18081/export/by-class

访问127.0.0.1:18081/export/by-map

参考资料

[1].EasyExcel官网文档

[2].demo

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

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

相关文章

Spring Boot框架:校园社团信息管理的现代化解决方案

3系统分析 3.1可行性分析 通过对本校园社团信息管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本校园社团信息管理系统采用SSM框架&#xff0c;JAVA作…

基于SpringBoot+Vue的前后端分离的大学自动排课系统

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 在这个背景下&#xf…

探索无线网IP地址:定义、修改方法及实践指南

在数字化时代&#xff0c;无线网络已成为我们日常生活和工作中不可或缺的一部分。它让我们能够随时随地接入互联网&#xff0c;享受信息交流的便利。然而&#xff0c;对于无线网络背后的技术细节&#xff0c;如IP地址&#xff0c;许多用户可能并不十分了解。IP地址&#xff0c;…

Spring IoC——IoC 容器的使用

1. 应用分层 应用分层是一种软件开发设计思想&#xff0c;它将应用程序分成 N 个层次&#xff0c;这 N 个层次分别负责各自的职责&#xff0c;多个层次之间协同提供完整的功能&#xff0c;根据项目的复杂度&#xff0c;可以分成三层&#xff0c;四层或更多层&#xff0c;MVC 就…

人工智能进程;算子加速的具体计算部分;大模型GPT5:参数18万亿;大模型面临问题

目录 人工智能进程 算子加速的简单理解,举例说明 一、简单理解 二、举例说明 一、算子加速的具体计算部分 二、举例说明 三、算子加速是否仅针对GPU 大模型GPT5:参数18万亿 大模型面临问题 算力集群设计框架 人工智能进程

深入理解Java集合:从基础到高级应用

深入理解Java集合&#xff1a;从基础到高级应用 1. 数组与集合的区别 1.1 相同点 数组和集合都是用于存储多个数据的容器&#xff0c;但它们的使用场景和特性各有不同。 1.2 不同点 长度&#xff1a;数组的长度在创建时就固定了&#xff0c;而集合的长度是动态可变的&…

【自动化测试之oracle数据库】MacOs如何安装oracle- client

操作系统为Mac OS&#xff0c;本地在pycharm上跑自动化脚本时&#xff0c;因为有操作oracle数据库的部分&#xff0c;所以需要安装oracle数据库的客户端&#xff0c;并install cx_oracle,本文主要介绍如何在macOS上完成安装&#xff0c;并在python自动化测试代码中配置&#xf…

如何在vscode中使用鼠标滑轮滚动来改变字体大小

实现内容&#xff1a;如何在vscode中使用鼠标滑轮滚动来改变字体大小 使用场景&#xff1a;我是在Ubuntu中安装的vscode 需求&#xff1a;因为最近在用这个&#xff0c;但是在使用过程中发现vscode的字体大小有点小&#xff0c;所以想改变下 实现滚轮滑动改变字体大小的具体步…

鸿蒙NEXT应用上架与分发步骤详解

大家好&#xff0c;我是 V 哥。今天的文章来聊一聊HarmonyOS NEXT应用上架。当你开发、调试完HarmonyOS应用/元服务&#xff0c;就可以前往AppGallery Connect申请上架&#xff0c;华为审核通过后&#xff0c;用户即可在华为应用市场获取您的HarmonyOS应用/元服务。 V 哥推荐&a…

【隐私计算篇】全同态加密应用场景案例(隐私云计算中的大模型推理、生物识别等)

1.题外话 最近因为奖项答辩&#xff0c;一直在忙材料准备&#xff0c;过程非常耗费时间和精力&#xff0c;很难有时间来分享。不过这段时间虽然很忙碌&#xff0c;但这期间有很多新的收获&#xff0c;特别是通过与领域内专家的深入交流和评审过程&#xff0c;对密码学和隐私计算…

安卓开发之登录页面(跳转版)

目录 前言&#xff1a;基础夯实&#xff1a;效果展示&#xff1a;核心代码&#xff1a;网盘源码&#xff1a; 前言&#xff1a; 熟悉安卓开发的基础知识&#xff0c;了解&#xff0c;弹窗&#xff0c;两个页面进行跳转&#xff0c;页面的布局&#xff0c;按钮&#xff0c;文本…

【牛客刷题实战】二叉树遍历

大家好&#xff0c;我是小卡皮巴拉 文章目录 目录 牛客题目&#xff1a; 二叉树遍历 题目描述 输入描述&#xff1a; 输出描述&#xff1a; 示例1 解题思路 问题理解 算法选择 具体思路 解题要点 完整代码&#xff08;C语言&#xff09; 兄弟们共勉 &#xff01;&…

多个项目同时进行,如何做好项目管理?

多项目管理相较于单一项目管理&#xff0c;要面临更大的挑战和难度。多项目管理需要同时管理和协调多个项目&#xff0c;使用项目管理工具可以帮助项目经理和团队成员更好地规划、执行和监控项目。以下是七款多项目管理软件&#xff0c;它们各具特色&#xff0c;能够满足不同项…

[vulnhub] Brainpan1

https://www.vulnhub.com/entry/brainpan-1,51/ 主机发现端口扫描 使用nmap扫描网段类存活主机 因为靶机是我最后添加的&#xff0c;所以靶机IP是166 nmap -sP 192.168.75.0/24 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-1…

Java避坑案例 - 线程池使用中的风险识别与应对

文章目录 线程池的基本概念创建线程池的注意事项实例1&#xff1a; newFixedThreadPool 使用无界队列&#xff0c;可能因任务积压导致 OOM实例2&#xff1a; newCachedThreadPool 会创建大量线程&#xff0c;可能因线程数量过多导致无法创建新线程。 线程池参数设置的最佳实践线…

Pytest-Bdd-Playwright 系列教程(5):仅执行测试用例的收集阶段

Pytest-Bdd-Playwright 系列教程&#xff08;5&#xff09;&#xff1a;仅执行测试用例的收集阶段 一、为什么需要仅收集测试用例二、应用场景三、方法详解【方法1】&#xff1a;添加pytest.ini文件的addopts配置项【方法2】&#xff1a;通过命令行参数运行 四、CI/CD 环境下的…

机器人技术基础(4章逆运动解算和雅克比矩阵)

逆运动解算&#xff1a; 雅克比矩阵&#xff1a; 将动力学分析转向运动的物体 下图中的 n o y 反映了机器人的姿态矩阵&#xff0c; 最后一列 p 反应了机器人在空间中的位置&#xff1a;

未来已来:人工智能赋能软件开发新篇章

引言 在数字化转型的浪潮中&#xff0c;数据已成为推动企业创新与增长的核心资产&#xff0c;而人工智能&#xff08;AI&#xff09;则是将这些数据转化为商业价值的关键动力。随着技术的迅速演进&#xff0c;AI 正逐步渗透到软件开发的各个环节&#xff0c;从需求预测到用户体…

C#实现视频会议录制(支持Windows、Linux、银河麒麟、统信UOS)

随着远程办公与异地协作越来越频繁&#xff0c;视频会议系统的使用也是越来越普遍。同时&#xff0c;用户对视频会议系统的功能也提出了更高的要求&#xff0c;比如&#xff0c;其中之一就是希望可以将整个视频会议的过程录制下来&#xff0c;以备之后可以查阅观看。 我们可以…

树莓派开发相关知识四 传感器-温湿度传感器

1、概述 使用DHT11温湿度传感器&#xff0c;传感周期为1s。 DHT11模块一般由3/4个引脚组成&#xff0c;每一次收集数据为40bit。 分别为&#xff1a; 高位在前、8bit湿度整数数据8bit湿度小数数据8bi温度整数数据8bit温度小数数据8bit校验和 我们需要解决的问题&#xff0c;…