前情回顾:https://blog.csdn.net/weixin_44718708/article/details/121683066
目录
一、提要
GetExcelUtil4.2导入工具随实际工作应用场景需求变化更新优化的一次记录:
1. 增加@DynamicRank注解功能,支持获取动态表头及对应栏位。目前仅支持动态表头在固定表头之后的情况。
2. 增加支持重复性检测。
3. 增加@Transform注解功能,支持字段值的转换。
4. 优化:
(1)注解数值格式(@ExcelNumberFormat)时,字段遇空值自动转化0
(2)设置起始标志的情况下,校准位置改为以标题栏为准(取数据行前推一行)
(3)异常信息提示的数字列序号校准为对应字母序号
二、场景需求
我们第四版工具仅支持实体类接收数据。
如图,A-I是固定栏位,后面红框里面的日期是根据实际到料信息编写的到料日期和到料数量,非固定栏位。
A-I列的数据可以定义相应个数、类型实体类字段进行获取,I列之后的动态栏位无法在同一个实体类内部定义获取。按原有4.1版本,直接使用List<String>获取动态表头,再根据动态表头List的长度使用List<Double>遍历获取表头对应列下的数据,在根据顺序将固定列数据、动态表头数据和表头列下数据组装起来即可。
按前面4.1版本的方法,代码量有点多,而且产生了二次遍历。于是新增了4.2版本,增加了获取动态栏位功能。
三、新增功能说明
1. @DynamicRank
/**
* excel取值时,获取动态表头及本行(/列)对应表头栏位
* 1. 注解作用于List类型字段
* 2. titleRank表示表头所在行(/列),必填(横向表格代表行序,纵向表格表示列序)
* 3. 从当前一直获取到最后一个表头及对应数据栏位
* 4. 作用于实体类的最后一个字段,该字段后的其他字段将不会赋值
* 5. enableDuplicateCheck复性检查:
* (1)依赖于实体类的equals方法的实现
* (2)会一定程度影响取值效率(看数据量),非必要不开启
*/
@Target(value = ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DynamicRank {
int titleRank();
boolean enableDuplicateCheck() default false;
}
@DynamicRank注解用于获取固定栏位之后的动态栏位,兼容横向和纵向表格。该注解作用于实体类中List类型的字段上,使用该注解的字段将认为是实体类的最后一个赋值字段。为确保成功获取动态栏位的值,务必保证用于取值的字段类型为List<DTO>。
@DynamicRank包含两个参数,titleRank和enableDuplicateCheck:
(1)titleRank为数字类型,表示表头所在栏(行/列,数字序号)。被@DynamicRank注解的字段将默认获取动态表头加表头对应栏位值,需要正确提供表头所在栏。被注解List<DTO>中DTO通常只包含两个字段,表头和栏位值。在给该DTO内字段赋值时,将忽略@DynamicRank注解。
(2)enableDuplicateCheck为boolean类型,表示是否开启数据重复性检测。值为true时,如果当前获取的DTO数据与其余已获取的DTO重复时,将抛出DataDuplicationException异常。判断重复规则将在后面说明。
接收Excel数据实体:
@Data
public class Test1DTO {
... ...
@DynamicRank(titleRank = 2, enableDuplicateCheck = true)
private List<Test2DTO> dataList;
... ...
}
接收动态栏位实体:
@Data
public class Test2DTO {
@ExcelDateFormat(message = "日期格式必须为yyyy/MM/dd!")
private String arriveDate;
@ExcelNumberFormat(format = "#", message = "数量必须为正整数!")
private Integer arriveQty;
// 根据业务需要自定义equals
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Test2DTO) {
Test2DTO another = (Test2DTO) obj;
return this.arriveDate.equals(another.getArriveDate());
}
return false;
}
}
2. 重复性检测
4.1版本的取值方法是这样的:
4.2版本是这样的:
对比可以发现4.2版本增加了一个boolean类型参数:
特别注意:
(1)重复性检测的规则是依赖于取值DTO的equals方法实现,使用时应当根据自己的业务需要,在取值DTO中重写equals方法。不然在enableDuplicateCheck为true时,将会调用父类Object的equals方法(比较对象地址),业务逻辑上的数据重复将无法检测出。
(2)在数据量足够大的情况下,开启重复性检测会增加开销。重复性检测功能是将当前获取到的数据与前面已经成功获取到的数据List遍历比对,重复时抛DataDuplicationException异常。假如获取的数据有100万,当前获取到第100万条数据,则需要重新遍历前面已取得的99.9999万条数据。所以对数据重复性要求不高,并且导入数据量大的业务不建议开启。当然,当对数据重复性要求高的话,数据遍历开销就是必要开销了。
加了一个默认不进行重复性检测的方法:
根据业务需要,对equals方法的重写(@Override是可以省略的,但建议保留,规范,防止拼写错误):
@Data
public class Test1DTO {
@NotNull(message = "A不能为空!")
private String a;
@NotNull(message = "B不能为空!")
private String b;
@NotNull(message = "C不能为空!")
private String c;
@NotNull(message = "D不能为空!")
private String d;
@NotNull(message = "E不能为空!")
private String e;
@NotNull(message = "F不能为空!")
private String f;
@ValueLimit(limit = {"value1", "value2"}, message = "G只能为value1、value2")
@Transform(expressions = {"value1 -> 1", "value2 -> 2"})
private Integer g;
private String h;
private Integer i;
@DynamicRank(titleRank = 2, enableDuplicateCheck = true)
private List<Test2DTO> dataList;
// 根据业务需要自定义equals
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Test1DTO) {
Test1DTO another = (Test1DTO) obj;
return this.a.equals(another.getA())
&& this.b.equals(another.getB())
&& this.c.equals(another.getC())
&& this.d.equals(another.getD())
&& this.e.equals(another.getE())
&& this.f.equals(another.getF())
&& this.g.equals(another.getG());
}
return false;
}
}
补充一点,由图可以看出,4.2版本将方法中的栏位数量参数cellNum拿掉了,取值个数以实体类字段数为准,所以在定DTO字段时,需明确需要获取的栏位。
3. @Transform
为满足栏位获取的值转换的需求,4.2版本新增了@Transform注解功能:
/**
* excel取值时,指定值变化成另一个值
* 表达式:a->b
*/
@Target(value = ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Transform {
String[] expressions();
}
使用说明:
(1)注解参数expressions的表达式格式为“a -> b”,a为要进行转变的值,b是转化后的值,两者以箭头“->”隔开。表达式意思为当字段值为a时,自动转化为b。
(2)可以搭配@ValueLimit注解使用,@ValueLimit限制值的范围,@Transform指定范围内值的转变。这种搭配的场景有:某物料有两种状态,“在库”、“出库”,分别使用数字1、2表示。后面@Transform的使用示例就是该类例子。
@Transform的使用示例:
@Data
public class Test1DTO {
... ...
@ValueLimit(limit = {"value1", "value2"}, message = "G只能为value1、value2")
@Transform(expressions = {"value1 -> 1", "value2 -> 2"})
private Integer g;
... ...
}
四、优化说明
1. @ExcelNumberFormat
@ExcelNumberFormat原先遇空值时抛出异常,改为遇到空的情况直接赋值0,优化用户体验。
2. 起始标志
起始标志读取位置从数据起始栏改为表头栏(读取数据起始栏),防止用户往模板插入数据时覆盖起始标志。
原先:
4.2版本:
标志说明:
1. 起始标志的作用是防止用户修改了模板格式后进行数据导入,如修改表头栏位置(例:模板表头行是4,用户修改模板格式后变成1),数据栏位与预期偏离而导致数据读取不完整(2-4行数据获取不到)或多读无关数据,而用户无从感知。加入起始标志后起始行与预期不对应时将抛出IllegalArgumentException异常停止操作,项目中将截获异常后摘取提示信息告知用户,保证数据的正确性。
2. 结束标志与起始标志作用类似,以结束标志做数据终点,防止用户在数据结束后插入了不可见的无关数据影响数据读取准确性(比如设置为白色的字体)。但这种情况比较少见,工具类中已经做出了遇到空行终止数据读取返回结果的处理(包括全空格行),所以结束标志比较少用到。
*3. 多说一句,工具类可以获取sheet名,有需求的话,可以将模板的sheet名设置一个特定的名字,在业务层判断获取的sheet名是否与预期的名字一致,以此判断用户是否使用了给定的模板进行数据上传。
3. 提示信息列序号
原本提示信息类似下面:
文件工作表1第2行第55列:日期格式不正确!
以数字提示列序,实际到Excel文档中,寻找第55列有点不友好,现将所有与列序有关的提示,序号对应到Excel中的字母列序。如下:
文件工作表1第2行第BC列:日期格式不正确!
五、GitHub地址
https://github.com/BadGuy-Darren/MyExcelImportTool.git/4.2/
未打包成jar依赖包,打包教程网上有,很简单的。