EasyExcel高效工具類:簡化Excel導入導出,支持多Sheet與枚舉轉換

文章目錄

  • 前言
  • 一、依賴坐標
  • 二、工具類:ExcelUtil
  • 三、測試
    • 1.實體類
    • 2.前置操作
    • 3.單Sheet導出
    • 4.單Sheet導入
    • 5.多Sheet導出
    • 6.多Sheet導入
    • 7.完整代碼
  • 四、擴展:自定義注解實現枚舉類型轉換
    • 1.枚舉接口
    • 2.枚舉類
    • 3.注解
    • 4.轉換類
    • 5.使用示例
    • 6.測試
  • 總結


前言

在現代應用開發中,高效可靠的Excel處理能力已成為企業級應用的剛需。本文介紹的Excel工具類基于阿里巴巴EasyExcel技術棧,提供從基礎數據導入導出、多Sheet復雜報表生成到枚舉類型自動轉換的一站式解決方案。通過簡潔的API設計和嚴謹的內存管理,開發者可輕松應對各種Excel處理場景,避免重復造輪子,顯著提升開發效率。

一、依賴坐標

核心依賴

		<!--表格處理——easyexcel--><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.2</version></dependency><!-- Lombok(簡化代碼結構) --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version></dependency><!-- Spring Web(文件上傳支持) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>

依賴說明

依賴核心功能版本要求優勢特性
EasyExcelExcel 讀寫操作≥3.3.2低內存占用、高性能處理
Lombok簡化代碼結構(非必需)-減少樣板代碼
Spring Web文件上傳支持(非必需)-簡化 Web 集成

二、工具類:ExcelUtil

核心功能設計

方法名參數功能描述返回值示例
importExcelfile=訂單.xlsx, clazz=Order.class導入訂單數據到Order對象列表List<Order>
exportSinglebaseName="訂單", sheet=orderSheet導出訂單數據到單Sheet文件/exports/訂單_20240520.xlsx
exportMultibaseName="報表", sheets=[sheet1,sheet2]導出多Sheet財務報表/exports/報表_20240520.xlsx(多sheet)

Sheet 數據封裝類

@Data
@AllArgsConstructor
public static class Sheet<T> {private final String name;   // Sheet名稱private final List<T> data;  // 數據列表private final Class<T> clazz;// 數據模型類
}            

完整代碼

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/*** Excel 工具類*/
@Slf4j
@RequiredArgsConstructor
@Component
public class ExcelUtil {// 默認導出目錄(可自定義)@Value("${vehicle.export}")//這里通過配置文件配置,然后用spring提供的value注解注入的,也可以直接指定private  String defaultExportDir;/*** 導入整張表*/public <T> List<T> importExcel(MultipartFile file, Class<T> clazz) throws IOException {if (file.isEmpty()) {throw new IllegalArgumentException("上傳的文件為空");}return EasyExcel.read(file.getInputStream()).head(clazz).sheet().doReadSync();}/*** 導出單個 Sheet*/public Path exportSingle(String baseName, Sheet<?> sheet) throws IOException {String filepath = generateUniqueFileName(baseName);EasyExcel.write(filepath, sheet.getClazz()).sheet(sheet.getName()).doWrite(sheet.getData());return Path.of(filepath);}/*** 導出多個 Sheet*/public Path exportMulti(String baseName, List<Sheet<?>> sheets) throws IOException {String filepath = generateUniqueFileName(baseName);// 創建 ExcelWriterExcelWriter writer = EasyExcel.write(filepath).build();// 寫入每個 sheetfor (int i = 0; i < sheets.size(); i++) {Sheet<?> sheet = sheets.get(i);WriteSheet writeSheet = EasyExcel.writerSheet(i, sheet.getName()).head(sheet.getClazz()).build();writer.write(sheet.getData(), writeSheet);}// 手動關閉 ExcelWriterwriter.finish();return Path.of(filepath);}/*** 生成帶時間戳的唯一文件名(避免覆蓋)*/private String generateUniqueFileName(String baseName) throws IOException {// 確保目錄存在File dir = new File(defaultExportDir);if (!dir.exists()) {dir.mkdirs();}// 使用時間戳和隨機數生成唯一文件名String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));String randomSuffix = java.util.UUID.randomUUID().toString().substring(0, 4); // 隨機后綴,避免極小概率的沖突return String.format("%s%s_%s_%s.xlsx", defaultExportDir, baseName, timestamp, randomSuffix);}/*** Sheet 數據封裝類*/@AllArgsConstructor@Datapublic static class Sheet<T> {private final String name;private final List<T> data;private final Class<T> clazz;}
}

三、測試

1.實體類

// 測試實體類 - 用戶
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class User {@ExcelProperty("用戶ID")private Long id;@ExcelProperty("姓名")private String name;@ExcelProperty("郵箱")private String email;@ExcelProperty("注冊日期")private LocalDate registerDate;
}// 測試實體類 - 產品
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Product {@ExcelProperty("產品ID")private String productId;@ExcelProperty("產品名稱")private String name;@ExcelProperty("價格")private Double price;@ExcelProperty("庫存數量")private Integer stock;
}

2.前置操作

private ExcelUtil excelUtil;
private List<User> testUsers;
private List<Product> testProducts;// 導出目錄 - src/test/resources/excel
private static final String EXPORT_DIR = "src/test/resources/excel/";@BeforeEach
void setUp() throws NoSuchFieldException, IllegalAccessException {// 初始化ExcelUtil,設置導出目錄excelUtil = new ExcelUtil();//反射獲取數據Field field = ExcelUtil.class.getDeclaredField("defaultExportDir");field.setAccessible(true);field.set(excelUtil, EXPORT_DIR);// 準備測試用戶數據testUsers = new ArrayList<>();testUsers.add(new User(1L, "張三", "zhangsan@example.com", LocalDate.of(2023, 1, 15)));testUsers.add(new User(2L, "李四", "lisi@example.com", LocalDate.of(2023, 3, 22)));testUsers.add(new User(3L, "王五", "wangwu@example.com", LocalDate.of(2023, 5, 30)));// 準備測試產品數據testProducts = new ArrayList<>();testProducts.add(new Product("P001", "筆記本電腦", 5999.0, 120));testProducts.add(new Product("P002", "智能手機", 3999.0, 200));testProducts.add(new Product("P003", "平板電腦", 2999.0, 150));
}

3.單Sheet導出

/*** 測試單Sheet導出功能* 文件將導出到 src/test/resources/excel 目錄*/@Testvoid testSingleSheetExport() throws IOException {// 1. 創建用戶數據SheetExcelUtil.Sheet<User> userSheet = new ExcelUtil.Sheet<>("用戶列表", testUsers, User.class);// 2. 導出用戶數據到單Sheet ExcelPath exportPath = excelUtil.exportSingle("test_users_export", userSheet);// 3. 打印文件路徑System.out.println("單Sheet導出路徑: " + exportPath.toAbsolutePath());// 4. 驗證文件已創建assertTrue(Files.exists(exportPath), "導出的Excel文件應存在");assertTrue(exportPath.toString().endsWith(".xlsx"), "文件應為.xlsx格式");assertTrue(Files.size(exportPath) > 0, "文件大小應大于0");}

在這里插入圖片描述

4.單Sheet導入

修改文件名成剛生成的文件

在這里插入圖片描述

	/*** 測試單Sheet導入功能 (文件名需要修改成對應生成的文件)* 使用預先導出的文件進行導入測試*/@Testvoid testSingleSheetImport() throws IOException {File importFile = new File(EXPORT_DIR + "test_users_export_20250806202812_93bf.xlsx");System.out.println("使用導入文件: " + importFile.getAbsolutePath());// 準備MultipartFilebyte[] fileContent = Files.readAllBytes(importFile.toPath());MockMultipartFile mockFile = new MockMultipartFile("users.xlsx", "users.xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",fileContent);// 導入用戶數據List<User> importedUsers = excelUtil.importExcel(mockFile, User.class);// 驗證導入結果assertEquals(testUsers.size(), importedUsers.size(), "導入的用戶數量應匹配");for (int i = 0; i < testUsers.size(); i++) {User original = testUsers.get(i);User imported = importedUsers.get(i);assertEquals(original.getId(), imported.getId(), "用戶ID應匹配");assertEquals(original.getName(), imported.getName(), "用戶名應匹配");assertEquals(original.getEmail(), imported.getEmail(), "郵箱應匹配");assertEquals(original.getRegisterDate(), imported.getRegisterDate(), "注冊日期應匹配");}// 打印導入結果System.out.println("成功導入用戶數據: " + importedUsers.size() + " 條");importedUsers.forEach(user ->System.out.printf("ID: %d, 姓名: %s, 郵箱: %s%n",user.getId(), user.getName(), user.getEmail()));}

在這里插入圖片描述

5.多Sheet導出

	/*** 測試多Sheet導出功能* 文件將導出到 src/test/resources/excel 目錄*/@Testvoid testMultiSheetExport() throws IOException {// 1. 準備多Sheet數據ExcelUtil.Sheet<User> userSheet = new ExcelUtil.Sheet<>("用戶數據", testUsers, User.class);ExcelUtil.Sheet<Product> productSheet = new ExcelUtil.Sheet<>("產品數據", testProducts, Product.class);List<ExcelUtil.Sheet<?>> sheets = new ArrayList<>();sheets.add(userSheet);sheets.add(productSheet);// 2. 導出到多Sheet ExcelPath exportPath = excelUtil.exportMulti("test_multi_sheet_export", sheets);// 3. 打印文件路徑System.out.println("多Sheet導出路徑: " + exportPath.toAbsolutePath());// 4. 驗證文件已創建assertTrue(Files.exists(exportPath), "導出的Excel文件應存在");}

在這里插入圖片描述
![(https://i-blog.csdnimg.cn/direct/b57095af4a404fb8980e8e0df0ee27be.png)

6.多Sheet導入

在這里插入圖片描述

	/*** 測試多Sheet導入功能* 使用預先導出的文件進行導入測試*/@Testvoid testMultiSheetImport() throws IOException {File importFile = new File(EXPORT_DIR + "test_multi_sheet_export_20250806203417_2318.xlsx");System.out.println("使用導入文件: " + importFile.getAbsolutePath());// 導入用戶數據(第一個Sheet)byte[] fileContent = Files.readAllBytes(importFile.toPath());MockMultipartFile userFile = new MockMultipartFile("multi_sheet.xlsx", "multi_sheet.xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",fileContent);List<User> importedUsers = excelUtil.importExcel(userFile, User.class);// 驗證用戶數據assertEquals(testUsers.size(), importedUsers.size(), "導入的用戶數量應匹配");// 導入產品數據(第二個Sheet)MockMultipartFile productFile = new MockMultipartFile("multi_sheet.xlsx", "multi_sheet.xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",fileContent);List<Product> importedProducts = EasyExcel.read(productFile.getInputStream()).head(Product.class).sheet(1)  // 第二個Sheet(索引從0開始).doReadSync();// 驗證產品數據assertEquals(testProducts.size(), importedProducts.size(), "導入的產品數量應匹配");for (int i = 0; i < testProducts.size(); i++) {Product original = testProducts.get(i);Product imported = importedProducts.get(i);assertEquals(original.getProductId(), imported.getProductId(), "產品ID應匹配");assertEquals(original.getName(), imported.getName(), "產品名稱應匹配");assertEquals(original.getPrice(), imported.getPrice(), 0.001, "產品價格應匹配");assertEquals(original.getStock(), imported.getStock(), "庫存數量應匹配");}// 打印導入結果System.out.println("成功導入用戶數據: " + importedUsers.size() + " 條");System.out.println("成功導入產品數據: " + importedProducts.size() + " 條");}

在這里插入圖片描述

7.完整代碼

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fc.utils.ExcelUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockMultipartFile;import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;public class ExcelUtilTest {// 測試實體類 - 用戶@Data@AllArgsConstructor@NoArgsConstructorpublic static class User {@ExcelProperty("用戶ID")private Long id;@ExcelProperty("姓名")private String name;@ExcelProperty("郵箱")private String email;@ExcelProperty("注冊日期")private LocalDate registerDate;}// 測試實體類 - 產品@Data@AllArgsConstructor@NoArgsConstructorpublic static class Product {@ExcelProperty("產品ID")private String productId;@ExcelProperty("產品名稱")private String name;@ExcelProperty("價格")private Double price;@ExcelProperty("庫存數量")private Integer stock;}private ExcelUtil excelUtil;private List<User> testUsers;private List<Product> testProducts;// 導出目錄 - src/test/resources/excelprivate static final String EXPORT_DIR = "src/test/resources/excel/";@BeforeEachvoid setUp() throws NoSuchFieldException, IllegalAccessException {// 初始化ExcelUtil,設置導出目錄excelUtil = new ExcelUtil();//反射獲取數據Field field = ExcelUtil.class.getDeclaredField("defaultExportDir");field.setAccessible(true);field.set(excelUtil, EXPORT_DIR);// 準備測試用戶數據testUsers = new ArrayList<>();testUsers.add(new User(1L, "張三", "zhangsan@example.com", LocalDate.of(2023, 1, 15)));testUsers.add(new User(2L, "李四", "lisi@example.com", LocalDate.of(2023, 3, 22)));testUsers.add(new User(3L, "王五", "wangwu@example.com", LocalDate.of(2023, 5, 30)));// 準備測試產品數據testProducts = new ArrayList<>();testProducts.add(new Product("P001", "筆記本電腦", 5999.0, 120));testProducts.add(new Product("P002", "智能手機", 3999.0, 200));testProducts.add(new Product("P003", "平板電腦", 2999.0, 150));}/*** 測試單Sheet導出功能* 文件將導出到 src/test/resources/excel 目錄*/@Testvoid testSingleSheetExport() throws IOException {// 1. 創建用戶數據SheetExcelUtil.Sheet<User> userSheet = new ExcelUtil.Sheet<>("用戶列表", testUsers, User.class);// 2. 導出用戶數據到單Sheet ExcelPath exportPath = excelUtil.exportSingle("test_users_export", userSheet);// 3. 打印文件路徑System.out.println("單Sheet導出路徑: " + exportPath.toAbsolutePath());// 4. 驗證文件已創建assertTrue(Files.exists(exportPath), "導出的Excel文件應存在");assertTrue(exportPath.toString().endsWith(".xlsx"), "文件應為.xlsx格式");assertTrue(Files.size(exportPath) > 0, "文件大小應大于0");}/*** 測試多Sheet導出功能* 文件將導出到 src/test/resources/excel 目錄*/@Testvoid testMultiSheetExport() throws IOException {// 1. 準備多Sheet數據ExcelUtil.Sheet<User> userSheet = new ExcelUtil.Sheet<>("用戶數據", testUsers, User.class);ExcelUtil.Sheet<Product> productSheet = new ExcelUtil.Sheet<>("產品數據", testProducts, Product.class);List<ExcelUtil.Sheet<?>> sheets = new ArrayList<>();sheets.add(userSheet);sheets.add(productSheet);// 2. 導出到多Sheet ExcelPath exportPath = excelUtil.exportMulti("test_multi_sheet_export", sheets);// 3. 打印文件路徑System.out.println("多Sheet導出路徑: " + exportPath.toAbsolutePath());// 4. 驗證文件已創建assertTrue(Files.exists(exportPath), "導出的Excel文件應存在");}/*** 測試單Sheet導入功能 (文件名需要修改成對應生成的文件)* 使用預先導出的文件進行導入測試*/@Testvoid testSingleSheetImport() throws IOException {File importFile = new File(EXPORT_DIR + "test_users_export_*.xlsx");System.out.println("使用導入文件: " + importFile.getAbsolutePath());// 準備MultipartFilebyte[] fileContent = Files.readAllBytes(importFile.toPath());MockMultipartFile mockFile = new MockMultipartFile("users.xlsx", "users.xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",fileContent);// 導入用戶數據List<User> importedUsers = excelUtil.importExcel(mockFile, User.class);// 驗證導入結果assertEquals(testUsers.size(), importedUsers.size(), "導入的用戶數量應匹配");for (int i = 0; i < testUsers.size(); i++) {User original = testUsers.get(i);User imported = importedUsers.get(i);assertEquals(original.getId(), imported.getId(), "用戶ID應匹配");assertEquals(original.getName(), imported.getName(), "用戶名應匹配");assertEquals(original.getEmail(), imported.getEmail(), "郵箱應匹配");assertEquals(original.getRegisterDate(), imported.getRegisterDate(), "注冊日期應匹配");}// 打印導入結果System.out.println("成功導入用戶數據: " + importedUsers.size() + " 條");importedUsers.forEach(user ->System.out.printf("ID: %d, 姓名: %s, 郵箱: %s%n",user.getId(), user.getName(), user.getEmail()));}/*** 測試多Sheet導入功能* 使用預先導出的文件進行導入測試*/@Testvoid testMultiSheetImport() throws IOException {File importFile = new File(EXPORT_DIR + "test_multi_sheet_export_*.xlsx");System.out.println("使用導入文件: " + importFile.getAbsolutePath());// 導入用戶數據(第一個Sheet)byte[] fileContent = Files.readAllBytes(importFile.toPath());MockMultipartFile userFile = new MockMultipartFile("multi_sheet.xlsx", "multi_sheet.xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",fileContent);List<User> importedUsers = excelUtil.importExcel(userFile, User.class);// 驗證用戶數據assertEquals(testUsers.size(), importedUsers.size(), "導入的用戶數量應匹配");// 導入產品數據(第二個Sheet)MockMultipartFile productFile = new MockMultipartFile("multi_sheet.xlsx", "multi_sheet.xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",fileContent);List<Product> importedProducts = EasyExcel.read(productFile.getInputStream()).head(Product.class).sheet(1)  // 第二個Sheet(索引從0開始).doReadSync();// 驗證產品數據assertEquals(testProducts.size(), importedProducts.size(), "導入的產品數量應匹配");for (int i = 0; i < testProducts.size(); i++) {Product original = testProducts.get(i);Product imported = importedProducts.get(i);assertEquals(original.getProductId(), imported.getProductId(), "產品ID應匹配");assertEquals(original.getName(), imported.getName(), "產品名稱應匹配");assertEquals(original.getPrice(), imported.getPrice(), 0.001, "產品價格應匹配");assertEquals(original.getStock(), imported.getStock(), "庫存數量應匹配");}// 打印導入結果System.out.println("成功導入用戶數據: " + importedUsers.size() + " 條");System.out.println("成功導入產品數據: " + importedProducts.size() + " 條");}
}

四、擴展:自定義注解實現枚舉類型轉換

自定義注解實現枚舉定義的code->description的轉化

1.枚舉接口

public interface IEnum {int getCode();String getDescription();
}

2.枚舉類

import lombok.Getter;
@Getter
public enum TransportType implements IEnum{SINGLE(1, "單邊"),BILATERAL(2, "往返");private final int code;private final String description;TransportType(int code, String description) {this.code = code;this.description = description;}public static TransportType fromCode(int code) {for (TransportType status : TransportType.values()) {if (status.getCode() == code) {return status;}}throw new IllegalArgumentException("狀態異常: " + code);}
}

3.注解

import com.fc.enums.IEnum;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumClass {/*** 指定枚舉類,必須實現 IEnum 且有 fromCode(int) 靜態方法*/Class<? extends IEnum> value();
}

4.轉換類

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.fc.anno.EnumClass;
import com.fc.enums.IEnum;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;public class EnumConverter implements Converter<Object> {/*** key   -> 枚舉 Class* value -> description -> code 的映射(導入用)*/private static final Map<Class<?>, Map<String, Integer>> DESC_TO_CODE_CACHE = new ConcurrentHashMap<>();/*** key   -> 枚舉 Class* value -> code -> description 的映射(導出用)*/private static final Map<Class<?>, Map<Integer, String>> CODE_TO_DESC_CACHE = new ConcurrentHashMap<>();@Overridepublic Class<?> supportJavaTypeKey() {return Object.class;   // 支持任意枚舉}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}@Overridepublic Object convertToJavaData(ReadCellData<?> cellData,ExcelContentProperty contentProperty,GlobalConfiguration globalConfiguration) throws Exception {String cellValue = cellData.getStringValue();if (cellValue == null || cellValue.trim().isEmpty()) {return null;}Class<? extends IEnum> enumClass = getEnumClass(contentProperty);Map<String, Integer> descToCode = DESC_TO_CODE_CACHE.computeIfAbsent(enumClass, this::buildDescToCodeMap);Integer code = descToCode.get(cellValue.trim());if (code == null) {throw new IllegalArgumentException("找不到對應枚舉描述:" + cellValue);}Method fromCode = enumClass.getDeclaredMethod("fromCode", int.class);return fromCode.invoke(null, code);   // 返回 Integer 或枚舉都行}@Overridepublic WriteCellData<?> convertToExcelData(Object value,ExcelContentProperty contentProperty,GlobalConfiguration globalConfiguration) throws Exception {if (value == null) {return new WriteCellData<>("");}Class<? extends IEnum> enumClass = getEnumClass(contentProperty);Map<Integer, String> codeToDesc = CODE_TO_DESC_CACHE.computeIfAbsent(enumClass, this::buildCodeToDescMap);int code;if (value instanceof Number) {code = ((Number) value).intValue();} else if (value instanceof IEnum) {code = ((IEnum) value).getCode();} else {throw new IllegalArgumentException("不支持的類型:" + value.getClass());}return new WriteCellData<>(codeToDesc.getOrDefault(code, ""));}private static Class<? extends IEnum> getEnumClass(ExcelContentProperty contentProperty) {/* 重點:從注解里拿到枚舉類 */EnumClass enumClassAnno = contentProperty.getField().getAnnotation(EnumClass.class);if (enumClassAnno == null) {throw new IllegalStateException("字段必須使用 @EnumClass 指定枚舉");}return enumClassAnno.value();}private Map<Integer, String> buildCodeToDescMap(Class<?> enumClass) {return Arrays.stream(enumClass.getEnumConstants()).map(o -> (IEnum) o).collect(Collectors.toMap(IEnum::getCode, IEnum::getDescription));}private Map<String, Integer> buildDescToCodeMap(Class<?> enumClass) {return Arrays.stream(enumClass.getEnumConstants()).map(o -> (IEnum) o).collect(Collectors.toMap(IEnum::getDescription, IEnum::getCode));}
}

5.使用示例

在這里插入圖片描述

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {@ExcelProperty("產品ID")private String productId;@ExcelProperty("產品名稱")private String name;@ExcelProperty("價格")private Double price;@ExcelProperty(value = "運輸類型",converter = EnumConverter.class)//使用自定義的轉換類@EnumClass(value = TransportType.class)//添加注解指定轉換的枚舉類,注意一定要實現接口IEnumprivate Integer transportType;
}

6.測試

在這里插入圖片描述
在這里插入圖片描述

完整測試代碼

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fc.anno.EnumClass;
import com.fc.convert.EnumConverter;
import com.fc.enums.TransportType;
import com.fc.utils.ExcelUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockMultipartFile;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;public class ExcelUtilTest {// 測試實體類 - 產品@Data@AllArgsConstructor@NoArgsConstructorpublic static class Product {@ExcelProperty("產品ID")private String productId;@ExcelProperty("產品名稱")private String name;@ExcelProperty("價格")private Double price;@ExcelProperty(value = "運輸類型",converter = EnumConverter.class)@EnumClass(value = TransportType.class)private Integer transportType;}private ExcelUtil excelUtil;private List<Product> testProducts;// 導出目錄 - src/test/resources/excelprivate static final String EXPORT_DIR = "src/test/resources/excel/";@BeforeEachvoid setUp() throws NoSuchFieldException, IllegalAccessException {// 初始化ExcelUtil,設置導出目錄excelUtil = new ExcelUtil();//反射獲取數據Field field = ExcelUtil.class.getDeclaredField("defaultExportDir");field.setAccessible(true);field.set(excelUtil, EXPORT_DIR);// 準備測試產品數據testProducts = new ArrayList<>();testProducts.add(new Product("P001", "水泥", 5999.0, 1));testProducts.add(new Product("P002", "河沙", 3999.0, 1));testProducts.add(new Product("P003", "磚塊", 2999.0, 2));}/*** 測試單Sheet導出功能* 文件將導出到 src/test/resources/excel 目錄*/@Testvoid testSingleSheetExport() throws IOException {// 1. 創建用戶數據SheetExcelUtil.Sheet<Product> productSheet = new ExcelUtil.Sheet<>("產品列表", testProducts, Product.class);// 2. 導出用戶數據到單Sheet ExcelPath exportPath = excelUtil.exportSingle("test_produces_export", productSheet);// 3. 打印文件路徑System.out.println("單Sheet導出路徑: " + exportPath.toAbsolutePath());// 4. 驗證文件已創建assertTrue(Files.exists(exportPath), "導出的Excel文件應存在");assertTrue(exportPath.toString().endsWith(".xlsx"), "文件應為.xlsx格式");assertTrue(Files.size(exportPath) > 0, "文件大小應大于0");}
}

總結

本Excel處理工具類旨在為開發者提供一套簡潔高效的Excel操作解決方案。基于成熟的EasyExcel技術棧,覆蓋了從基礎數據導入導出、多Sheet復雜報表生成到枚舉類型自動轉換的完整場景。通過合理的API設計、嚴謹的資源管理和智能的類型轉換機制,顯著降低了Excel處理的開發門檻。

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

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

相關文章

技術速遞|GitHub Copilot for Eclipse 邁出重要一步

我們非常高興地宣布&#xff1a;2025 年 7 月 22 日&#xff0c;GitHub Copilot for Eclipse 又邁出了重要一步&#xff0c;Eclipse 變得更智能、更快捷&#xff0c;而且與 Eclipse 的集成也更無縫了&#xff01;這是繼新功能上線以來&#xff0c;又一次質的提升。 &#x1f…

Coze Loop:開源智能體自動化流程編排平臺原理與實踐

項目簡介 Coze Loop 是 Coze 團隊開源的智能體自動化流程編排平臺。它以“Loop”為核心概念,支持開發者通過低代碼/可視化方式,將多種 AI Agent、插件、API、數據流等靈活編排為自動化工作流,實現復雜的智能體協作、任務自動化和多模態數據處理。Coze Loop 適用于企業自動化…

[GESP202309 四級] 2023年9月GESP C++四級上機題題解,附帶講解視頻!

本文為2023年9月GESP C四級的上機題目的詳細題解&#xff01;覺得寫的不錯或者有幫助可以點個贊啦。 目錄 題目一講解視頻: 題目二講解視頻: 題目一:進制轉換 解題思路: 代碼(C): 題目二:變長編碼 解題思路: 代碼(C): 題目一講解視頻: 2023年9月GESP C四級上機題一題目…

【AI編程工具IDE/CLI/插件專欄】-國外IDE與Cursor能力對比

AI編程專欄(二) - Cursor 深度使用指南 Cursor 深度使用指南(二) - 新能力使用教程 從Trae 2.0與CodeBuddy IDE發布&#xff0c;談大廠布局IDE 如何選擇AI IDE&#xff1f;對比Cursor分析功能差異 AI編程工具IDE/CLI/插件專欄-熱門AI編程CLI初識與IDE對 前面文章介紹過了國…

word2vector細致分解(CBOW, SKIP_GRAM, 層次soft Max, 負采樣)

1 前世今生&#xff1a;NGRAM NGRAM&#xff1a;將詞當成一個離散的單元&#xff08;因此存在一定的局限性&#xff0c;沒有考慮到詞與詞之間的關系&#xff09; neural network language model&#xff1a;只能處理定長序列&#xff0c;訓練慢。使用RNN之后有所改善 2 兩種訓…

Elasticsearch向量庫

在Elasticsearch&#xff08;ES&#xff09;最新版本&#xff08;目前8.x系列&#xff09;中&#xff0c;無需額外的“embedding插件”&#xff0c;因為ES從7.14版本開始就原生支持向量數據類型&#xff08;dense_vector&#xff09; 和向量搜索能力&#xff0c;可直接作為向量…

嵌入式學習的第四十四天-ARM

一、ARM內核基礎知識1.ALU算術邏輯單元&#xff1b;完成運算的電路2.通用寄存器&#xff1a;R0~R15R13&#xff08;SP&#xff09;&#xff1a;棧指針寄存器&#xff1a;指向棧的指針&#xff08;指向正確的位置&#xff09;&#xff0c;為了保護現場 R14&#xff08;LR…

QML開發:QML中的基本元素

文章目錄一、概述二、常用基本元素2.1 基礎視覺元素&#xff08;常用于布局和顯示&#xff09;2.1.1 元素 Item 的介紹和使用2.1.2 元素 Rectangle 的介紹和使用2.1.3 元素 Image 的介紹和使用2.1.4 元素 Text 的介紹和使用2.2 交互元素&#xff08;用于接收用戶操作&#xff0…

Spring AI 項目實戰(二十二):Spring Boot + AI +DeepSeek實現智能合同數據問答助手?(附完整源碼)

系列文章 序號 文章名稱 1 Spring AI 項目實戰(一):Spring AI 核心模塊入門 2 Spring AI 項目實戰(二):Spring Boot + AI + DeepSeek 深度實戰(附完整源碼) 3 Spring AI 項目實戰(三):Spring Boot + AI + DeepSeek 打造智能客服系統(附完整源碼) 4

從 0 到 1 創建 InfluxDB 3 表:標簽、字段、命名規范一篇講透

前言 在使用 InfluxDB 3 存儲時序數據時,表的設計堪比蓋房子打地基,地基打歪,數據“塌方”指日可待。InfluxDB 雖然不是傳統意義上的關系型數據庫,但它有自己的一套“審美”:標簽(Tags)和字段(Fields)是它的雙核心,誰先誰后,關系重大,順序寫錯,查詢性能立馬打折。…

[sqlserver] 分析SQL Server中執行效率較低的SQL語句

查詢性能分析較低的SQL語句 -- 查詢性能分析 SELECT TOP 50qs.creation_time AS [編譯時間],qs.last_execution_time AS [最后執行時間],qs.execution_count AS [執行次數],qs.total_worker_time/1000 AS [CPU總時間(ms)],qs.total_elapsed_time/1000 AS [總耗時(ms)],(qs.tota…

SmartX 用戶建云實踐|寶信軟件:搭建“雙架構”私有云平臺,靈活滿足多種業務需求

上海寶信軟件股份有限公司&#xff08;以下簡稱寶信軟件&#xff09;系中國寶武實際控制、寶鋼股份控股的上市軟件企業&#xff0c;是中國領先的工業軟件行業應用解決方案和服務提供商&#xff0c;為寶武集團提供整體 IT 基礎架構解決方案與服務。為統一管理寶武集團旗下分散在…

應用科普 | 漫談6G通信的未來

【摘要前言】2019年推出的5G無線通信將移動設備的性能提升到了一個新的水平。首批應用利用5G提供移動寬帶&#xff0c;使消費者能夠以遠超以往的速度進行流媒體傳輸、游戲和連接。隨著技術的成熟&#xff0c;它已成為物聯網的關鍵組成部分&#xff0c;將機器匯集到一個全球網絡…

從零開始用 Eclipse 寫第一個 Java 程序:HelloWorld 全流程 + 避坑指南

對于 Java 初學者來說&#xff0c;第一次用 Eclipse 寫程序往往會手足無措 —— 找不到新建項目的入口、不知道包和類該怎么命名、運行時控制臺突然消失…… 別慌&#xff01;本文以最經典的 “HelloWorld” 為例&#xff0c;手把手帶你走完從 Eclipse 項目創建到程序運行的完整…

NVIDIA Isaac GR00T N1.5 源碼剖析與復現

? 0. 前言 2025.6.11 NVIDIA Isaac GR00T N1 進化&#xff0c;英偉達發布了NVIDIA Isaac GR00T N1.5模型&#xff0c;效果比原先提高了不少&#xff0c;故來復現一下&#xff0c;看看能否應用于我的項目中&#xff1a; 代碼頁 項目頁 模型頁 ? 以下是使用 GR00T N1.5 的一般…

手把手教你馴服Apache IoTDB時序數據庫,開啟時序數據管理新征程!

手把手教你馴服Apache IoTDB&#xff0c;開啟時序數據管理新征程&#xff01; 本文是一篇幽默風趣的 Apache IoTDB 時序數據庫安裝使用教程。從 “這東西能不能吃” 的靈魂拷問切入&#xff0c;先科普 IoTDB 的 “真實身份”—— 一款專為時序數據設計的數據庫利器&#xff0c;…

劇本殺小程序系統開發:開啟沉浸式推理社交新紀元

在數字化浪潮席卷的當下&#xff0c;傳統娛樂方式正經歷著前所未有的變革&#xff0c;劇本殺這一融合了推理、角色扮演與社交互動的熱門游戲&#xff0c;也搭上了科技的快車&#xff0c;劇本殺小程序系統開發應運而生&#xff0c;為玩家們開啟了一扇通往沉浸式推理社交新世界的…

Ubuntu系統VScode實現opencv(c++)視頻的處理與保存

通過OpenCV等計算機視覺工具&#xff0c;開發者可以像處理靜態圖像一樣對視頻流逐幀分析&#xff1a;從簡單的裁剪、旋轉、色彩校正&#xff0c;到復雜的穩像、目標跟蹤、超分辨率重建。而如何將處理后的高幀率、高動態范圍數據高效壓縮并封裝為通用格式&#xff08;如MP4、AVI…

三坐標測量技術解析:從基礎原理到斜孔測量難點突破

基礎原理 三坐標測量儀&#xff08;Coordinate Measuring Machine&#xff0c;CMM&#xff09;這種集機械、電子、計算機技術于一體的三維測量設備&#xff0c;其核心技術原理在于&#xff1a;當接觸式或非接觸式測頭接觸感應到工件表面時&#xff0c;測量系統會瞬間記錄三個坐…

【MySQL基礎篇】:MySQL常用內置函數以及實用示例

?感謝您閱讀本篇文章&#xff0c;文章內容是個人學習筆記的整理&#xff0c;如果哪里有誤的話還請您指正噢? ? 個人主頁&#xff1a;余輝zmh–CSDN博客 ? 文章所屬專欄&#xff1a;MySQL篇–CSDN博客 文章目錄內置函數一.日期函數二.字符串函數三.數學函數四.其他函數內置函…