SpringBoot integrates EasyExcel to implement the import & export function of complex Excel tables

In the development of back-end management systems, there is often a function to export current table data. Some front-end table components can do this directly, but they are not flexible enough. Because the data obtained by the front-end is always processed, if you want to get the original data, it must be processed by the back-end. In addition, the import of Excel is also very common, and data processing is also a troublesome thing. Common toolkits such as Apache POI can help us simplify these processes, but it also has some shortcomings, such as too many classes to be created. .

So, besides using the Apache POI package, are there any other options? Of course! Here I recommend a very simple and easy-to-use open source component:Alibaba EasyExcel

First, release the official website address, welcome everyone to star (currently 24K):https://alibaba-easyexcel.github.io/docs/current/

EasyExcel is a simple, memory-saving open source project for reading and writing Excel based on Java. Supports reading and writing 100M Excel while saving memory as much as possible.

64M memory reads 75M (46W rows and 25 columns) Excel (version 3.0.2+) in 20 seconds

The core class of Alibaba EasyExcel isEasyExcelclass

  
     * 最简单的读
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
     * <p>3. 直接读即可
     */
    @Test
    public void simpleRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
    }

     * 最简单的写
     * <p>1. 创建excel对应的实体对象 参照{@link com.alibaba.easyexcel.test.demo.write.DemoData}
     * <p>2. 直接写即可
     */
    @Test
    public void simpleWrite() {
        String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx";
        
        
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
    }

1. Basic project information

(1) Project structure

(2)pom.xml

<?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.1</version>
        <relativePath/> 
    </parent>
    <groupId>com.example</groupId>
    <artifactId>SpringBoot-easyexcel</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringBoot-easyexcel</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>

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

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

        
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>

        
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


2. File export

Let’s show the effect first.

(1) Single sheet&header merge

(2) Export multiple sheets

The following is the export-related class code

ExportController.java

package com.example.springbooteasyexcel.controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.example.springbooteasyexcel.data.Mock;
import com.example.springbooteasyexcel.sheet.CitySheet;
import com.example.springbooteasyexcel.sheet.CompanySheet;
import com.example.springbooteasyexcel.sheet.UserSheet;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
@RequestMapping("/export")
public class ExportController {

    
     * @param response
     * @url <a>http://localhost:8080/export/test1</a>
     * 在Excel中写入单个sheet
     */
    @RequestMapping("/test1")
    public void test1(HttpServletResponse response) {
        
        try {
            
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            
            response.setCharacterEncoding("utf-8");
            
            String fileName = URLEncoder.encode("用户信息表", "UTF-8").replaceAll("\\+", "%20");
            
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            
            EasyExcel.write(response.getOutputStream(), UserSheet.class).inMemory(true).sheet("用户信息表").doWrite(Mock.userList());
        } catch (IOException e) {
            throw new RuntimeException("数据或文件损坏,无法下载");
        }
    }

    
     * 在Excel中写入多个sheet
     *
     * @url <a>http://localhost:8080/export/test2</a>
     */
    @RequestMapping("/test2")
    public void test2(HttpServletResponse response) throws Exception {
        
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        
        response.setCharacterEncoding("utf-8");
        
        String fileName = URLEncoder.encode("信息表", "UTF-8").replaceAll("\\+", "%20");
        
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        
        try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).inMemory(true).build()) {
            
            WriteSheet userInfoSheet = EasyExcel.writerSheet(0, "用户信息表").head(UserSheet.class).build();
            excelWriter.write(Mock.userList(), userInfoSheet);
            
            WriteSheet cityInfoSheet = EasyExcel.writerSheet(1, "城市信息表").head(CitySheet.class).build();
            excelWriter.write(Mock.cityList(), cityInfoSheet);
            
            WriteSheet companyInfoSheet = EasyExcel.writerSheet(2, "公司信息表").head(CompanySheet.class).build();
            excelWriter.write(Mock.companyList(), companyInfoSheet);
        }
    }
}

Mock.java

The following data comes from the Internet. If there is any infringement, please contact us to delete it.

package com.example.springbooteasyexcel.data;


import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.RichTextStringData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.example.springbooteasyexcel.sheet.CitySheet;
import com.example.springbooteasyexcel.sheet.CompanySheet;
import com.example.springbooteasyexcel.sheet.UserSheet;
import org.apache.poi.ss.usermodel.IndexedColors;

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

public class Mock {

    public static List<UserSheet> userList() {
        List<UserSheet> list = new ArrayList<>(10);
        list.add(UserSheet.builder().userId(001L).userName("张三").userPhone("11112223123").userEmail("[email protected]").userAddress("北京朝阳区").gender(buildCellData("男")).registerTime(Calendar.getInstance().getTime()).build());
        list.add(UserSheet.builder().userId(002L).userName("李四").userPhone("11112223123").userEmail("[email protected]").userAddress("南京玄武门").gender(buildCellData("女")).registerTime(Calendar.getInstance().getTime()).build());
        list.add(UserSheet.builder().userId(003L).userName("王五").userPhone("11112223123").userEmail("[email protected]").userAddress("杭州未来科技城").gender(buildCellData("男")).registerTime(Calendar.getInstance().getTime()).build());
        list.add(UserSheet.builder().userId(004L).userName("赵六").userPhone("11112223123").userEmail("[email protected]").userAddress("上海徐家汇").gender(buildCellData("女")).registerTime(Calendar.getInstance().getTime()).build());
        return list;
    }

    private static WriteCellData<String> buildCellData(String gender) {
        
        WriteCellData<String> cellData = new WriteCellData<>();
        
        cellData.setType(CellDataTypeEnum.RICH_TEXT_STRING);
        
        RichTextStringData richTextStringData = new RichTextStringData();
        cellData.setRichTextStringDataValue(richTextStringData);
        richTextStringData.setTextString(gender);
        WriteFont writeFont = new WriteFont();
        if ("男".equalsIgnoreCase(gender)) {
         
            writeFont.setColor(IndexedColors.RED.getIndex());
        } else if ("女".equalsIgnoreCase(gender)) {
         
            writeFont.setColor(IndexedColors.GREEN.getIndex());
        }
        
        richTextStringData.applyFont(writeFont);
        return cellData;
    }

    public static List<CitySheet> cityList() {
        List<CitySheet> list = new ArrayList<>(10);
        list.add(CitySheet.builder().cityName("杭州市").cityDesc("杭州市一般指杭州。 杭州,简称“杭”,古称临安、钱塘,浙江省辖地级市、省会、副省级市、特大城市、国务院批复确定的浙江省经济、文化、科教中心,长江三角洲中心城市之一,环杭州湾大湾区核心城市、G60科创走廊中心城市。").build());
        list.add(CitySheet.builder().cityName("合肥市").cityDesc("合肥市一般指合肥。 合肥,简称“庐”或“合”,古称庐州、庐阳、合淝,安徽省辖地级市、省会,是合肥都市圈中心城市,国务院批复确定的中国长三角城市群副中心城市,全国四大科教基地、现代制造业基地和综合交通枢纽。").build());
        list.add(CitySheet.builder().cityName("武汉市").cityDesc("武汉市一般指武汉。 武汉,简称“汉”,别称江城,是湖北省省会,中部六省唯一的副省级市,超大城市,中国中部地区的中心城市,全国重要的工业基地、科教基地和综合交通枢纽,联勤保障部队机关驻地。").build());
        list.add(CitySheet.builder().cityName("深圳市").cityDesc("深圳市一般指深圳。 深圳,简称“深”,别称鹏城,广东省辖地级市,是广东省副省级市,国家计划单列市,超大城市,国务院批复确定的中国经济特区、全国性经济中心城市、国际化城市、科技创新中心、区域金融中心、商贸物流中心。").build());
        return list;
    }

    public static List<CompanySheet> companyList() {
        List<CompanySheet> list = new ArrayList<>(10);
        list.add(CompanySheet.builder().companyName("阿里巴巴").companyBoss("马云").companyBase("杭州市").companyDesc("阿里巴巴集团经营多项业务,另外也从关联公司的业务和服务中取得经营商业生态系统上的支援。业务和关联公司的业务包括:淘宝网、天猫、聚划算、全球速卖通、阿里巴巴国际交易市场、1688、阿里妈妈、阿里云、蚂蚁集团 [408]  、菜鸟网络等。").build());
        list.add(CompanySheet.builder().companyName("字节跳动").companyBoss("张一鸣").companyBase("北京市").companyDesc("字节跳动的全球化布局始于2015年 [3]  ,“技术出海”是字节跳动全球化发展的核心战略 [4]  ,其旗下产品有今日头条、西瓜视频、抖音、头条百科、皮皮虾、懂车帝、悟空问答等。").build());
        list.add(CompanySheet.builder().companyName("腾讯").companyBoss("马化腾").companyBase("深圳市").companyDesc("社交和通信服务QQ及微信/WeChat、社交网络平台QQ空间、腾讯游戏旗下QQ游戏平台、门户网站腾讯网、腾讯新闻客户端和网络视频服务腾讯视频等。").build());
        list.add(CompanySheet.builder().companyName("百度").companyBoss("李彦宏").companyBase("北京市").companyDesc("百度(Baidu)是拥有强大互联网基础的领先AI公司。百度愿景是:成为最懂用户,并能帮助人们成长的全球顶级高科技公司。").build());
        return list;
    }
}


CitySheet.java

package com.example.springbooteasyexcel.sheet;

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

@Data
@Builder
public class CitySheet {

    @ExcelProperty(value = "城市名称", index = 0)
    @ColumnWidth(10)
    private String cityName;

    @ExcelProperty(value = "城市介绍", index = 1)
    @ColumnWidth(60)
    private String cityDesc;

}


CompanySheet.java

package com.example.springbooteasyexcel.sheet;


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

@Data
@Builder
public class CompanySheet {

    @ExcelProperty(value = "公司名称", index = 0)
    @ColumnWidth(10)
    private String companyName;

    @ExcelProperty(value = "公司创始人", index = 1)
    @ColumnWidth(10)
    private String companyBoss;

    @ExcelProperty(value = "公司总基地", index = 2)
    @ColumnWidth(10)
    private String companyBase;

    @ExcelProperty(value = "公司简介", index = 3)
    @ColumnWidth(50)
    private String companyDesc;
}


UserSheet.java

package com.example.springbooteasyexcel.sheet;

import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.metadata.data.WriteCellData;
import lombok.Builder;
import lombok.Data;

import java.util.Date;

@Data
@Builder
public class UserSheet {

    @ExcelProperty(value = "用户ID", index = 0)
    @ColumnWidth(10)
    private Long userId;

    @ExcelProperty(value = "用户名称", index = 1)
    @ColumnWidth(10)
    private String userName;

    @ExcelProperty(value = {"基本信息", "手机号码"}, index = 2)
    @ColumnWidth(20)
    private String userPhone;

    @ExcelProperty(value = {"基本信息", "电子邮箱"}, index = 3)
    @ColumnWidth(20)
    private String userEmail;

    @ExcelProperty(value = {"基本信息", "地址"}, index = 4)
    @ColumnWidth(20)
    private String userAddress;

    @ExcelProperty(value = "注册时间", index = 5)
    @ColumnWidth(20)
    private Date registerTime;

    @ExcelProperty(value = "性别,男:红色/女:绿色")
    @ColumnWidth(30)
    private WriteCellData<String> gender;

    
     * 忽略这个字段
     */
    @ExcelIgnore
    private Integer age;
}


SpringBootEasyexcelApplication.java

package com.example.springbooteasyexcel;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootEasyexcelApplication {

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

}


3. File import

Excel is imported into the database. I use a flow chart to show how to use EasyExcel to import.

EasyExcel has an AnalysisEventListener. You can customize a Listener to inherit AnalysisEventListener. There is an invoke method in it, and each piece of data will enter this method. We can do verification, storage, exception throwing and other actions here. EasyExcel streamlines these processes, making it very comfortable to write code. Of course, there are some points that need to be paid attention to. For example, the custom Listener cannot be managed by spring. It needs to be new every time it reads excel, and then the spring constructor method is used to pass it in.

Below are some class codes related to import. The most important thing is UserExcelReadListener. You can take a closer look.

UserData.java

package com.example.springbooteasyexcel.read;

import java.util.Date;

import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.metadata.data.WriteCellData;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class UserData {

    private Long userId;

    private String userName;

    private Integer age;

    private String userPhone;

    private String userEmail;

    private String userAddress;

    private Date registerTime;

    private String gender;

}


UserExcelReadListener.java

package com.example.springbooteasyexcel.read;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.util.StringUtils;

import lombok.extern.slf4j.Slf4j;


 * 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
 */
@Slf4j
public class UserExcelReadListener extends AnalysisEventListener<UserData> {

    
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;

    
     * 创建一个Pattern对象,使用正则表达式校验手机号格式
     */
    private static final Pattern PHONE_REGEX = Pattern.compile("^1[0-9]{10}$");

    
     * 缓存的数据
     */
    private List<UserData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    
     * 错误信息列表
     */
    private final List<String> errorMsgList = new ArrayList<>(BATCH_COUNT);

    @Override
    public void invoke(UserData userData, AnalysisContext analysisContext) {
        log.info("解析到一条数据:{}", userData);
        int rowIndex = analysisContext.readRowHolder().getRowIndex();
        String name = userData.getUserName();
        String phone = userData.getUserPhone();
        String gender = userData.getGender();
        String email = userData.getUserEmail();
        Integer age = userData.getAge();
        String address = userData.getUserAddress();
        
        if (nameValid(rowIndex, name) && phoneValid(rowIndex, phone) && genderValid(rowIndex, gender) &&
            emailValid(rowIndex, email) && ageValid(rowIndex, age) && addressValid(rowIndex, address)) {
            cachedDataList.add(userData);
        }
        
        if (cachedDataList.size() >= BATCH_COUNT) {
            
            
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        log.info("所有数据解析完成!全部校验通过的数据有{}条", cachedDataList.size());
        
        

    }

    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        if (exception instanceof RuntimeException) {
            throw exception;
        }
        int index = context.readRowHolder().getRowIndex() + 1;
        errorMsgList.add("第" + index + "行解析错误");
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        int totalRows = context.readSheetHolder().getApproximateTotalRowNumber() - 1;
        int maxNum = 2000;
        if (totalRows > maxNum) {
            errorMsgList.add("数据量过大,单次最多上传2000条");
            throw new RuntimeException("数据量过大,单次最多上传2000条");
        }
    }

    public List<String> getErrorMsgList() {
        return errorMsgList;
    }

    
     * 名称的校验
     *
     * @param rowIndex 行数
     * @param name     名称
     */
    private Boolean nameValid(Integer rowIndex, String name) {
        if (StringUtils.isBlank(name)) {
            errorMsgList.add("第" + rowIndex + "行,'姓名'不能为空");
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    private Boolean phoneValid(int rowIndex, String phone) {
        if (StringUtils.isBlank(phone)) {
            errorMsgList.add("第" + rowIndex + "行,'手机号'不能为空");
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    
     * 性别的校验
     *
     * @param rowIndex 行数
     * @param gender   性别
     */
    private Boolean genderValid(int rowIndex, String gender) {
        if (StringUtils.isBlank(gender)) {
            errorMsgList.add("第" + rowIndex + "行,'性别'不能为空");
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    
     * 地址校验
     *
     * @param rowIndex 行数
     * @param address  地址
     */
    private Boolean addressValid(int rowIndex, String address) {
        
        if (StringUtils.isBlank(address)) {
            errorMsgList.add("第 " + rowIndex + " 行,'地址'不能为空");
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    
     * 年龄的校验
     *
     * @param rowIndex 行数
     * @param age      年龄
     */
    private Boolean ageValid(int rowIndex, Integer age) {
        
        if (Objects.isNull(age)) {
            errorMsgList.add("第 " + rowIndex + " 行'年龄'不能为空");
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    
     * 邮箱的校验
     *
     * @param rowIndex 行数
     * @param email    邮箱
     */
    private Boolean emailValid(int rowIndex, String email) {
        
        if (StringUtils.isBlank(email)) {
            errorMsgList.add("第 " + rowIndex + " 行'邮箱'不能为空");
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }
}


package com.example.springbooteasyexcel.read;

import com.alibaba.excel.EasyExcel;

public class ReadExcelTest {

    public static void main(String[] args) {
        UserExcelReadListener userExcelReadListener = new UserExcelReadListener();
        EasyExcel.read("用户信息表.xlsx", UserData.class, userExcelReadListener).sheet().doRead();
        System.out.println(userExcelReadListener.getErrorMsgList());
    }
}


Results of the

1. Alibaba EasyExcel not only supports writing Excel, but also supports reading and filling Excel. If you are interested, you can research it yourself. The official website address has been posted above. I will only serve as a guide here.

2. There are three commonly used annotations: @ExcelProperty, @ColumnWidth, and @ExcelIgnore. (1)@ExcelPropertyNot only determines the table header, but also merges rows. The usage is as follows:

  @ExcelProperty(value = {"基本信息", "手机号码"}, index = 2)
    @ColumnWidth(20)
    private String userPhone;

    @ExcelProperty(value = {"基本信息", "电子邮箱"}, index = 3)
    @ColumnWidth(20)
    private String userEmail;

    @ExcelProperty(value = {"基本信息", "地址"}, index = 4)
    @ColumnWidth(20)
    private String userAddress;

The effect is as follows:

(2) @ColumnWidthMain is restraint Review

(3)@ExcelIgnoreIgnore fields that do not need to be output. 3. There are two forms of writing (1) Writing to file

  
     * 最简单的写
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 直接写即可
     */
    @Test
    public void simpleWrite() {
        

        
        
        String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        
        
        EasyExcel.write(fileName, DemoData.class)
            .sheet("模板")
            .doWrite(() -> {
                
                return data();
            });

        
        fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        
        
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());

        
        fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            excelWriter.write(data(), writeSheet);
        }
    }

(2) When writing to the Web stream, the ContentType and CharacterEncoding here should not be garbled, otherwise it will easily be garbled or the file will be damaged.

 
     * 文件下载(失败了会返回一个有部分数据的Excel)
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DownloadData}
     * <p>
     * 2. 设置返回的 参数
     * <p>
     * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大
     */
    @GetMapping("download")
    public void download(HttpServletResponse response) throws IOException {
        
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        
        String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());
    }

(3) Making good use of custom Listener can optimize the code and make the logic clearer.

Guess you like

Origin blog.csdn.net/weixin_54542328/article/details/134856298