easyexcel版本3.1.5
使用自定义注解的方式来定义校验的类型,避免冗余代码。
java
//校验value不能为空,且长度最大为30
@RowCheck(value = {RowCheckType.EMPTY,RowCheckType.LENGTH},max = 30)
private String value;
具体代码:
首先定义校验类型枚举RowCheckType:
java
package com.zhou.util.easyexcel;
/**
* 校验类型枚举
* @author lang.zhou
*/
public enum RowCheckType {
EMPTY, //非空
EMAIL, //邮件
PATTERN, //正则
LENGTH, //长度
DATE, //日期
ENUM //枚举
}
定义校验不通过默认的提示语模板:
java
package com.zhou.zhou.util.easyexcel;
/**
* @author lang.zhou
*/
public class CheckMessage {
public static final String DEFAULT = "default";
public static final String EMPTY = "第{n}行{c}不能为空";
public static final String EMAIL = "第{n}行{c}邮箱格式错误";
public static final String PATTERN = "第{n}行{c}格式错误";
public static final String LENGTH = "第{n}行{c}长度超过{len}";
public static final String DATE = "第{n}行{c}日期格式错误";
public static final String ENUM = "第{n}行{c}不在枚举中";
}
定义校验的接口类ValueChecker :
java
package com.zhou.util.easyexcel.checker;
import com.zhou.util.easyexcel.RowCheckType;
/**
* @author lang.zhou
*/
public interface ValueChecker {
/**
* 实现校验逻辑
*/
boolean check(Object value);
/**
* 获得校验类型
*/
RowCheckType getType();
/**
* 获得默认校验提示
*/
String getDefaultMessage();
}
校验工厂类CheckFactory:
java
package com.zhou.util.easyexcel.checker;
import com.zhou.util.easyexcel.RowCheck;
import com.zhou.util.easyexcel.RowCheckType;
/**
* @author lang.zhou
*/
public class CheckerFactory {
public static ValueChecker createChecker(RowCheckType type, RowCheck check){
ValueChecker checker;
if(type == RowCheckType.EMPTY){
checker = new EmptyChecker();
}else if(type == RowCheckType.EMAIL){
checker = new EmailChecker();
}else if(type == RowCheckType.PATTERN){
checker = new PatternChecker(check.format());
}else if(type == RowCheckType.LENGTH){
checker = new LengthChecker(check.max());
}else if(type == RowCheckType.DATE){
checker = new DateChecker(check.format());
}else if(type == RowCheckType.ENUM){
checker = new EnumChecker(check.format());
}else{
checker = new EmptyChecker();
}
return checker;
}
}
封装的注解@RowCheck:
java
package com.zhou.util.easyexcel;
import java.lang.annotation.*;
/**
* @author lang.zhou
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RowCheck {
/**
* 校验类型
*/
RowCheckType[] value() default {};
String[] msg() default {CheckMessage.DEFAULT};
/**
* 正则校验/日期格式/枚举json
*/
String format() default "";
/**
* 最大长度校验
*/
int max() default 0;
}
再定义一个easyexcel导入的映射实体类和excel模板如下:
java
package com.zhou.util.easyexcel.model;
import com.alibaba.excel.annotation.ExcelProperty;
import com.zhou.util.easyexcel.RowCheck;
import com.zhou.util.easyexcel.RowCheckType;
import lombok.Data;
import java.io.Serializable;
/**
* @author lang.zhou
*/
@Data
public class ImportModel implements Serializable {
private static final long serialVersionUID = 1L;
@ExcelProperty(index = 0, value = "用户名")
@RowCheck(value = {RowCheckType.EMPTY,RowCheckType.LENGTH},max = 30)
private String username;
@ExcelProperty(index = 1, value = "年龄")
@RowCheck(value = RowCheckType.PATTERN,msg = "第{n}行年龄必须是整数")
private String age;
@ExcelProperty(index = 2, value = "邮箱")
@RowCheck(value = {RowCheckType.EMPTY,RowCheckType.EMAIL,RowCheckType.LENGTH},msg = {CheckMessage.DEFAULT,CheckMessage.DEFAULT,CheckMessage.DEFAULT},max = 3000)
private String email;
}
实现校验的核心逻辑:
java
package com.zhou.util.easyexcel;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.zhou.util.easyexcel.checker.CheckerFactory;
import com.zhou.util.easyexcel.checker.EnumChecker;
import com.zhou.util.easyexcel.checker.ValueChecker;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;
/**
* @author lang.zhou
*/
@EqualsAndHashCode(callSuper = true)
@Data
public abstract class AbstractExcelReadListener<T> extends AnalysisEventListener<T> {
/**
* 每次读取数量
*/
protected int batchSize = 1000;
/**
* 数据总理
*/
protected int dataCount = 0;
/**
* 回调次数
*/
protected int times = 0;
protected int sheetIndex = 0;
protected Class<T> headerClass;
/**
* 数据读取开始行
*/
protected int headerLine = 1;
protected Map<String, ValueChecker[]> checkTypeMap = new LinkedHashMap<>(0);
protected Map<String,RowCheck> checkMap = new LinkedHashMap<>(0);
protected Map<String,String> columnDesc = new LinkedHashMap<>(0);
/**
* 读取的数据缓存
*/
protected List<T> dataList = new ArrayList<>();
public AbstractExcelReadListener(Class<T> headerClass) {
this.headerClass = headerClass;
}
/**
* 读取一行后回调
* @param data (列索引0开始 -> 值)
*/
@Override
public void invoke(T data, AnalysisContext context) {
dataList.add(data);
if(dataList.size() >= batchSize){
callback();
}
}
/**
* 读取结束回调
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
callback();
}
/**
* 读取头信息时初始化校验器
*/
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
if(headerClass != null){
Field[] fields = headerClass.getDeclaredFields();
for (Field field : fields) {
RowCheck check = field.getAnnotation(RowCheck.class);
ExcelProperty ep = field.getAnnotation(ExcelProperty.class);
if(check != null){
RowCheckType[] checkType = check.value();
if(checkType.length > 0){
ValueChecker[] valueChecks = new ValueChecker[checkType.length];
for (int i = 0; i < checkType.length; i++) {
RowCheckType rowCheckType = checkType[i];
//拿到校验器
ValueChecker checker = CheckerFactory.createChecker(rowCheckType, check);
valueChecks[i] = checker;
}
checkTypeMap.put(field.getName(),valueChecks);
checkMap.put(field.getName(),check);
}
}
if(ep != null){
columnDesc.put(field.getName(),ep.value()[0]);
}
}
}
}
private void callback(){
if(dataList.size() > 0){
dataCount += dataList.size();
this.dataCallback(dataList, ++ times);
dataList.clear();
}
}
/**
* 获取dataList中当前索引在全部批次数据中的行数
* @param i 数据在当前批次dataList中的索引
*/
protected int getRowNum(int i){
return (times - 1) * batchSize + i + 1;
}
/**
* 读取batchSize行后回调
*/
public abstract void dataCallback(List<T> dataList,int times);
public void read(InputStream stream){
ExcelReader excelReader = EasyExcelFactory.read(stream, this).headRowNumber(headerLine).build();
ReadSheet readSheet = EasyExcel.readSheet(sheetIndex).head(headerClass).build();
excelReader.read(readSheet);
excelReader.finish();
}
private String getMsg(ValueChecker checker, String[] msg, int i){
String err = i < msg.length ? msg[i] : checker.getDefaultMessage();
if(Objects.equals(err,CheckMessage.DEFAULT)){
err = checker.getDefaultMessage();
}
return err;
}
/**
* 校验一行数据(性能有待验证)
* @param t 数据行
* @param errMsg 存放校验错误信息
* @return true-校验通过
*/
@SneakyThrows
protected boolean checkRow(T t, Set<String> errMsg){
if(checkTypeMap.size() > 0){
int dataIndex = dataList.indexOf(t);
int rowNum = getRowNum(dataIndex);
Field[] fields = headerClass.getDeclaredFields();
for (Field field : fields) {
RowCheck check = checkMap.get(field.getName());
ValueChecker[] checkers = checkTypeMap.get(field.getName());
if(checkers != null){
String[] msg = check.msg();
field.setAccessible(true);
Object o = field.get(t);
for (int i = 0; i < checkers.length; i++) {
ValueChecker checker = checkers[i];
if (!checker.check(o)) {
String err = getMsg(checker,msg,i);
if(StringUtils.isNotBlank(err)){
err = err.replace("{n}",String.valueOf(rowNum))
.replace("{v}",String.valueOf(o))
.replace("{len}",String.valueOf(check.max()))
.replace("{c}",columnDesc.get(field.getName()));
errMsg.add(err);
return false;
}
}
if(checker instanceof EnumChecker){
Object enumKey = ((EnumChecker) checker).getEnumKey(o);
field.set(t,enumKey);
}
}
}
}
}
return true;
}
}
其他的校验实现类:
java
@Data
public class EmptyChecker implements ValueChecker {
private RowCheckType type = RowCheckType.EMPTY;
private String defaultMessage = CheckMessage.EMPTY;
@Override
public boolean check(Object value) {
return !isEmpty(value);
}
public static boolean isEmpty(Object o){
if(o==null){
return true;
}
if(o instanceof String){
return StringTool.isBlank(o.toString());
}
if(o instanceof Collection){
return ((Collection<?>) o).isEmpty();
}
if(o instanceof Map){
return ((Map<?,?>) o).isEmpty();
}
if(o.getClass().isArray()){
return Array.getLength(o) == 0;
}
return false;
}
}
@Data
public class DateChecker implements ValueChecker {
private RowCheckType type = RowCheckType.DATE;
private String defaultMessage = CheckMessage.DATE;
private String format;
public DateChecker(String format) {
this.format = format;
}
@Override
public boolean check(Object value) {
if(value != null){
Date date = TimeUtil.formatDate(value.toString(), format);
return date != null;
}
return true;
}
}
@Data
public class EmailChecker implements ValueChecker {
public static final Pattern EMAIL_PATTERN = Pattern.compile("^\\w+([-\\\\.]\\w+)*@\\w+([-\\\\.]\\w+)*\\.\\w+([-\\\\.]\\w+)*$");
private RowCheckType type = RowCheckType.EMAIL;
private String defaultMessage = CheckMessage.EMAIL;
@Override
public boolean check(Object value) {
if(value != null){
String str = value.toString();
return StringUtils.isNotBlank(str) && EMAIL_PATTERN.matcher(str).matches();;
}
return true;
}
}
/**
* @author lang.zhou
*/
@Data
public class EnumChecker implements ValueChecker {
private RowCheckType type = RowCheckType.ENUM;
private String defaultMessage = CheckMessage.ENUM;
private Map<String,?> json;
public EnumChecker(String format) {
try {
this.json = reserveMap(JSON.parseObject(format));
}catch (Exception e){
//ignore
}
}
private Map<String,Object> reserveMap(Map<String, String> enums){
Map<String,Object> map = new HashMap<>(enums.size());
for (Map.Entry<String, String> entry : enums.entrySet()) {
map.put(entry.getValue(),entry.getKey());
}
return map;
}
@Override
public boolean check(Object value) {
if(value != null){
return getEnumKey(value) != null;
}
return true;
}
public Object getEnumKey(Object value) {
if(value != null && json != null){
return json.get(value.toString());
}
return null;
}
}
@Data
public class LengthChecker implements ValueChecker {
private RowCheckType type = RowCheckType.LENGTH;
private String defaultMessage = CheckMessage.LENGTH;
private int max = 0;
public LengthChecker(int max) {
this.max = max;
}
@Override
public boolean check(Object value) {
if(value != null){
return value.toString().length() <= max;
}
return true;
}
}
@Data
public class PatternChecker implements ValueChecker {
private RowCheckType type = RowCheckType.PATTERN;
private String defaultMessage = CheckMessage.PATTERN;
private Pattern pattern;
public PatternChecker(String format) {
pattern = Pattern.compile(format);
}
@Override
public boolean check(Object value) {
if(value != null){
return pattern.matcher(value.toString()).matches();
}
return true;
}
}
调用方式:
java
//错误信息
Set<String> err = new LinkedHashSet<>();
AbstractExcelReadListener<ImportModel> listener = new AbstractExcelReadListener<ImportModel>(ImportModel.class) {
@Override
public void dataCallback(List<ImportModel> dataList, int times) {
log.info("第{}次读取后保存",times);
for (int i = 0; i < dataList.size(); i++) {
ImportModel dto = dataList.get(i);
//存放校验信息
Set<String> oneErr = new LinkedHashSet<>();
try{
//内置校验
if(!this.checkRow(dto,oneErr)){
continue;
}
//todo
//这里可以进行自定义校验
}finally {
err.addAll(oneErr);
}
}
}
};
listener.read(stream);