Spring Boot集成EasyExcel

1. 初始化Spring Boot项目

首先,使用Spring Initializr(https://start.spring.io/)生成一个基本的Spring Boot项目。选择以下依赖项:

  • Spring Web
  • Lombok (用于减少样板代码)
  • SLF4J (用于日志记录)

2. 添加依赖

在你的pom.xml文件中添加EasyExcel的Maven依赖。确保版本号是最新的,你可以访问Maven仓库来获取最新版。

XML 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>最新的版本号</version>
</dependency>

3. 创建实体类

假设我们需要处理一个用户信息表,包含姓名和年龄两个字段。以下是实体类的设计,并使用Lombok简化代码:

java 复制代码
package com.example.demo.model;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

// 使用@Data注解自动生成getter、setter等方法
@Data
public class UserData {
    // 指定Excel列标题为"姓名"
    @ExcelProperty("姓名")
    private String name; // 用户姓名

    // 指定Excel列标题为"年龄"
    @ExcelProperty("年龄")
    private Integer age; // 用户年龄
}

4. 创建监听器类

创建一个监听器类来处理每一行的数据,并在服务类中调用它。我们使用@Slf4j注解简化日志记录:

java 复制代码
package com.example.demo.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.example.demo.model.UserData;
import lombok.extern.slf4j.Slf4j;

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

@Slf4j // 使用@Slf4j注解简化日志记录
public class UserDataListener extends AnalysisEventListener<UserData> {

    private final List<UserData> dataList = new ArrayList<>(); // 存储读取的数据

    /**
     * 当解析一行数据时调用
     * @param userData 解析得到的用户数据
     * @param analysisContext 上下文对象
     */
    @Override
    public void invoke(UserData userData, AnalysisContext analysisContext) {
        dataList.add(userData); // 将每一行的数据添加到列表中
        log.info("读取到一条数据: {} {}", userData.getName(), userData.getAge()); // 日志记录
    }

    /**
     * 所有数据解析完成后调用
     * @param analysisContext 上下文对象
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        log.info("所有数据解析完成"); // 数据解析完成后记录日志
    }

    /**
     * 获取读取的数据列表
     * @return 读取的数据列表
     */
    public List<UserData> getDataList() {
        return dataList; // 返回读取的数据列表
    }
}

5. 实现服务类

编写一个服务类来实现数据的读写操作,并增加异常处理和日志记录。我们将在此处添加分页功能,以确保当单个页面数据达到一定量时重新生成新的页面进行写入:

java 复制代码
package com.example.demo.service;

import com.alibaba.excel.EasyExcel;
import com.example.demo.listener.UserDataListener;
import com.example.demo.model.UserData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

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

@Slf4j // 使用@Slf4j注解简化日志记录
@Service
public class ExcelService {

    private static final int PAGE_SIZE = 100; // 每页最大数据条数

    /**
     * 写入Excel文件
     * @param filePath 文件路径
     * @param data 要写入的数据列表
     */
    public void writeExcel(String filePath, List<UserData> data) {
        try {
            log.info("开始写入Excel文件: {}", filePath); // 记录日志

            int totalRows = data.size();
            int totalPages = (int) Math.ceil((double) totalRows / PAGE_SIZE);

            for (int page = 0; page < totalPages; page++) {
                int fromIndex = page * PAGE_SIZE;
                int toIndex = Math.min(fromIndex + PAGE_SIZE, totalRows);

                List<UserData> currentPageData = data.subList(fromIndex, toIndex);

                String sheetName = "用户信息" + (page + 1); // 设置工作表名称

                // 开始写入Excel文件
                EasyExcel.write(filePath, UserData.class)
                        .sheet(sheetName) // 设置工作表名称
                        .doWrite(currentPageData); // 执行写入操作

                log.info("写入第 {} 页数据完成", page + 1); // 写入完成后记录日志
            }

            log.info("写入Excel文件完成"); // 写入完成后记录日志
        } catch (Exception e) {
            log.error("写入Excel失败", e); // 记录错误日志
        }
    }

    /**
     * 读取所有工作表的Excel文件
     * @param inputStream 输入流
     * @return 读取的数据列表
     */
    public List<UserData> readAllSheets(InputStream inputStream) {
        List<UserData> allData = new ArrayList<>();
        try {
            log.info("开始读取所有工作表的Excel文件"); // 记录日志

            // 获取Excel文件中的所有Sheet信息
            List<String> sheetNames = EasyExcel.read(inputStream).excelExecutor().sheetList().get();

            for (String sheetName : sheetNames) {
                log.info("开始读取工作表: {}", sheetName);

                UserDataListener listener = new UserDataListener();
                // 执行读取操作
                EasyExcel.read(inputStream, UserData.class, listener)
                        .sheet(sheetName) // 指定工作表名称
                        .doRead(); // 执行读取操作

                // 处理listener.getDataList()
                allData.addAll(listener.getDataList());
                log.info("读取工作表 {} 完成", sheetName);
            }

            log.info("读取所有工作表的Excel文件完成"); // 读取完成后记录日志
        } catch (Exception e) {
            log.error("读取所有工作表的Excel失败", e); // 记录错误日志
        }
        return allData;
    }

    /**
     * 读取特定工作表的Excel文件
     * @param filePath 文件路径
     * @param sheetIndex 工作表索引(从0开始)
     */
    public void readSpecificSheet(String filePath, int sheetIndex) {
        try {
            log.info("开始读取特定工作表的Excel文件: {}, Sheet Index: {}", filePath, sheetIndex); // 记录日志

            UserDataListener listener = new UserDataListener();
            // 执行读取操作
            EasyExcel.read(filePath, UserData.class, listener)
                    .sheet(sheetIndex) // 指定工作表索引
                    .doRead(); // 执行读取操作

            // 处理listener.getDataList()
            for (UserData userData : listener.getDataList()) {
                log.info("{} {}", userData.getName(), userData.getAge()); // 记录每条数据的日志
            }
            log.info("读取特定工作表的Excel文件完成"); // 读取完成后记录日志
        } catch (Exception e) {
            log.error("读取特定工作表的Excel失败", e); // 记录错误日志
        }
    }
     /**
     * 使用模板填充数据并生成新的Excel文件
     * @param templateFilePath 模板文件路径
     * @param outputFilePath 输出文件路径
     * @param data 要填充的数据列表
     */
    public void fillTemplate(String templateFilePath, String outputFilePath, List<UserData> data) {
        try {
            log.info("开始使用模板填充数据: {}, 输出文件路径: {}", templateFilePath, outputFilePath); // 记录日志

            // 使用模板填充数据
            EasyExcel.write(outputFilePath)
                    .withTemplate(templateFilePath)
                    .sheet()
                    .doFill(data);

            log.info("模板填充数据完成"); // 填充完成后记录日志
        } catch (Exception e) {
            log.error("模板填充数据失败", e); // 记录错误日志
        }
    }
    
    /**
     * 下载文件并在失败时返回JSON
     * @param response HttpServletResponse 对象
     * @throws IOException 如果写入文件失败
     */
    public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
        try {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");

            // 这里URLEncoder.encode可以防止中文乱码
            String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

            
            List<UserData> data = new ArrayList<>();
            for (int i = 1; i <= 100; i++) { // 假设我们有100条数据
                data.add(new UserData().setName("张三" + i).setAge(20 + i % 50));
            }

            EasyExcel.write(response.getOutputStream(), UserData.class).autoCloseStream(Boolean.FALSE).sheet("模板")
                    .doWrite(data);
        } catch (Exception e) {
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = Map.of("status", "failure", "message", "下载文件失败: " + e.getMessage());
            response.getWriter().println(JSON.toJSONString(map));
        }
    }
}
}

6. 创建控制器类

为了方便测试,我们可以创建一个简单的控制器类来调用服务类中的方法:

java 复制代码
package com.example.demo.controller;

import com.example.demo.service.ExcelService;
import lombok.extern.slf4j.Slf4j;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Slf4j // 使用@Slf4j注解简化日志记录
@RestController
@RequestMapping("/excel")
public class ExcelController {

    @Autowired
    private ExcelService excelService; // 自动注入ExcelService
    /**
     * 文件上传
     * @param file 上传的文件
     * @return 成功消息
     * @throws IOException 如果读取文件失败
     */
    @PostMapping("upload")
    @ResponseBody
    public String upload(@RequestParam("file") MultipartFile file) throws IOException {
        InputStream inputStream = file.getInputStream();
        List<UserData> data = excelService.readAllSheets(inputStream);
        log.info("成功读取到 {} 条数据", data.size());
        return "success";
    }
    
    /**
     * 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel)
     *
     * @param response HttpServletResponse 对象
     * @throws IOException 如果写入文件失败
     */
    @GetMapping("downloadFailedUsingJson")
    public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
        excelService.downloadFailedUsingJson(response);
    }

    /**
     * 写入Excel文件
     * @param filePath 文件路径
     * @return 成功消息
     */
    @GetMapping("/write")
    public String writeExcel(@RequestParam String filePath) {
        log.info("准备写入Excel文件: {}", filePath); // 记录日志

        List<UserData> data = new ArrayList<>();
        for (int i = 1; i <= 500; i++) { // 假设我们有500条数据
            data.add(new UserData().setName("张三" + i).setAge(20 + i % 50));
        }

        excelService.writeExcel(filePath, data); // 调用写入方法
        log.info("写入Excel文件完成"); // 返回成功消息
        return "写入Excel成功"; // 返回成功消息
    }

    /**
     * 读取所有工作表的Excel文件
     * @param filePath 文件路径
     * @return 成功消息
     */
    @GetMapping("/read/allSheets")
    public String readAllSheets(@RequestParam String filePath) {
        log.info("准备读取所有工作表的Excel文件: {}", filePath); // 记录日志
        excelService.readAllSheets(filePath); // 调用读取方法
        log.info("读取所有工作表的Excel文件完成"); // 返回成功消息
        return "读取所有工作表的Excel成功"; // 返回成功消息
    }

    /**
     * 读取特定工作表的Excel文件
     * @param filePath 文件路径
     * @param sheetIndex 工作表索引(从0开始)
     * @return 成功消息
     */
    @GetMapping("/read/specificSheet")
    public String readSpecificSheet(@RequestParam String filePath, @RequestParam int sheetIndex) {
        log.info("准备读取特定工作表的Excel文件: {}, Sheet Index: {}", filePath, sheetIndex); // 记录日志
        excelService.readSpecificSheet(filePath, sheetIndex); // 调用读取方法
        log.info("读取特定工作表的Excel文件完成"); // 返回成功消息
        return "读取特定工作表的Excel成功"; // 返回成功消息
    }
    /**
     * 使用模板填充数据并生成新的Excel文件
     * @param templateFilePath 模板文件路径
     * @param outputFilePath 输出文件路径
     * @return 成功消息
     */
    @GetMapping("/fill/template")
    public String fillTemplate(@RequestParam String templateFilePath, @RequestParam String outputFilePath) {
        log.info("准备使用模板填充数据: {}, 输出文件路径: {}", templateFilePath, outputFilePath); // 记录日志

        List<UserData> data = new ArrayList<>();
        for (int i = 1; i <= 500; i++) { // 假设我们有500条数据
            data.add(new UserData().setName("张三" + i).setAge(20 + i % 50));
        }

        excelService.fillTemplate(templateFilePath, outputFilePath, data); // 调用模板填充方法
        log.info("模板填充数据完成"); // 返回成功消息
        return "模板填充数据成功"; // 返回成功消息
    }
}

7. 异常处理与日志记录

在实际应用中,建议增加更多的异常处理逻辑和日志记录,以便更好地调试和维护。我们已经在服务类中添加了基本的日志记录和异常处理机制。

全局异常处理

你可以在项目中添加全局异常处理器来捕获和处理未处理的异常:

java 复制代码
package com.example.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import lombok.extern.slf4j.Slf4j;

@Slf4j // 使用@Slf4j注解简化日志记录
@ControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理所有异常
     * @param e 异常对象
     * @return 错误响应
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception e) {
        log.error("发生错误: ", e); // 记录错误日志
        return new ResponseEntity<>("发生错误: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); // 返回错误消息
    }
}

8. 启动应用程序

确保你的主应用程序类正确配置:

java 复制代码
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import lombok.extern.slf4j.Slf4j;

@SpringBootApplication
@Slf4j // 使用@Slf4j注解简化日志记录
public class DemoApplication {

    /**
     * 主函数,启动Spring Boot应用
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args); // 启动Spring Boot应用
        log.info("Spring Boot应用已启动"); // 记录启动日志
    }
}

9. 测试

启动应用程序后,你可以通过浏览器或Postman等工具访问以下URL来测试Excel的读写功能:

  • 写入Excel:http://localhost:8080/excel/write?filePath=/path/to/your/output.xlsx
  • 读取Excel:http://localhost:8080/excel/read?filePath=/path/to/your/input.xlsx

总结

以上是完整的Spring Boot集成EasyExcel的详细步骤和代码示例,包括详细的注释以及实现了当单个页面数据达到一定量时重新生成新的页面进行写入的功能。

相关推荐
似水流年流不尽思念16 分钟前
容器化技术了解吗?主要解决什么问题?原理是什么?
后端
Java水解17 分钟前
Java中的四种引用类型详解:强引用、软引用、弱引用和虚引用
java·后端
i听风逝夜18 分钟前
看好了,第二遍,SpringBoot单体应用真正的零停机无缝更新代码
后端
柏油1 小时前
可视化 MySQL binlog 监听方案
数据库·后端·mysql
舒一笑2 小时前
Started TttttApplication in 0.257 seconds (没有 Web 依赖导致 JVM 正常退出)
jvm·spring boot·后端
M1A12 小时前
Java Enum 类:优雅的常量定义与管理方式(深度解析)
后端
AAA修煤气灶刘哥2 小时前
别再懵了!Spring、Spring Boot、Spring MVC 的区别,一篇讲透
后端·面试
柏油3 小时前
MySQL 字符集 utf8 与 utf8mb4
数据库·后端·mysql
程序猿阿越3 小时前
Kafka源码(三)发送消息-客户端
java·后端·源码阅读