From a5917f823b7ae670524bc3d0f0a6f22fa76253cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B7=E5=86=B7?= <2270033969@qq.com> Date: Sat, 31 Aug 2024 10:44:03 +0800 Subject: [PATCH] =?UTF-8?q?doc(readme):=20=E8=A1=A5=E5=85=85=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 506 ++---------------- .../excel/ResponseExcelAutoConfiguration.java | 105 ++-- .../excel/converters/DictTypeConvert.java | 190 ++++--- .../excel/handler/DictDataProvider.java | 113 ++-- .../excel/enums/DemoControllerTest.java | 186 +++---- .../pig4cloud/plugin/excel/enums/SexEnum.java | 5 +- 6 files changed, 327 insertions(+), 778 deletions(-) diff --git a/README.md b/README.md index 059b740..d798e09 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,29 @@ -# excel-spring-boot-starter +以下是基于你提供的内容生成的开源项目 `excel-spring-boot-starter` 的 README 示例: -此项目底层基于 `Easyexcel` 实现 Excel 的读写。 +--- -EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。 -64M内存1分钟内读取75M(46W行25列)的Excel,当然还有急速模式能更快,但是内存占用会在100M多一点 +# Excel Spring Boot Starter +`excel-spring-boot-starter` 是一个基于 `EasyExcel` 实现的 Spring Boot Starter,用于简化 Excel 的读写操作。`EasyExcel` 是一个 Java 开源项目,旨在以尽可能低的内存消耗实现对 Excel 文件的读写。通过 `EasyExcel`,你可以在仅使用 64M 内存的情况下,在 1 分钟内读取 75M(46 万行,25 列)的 Excel 文件。 + +- 更多详细的使用说明,请参考文档:[https://www.yuque.com/pig4cloud/ogf9nv](https://www.yuque.com/pig4cloud/ogf9nv) + +## 功能概述 + +- 轻松集成到 Spring Boot 项目中,快速实现 Excel 文件的导入和导出。 +- 通过注解配置导入和导出的 Excel 文件格式。 +- 提供了简洁易用的 API,极大地减少了手动处理 Excel 文件的工作量。 ## 依赖引用 -- 项目已上传至 maven 仓库,直接引入即可使用 +项目已经上传至 Maven 中央仓库,只需引入以下依赖即可使用: -| 版本 | 支持 | -|-------|--| -| 3.3.0 | 适配 SpringBoot3.x | -| 1.2.7 | 适配 SpringBoot2.x | +| 版本 | 支持版本 | +|---------|-------------------| +| 3.3.0 | 适配 Spring Boot 3.x | +| 1.2.7 | 适配 Spring Boot 2.x | + +在 `pom.xml` 中添加以下依赖: ```xml @@ -25,7 +35,9 @@ EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目 ## 导入 Excel -- 接口类定义List 接受表格对应的数据 使用 @RequestExcel 标记 +### 控制器示例 + +你可以通过在接口方法中使用 `@RequestExcel` 注解来接收上传的 Excel 文件并将其解析为 Java 对象列表: ```java @PostMapping("/upload") @@ -33,10 +45,11 @@ public void upload(@RequestExcel List dataList, BindingResult bindingR // JSR 303 校验通用校验获取失败的数据 List errorMessageList = (List) bindingResult.getTarget(); } - ``` -- 实体声明 +### 实体类定义 + +需要先定义与 Excel 表格对应的实体类,并使用 `@ExcelProperty` 注解来标注 Excel 列的索引: ```java @Data @@ -49,13 +62,15 @@ public class Demo { } ``` -- 测试表格 +### 示例表格 + +下图展示了与上述实体类对应的 Excel 表格: -![](https://minio.pigx.vip/oss/1618560470.png) +![Example Excel](https://minio.pigx.top/oss/1618560470.png) ## 导出 Excel -只需要在 `Controller` 层返回 List 并增加 `@ResponseExcel`注解即可 +你只需在控制器方法中返回一个 `List`,并使用 `@ResponseExcel` 注解即可将数据导出为 Excel 文件: ```java @Documented @@ -76,463 +91,6 @@ public @interface ResponseExcel { } ``` -### 基础用法 - -- 返回单 `sheet`, 全部字段导出 -- - -```java -@ResponseExcel(name = "test", sheets = @Sheet(sheetName = "testSheet1")) -@GetMapping("/e1") -public List e1() { - List dataList = new ArrayList<>(); - for (int i = 0; i < 100; i++) { - DemoData data = new DemoData(); - data.setUsername("tr1" + i); - data.setPassword("tr2" + i); - dataList.add(data); - } - return dataList; -} - -// 实体对象 -@Data -public class DemoData { - private String username; - private String password; -} - -``` - -![](http://pigx.vip/20200331162637_DVMcXW_Screenshot.jpeg) - -- 自定义字段属性 - -```java -@Data -public class DemoData { - @ColumnWidth(50) // 定义宽度 - @ExcelProperty("用户名") // 定义列名称 - @ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 40) - private String username; - @ExcelProperty("密码") - private String password; -} -``` - -![](http://pigx.vip/20200331163948_E91zjM_Screenshot.jpeg) - -- 忽略部分字段 - -```java -@Data -public class DemoData { - @ColumnWidth(50) // 定义宽度 - @ExcelProperty("用户名") // 定义列名称 - @ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 40) - private String username; - @ExcelIgnore // 忽略这个字段 - private String password; -} -``` - -![](http://pigx.vip/20200331164144_l2gwfD_Screenshot.jpeg) - -### 导出动态文件名 - -**ResponseExcel name属性支持 SpEL 表达式获取参数列表动态输出文件名** - -```java -@ResponseExcel(name = "#param + '导出'") -@GetMapping("/export") -public List export(String param) { - return list; -} -``` -### 字典转换 - -- 方式一: 固定枚举 - -```java - @ExcelProperty(value = "列1") - // 指定对应的枚举类 (字符串) - @DictTypeProperty(enums = SexEnum.class) - private String sex; -``` - -```java -@Getter -@RequiredArgsConstructor -// 必须继承 DictEnum -public enum SexEnum implements DictEnum { - MALE("0", "男"), - FEMALE("1", "女"); - // 必须有的字段 - private final String value; - // 必须有的字段 - private final String label; -} -``` - -- 方式二: 读取系统字典 - -```java -@Autowired -private DictDataProvider dictDataProvider; - -// 在系统启动完毕后,添加字典数据 -dictDataProvider.addDict("sex_type", "0", "男"); -dictDataProvider.addDict("sex_type", "1", "女"); -``` - -```java -@ExcelProperty(value = "列1") -// 指定对应的字典类型 -@DictTypeProperty("sex_type") -private String sex; -``` - - -### 导出并加密 - -```java -@ResponseExcel(name = "lengleng", password = "lengleng") -@GetMapping("/e1") -public List e1() { - return list(); -} -``` - -![](http://pigx.vip/20200331164945_6fsEsG_Screenshot.jpeg) - -### 导出多sheet - -```java -@ResponseExcel(name = "lengleng", sheets = { - @Sheet(sheetName = "第一个Sheet"), - @Sheet(sheetName = "第二个sheet") -}) -@GetMapping("/e1") -public List> e1() { - List> lists = new ArrayList<>(); - lists.add(list()); - lists.add(list()); - return lists; -} -``` - -![](http://pigx.vip/20200331164527_sbYDsC_Screenshot.jpeg) - -### 导出不同的 Sheet - -这里两个 sheet 导出不同类型的对象,只导出 DemoData 中的 username 属性,且将 testData 中的 number 属性排除。 - -```java -@Controller -@RequestMapping("public/excel") -public class ExportMultiSheetController { - - @ResponseExcel(name = "不同Sheet的导出", sheets = { - @Sheet(sheetName = "demoData", includes = {"username"}), - @Sheet(sheetName = "testData", excludes = {"number"}) - }) - @GetMapping("/different-sheet") - public List multiDifferent() { - List lists = new ArrayList<>(); - lists.add(demoDatalist()); - lists.add(testDatalist()); - return lists; - } - - private List demoDatalist(){ - List dataList = new ArrayList<>(); - for (int i = 0; i < 100; i++) { - DemoData data = new DemoData(); - data.setUsername("tr1" + i); - data.setPassword("tr2" + i); - dataList.add(data); - } - return dataList; - } - - private List testDatalist(){ - List dataList = new ArrayList<>(); - for (int i = 0; i < 100; i++) { - TestData data = new TestData(); - data.setStr("str" + i); - data.setNumber(i); - data.setLocalDateTime(LocalDateTime.now()); - dataList.add(data); - } - return dataList; - } - - // 实体对象 - @Data - public static class DemoData { - private String username; - private String password; - } - - @Data - public static class TestData { - private String str; - private Integer number; - @ColumnWidth(50) // 定义宽度 - private LocalDateTime localDateTime; - } - -} -``` - -![导出不同的 Sheet](https://hccake-img.oss-cn-shanghai.aliyuncs.com/ballcat/doc/excel-different.png) - -### 导出并自定义头信息 - -**测试实体类:** - -```java -@Data -public class SimpleData { - @ExcelProperty("字符串标题") - private String string; - @ExcelProperty("日期标题") - private Date date; - @ExcelProperty("数字标题") - private Integer number; - // 忽略 - @ExcelIgnore - private String ignore; -} -``` - -**自定义头信息生成器**: - -> 注意需要实现 `HeadGenerator` 接口,且注册为一个 spring bean. - -```java -@Component -public class SimpleDataHeadGenerator implements HeadGenerator { - @Override - public HeadMeta head(Class clazz) { - HeadMeta headMeta = new HeadMeta(); - headMeta.setHead(simpleDataHead()); - // 排除 number 属性 - headMeta.setIgnoreHeadFields(new HashSet<>(Collections.singletonList("number"))); - return headMeta; - } - - private List> simpleDataHead() { - List> list = new ArrayList<>(); - List head0 = new ArrayList<>(); - head0.add("自定义字符串标题" + System.currentTimeMillis()); - List head1 = new ArrayList<>(); - head1.add("自定义日期标题" + System.currentTimeMillis()); - list.add(head0); - list.add(head1); - return list; - } -} -``` - -该头生成器,将固定返回 自定义字符串标题 和 自定义日期标题 两列头信息,实际使用时可根据业务动态处理,方便在一些权限控制时动态修改或者增删列头。 - -**测试代码:** - -```java -@RequestMapping("/head") -@RestController -public class ExcelHeadTestController { - - @ResponseExcel(name = "customHead", headGenerator = SimpleDataHeadGenerator.class) - @GetMapping - public List multi() { - List list = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - SimpleData simpleData = new SimpleData(); - simpleData.setString("str" + i); - simpleData.setNumber(i); - simpleData.setDate(new Date()); - list.add(simpleData); - } - return list; - } -} -``` - -![自定义头信息](https://hccake-img.oss-cn-shanghai.aliyuncs.com/ballcat/doc/excel-customHeader.png) - -## 国际化的导入导出 - -国际化配置基于 Spring 的 MessageSource,开启国际化时,spring 容器中必须有一个 MessageSource 的 Bean。 - -> 具体 Spring -> -的国际化使用这里不再展开,想要了解的可以参看官方文档 [Spring MessageSource 使用](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#context-functionality-messagesource) -> -以及 [SpringBoot 国际化配置 ](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.internationalization) - - - -**首先在 resource 下,新建国际化配置文件** - -- messages.properties - - ```properties - DemoData.username=Username - DemoData.age=Age - ``` - -- messages_en_US.properties - - ```properties - DemoData.username=Username - DemoData.age=Age - ``` - -- messages_zh_CN.properties - - ```properties - DemoData.username=用户名 - DemoData.age=年龄 - ``` - -**测试类的注解信息上,使用 `{}` 标记配置文件中的 key** - -```java -@Data -public class DemoData { - @ExcelProperty(value = "{DemoData.username}", index = 0) - private String username; - @ExcelProperty(value = "{DemoData.age}", index = 1) - private Integer age; -} -``` - -**导出注解上设置 i18nHeader=true** - -```java - @ResponseExcel(name = "i18nExport", i18nHeader = true) - @GetMapping("excelExport") - public List i18nExport() { - List list = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - DemoData demoData = new DemoData(); - demoData.setUsername("username:" + i); - demoData.setAge(i); - list.add(demoData); - } - return list; - } -``` - -**使用 Postman 测试导出** - -请求头上使用 `Accept-Language` 指定当前语言区域,中文是 `zh-CN`, 英文是 `en-US` - -> SpringBoot 的国际化默认会读取请求头中的 `Accept-Language` 进行判断当前区域,可以通过定制 `LocaleResolver` 替换这一默认行为 - -![](https://hccake-img.oss-cn-shanghai.aliyuncs.com/ballcat/doc/excel-i18n-export.png) - -**导出效果** - -![导出效果](https://hccake-img.oss-cn-shanghai.aliyuncs.com/ballcat/doc/excel-i18n-export2.png) - -**导入 controller** - -注意,这里导入接受的对象如果和导出是同一个的话,由于列名是国际化配置的占位符,无法和实际上传文件进行对应,所以需要给该对象的属性指定 index,导入文件根据 index 进行数据映射。 - -当然,也可以使用额外的导入类来接收导入信息。 - -```java - @PostMapping("i18n") - @ResponseBody - public List importExcel(@RequestExcel List list) { - return list; - } -``` - -- 导入获取excel 行号,实体属性增加 @ExcelLine 注解即可 - -```java -/** - * 导入时候回显行号 - */ -@ExcelLine -@ExcelIgnore -private Long lineNum; -``` - -**使用 Postman 测试导入** - -![](https://hccake-img.oss-cn-shanghai.aliyuncs.com/ballcat/doc/excel-i18n-import.png) - -## 添加全局自定义转换器(Converter) - -`0.0.7` 版本开始添加了全局自定义转换器注入的功能,你只需要将自定义的 `Converter` 注册成 `Spring bean` 即可。 - -示例代码如下(对 set 类型转换): - -```java -@Data -public class TestModel { - @ExcelProperty("名称集合") - private Set nameSet; -} - -/** - * 集合转换器 - * - * @author L.cm - */ -@Component -public class SetConverter implements Converter> { - private final ConversionService conversionService; - - SetConverter() { - this.conversionService = DefaultConversionService.getSharedInstance(); - } - - @Override - public Class supportJavaTypeKey() { - return Set.class; - } - - @Override - public CellDataTypeEnum supportExcelTypeKey() { - return CellDataTypeEnum.STRING; - } - - @Override - public Set convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { - String[] value = StringUtils.delimitedListToStringArray(cellData.getStringValue(), ","); - return (Set) conversionService.convert(value, TypeDescriptor.valueOf(String[].class), new TypeDescriptor(contentProperty.getField())); - } - - @Override - public CellData convertToExcelData(Set value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { - return new CellData<>(StringUtils.collectionToCommaDelimitedString(value)); - } - -} -``` - -## 高级用法模板导出 - -```java -/** - * 默认读取 classpath:excel/ 目录下的模板文件,具体模板使用参考官方文档 - * - */ -@ResponseExcel(name = "模板测试excel", sheet = "sheetName",template = "example.xlsx") -@GetMapping("/e1") -public List e1() { - return list(); -} -``` - -## 其他用法 +## 使用文档 -- 理论上支持 [alibaba/easyexcel](https://github.com/alibaba/easyexcel) v2.2.10 版本的大部分配置 -- 支持 [alibaba/easyexcel](https://www.yuque.com/easyexcel/doc/write) 原生的配置注解 -- [github stater 地址,可fork 魔改](https://github.com/pigxcloud/excel-spring-boot-starter) +更多详细的使用说明,请参考文档:[https://www.yuque.com/pig4cloud/ogf9nv](https://www.yuque.com/pig4cloud/ogf9nv) diff --git a/src/main/java/com/pig4cloud/plugin/excel/ResponseExcelAutoConfiguration.java b/src/main/java/com/pig4cloud/plugin/excel/ResponseExcelAutoConfiguration.java index 1295d50..c40dd61 100644 --- a/src/main/java/com/pig4cloud/plugin/excel/ResponseExcelAutoConfiguration.java +++ b/src/main/java/com/pig4cloud/plugin/excel/ResponseExcelAutoConfiguration.java @@ -31,67 +31,66 @@ */ @AutoConfiguration @RequiredArgsConstructor -@Import({ExcelHandlerConfiguration.class, SpringContextKit.class}) +@Import({ ExcelHandlerConfiguration.class, SpringContextKit.class }) @EnableConfigurationProperties(ExcelConfigProperties.class) public class ResponseExcelAutoConfiguration { - private final RequestMappingHandlerAdapter requestMappingHandlerAdapter; + private final RequestMappingHandlerAdapter requestMappingHandlerAdapter; - private final ResponseExcelReturnValueHandler responseExcelReturnValueHandler; + private final ResponseExcelReturnValueHandler responseExcelReturnValueHandler; - /** - * SPEL 解析处理器 - * - * @return NameProcessor excel名称解析器 - */ - @Bean - @ConditionalOnMissingBean - public NameProcessor nameProcessor() { - return new NameSpelExpressionProcessor(); - } + /** + * SPEL 解析处理器 + * @return NameProcessor excel名称解析器 + */ + @Bean + @ConditionalOnMissingBean + public NameProcessor nameProcessor() { + return new NameSpelExpressionProcessor(); + } - /** - * Excel名称解析处理切面 - * - * @param nameProcessor SPEL 解析处理器 - * @return DynamicNameAspect - */ - @Bean - @ConditionalOnMissingBean - public DynamicNameAspect dynamicNameAspect(NameProcessor nameProcessor) { - return new DynamicNameAspect(nameProcessor); - } + /** + * Excel名称解析处理切面 + * @param nameProcessor SPEL 解析处理器 + * @return DynamicNameAspect + */ + @Bean + @ConditionalOnMissingBean + public DynamicNameAspect dynamicNameAspect(NameProcessor nameProcessor) { + return new DynamicNameAspect(nameProcessor); + } - /** - * 追加 Excel返回值处理器 到 springmvc 中 - */ - @PostConstruct - public void setReturnValueHandlers() { - List returnValueHandlers = requestMappingHandlerAdapter - .getReturnValueHandlers(); + /** + * 追加 Excel返回值处理器 到 springmvc 中 + */ + @PostConstruct + public void setReturnValueHandlers() { + List returnValueHandlers = requestMappingHandlerAdapter + .getReturnValueHandlers(); - List newHandlers = new ArrayList<>(); - newHandlers.add(responseExcelReturnValueHandler); - assert returnValueHandlers != null; - newHandlers.addAll(returnValueHandlers); - requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers); - } + List newHandlers = new ArrayList<>(); + newHandlers.add(responseExcelReturnValueHandler); + assert returnValueHandlers != null; + newHandlers.addAll(returnValueHandlers); + requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers); + } - /** - * 追加 Excel 请求处理器 到 springmvc 中 - */ - @PostConstruct - public void setRequestExcelArgumentResolver() { - List argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers(); - List resolverList = new ArrayList<>(); - resolverList.add(new RequestExcelArgumentResolver()); - resolverList.addAll(argumentResolvers); - requestMappingHandlerAdapter.setArgumentResolvers(resolverList); - } + /** + * 追加 Excel 请求处理器 到 springmvc 中 + */ + @PostConstruct + public void setRequestExcelArgumentResolver() { + List argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers(); + List resolverList = new ArrayList<>(); + resolverList.add(new RequestExcelArgumentResolver()); + resolverList.addAll(argumentResolvers); + requestMappingHandlerAdapter.setArgumentResolvers(resolverList); + } + + @Bean + @ConditionalOnMissingBean + public DictDataProvider dictDataProvider() { + return new DefaultDictDataProvider(); + } - @Bean - @ConditionalOnMissingBean - public DictDataProvider dictDataProvider() { - return new DefaultDictDataProvider(); - } } diff --git a/src/main/java/com/pig4cloud/plugin/excel/converters/DictTypeConvert.java b/src/main/java/com/pig4cloud/plugin/excel/converters/DictTypeConvert.java index e6e8021..422916e 100644 --- a/src/main/java/com/pig4cloud/plugin/excel/converters/DictTypeConvert.java +++ b/src/main/java/com/pig4cloud/plugin/excel/converters/DictTypeConvert.java @@ -24,102 +24,98 @@ @Slf4j public enum DictTypeConvert implements Converter { - /** - * 实例 - */ - INSTANCE; - - /** - * 支持 Java Type Key - * - * @return {@link Class } - */ - @Override - public Class supportJavaTypeKey() { - return String.class; - } - - /** - * 支持 Excel 键入键 - * - * @return {@link CellDataTypeEnum } - */ - @Override - public CellDataTypeEnum supportExcelTypeKey() { - return CellDataTypeEnum.STRING; - } - - /** - * 转换为 Java 数据 - * - * @param cellData 单元格数据 - * @param contentProperty content 属性 - * @param globalConfiguration 全局配置 - * @return {@link String } - * @throws Exception 异常 - */ - @Override - public String convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, - GlobalConfiguration globalConfiguration) throws Exception { - if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { - return cellData.getStringValue(); - } - Field field = contentProperty.getField(); - - DictTypeProperty declaredAnnotation = field.getDeclaredAnnotation(DictTypeProperty.class); - if (Objects.isNull(declaredAnnotation)) { - return cellData.getStringValue(); - } - - if (declaredAnnotation.enums().length != 0) { - - DictEnum[] enums = declaredAnnotation.enums()[0].getEnumConstants(); - return DictEnum.getValueByLabel(enums, cellData.getStringValue()); - } - - DictDataProvider dictDataProvider = SpringContextKit.getBean(DictDataProvider.class); - DictEnum[] dictEnums = dictDataProvider.getDict(declaredAnnotation.value()); - if (dictEnums == null) { - return cellData.getStringValue(); - } - return DictEnum.getValueByLabel(dictEnums, cellData.getStringValue()); - } - - /** - * 转换为 Excel 数据 - * - * @param value 价值 - * @param contentProperty content 属性 - * @param globalConfiguration 全局配置 - * @return {@link WriteCellData }<{@link String }> - */ - @Override - public WriteCellData convertToExcelData(String value, ExcelContentProperty contentProperty, - GlobalConfiguration globalConfiguration) throws Exception { - Field field = contentProperty.getField(); - - DictTypeProperty declaredAnnotation = field.getDeclaredAnnotation(DictTypeProperty.class); - if (Objects.isNull(declaredAnnotation)) { - return new WriteCellData<>(value); - } - - if (declaredAnnotation.enums().length != 0) { - DictEnum[] enums = declaredAnnotation.enums()[0].getEnumConstants(); - return new WriteCellData<>(DictEnum.getLabelByValue(enums, value)); - } - - DictDataProvider dictDataProvider = SpringContextKit.getBean(DictDataProvider.class); - DictEnum[] dictEnums = dictDataProvider.getDict(declaredAnnotation.value()); - - if (dictEnums == null) { - return new WriteCellData<>(value); - } - - String labelByValue = DictEnum.getLabelByValue(dictEnums, value); - if (labelByValue != null) { - return new WriteCellData<>(labelByValue); - } - return new WriteCellData<>(value); - } + /** + * 实例 + */ + INSTANCE; + + /** + * 支持 Java Type Key + * @return {@link Class } + */ + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + /** + * 支持 Excel 键入键 + * @return {@link CellDataTypeEnum } + */ + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + /** + * 转换为 Java 数据 + * @param cellData 单元格数据 + * @param contentProperty content 属性 + * @param globalConfiguration 全局配置 + * @return {@link String } + * @throws Exception 异常 + */ + @Override + public String convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws Exception { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return cellData.getStringValue(); + } + Field field = contentProperty.getField(); + + DictTypeProperty declaredAnnotation = field.getDeclaredAnnotation(DictTypeProperty.class); + if (Objects.isNull(declaredAnnotation)) { + return cellData.getStringValue(); + } + + if (declaredAnnotation.enums().length != 0) { + + DictEnum[] enums = declaredAnnotation.enums()[0].getEnumConstants(); + return DictEnum.getValueByLabel(enums, cellData.getStringValue()); + } + + DictDataProvider dictDataProvider = SpringContextKit.getBean(DictDataProvider.class); + DictEnum[] dictEnums = dictDataProvider.getDict(declaredAnnotation.value()); + if (dictEnums == null) { + return cellData.getStringValue(); + } + return DictEnum.getValueByLabel(dictEnums, cellData.getStringValue()); + } + + /** + * 转换为 Excel 数据 + * @param value 价值 + * @param contentProperty content 属性 + * @param globalConfiguration 全局配置 + * @return {@link WriteCellData }<{@link String }> + */ + @Override + public WriteCellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws Exception { + Field field = contentProperty.getField(); + + DictTypeProperty declaredAnnotation = field.getDeclaredAnnotation(DictTypeProperty.class); + if (Objects.isNull(declaredAnnotation)) { + return new WriteCellData<>(value); + } + + if (declaredAnnotation.enums().length != 0) { + DictEnum[] enums = declaredAnnotation.enums()[0].getEnumConstants(); + return new WriteCellData<>(DictEnum.getLabelByValue(enums, value)); + } + + DictDataProvider dictDataProvider = SpringContextKit.getBean(DictDataProvider.class); + DictEnum[] dictEnums = dictDataProvider.getDict(declaredAnnotation.value()); + + if (dictEnums == null) { + return new WriteCellData<>(value); + } + + String labelByValue = DictEnum.getLabelByValue(dictEnums, value); + if (labelByValue != null) { + return new WriteCellData<>(labelByValue); + } + return new WriteCellData<>(value); + } } diff --git a/src/main/java/com/pig4cloud/plugin/excel/handler/DictDataProvider.java b/src/main/java/com/pig4cloud/plugin/excel/handler/DictDataProvider.java index d950c3d..3a4a29e 100644 --- a/src/main/java/com/pig4cloud/plugin/excel/handler/DictDataProvider.java +++ b/src/main/java/com/pig4cloud/plugin/excel/handler/DictDataProvider.java @@ -14,73 +14,68 @@ */ public interface DictDataProvider { - static Map cache = new HashMap<>(); + static Map cache = new HashMap<>(); - /** - * 获取缓存 - * - * @return {@link Map }<{@link String }, {@link DictEnum[] }> - */ - default Map getCache() { - return cache; - } + /** + * 获取缓存 + * @return {@link Map }<{@link String }, {@link DictEnum[] }> + */ + default Map getCache() { + return cache; + } - /** - * 获取 dict - * - * @param type 类型 - * @return {@link DictEnum[] } - */ - default DictEnum[] getDict(String type) { - return cache.get(type); - } + /** + * 获取 dict + * @param type 类型 + * @return {@link DictEnum[] } + */ + default DictEnum[] getDict(String type) { + return cache.get(type); + } - /** - * 添加 dict - * - * @param type 类型 - * @param key key - * @param value value - */ - default void addDict(String type, String key, String value) { - // 1. 获取当前已有的 DictEnum 数组 - DictEnum[] existingEnums = cache.get(type); + /** + * 添加 dict + * @param type 类型 + * @param key key + * @param value value + */ + default void addDict(String type, String key, String value) { + // 1. 获取当前已有的 DictEnum 数组 + DictEnum[] existingEnums = cache.get(type); - // 2. 创建新的 DictEnum 对象 - DictEnum newEnum = new DictEnum() { - @Override - public String getValue() { - return key; - } + // 2. 创建新的 DictEnum 对象 + DictEnum newEnum = new DictEnum() { + @Override + public String getValue() { + return key; + } - @Override - public String getLabel() { - return value; - } - }; + @Override + public String getLabel() { + return value; + } + }; - // 3. 使用 ArrayUtils.add 将新元素添加到数组中 - DictEnum[] newEnums = ArrayUtils.add(existingEnums, newEnum); + // 3. 使用 ArrayUtils.add 将新元素添加到数组中 + DictEnum[] newEnums = ArrayUtils.add(existingEnums, newEnum); - // 4. 将新数组放入 cache 中 - cache.put(type, newEnums); - } + // 4. 将新数组放入 cache 中 + cache.put(type, newEnums); + } + /** + * 删除 dict + * @param type 类型 + */ + default void delDict(String type) { + cache.remove(type); + } - /** - * 删除 dict - * - * @param type 类型 - */ - default void delDict(String type) { - cache.remove(type); - } - - /** - * 重新加载 dict - */ - default void clear() { - cache.clear(); - } + /** + * 重新加载 dict + */ + default void clear() { + cache.clear(); + } } diff --git a/src/test/java/com/pig4cloud/plugin/excel/enums/DemoControllerTest.java b/src/test/java/com/pig4cloud/plugin/excel/enums/DemoControllerTest.java index 3dfc5a8..1453611 100644 --- a/src/test/java/com/pig4cloud/plugin/excel/enums/DemoControllerTest.java +++ b/src/test/java/com/pig4cloud/plugin/excel/enums/DemoControllerTest.java @@ -28,98 +28,98 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class DemoControllerTest { - @Autowired - private WebApplicationContext webApplicationContext; - - private MockMvc mockMvc; - - @BeforeEach - public void setUp() { - mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); - } - - /** - * 单元测试,写出excel 字段固定枚举 enum - * @throws Exception - */ - @Test - void testFileDownloadAndSave() throws Exception { - MvcResult result = mockMvc.perform(get("/demo/test")).andExpect(status().isOk()).andReturn(); - - MockHttpServletResponse response = result.getResponse(); - byte[] fileContent = response.getContentAsByteArray(); - - // 验证响应不为空 - assertThat(fileContent).isNotEmpty(); - - // 保存文件到 resources 目录 - saveFileToResources("downloaded-example.xlsx", fileContent); - } - - @Autowired - private DictDataProvider dictDataProvider; - - /** - * 单元测试,写出excel 字段指定枚举 type - * @throws Exception - */ - @Test - void test2FileDownloadAndSave() throws Exception { - dictDataProvider.addDict("sex_type", "0", "男"); - dictDataProvider.addDict("sex_type", "1", "女"); - - MvcResult result = mockMvc.perform(get("/demo/test2")).andExpect(status().isOk()).andReturn(); - - MockHttpServletResponse response = result.getResponse(); - byte[] fileContent = response.getContentAsByteArray(); - - // 验证响应不为空 - assertThat(fileContent).isNotEmpty(); - - // 保存文件到 resources 目录 - saveFileToResources("downloaded-example.xlsx", fileContent); - } - - private void saveFileToResources(String filename, byte[] content) throws IOException { - // 获取 resources 目录路径 - File resourceDirectory = new File("src/test/resources"); - if (!resourceDirectory.exists()) { - resourceDirectory.mkdirs(); - } - - // 创建目标文件 - File outputFile = new File(resourceDirectory, filename); - try (FileOutputStream fos = new FileOutputStream(outputFile)) { - fos.write(content); - } - - // 验证文件是否成功写入 - assertThat(outputFile.exists()).isTrue(); - assertThat(outputFile.length()).isEqualTo(content.length); - } - - @Test - void testFileUpload() throws Exception { - - ClassPathResource classPathResource = new ClassPathResource("tmp/enums.xlsx"); - - // 创建一个模拟的 Excel 文件 - MockMultipartFile mockFile = new MockMultipartFile("file", // 参数名称,应该与 Controller - // 中接收的文件参数名称一致 - "enums.xlsx", // 上传的文件名 - MediaType.MULTIPART_FORM_DATA_VALUE, // 文件类型 - classPathResource.getContentAsByteArray() // 文件内容,可以根据需要替换为实际的Excel内容 - ); - - // 模拟文件上传请求 - MvcResult result = mockMvc.perform(multipart("/demo/upload") // 上传文件的URL - .file(mockFile) // 添加文件 - .contentType(MediaType.MULTIPART_FORM_DATA) // 设置内容类型为 multipart/form-data - ) - .andExpect(status().isOk()) // 验证返回的 HTTP 状态码是 200 OK - .andReturn(); - - // 这里可以进一步验证上传处理后的结果,例如返回的消息、数据库中的数据等 - } + @Autowired + private WebApplicationContext webApplicationContext; + + private MockMvc mockMvc; + + @BeforeEach + public void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + } + + /** + * 单元测试,写出excel 字段固定枚举 enum + * @throws Exception + */ + @Test + void testFileDownloadAndSave() throws Exception { + MvcResult result = mockMvc.perform(get("/demo/test")).andExpect(status().isOk()).andReturn(); + + MockHttpServletResponse response = result.getResponse(); + byte[] fileContent = response.getContentAsByteArray(); + + // 验证响应不为空 + assertThat(fileContent).isNotEmpty(); + + // 保存文件到 resources 目录 + saveFileToResources("downloaded-example.xlsx", fileContent); + } + + @Autowired + private DictDataProvider dictDataProvider; + + /** + * 单元测试,写出excel 字段指定枚举 type + * @throws Exception + */ + @Test + void test2FileDownloadAndSave() throws Exception { + dictDataProvider.addDict("sex_type", "0", "男"); + dictDataProvider.addDict("sex_type", "1", "女"); + + MvcResult result = mockMvc.perform(get("/demo/test2")).andExpect(status().isOk()).andReturn(); + + MockHttpServletResponse response = result.getResponse(); + byte[] fileContent = response.getContentAsByteArray(); + + // 验证响应不为空 + assertThat(fileContent).isNotEmpty(); + + // 保存文件到 resources 目录 + saveFileToResources("downloaded-example.xlsx", fileContent); + } + + private void saveFileToResources(String filename, byte[] content) throws IOException { + // 获取 resources 目录路径 + File resourceDirectory = new File("src/test/resources"); + if (!resourceDirectory.exists()) { + resourceDirectory.mkdirs(); + } + + // 创建目标文件 + File outputFile = new File(resourceDirectory, filename); + try (FileOutputStream fos = new FileOutputStream(outputFile)) { + fos.write(content); + } + + // 验证文件是否成功写入 + assertThat(outputFile.exists()).isTrue(); + assertThat(outputFile.length()).isEqualTo(content.length); + } + + @Test + void testFileUpload() throws Exception { + + ClassPathResource classPathResource = new ClassPathResource("tmp/enums.xlsx"); + + // 创建一个模拟的 Excel 文件 + MockMultipartFile mockFile = new MockMultipartFile("file", // 参数名称,应该与 Controller + // 中接收的文件参数名称一致 + "enums.xlsx", // 上传的文件名 + MediaType.MULTIPART_FORM_DATA_VALUE, // 文件类型 + classPathResource.getContentAsByteArray() // 文件内容,可以根据需要替换为实际的Excel内容 + ); + + // 模拟文件上传请求 + MvcResult result = mockMvc.perform(multipart("/demo/upload") // 上传文件的URL + .file(mockFile) // 添加文件 + .contentType(MediaType.MULTIPART_FORM_DATA) // 设置内容类型为 multipart/form-data + ) + .andExpect(status().isOk()) // 验证返回的 HTTP 状态码是 200 OK + .andReturn(); + + // 这里可以进一步验证上传处理后的结果,例如返回的消息、数据库中的数据等 + } } diff --git a/src/test/java/com/pig4cloud/plugin/excel/enums/SexEnum.java b/src/test/java/com/pig4cloud/plugin/excel/enums/SexEnum.java index 9571e64..6a46d86 100644 --- a/src/test/java/com/pig4cloud/plugin/excel/enums/SexEnum.java +++ b/src/test/java/com/pig4cloud/plugin/excel/enums/SexEnum.java @@ -13,11 +13,12 @@ // 必须继承 DictEnum public enum SexEnum implements DictEnum { - MALE("0", "男"), - FEMALE("1", "女"); + MALE("0", "男"), FEMALE("1", "女"); // 必须有的字段 private final String value; + // 必须有的字段 private final String label; + }