文章目录

  • 前言
  • 一、依赖坐标
  • 二、工具类: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博客 文章目录内置函数一.日期函数二.字符串函数三.数学函数四.其他函数内置函…