自定义注解类型和常用场景
在我们日常开发过程中比较常用的自定义注解有以下几种:
@Target(ElementType.FIELD) 该定义作用在类的属性上
@Target(ElementType.METHOD) 该定义作用在类的方法上
其他的声明范围有:
ElementType.TYPE:接口、类、枚举、注解
ElementType.PARAMETER:方法参数
ElementType.CONSTRUCTOR:构造函数
ElementType.LOCAL_VARIABLE:局部变量
ElementType.ANNOTATION_TYPE:注解
ElementType.PACKAGE:包
2.常用的场景一般有:全局日志打印、登录权限校验、类字段校验、统一对外接口特殊处理、以及其他系统多场景下经常会使用到重复代码、需要统一风格的地方都可以使用,减少冗余代码。
FiEID-自定义注解完整步骤
1.定义一个自定义的Field注解
2.针对该注解,创建一个方法进行逻辑实现
3.如何作用在声明类的属性上
4.调用类如何触发
下面举例说明,假如我们需要对导入excel的字段进行格式校验,则可以自定义注解来实现该场景,可作用于整个项目的类属性,进行统一的格式校验。
定义一个自定义的Field注解
CheckExcelField 注解定义了四个参数,以及对type参数定义了几种类型格式校验,在后续的对象中可以看到具体的使用方式。
import java.lang.annotation.*;
/**
* 标记Excel单元格校验字段
* 校验工具类ExcelCheckUtil
* 在DTO中标记对应字段
* 校验时使用ExcelCheckUtil类的checkExcelRow方法,传入需校验的DTO对象,依据返回值是否为空即可判断标记字段是否都符合格式规范
*
* type 校验类型 目前有3种校验
* 1、字符串长度校验 type 为String length 不为0 若长度大于length,则会返回提示信息
* 2、数字校验 type 为BigDecimal 若存在非正常数字值,则会返回提示信息
* 3、日期校验 type 为Date 首先会将yyyy/M/d以及yyyy-MM-dd格式的统一为yyyy/MM/dd格式,若转换失败或不为这两种格式,则会返回提示信息
* length 代表长度校验
* min 最小值
* max 最大值
*/
@Target(ElementType.FIELD)//表示字段级注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckExcelField {
String type() default "String";
int length() default 0;
int min() default 0;
int max() default 0;
}
创建一个方法进行逻辑实现
该方法则是对字段注解CheckExcelField的解析,用于处理所有使用了该注解的对象,进行字段内容校验,可以对方法进行改造,以适应各自的项目。
import com.alibaba.excel.annotation.ExcelProperty;
import com.power.common.annotation.CheckExcelField;
import cn.hutool.core.bean.BeanUtil;
import org.apache.commons.lang.StringUtil;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ExcelCheckUtil {
private static final String DECIMAL_REGEX = "^[0-9]\\d*\\.?\\d*";
private static final Pattern DECIMAL_PATTERN = Pattern.compile(DECIMAL_REGEX);
public static final String SEPARATOR_FIELD = "#";
public static final String STRING_TOO_LONG = "单元格值过长,请检查!";
public static final String DATE_FORMAT_FAIL = "日期格式有误,请检查!例:2024/01/01";
public static final String BIGDECIMAL_FOMAT_FAIL = "数值格式有误,请检查!";
public static final String BIGDECIMAL_FOMAT_FAIL2 = "数值格式有误,金额字段只能保留两位,请检查!";
public static final String NUMBER_SCOPE_FAIL = "单元格值不在要求范围内,请检查!";
public static <T> String checkExcelRow(T t) {
if (t == null) {
throw new Exception("check Object can not be null");
}
Class tClass = t.getClass();
StringBuilder sb = new StringBuilder();
for (Field f : tClass.getDeclaredFields()) {
CheckExcelField df = f.getAnnotation(CheckExcelField.class);
if (df == null) {
continue;
}
String type = df.type();
int length = df.length();
int min = df.min();
int max = df.max();
String fieldValue = String.valueOf(BeanUtil.getFieldValue(t, f.getName()));
if (fieldValue == null || StringUtils.isBlank(fieldValue)||"null".equals(fieldValue)) {
continue;
}
ExcelProperty ep = f.getAnnotation(ExcelProperty.class);
if (BigDecimal.class.getSimpleName().equals(type)) {
try{
fieldValue = new BigDecimal(fieldValue).toPlainString();
}catch (Exception e){
}
fieldValue = fieldValue.replace(",", "");
Matcher m = DECIMAL_PATTERN.matcher(fieldValue);
if (!m.matches()) {
extracted(sb, ep, BIGDECIMAL_FOMAT_FAIL);
}
//数据字段类型为BigDecimal
if(f.getType().getTypeName().equals(BigDecimal.class.getTypeName())){
if(fieldValue.contains(".")&&fieldValue.substring(fieldValue.lastIndexOf(".")+1).length()>2){
extracted(sb, ep, BIGDECIMAL_FOMAT_FAIL2);
}
continue;
}else{
BeanUtil.setFieldValue(t, f.getName(), fieldValue);
}
} else if (String.class.getSimpleName().equals(type)) {
if (fieldValue.length() > length) {
extracted(sb, ep, STRING_TOO_LONG);
}
} else if (Date.class.getSimpleName().equals(type)) {
try {
if (fieldValue.contains("/") && fieldValue.length() == 7) { // yyyy/mm
SimpleDateFormat format = new SimpleDateFormat("yyyy/MM");
format.setLenient(false);
Date datef = new Date(format.parse(fieldValue).getTime());
String str = new SimpleDateFormat("yyyy/MM").format(datef);
BeanUtil.setFieldValue(t, f.getName(), str);
} else if (fieldValue.contains("/") && fieldValue.length() <= length) {
SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
format.setLenient(false);
Date datef = new Date(format.parse(fieldValue).getTime());
String str = new SimpleDateFormat("yyyy/MM/dd").format(datef);
BeanUtil.setFieldValue(t, f.getName(), str);
} else if (fieldValue.contains("-") && fieldValue.length() <= length) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
format.setLenient(false);
Date datef = new Date(format.parse(fieldValue).getTime());
String str = new SimpleDateFormat("yyyy/MM/dd").format(datef);
BeanUtil.setFieldValue(t, f.getName(), str);
} else {
throw new Exception();
}
} catch (Exception e) {
extracted(sb, ep, DATE_FORMAT_FAIL);
}
} else if (StringUtils.equals("Number", type)) {
try {
int value = Integer.valueOf(fieldValue);
if (!(value >= min && value <= max)) {
extracted(sb, ep, NUMBER_SCOPE_FAIL);
}
} catch (Exception e) {
extracted(sb, ep, NUMBER_SCOPE_FAIL);
}
}
}
return sb.toString();
}
// 错误提示组装
private static void extracted(StringBuilder sb, ExcelProperty ep, String msg) {
if (sb.length() != 0) {
sb.append(SEPARATOR_FIELD);
}
sb.append(ep.value()[0]).append(",").append(msg);
}
}
声明在类的属性上
该对象用注解在字段上声明了几种类型,在后续调用过程中,会根据对象类型来进行校验相应的字段。
@Date
@ApiModel(value = "UserInfoDto", description = "用户对象Dto")
public class UserInfoDto implements Serializable {
private static final long serialVersionUID = 1L;
@ExcelProperty(value = "主键")
private Long id;
@ExcelProperty("用户名称")
private String userName;
@ExcelProperty("用户存款")
@CheckExcelField(type = "BigDecimal")
private BigDecimal amount;
@ExcelProperty(value = "毕业日期")
@CheckExcelField(type = "Date",length = 10)
private String graduateDate;
@ExcelProperty(value = "毕业简述")
@CheckExcelField(length = 2000)
private String graduateAns;
@ExcelProperty(value = "毕业简述")
@CheckExcelField(type = "Number",min=18,max=30)
private String graduateAns;
}
调用类如何触发
读取文件,并把excel中的数据映射到USerInfoDto对象中,在用ExcelRowCheckUtil.checkExcelRow()方法对每一行数据进行字段校验,如果有异常则提示。
@Service
public class UserService {
public String importUserExcel(MultipartFile file) throws Exception {
String msg = ""
// 获取文件流
InputStream inputStream = file.getInputStream();
// 实例化实现了AnalysisEventListener接口的类
ExcelListener listener = new ExcelListener();
// 读取第一个sheet
EasyExcelFactory.read(inputStream, UserInfoDto.class, listener).headRowNumber(1).build().read(new ReadSheet(0));
// 获取数据
List<Object> list = listener.getDatas();
// 数据校验
dataCheck(list);
//其他逻辑处理
msg=otherMethod();
return msg;
}
private static void dataCheck(List<Object> list) {
List<String> errorMessageList = new ArrayList<>();
for(int i = 0; i < list.size(); i++){
String result = "";
//针对导入的excel中的每一行数据的字段进行格式校验,如果有返回,则提示报错
if((result = ExcelCheckUtil.checkExcelRow(list.get(i))).length() > 0){
StringBuilder sb = new StringBuilder();
sb.append("<br/>第").append(i+2).append("行数据校验不通过:");
List<String> fieldErrors = Arrays.asList(result.split(ExcelRowCheckUtil.SEPARATOR_FIELD));
for(String err : fieldErrors){
sb.append("<br/>输入值:").append(err);
}
errorMessageList.add(sb.toString());
}
}
if (!errorMessageList.isEmpty()) {
throw new Exception(errorMessageList.stream().collect(Collectors.joining("</br>")));
}
}
}