no

阅读: 评论:0

no

no

项目的github地址是,欢迎各位的Star和Issues

项目简述

no-excel是一个excel导入导出工具类,提供基于注解方式使用,并提供了excel下拉框和级联下拉框的生成,同时对基本类型包括时间枚举提供了自动转换的功能,旨在提供简单便捷通用的excel导入导出功能。

快速开始

引入no-excel依赖

<dependency><groupId>io.github.amerainc</groupId><artifactId>no-excel</artifactId><version>2021.12.24</version>
</dependency>

首先创建一个测试用的实体类(测试样例在test文件夹下)

@ExcelEntity(title = "测试")
@Data
public class TestEntity{//require选项必填时输出会带有*好,读取时无必填字段则会抛出异常@ExcelField(name = "必填选项", require = true)private String str;//在枚举类的情况下,param参数可以指定excel写入读取时使用的枚举类属性@ExcelField(name = "枚举类转换", param = "i18n")private ColorEnum colorEnum;@ExcelField(name = "长整型")private Long number;@ExcelField(name = "时间")private Date date;
}
public enum ColorEnum {RED("红色"),GOLD("金黄色"),YELLOW("黄色");String i18n;ColorEnum(String i18n){this.i18n = i18n;}public String getI18n() {return i18n;}
}

excel导出

为了能够重复使用,工具类在导出后并不会主动将workbook关闭,工具类也同时实现了closeable接口,因此建议使用try-resource的方式来使用。

普通导出

   @Testpublic void writeFile() {//初始化要写入的数据List<TestEntity> testEntityList = new ArrayList<>();TestEntity testEntity = new TestEntity();testEntity.setDate(new Date());testEntity.setStr("测试文字");testEntity.setNumber(123L);testEntity.setColorEnum(ColorEnum.GOLD);for (int i = 0; i < 100; i++) {testEntityList.add(testEntity);}//写入ExcelWriterBuilder<TestEntity> builder = ExcelWriterBuilder.builder(TestEntity.class);try (ExcelWriter<TestEntity> excelWriter = builder.build()) {excelWriter.writeDataAndClose(testEntityList, new FileOutputStream("createFile.xls"));} catch (IOException e) {e.printStackTrace();}}

导出的文件如下图所示,同时枚举类则会生成下拉菜单

导出模板

如果不是为了导出数据,只是为了提供一份供用户填写的模板,也可以直接调用写入模板的方法。

    @Testpublic void writeTemplate() {//构造模板try (ExcelWriter<TestEntity> excelWriter = ExcelWriterBuilder.builder(TestEntity.class).build();FileOutputStream fileOutputStream = new FileOutputStream("createTemplate.xls")) {excelWriter.writeTemplate(fileOutputStream);} catch (IOException e) {e.printStackTrace();}}

导出的文件如下图所示

web导出

同时也提供了web方式的导出

    @GetMapping("/test")public void test(HttpServletRequest request, HttpServletResponse response) {try (ExcelWriter<TestEntity> excelWriter = ExcelWriterBuilder.builder(TestEntity.class).build()) {excelWriter.writeTemplateToResponse("测试.xls",request,response);}}

导出效果如下所示

excel导入

直接读取将数据转换成实体类列表返回

    @Testpublic void readFile() {String path = getClass().getResource("/").getPath();ExcelReaderBuilder<TestEntity> builder = ExcelReaderBuilder.builder(TestEntity.class);try (ExcelReader<TestEntity> excelReader = builder.build(new File(path + "readFile.xls"))) {List<TestEntity> testEntities = adData();System.out.println(testEntities);}}

同时也提供了消费者模式进行消费,消费者模式下将会对数据逐行进行转换,并直接通过消费者进行消费

    @Testpublic void readFile2() {String path = getClass().getResource("/").getPath()+"readFile.xls";ExcelReaderBuilder<TestEntity> builder = ExcelReaderBuilder.builder(TestEntity.class);try (ExcelReader<TestEntity> excelReader = builder.build(new File(path))) {adData(testEntity -> {System.out.println(testEntity);});}}

如果处理比较耗时,也可以采用多线程的方式分片进行处理

    @Testpublic void readFile3() {ExecutorService executorService = wFixedThreadPool(5);String path = getClass().getResource("/").getPath()+"readFile.xls";ExcelReaderBuilder<TestEntity> builder = ExcelReaderBuilder.builder(TestEntity.class);try (ExcelReader<TestEntity> excelReader = builder.build(new File(path))) {//这里将数据分为五片,并交由五个线程处理adDataConcurrent(testEntity -> {System.out.println(testEntity);},executorService,5);}}

注解

@ExcelEntity

在用来承载excel数据的实体类上使用,用于指定一些全局性的参数

@ExcelEntity(title = "测试")
@Data
public class TestEntity{//require选项必填时输出会带有*好,读取时无必填字段则会抛出异常@ExcelField(name = "必填选项", require = true)private String str;//在枚举类的情况下,param参数可以指定excel写入读取时使用的枚举类属性@ExcelField(name = "枚举类转换", param = "i18n")private ColorEnum colorEnum;@ExcelField(name = "长整型")private Long number;@ExcelField(name = "时间")private Date date;
}
属性类型必填默认值说明
titleStringexcel导出时显示的标题名
showTitlebooleantrueexcel导出时是否有标题
showHeadbooleantrueexcel导出时是否有表头
maxSizeint500excel导入时读取的最大数据量限制,超出限制会抛出异常
titleStyleClass<? extends StyleProvider>DefaultTitleStyleProvider.class标题使用的excel样式
headRequireStyleClass<? extends StyleProvider>DefaultHeadRequireStyleProviderProvider.class当字段为必填时,标题显示的样式
headStyleClass<? extends StyleProvider>DefaultHeadStyleProvider.class普通表头样式
dataStyleClass<? extends StyleProvider>DefaultDataStyleProvider.class数据样式

@ExcelField

在需要导入导出的字段上进行使用,用于定义字段相关的信息

属性类型必填默认值说明
nameString字段名称,与excel表头对应
sortint0字段的输出顺序
requirebooleanfalse字段是否必填,必填时输出表头会带有*号,读取时无必填字段则会抛出异常
converterClass<? extends FieldConverter<?>>DefaultFieldConverter.class字段转换器,用于提供excel字段和实体类字段之间的转换
paramString“”用于提供给字段转换器额外的处理参数
cascadeDependString“”级联依赖字段的字段名(不填默认依赖前一个字段)

功能

字段转换器

FieldConverter

FieldConverter接口用于提供excel字段和实体类字段之间的转换,实现这个接口可以自定义字段转换的规则

parseToField(必须实现)

实现parseToField方法,用于提供excel数据转换成对应字段值的功能,下面以默认的Integer转换器为例

    @Overridepublic Integer parseToField(String excelData) {try {return Integer.parseInt(excelData);} catch (NumberFormatException numberFormatException) {throw new NoExcelException("必须为整型");}}
parseToExcelData(必须实现)

实现parseToExcelData方法用于提供将字段值转换为excel数据的功能

    @Overridepublic String parseToExcelData(Integer fieldData) {String();}
initData

initData方法会在转换器初始化调用,以默认的日期转换器为例,在初始化时可以通过填写注解上param来指定输出到excel的日期格式

public class DefaultDateFieldConverter implements FieldConverter<Date> {/*** 输出时的日期格式*/private String printDateFormat = "yyyy/MM/dd HH:mm:ss";......@Overridepublic String parseToExcelData(Date fieldData) {return DateFormatUtil.formatDate(fieldData, this.printDateFormat);}@Overridepublic void initData(ExcelFieldMeta excelFieldMeta) {//如果有参数则使用参数作为日期格式if (StrUtil.Param())) {this.printDateFormat = Param();}}
match(用于默认字段转换器)

match方法用于默认字段转换器进行匹配使用,如果是在注解上直接指定的转换器则无需实现

    default boolean match(ExcelFieldMeta excelFieldMeta) {Class<?> firstGenericType = Class());return firstGenericType != null && firstGenericType.FieldClz());}
order(用于默认字段转换器)

order方法用于决定默认字段转换器进行匹配时优先级越小优先级越高

    default int order() {return 0;}
isSingleton

项目对字段转换器做了缓存,通过isSingleton觉得当前字段转换器在使用时是否为单例,默认为非单例。如整型等不需要配置的转换器使用了单例,而日期转换器可以自定义输出格式,使用了非单例的形式。

    @Overridedefault boolean isSingleton(){return false;}

DefaultFieldConverter(默认字段转换器)

DefaultFieldConverter是注解中默认使用的字段转换器,DefaultFieldConverter是一个代理类,通过match方法对所有的默认字段转换器进行匹配并实现代理。

DefaultFieldConverter通过spi的方式将所有的默认字段转换器放入匹配列表,并在使用时通过init方法进行初始化匹配到对应的默认字段转换器进行代理,匹配逻辑为由order决定转换器的匹配顺序,并通过match方法匹配使用首个匹配成功的字段转换器

spi如下,目前总共实现了8个默认字段转换器

l.convert.impl.DefaultDateFieldConverter
l.convert.impl.DefaultDoubleFieldConverter
l.convert.impl.DefaultEnumFieldConverter
l.convert.impl.DefaultIntegerFieldConverter
l.convert.impl.DefaultLongFieldConverter
l.convert.impl.DefaultShortFieldConverter
l.convert.impl.DefaultStringFieldConverter
l.convert.impl.DefaultObjectFieldConvert

如果想要自己的字段转换器也能通过DefaultFieldConverter进行代理,则在项目的resoures/META-INF/services下创建文件,文件名为l.convert.FieldConverter,并在文件中写上自定义的字段转换器的全类名

BaseMapFieldConverter(基础字段映射转换器)

BaseMapFieldConverter是字段映射转换器的抽象类,实现BaseMapFieldConverter可以轻松实现枚举字典等具有映射关系的数据转换,并且在导出时可以通过映射值生成excel的下拉框选项

继承BaseMapFieldConverter并实现方法fieldToExcelDataMap,通过实现这个方法并返回一个实体类字段到excel字段的数据映射表

下面用默认的枚举字段转换器为例,返回的键是枚举值,值在没有定义param的情况下默认用name,同时也可以通过param来指定excel的字段。同时使用LinkedHashMap是因为生成下拉框数据时会使用到map的values,用LinkedHashMap可以保证下拉框列表的顺序。

public class DefaultEnumFieldConverter extends BaseMapFieldConverter<Enum<?>> {@Override@SneakyThrowspublic Map<Enum<?>, String> fieldToExcelDataMap(ExcelFieldMeta excelFieldMeta) {Class<Enum<?>> fieldClz = (Class<Enum<?>>) FieldClz();String param = Param();Enum<?>[] enums = EnumConstants();return Arrays.stream(enums).Map(anEnum -> anEnum, anEnum -> {if (StrUtil.isEmpty(param)) {return anEnum.name();} else {FieldValue(anEnum, param).toString();}},(a,b)->a, LinkedHashMap::new));}
}

BaseCascadeConverter(基础级联字段转换器)

BaseCascadeConverter是级联字段转换器的抽象类,是在字段映射转换器的基础上实现的,可以用来生成带有级联下拉框的excel字段。

继承BaseCascadeConverter并实现cascadeMap,返回一个格式为Map<级联的excel值,Map<属性值,excel值>>的映射表即可实现级联的下拉框选项

下面实例实现了BaseCascadeConverter,手动创建了一个和之前的ColorEnum进行级联的map数据

public class CascadeConverter extends BaseCascadeConverter<String> implements CascadeProvider {@Overridepublic Map<String, Map<String, String>> cascadeMap(ExcelFieldMeta excelFieldMeta) {Map<String, Map<String, String>> map = new LinkedHashMap<>();Map<String, String> red = new LinkedHashMap<>();red.put("bred","大红色");red.put("sred","小红色");Map<String, String> yellow = new LinkedHashMap<>();yellow.put("byellow","大黄色");yellow.put("syellow","小黄色");Map<String, String> gold = new LinkedHashMap<>();gold.put("bgold","大金黄");gold.put("sgold","小金黄");map.put("红色",red);map.put("黄色", yellow);map.put("金黄色",gold);return map;}
}

在测试用的实体类中增加级联字段用来测试,将converter设置为自定义的CascadeConverter同时将级联的依赖字段选为枚举类转换字段

@ExcelEntity(title = "测试")
@Data
public class TestEntity{//require选项必填时输出会带有*号,读取时无必填字段则会抛出异常@ExcelField(name = "必填选项", require = true)private String str;//在枚举类的情况下,param参数可以指定excel写入读取时使用的枚举类属性@ExcelField(name = "枚举类转换", param = "i18n")private ColorEnum colorEnum;@ExcelField(name = "长整型")private Long number;@ExcelField(name = "时间")private Date date;@ExcelField(name = "级联",converter = CascadeConverter.class,cascadeDepend = "枚举类转换")private String cascade;
}

使用ExcelWriter的writeTemplate方法导出excel模板并查看,级联效果如下

错误行校验

工作中经常有业务是用户直接通过excel的形式进行批量的导入操作,由于excel不可控,所以总是会产生一些错误的数据,错误行校验功能与字段转换功能紧密贴合,在字段转换时达到字段的校验功能

需要使用错误行校验首先实体类继承BaseErrMsg,BaseErrMsg用来存储校验的错误信息

@ExcelEntity(title = "测试")
@Getter
@Setter
@ToString
public class TestEntity extends BaseErrMsg {//require选项必填时输出会带有*号,读取时无必填字段则会抛出异常@ExcelField(name = "必填选项", require = true)private String str;//在枚举类的情况下,param参数可以指定excel写入读取时使用的枚举类属性@ExcelField(name = "枚举类转换", param = "i18n")private ColorEnum colorEnum;@ExcelField(name = "长整型")private Long number;@ExcelField(name = "时间")private Date date;@ExcelField(name = "级联",converter = CascadeConverter.class,cascadeDepend = "枚举类转换")private String cascade;
}

然后再字段转换器中校验不符合的地方抛出NoExcelException异常,工具类就会捕获这个异常并写入到BaseErrMsg的属性中,以默认长整型转换器为例,在解析Integer失败时抛出了必须为长整型的异常

    @Overridepublic Long parseToField(String excelData) {try {return Long.parseLong(excelData);} catch (NumberFormatException numberFormatException) {throw new NoExcelException("必须为长整型");}}

这里尝试读取下面的数据,下面第一行数据有两处错误,必填选项未填,长整型也不对

下面的代码读取了这个失败文件并将成功的数据和失败的数据分离,在控制台输出成功的数据,并将失败数据导出到errFile.xls

 @Testpublic void readerrorFile() {//读取excelString path = getClass().getResource("/").getPath()+"readerrFile.xls";ExcelReaderBuilder<TestEntity> builder = ExcelReaderBuilder.builder(TestEntity.class);List<TestEntity> testEntities;try (ExcelReader<TestEntity> excelReader = builder.build(new File(path))) {testEntities = adData();}//过滤成功的数据List<TestEntity> success = testEntities.stream().filter(BaseErrMsg::hasNotErrMsg).List());System.out.println(success);//过滤成功的数据List<TestEntity> error = testEntities.stream().filter(BaseErrMsg::hasErrMsg).List());//将失败的数据重新导成exceltry (ExcelWriter<TestEntity> excelWriter = ExcelWriterBuilder.builder(TestEntity.class).build()) {excelWriter.writeDataAndClose(error,new FileOutputStream("errFile.xls"));} catch (FileNotFoundException e) {e.printStackTrace();}}

可以看到结果如下图,控制台输出了成功的数据行,而excel中的导出了错误行,并且含有错误行的错误信息

忽略字段

有时候导入导出希望动态的选择某些字段,而不是对标有注解的字段进行全量的导入导出,比如继承了BaseErrMsg后,在生成模板或导入数据时并不希望有错误信息行的出现,导入数据时也不希望读到excel中的错误信息

ignoreWithFieldName

通过Builder上提供的ignoreWithFieldName方法可以在导入导出时忽略这些字段,列如忽略错误信息经常用到,因此也提供了一个快捷方法

    public Builder ignoreErrMsg() {return this.ignoreWithFieldName(BaseErrMsg::getErrMsg);}

使用如下,忽略指定行后构建实例,即可

    @Testpublic void writeTemplate() {//构造模板try (ExcelWriter<TestEntity> excelWriter = ExcelWriterBuilder.builder(TestEntity.class)//忽略errMsg行.ignoreErrMsg()//构建.build();FileOutputStream fileOutputStream = new FileOutputStream("createTemplate.xls")) {excelWriter.writeTemplate(fileOutputStream);} catch (IOException e) {e.printStackTrace();}}

修改样式

StyleProvider

通过实现StyleProvider可以自定义标题,表头,数据的样式

editStyle

实现editStyle方法能够修改单元格样式

    @Overridepublic void editStyle(CellStyle style, Workbook workbook) {style.setFillForegroundColor(HSSFColor.HSSFColorPredefined.GREY_Index());style.setFillPattern(FillPatternType.SOLID_FOREGROUND);style.setBorderBottom(BorderStyle.THIN);style.setBorderLeft(BorderStyle.THIN);style.setBorderRight(BorderStyle.THIN);style.setBorderTop(BorderStyle.THIN);style.setAlignment(HorizontalAlignment.CENTER);style.setVerticalAlignment(VerticalAlignment.CENTER);}
editFont

实现editFont方法能够修改字体样式

   @Overridepublic void editFont(Font font) {font.setBold(Boolean.FALSE);font.setFontHeightInPoints((short)10);}

本文发布于:2024-02-01 18:50:39,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170678464038723.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23