使用案例
- 使用方法
// 读取数据,hutool读取数据成 List<List<Object>> 的形式
List<List<Object>> list = excelReader.read();
// 默认方式:对注解上的校验并转换
List<QUserTemplet> qUserImportList = CheckUtil.check(list, QUserTemplet.class);
// 携带自定义校验方式:或做其他动作
List<Long> deptIds = departmentMapper.selectAll(); // 查询所有已知部门
List<QUserTemplet> qUserImportList = CheckUtil.check(list, QUserTemplet.class, (qUserTemplet, row) -> {
// 判断部门是否存在
if (!CollUtil.contains(deptIds, qUserTemplet.getDepartmentId())) {
throw new CheckException("第" + row + "行,部门不存在,请输入正确ID");
}
// 生成 加密密码 和 盐
String salt = IdUtil.simpleUUID();
String pass = EncryptKit.encryptSha1(qUserTemplet.getPassword(), salt);
qUserTemplet.setPassword(pass);
qUserTemplet.setSalt(salt);
});
- 需要转换的实体
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.wsitm.web.basic.util.Constant;
import org.wsitm.web.basic.util.check.Check;
import org.wsitm.web.basic.util.export.Header;
import java.io.Serializable;
/**
* 用户表参数
* <p>
*
* @author lzy on 2021/4/20 9:12
*/
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class QUserTemplet implements Serializable {
/**
* 用户(唯一)
*/
@Check(regex = "^[a-z0-9_-]{3,16}$", message = "用户名不符合要求")
private String account;
/**
* 用户名称
*/
@Check(notEmpty = true, message = "用户名称不能为空")
private String fullname;
/**
* 角色描述
*/
@Check(regex = "^(((\\d+,)+\\d+)|\\d)$", message = "角色请输入数字且以逗号分隔")
private String roles;
/**
* 用户密码
*/
@Check(regex = "^(?![a-zA-Z]+$)(?!\\d+$)(?![!@#$%^&*]+$)[\\w!@#$%^&*]{6,18}$", message = "密码不符合要求")
private String password;
/**
* 性别
*/
@Check(regex = "[01]", message = "性别请输入1或0")
private Integer sex;
/**
* 城市id
*/
@Check(regex = "\\d+", message = "地市请输入数字")
private Long cityId;
/**
* 电话
*/
@Check(regex = "^[1][0-9]{10}$", message = "电话号码格式不正确")
private String telephone;
/**
* 邮箱
*/
@Check(regex = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$", message = "邮箱号码格式不正确")
private String email;
/**
* 部门组织ID
*/
@Check(regex = "\\d+", message = "部门ID请输入数字")
private Long departmentId;
/**
* 描述
*/
@Check()
private String remark;
private static final long serialVersionUID = 1L;
}
源码
package org.wsitm.web.basic.util.check;
import java.lang.annotation.*;
/**
* 检查校验数据注解
*
* @author lzy
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Check {
/**
* 校验,不能为空
*
* @return true、不能为空,true:表示不能、false:可以为空
*/
boolean notEmpty() default false;
/**
* 校验,正则
*
* @return 正则
*/
String regex() default "";
/**
* 校验,时间的格式
*
* @return 时间的格式
*/
String dateFormat() default "";
/**
* 异常的描述
*
* @return 异常的描述
*/
String message() default "Incorrect field";
}
注意:转换校验只转换携带@Check注解的字段,没有设置排序注解,所以排序默认是从上到下且携带@Check注解的字段
package org.wsitm.web.basic.util.check;
import java.io.Serializable;
import java.util.List;
/**
* 业务异常
*
* @author lzy
*/
@SuppressWarnings({"unused", "rawtypes"})
public class CheckException extends RuntimeException implements Serializable {
private static final long serialVersionUID = 1L;
private int status = 500;
private String detailedError;
private List tempList;
public CheckException(String message) {
super(message);
}
public CheckException(String message, int status) {
super(message);
this.status = status;
}
public CheckException(String message, String detailedError) {
super(message);
this.detailedError = detailedError;
}
public CheckException(String message, String detailedError, int status) {
super(message);
this.detailedError = detailedError;
this.status = status;
}
public CheckException(String message, String detailedError, List tempList) {
super(message);
this.detailedError = detailedError;
this.tempList = tempList;
}
public CheckException(String message, String detailedError, List tempList, int status) {
super(message);
this.detailedError = detailedError;
this.status = status;
this.tempList = tempList;
}
public CheckException(String message, String detailedError, List tempList, int status, Throwable e) {
super(message, e);
this.detailedError = detailedError;
this.tempList = tempList;
this.status = status;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getDetailedError() {
return detailedError;
}
public void setDetailedError(String detailedError) {
this.detailedError = detailedError;
}
public List getTempList() {
return tempList;
}
public void setTempList(List tempList) {
this.tempList = tempList;
}
}
注意:自定义异常,主要用于携带校验异常的数据,便于业务记录
package org.wsitm.web.basic.util.check;
import cn.hutool.core.annotation.Alias;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* 数据检测并转换工具
*
* @author lzy
*/
public class CheckUtil {
/**
* 批量检测数据是否合理,并返回指定类型数
*
* @param list 原始数据
* @param clazz 返回类型
* @param <T> 指定类型
* @return 校验并转换后的数据
*/
public static <T> List<T> check(List<List<Object>> list, Class<T> clazz) throws CheckException {
return check(list, clazz, 1, true, null);
}
/**
* 批量检测数据是否合理,并返回指定类型数
*
* @param list 原始数据
* @param clazz 返回类型
* @param checkRow 函数,自定义校验,参数:对象,行数
* @param <T> 指定类型
* @return 校验并转换后的数据
*/
public static <T> List<T> check(List<List<Object>> list, Class<T> clazz, CheckConsumer<T, Integer> checkRow) throws CheckException {
return check(list, clazz, 1, true, checkRow);
}
/**
* 批量检测数据是否合理,并返回指定类型数
*
* @param list 原始数据
* @param clazz 返回类型
* @param skipRow 跳过的头部的行数
* @param checkAll 是否校验全部,是:表示全部校验完成才返回结果集。否:表示一旦发生异常直接返回
* @param checkRow 函数,自定义校验,参数:对象,行数
* @param <T> 指定类型
* @return 校验并转换后的数据
*/
public static <T> List<T> check(List<List<Object>> list, Class<T> clazz,
int skipRow, boolean checkAll, CheckConsumer<T, Integer> checkRow) throws CheckException {
Field[] fields = ReflectUtil.getFields(clazz, field -> field.getAnnotation(Check.class) != null);
Map<String, Pattern> patternMap = new HashMap<>();
List<String> msgList = new ArrayList<>();
List<T> result = new ArrayList<>();
for (int i = 0, size = list.size(); i < size; i++) {
int row = i + 1;
if (skipRow >= row) {
continue;
}
List<Object> line = list.get(i);
if (checkAll) {
try {
checkAllField(fields, line, patternMap, row, result, clazz, checkRow);
} catch (Exception exception) {
msgList.add(exception.getMessage());
}
} else {
checkAllField(fields, line, patternMap, row, result, clazz, checkRow);
}
}
if (CollUtil.isNotEmpty(msgList)) {
String msg = String.format("校验通过个数:%s,校验失败个数:%s", (list.size() - skipRow - msgList.size()), msgList.size());
throw new CheckException(msg, String.join("\n", msgList), result);
}
return result;
}
/**
* 校验n行的所有字段
*
* @param fields 字段数组
* @param line 行的原数据
* @param patternMap 枚举MAP
* @param row 行数
* @param result 结果集
* @param clazz 类型
* @param checkRow 函数,自定义校验,参数:对象,行数
* @param <T> 类型
*/
private static <T> void checkAllField(Field[] fields, List<Object> line,
Map<String, Pattern> patternMap, int row,
List<T> result, Class<T> clazz, CheckConsumer<T, Integer> checkRow) throws CheckException {
Map<String, Object> item = MapUtil.newHashMap();
for (int i = 0, size = fields.length; i < size; i++) {
if (CollUtil.isNotEmpty(line) && line.size() > i) {
Field field = fields[i];
Check check = field.getAnnotation(Check.class);
String key = field.getName();
String tranKey = null;
Alias alias = field.getAnnotation(Alias.class);
if (alias != null) {
tranKey = alias.value();
}
Object value = line.get(i);
if (check.notEmpty() && StrUtil.isEmpty(String.valueOf(value))) {
throw new CheckException("第" + row + "行," + check.message());
}
if (StrUtil.isNotEmpty(check.regex())) {
Pattern pattern = patternMap.computeIfAbsent(key, k -> Pattern.compile(check.regex(), Pattern.CASE_INSENSITIVE));
if (!pattern.matcher(String.valueOf(value)).find()) {
throw new CheckException("第" + row + "行," + check.message());
}
}
if (StrUtil.isNotEmpty(check.dateFormat())) {
value = checkDateTime(line, check, field.getType(), row);
}
item.put(StrUtil.emptyToDefault(tranKey, key), value);
}
}
T t = BeanUtil.toBean(item, clazz);
if (checkRow != null) {
// 个性化校验回调
checkRow.accept(t, row);
}
result.add(t);
}
private static Object checkDateTime(Object value, Check check, Class<?> type, int row) throws CheckException {
try {
if (type.isAssignableFrom(CharSequence.class) || type.isAssignableFrom(Number.class)) {
// bean中的类型是 字符串/整形(yyyyMMdd) 时,校验是否符合格式
if (value instanceof DateTime) {
return ((DateTime) value).toString(check.dateFormat());
}
if (value instanceof LocalDate) {
return ((LocalDate) value).format(DateTimeFormatter.ofPattern(check.dateFormat()));
}
if (value instanceof LocalDateTime) {
return ((LocalDateTime) value).format(DateTimeFormatter.ofPattern(check.dateFormat()));
}
}
return value;
} catch (Exception e) {
throw new CheckException("第" + row + "行," + check.message());
}
}
@FunctionalInterface
public interface CheckConsumer<T, U> {
void accept(T t, U u) throws CheckException;
}
}