Spring Boot 3 + EasyExcel 文件導入導出實現

SpringBoot集成EasyExcel 3.x:高效實現Excel數據的優雅導入與導出

在現代企業應用中,Excel作為數據交換的重要工具,幾乎無處不在。如何高效且優雅地實現Excel數據的導入與導出,是每個開發者都需要面對的問題。EasyExcel是阿里巴巴開源的一個高性能Excel處理庫,它可以大大簡化Excel操作。在本文中,我將介紹如何在SpringBoot項目中集成EasyExcel 3.x,并實現Excel數據的導入與導出。

1. 項目依賴配置 (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 http://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>3.2.0</version><relativePath/></parent><groupId>com.example</groupId><artifactId>excel-demo</artifactId><version>1.0.0</version><properties><java.version>17</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.3.2</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency></dependencies>
</project>

2. 實體類定義

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;/*** @Description: 用于Excel導入導出數據映射* @date 2025/9/15 15:34*/
@Data
public class TerminalImportVO {@ExcelProperty(value = "IMEI", index = 0)@NotBlank(message = "IMEI不能為空")@Size(min = 3, max = 20, message = "IMEI必須為3-15位數字")@Pattern(regexp = "\\d+", message = "IMEI必須為數字")@ColumnWidth(20)private String imei;
}

3. Excel監聽器(用于導入數據處理)

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cetcnav.entity.TerminalInfo;
import com.cetcnav.exception.ExcelImportException;
import com.cetcnav.service.TerminalInfoService;
import com.cetcnav.vo.TerminalImportVO;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;import java.util.ArrayList;
import java.util.List;
import java.util.Set;/*** @Description: Excel導入數據監聽器 處理終端數據的讀取和驗證* @date 2025-09-15 15:37*/
@Slf4j
public class TerminalExcelListener implements ReadListener<TerminalImportVO> {/*** 每隔100條存儲數據庫,然后清理list,方便內存回收*/private static final int BATCH_COUNT = 100;/*** 緩存的數據*/private List<TerminalInfo> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);private final TerminalInfoService terminalInfoService;public TerminalExcelListener(TerminalInfoService terminalInfoService) {this.terminalInfoService = terminalInfoService;}/*** 驗證器*/private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();/*** 錯誤信息收集*/private final List<String> errors = new ArrayList<>();/*** 總行數(包括表頭)*/@Getterprivate int totalRowNum = 0;/*** 成功處理的數據行數* -- GETTER --* 獲取成功處理的數量*/@Getterprivate int successCount = 0;/*** 是否已經拋出異常(防止重復拋出)*/private boolean hasThrownException = false;/*** 每一條數據解析都會來調用*/@Overridepublic void invoke(TerminalImportVO terminalImportVO, AnalysisContext analysisContext) {totalRowNum++;// Excel行號從1開始int currentRowNum = analysisContext.readRowHolder().getRowIndex() + 1;log.info("解析到第{}行數據: {}", currentRowNum, terminalImportVO);try {// 1. 基本數據校驗if (terminalImportVO.getImei() == null) {errors.add(String.format("第%d行%s", currentRowNum, "終端imei不能為空"));return;}// 2. 數據校驗 注解校驗Set<ConstraintViolation<TerminalImportVO>> violations = validator.validate(terminalImportVO);if (!violations.isEmpty()) {for (ConstraintViolation<TerminalImportVO> violation : violations) {String fieldName = violation.getPropertyPath().toString();String errorMsg = violation.getMessage();errors.add(String.format("第%d行%s", currentRowNum, errorMsg));}// 校驗失敗,跳過該行數據return;}// 3. 業務邏輯校驗if (!validateBusinessRules(terminalImportVO, currentRowNum)) {return;}// 4. 數據去重校驗(防止重復導入)if (isDuplicateUser(terminalImportVO, currentRowNum)) {return;}log.info("第{}行數據校驗通過", currentRowNum);// 校驗通過,添加到緩存列表TerminalInfo terminalInfo = new TerminalInfo();BeanUtils.copyProperties(terminalImportVO, terminalInfo);cachedDataList.add(terminalInfo);successCount++;// 達到BATCH_COUNT了,需要去存儲一次數據庫,防止數據幾萬條數據在內存,容易OOMif (cachedDataList.size() >= BATCH_COUNT) {saveData();// 存儲完成清理 listcachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);}} catch (Exception e) {errors.add(String.format("第%d行%s", currentRowNum, "數據處理異常"));log.error("第{}行數據處理異常", currentRowNum);}}/*** 所有數據解析完成后會來調用** @param analysisContext 分析上下文*/@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {try {// 這里也要保存數據,確保最后遺留的數據也存儲到數據庫saveData();log.info("Excel解析完成!總行數: {}, 成功: {}, 失敗: {}", totalRowNum - 1, successCount, errors.size());// 如果有錯誤,拋出包含所有錯誤信息的異常if (!errors.isEmpty() && !hasThrownException) {hasThrownException = true;// 減1是排除表頭行throw new ExcelImportException(errors, totalRowNum - 1);}} catch (Exception e) {log.error("數據最終處理異常:{}", e.getMessage());if (!hasThrownException) {hasThrownException = true;throw new RuntimeException("數據處理異常: " + e.getMessage());}}}/*** 保存數據到數據庫*/private void saveData() {if (cachedDataList.isEmpty()) {return;}try {log.info("開始保存{}條數據到數據庫", cachedDataList.size());terminalInfoService.saveBatch(cachedDataList);log.info("存儲數據庫成功!");} catch (Exception e) {log.error("數據保存失敗:{}", e);// 記錄批量保存失敗的錯誤for (TerminalInfo terminalInfo : cachedDataList) {int estimatedRowNum = totalRowNum - cachedDataList.size() + cachedDataList.indexOf(terminalInfo) + 1;errors.add(String.format("第%d行%s", estimatedRowNum, "數據保存失敗"));}// 從成功計數中減去這些失敗的數據successCount -= cachedDataList.size();}}/*** 業務規則校驗** @param terminalImportVO 終端數據* @param rowNum           行號* @return 是否通過校驗*/private boolean validateBusinessRules(TerminalImportVO terminalImportVO, int rowNum) {LambdaQueryWrapper<TerminalInfo> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(TerminalInfo::getImei, terminalImportVO.getImei());queryWrapper.eq(TerminalInfo::getDelFlag, 0);TerminalInfo terminalInfo = terminalInfoService.getOne(queryWrapper);if (ObjectUtil.isNotEmpty(terminalInfo)) {errors.add(String.format("第%d行%s", rowNum,  String.format("終端imei: %s 已存在!", terminalImportVO.getImei())));return false;}return true;}/*** 檢查是否重復終端imei** @param terminalImportVO 終端數據* @param rowNum           行號* @return 是否重復*/private boolean isDuplicateUser(TerminalImportVO terminalImportVO, int rowNum) {for (TerminalInfo terminalInfo : cachedDataList) {if (terminalInfo.getImei().equals(terminalImportVO.getImei())) {errors.add(String.format("第%d行%s", rowNum, "終端imei重復: " + terminalImportVO.getImei()));return true;}}return false;}/*** 獲取錯誤信息列表** @return 錯誤信息列表*/public List<String> getErrors() {return new ArrayList<>(errors);}/*** 獲取失敗數量** @return 失敗數量*/public int getFailedCount() {return errors.size();}
}

4. 控制器層

@GetMapping("/template")@Operation(summary = "下載導入模板")public void downloadTemplate(HttpServletResponse response) throws IOException {try {// 設置響應頭setExcelResponseHeader(response, "終端導入模板");// 創建一個空的用戶列表(只有表頭)List<TerminalImportVO> emptyList = List.of();// 寫入ExcelEasyExcel.write(response.getOutputStream(), TerminalImportVO.class).excelType(ExcelTypeEnum.XLSX).sheet("終端數據").doWrite(emptyList);log.info("模板下載成功");} catch (Exception e) {log.error("模板下載失敗", e);response.reset();response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().println("{\"code\":500,\"message\":\"模板下載失敗:\" + e.getMessage() + \"\"}");}}/*** 導入Excel數據*/@PostMapping("/import")@Operation(summary = "導入Excel數據")public ApiResponse<ExcelImportResponse> importExcel(@RequestParam("file") MultipartFile file) throws IOException {if (file.isEmpty()) {return ApiResponse.error("文件為空!");}// 檢查文件類型String filename = file.getOriginalFilename();if (filename == null || (!filename.endsWith(".xlsx") && !filename.endsWith(".xls"))) {return ApiResponse.error("請上傳Excel文件(.xlsx或.xls)");}TerminalExcelListener listener = new TerminalExcelListener(terminalInfoService);try {// 讀取Excel文件EasyExcel.read(file.getInputStream(), TerminalImportVO.class, listener).sheet().doRead();// 如果沒有錯誤,返回成功響應ExcelImportResponse response = new ExcelImportResponse(listener.getTotalRowNum(), listener.getSuccessCount(), listener.getFailedCount(), listener.getErrors());return ApiResponse.success(response.getSummaryMessage(), response);} catch (ExcelImportException e) {// 捕獲自定義異常,返回詳細的錯誤信息ExcelImportResponse response = new ExcelImportResponse(e.getTotalRows(), e.getSuccessRows(), e.getFailedRows(), e.getErrors());return ApiResponse.error(response.getSummaryMessage(), HttpStatusConstant.ERROR, response);} catch (Exception e) {log.error("導入失敗", e);return ApiResponse.error("導入失敗: " + e.getMessage());}}/*** 導出Excel數據*/@GetMapping("/export")@Operation(summary = "導出Excel數據")public void exportExcel(HttpServletResponse response) throws IOException {try {// 設置響應頭setExcelResponseHeader(response, "終端數據導出");// 獲取數據List<TerminalInfo> terminalInfoList = terminalInfoService.list();// 寫入ExcelEasyExcel.write(response.getOutputStream(), TerminalInfo.class).excelType(ExcelTypeEnum.XLSX).sheet("終端數據").doWrite(terminalInfoList);log.info("導出成功,共{}條數據", terminalInfoList.size());} catch (Exception e) {log.error("導出失敗", e);response.reset();response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().println("{\"code\":500,\"message\":\"導出失敗:\" + e.getMessage() + \"\"}");}}/*** 設置Excel響應頭*/private void setExcelResponseHeader(HttpServletResponse response, String rawFileName) throws IOException {String fileName = URLEncoder.encode(rawFileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");}

5. 全局異常處理

import com.cetcnav.dto.ApiResponse;
import com.cetcnav.dto.ExcelImportResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;import java.util.List;
import java.util.stream.Collectors;/*** @Description: 全局異常處理器* @date 2025-09-15 14:59*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 處理Excel導入異常*/@ExceptionHandler(ExcelImportException.class)public ApiResponse<Object> handleExcelImportException(ExcelImportException e) {log.warn("Excel導入數據校驗失敗: {}", e.getErrorSummary());return ApiResponse.error(e.getMessage(), 400);}/*** 處理文件大小超過限制異常*/@ExceptionHandler(MaxUploadSizeExceededException.class)public ApiResponse<Object> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {log.warn("文件大小超過限制", e);return ApiResponse.error("文件大小不能超過10MB");}/*** 處理所有其他異常*/@ExceptionHandler(Exception.class)public ApiResponse<Object> handleException(Exception e) {log.error("系統異常", e);return ApiResponse.error("系統異常: " + e.getMessage());}
}

6.Excel導入響應對象

import lombok.Getter;
import java.util.List;/*** @Description: Excel導入響應對象* @date 2025-09-15 16:24*/
@Getter
public class ExcelImportException extends RuntimeException {private final List<String> errors;private final int totalRows;private final int successRows;private final int failedRows;public ExcelImportException(List<String> errors, int totalRows) {super("Excel導入完成,但有部分數據校驗失敗,請查看詳細信息");this.errors = errors;this.totalRows = totalRows;this.successRows = totalRows - errors.size();this.failedRows = errors.size();}/*** 獲取簡化的錯誤摘要信息(用于日志等)*/public String getErrorSummary() {if (errors.isEmpty()) {return "無錯誤";}StringBuilder summary = new StringBuilder();summary.append("共").append(errors.size()).append("處錯誤:");// 按錯誤類型統計errors.forEach(message -> summary.append(message).append("; "));return summary.toString();}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/97971.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/97971.shtml
英文地址,請注明出處:http://en.pswp.cn/web/97971.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Ruby編程實踐:20個實用練習

1、編寫一個程序,計算一年有多少小時。 以下是兩種實現方式的代碼: 方式一: puts 24*365方式二: puts 24*365 puts "(or #{24*366} on a leap year)"2、編寫一個程序,計算十年中有多少分鐘。 以下兩種實現方式: 簡單計算(未考慮閏年數量差異): ru…

邏輯回歸(二):從原理到實戰 - 訓練、評估與應用指南

引言&#xff1a; 上期我們講了什么是邏輯回歸&#xff0c;了解了它如何利用Sigmoid函數將線性回歸的輸出轉化為概率&#xff0c;并通過最大似然估計來尋找最佳參數。今天&#xff0c;我們將繼續這段旅程&#xff0c;學習如何訓練這個 模型、如何評估它的表現&#xff0c;以及如…

9.8C++作業

思維導圖#include <iostream> #include <vector> #include <fstream> using namespace std;class Stu {friend ofstream &operator<<(ofstream &ofs,const Stu &stu); private:string name;string id;int age;double score; public:Stu(){…

Linux內存管理章節十六:非均勻的內存訪問:深入Linux NUMA架構內存管理

引言 在傳統的SMP&#xff08;對稱多處理&#xff09;系統中&#xff0c;所有CPU核心通過一條共享總線訪問同一塊內存&#xff0c;所有內存訪問延遲是均勻的&#xff08;UMA&#xff09;。然而&#xff0c;隨著CPU核心數量的增加&#xff0c;共享總線成為了巨大的性能和 scalab…

【論文翻譯】Seg-Zero: Reasoning-Chain Guided Segmentation via Cognitive Reinforcement

0. 摘要Traditional methods for reasoning segmentation rely on supervised fine-tuning with categorical labels and simple descriptions, limiting its out-of-domain generalization and lacking explicit reasoning processes. To address these limitations, we propo…

Playwright MCP瀏覽器自動化教程

你是否曾厭倦在編程軟件和瀏覽器之間反復切換&#xff0c;只為了檢查AI生成的代碼能否正常運行&#xff1f;現在&#xff0c;有了Playwright MCP&#xff08;Model Context Protocol&#xff09;&#xff0c;你可以直接讓AI自己操作瀏覽器&#xff0c;查看自己寫的代碼運行效果…

矩陣中遍歷某個點周圍的九個點

又是學習新知識的一天,以下為Java版本部分關鍵代碼int[] neighbors {0, 1, -1};int rows board.length;int cols board[0].length;int[][] copyBoard new int[rows][cols];for (int row 0; row < rows; row) {for (int col 0; col < cols; col) {int liveNeighbors…

單例模式:只有一個對象

目錄 什么是單例模式 能解決什么問題 使用場景 如何實現 __new__ 方法&#xff1a;經典又直接 裝飾器&#xff1a;不改類本身&#xff0c;也能單例 模塊本身就是單例 注意事項 總結 你有沒有過這樣的困擾&#xff1a; “為什么我明明只創建了一次數據庫連接&#xff0…

AI大模型學習(6)Yolo V8神經網絡的基礎應用

Yolo V8神經網絡的基礎應用2024-2025年最火的目標檢測神器&#xff0c;一篇文章讓你徹底搞懂&#xff01;&#x1f929;大家好呀&#xff01;今天我們要聊一聊計算機視覺領域的「明星模型」——YOLO神經網絡&#xff01;&#x1f3af; 如果你對「目標檢測」這個詞還比較陌生&am…

C++:imagehlp庫

imagehlp庫1. 簡介2. 主要函數與用途2.1PE 文件解析相關2.2 符號處理相關2.3 崩潰轉儲相關2.4 版本資源相關3. 使用示例3.1 解析內存地址對應的函數名和行號3.2 創建目錄使用示例1. 簡介 imagehlp 是 Windows 系統提供的一個圖像處理與調試輔助 API 庫&#xff08;Image Helpe…

如何在Anaconda中配置你的CUDA Pytorch cuNN環境(2025最新教程)

目錄 一、簡介 二、下載CUDA 三、下載Pytorch-GPU版本 四、下載CUDNN 五、總結 六、測試代碼 一、簡介 啥是Anaconda?啥是CUDA?啥是CUDNN&#xff1f;它們和Pytorch、GPU之間有啥關系? 怎么通俗解釋它們三者的用途和關系&#xff1f; 1.GPU(圖形處理單元&#xff09…

算法面試(1)-----目標檢測和圖像分類、語義分割的區別

操作系統&#xff1a;ubuntu22.04 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 目標檢測&#xff08;Object Detection&#xff09;、圖像分類&#xff08;Image Classification&#xff09;、語義分割&#xff08;Semantic Segmentation&#xff09; 是計算機視…

電腦散熱風扇有噪音怎么解決

一、初步檢查與清理斷電并拆機關閉電腦并拔掉電源&#xff0c;打開機箱側板&#xff08;筆記本需先拆除后蓋螺絲&#xff09;。操作前建議佩戴防靜電手環&#xff0c;避免靜電損壞硬件。清理風扇及散熱片灰塵使用壓縮空氣罐從風扇進風口吹走灰塵&#xff0c;或用軟毛刷輕輕刷去…

SeaweedFS深度解析(九):k8s環境使用helm部署Seaweedfs集群

上一篇&#xff1a;《SeaweedFS深度解析&#xff08;八&#xff09;&#xff1a;k8s環境使用Operator部署Seaweedfs集群》 鏈接: link #作者&#xff1a;閆乾苓 文章目錄k8s環境使用helm部署Seaweedfs集群準備鏡像seaweed-master-localpv-storageclass.yamlseaweed-volume-lo…

MATLAB繪制一個新穎的混沌圖像(新四翼混沌系統)

新四翼混沌系統:dx/dt a(y - x) yz dy/dt cx - y - xz dz/dt -bz xyMATLAB代碼:function plot_novel_chaotic_system() % 參數設置 a 10; b 8/3; c 28;% 初始條件 x0 [1, 1, 1];% 時間范圍 tspan [0 100];% 求解微分方程 [t, x] ode45((t, x) chaotic_system(t, x, …

金融數據---獲取股票日線數據

獲取股票日線的數據方式有很多&#xff0c;包括東方財富&#xff0c;同花順&#xff0c;tushare&#xff0c;這里我們就利用東方財富的數據&#xff0c;是免費的開源獲取&#xff0c;第一步先安裝akshare&#xff0c;pip安裝就可以py -m pip install akshareAkshare 股票數據獲…

Mac 真正多顯示器支持:TESmart USB-C KVM(搭載 DisplayLink 技術)如何實現

多顯示器已經不再是奢侈品&#xff0c;而是專業人士提升生產力的必需工具。無論是創意設計師、股票交易員還是軟件開發人員&#xff0c;多屏幕都能讓工作流程更高效、更有條理。 然而&#xff0c;Mac 用戶長期以來面臨一個主要障礙&#xff1a;macOS 原生不支持多流傳輸&#x…

【實時Linux實戰系列】靜態鏈接與libc選擇:musl vs glibc的時延權衡

背景與重要性 在實時系統開發中&#xff0c;選擇合適的C標準庫&#xff08;libc&#xff09;和鏈接方式對系統的啟動時間、線程性能和內存分配效率有著顯著影響。glibc和musl是兩種流行的C標準庫實現&#xff0c;它們在設計目標和性能表現上存在差異。通過對比這兩種libc在啟動…

Altium Designer(AD24)的三種文件組織形式,工程文件,自由文件與存盤文件

??《專欄目錄》 目錄 1,概述 2,工程文件 3,自由文件 4,存盤文件 5,文件轉換 5.1,工程文件于自由文件互轉換 5.2,工程文件于存盤文件互轉換 6,注意事項 1,概述 本文介紹Altium Designer 24軟件(后文簡稱AD24或軟件)的三種文件組織形式,工程文件,自由文件和存盤文…

Python+Selenium實現自動化測試

&#x1f345; 點擊文末小卡片 &#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快安裝selenium打開命令控制符輸入&#xff1a;pip install -U selenium火狐瀏覽器安裝firebug&#xff1a;www.firebug.com&#xff0c;調試所有網站語言&#xff0…