1. 解释
自定义注解,通过正则表达式来校验请求相关参数,也可用于校验是否可空等
2. 自定义注解
1. @Check
自定义
@Check
注解用来当做AOP
切点
package net.lesscoding.aop;import java.lang.annotation.*;/*** @author eleven* @date 2022/11/30 11:56* @description*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Check {public String value() default "";
}
2. @CheckProperties
自定义
@CheckProperties
注解用来检验请求上的多个参数
package net.lesscoding.aop;import net.lesscoding.aop.CheckProperty;import java.lang.annotation.*;/*** @author eleven* @date 2022/11/30 15:23* @description*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckProperties {/**** 属性名* @return {@code String}*/String value() default "";/*** 检查的属性值** @return {@code CheckProperty[]}*/CheckProperty[] checks() default {};/*** 下标* @return int*/int index() default 0;
}
3. @CheckProperty
自定义注解
@CheckProperty
用来校验单个属性
package net.lesscoding.aop;import net.lesscoding.enums.TimeType;import java.lang.annotation.*;/*** @author eleven* @date 2022/11/30 10:30* @description 只允许在属性和参数上添加*/
@Target({ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckProperty {/** 注解描述 */public String value() default "";/** 报错提示信息 */public String message() default "";/** 正则表达式 */public String regexp() default "";/** 校验的下标 */public int index() default -1;/** 时间格式 */public String pattern() default "";/** 时间类型 */public TimeType timetype() default TimeType.NON_TIME;/** notNull */public boolean notNull() default false;/** notBlank */public boolean notBlank() default false;
}
3. 其他相关类
1. 时间类型枚举类
这个类现在还没有用到,时间类型一般接收过来的时候就会有
@@JsonFormat
和@DateTimeFormat
注解约束类型,所以现在用不到
package net.lesscoding.enums;/*** @author eleven* @date 2022/12/5 10:05* @description*/
public enum TimeType {/*** LocalTime*/LOCAL_TIME,/*** LocalDateTime*/LOCAL_DATETIME,/*** LocalTime*/LOCAL_DATE,/*** Date 类型*/DATE,/*** Timestamp*/TIMESTAMP,/*** 非时间类型*/NON_TIME;
}
2. 注解处理类
如果方法上有
@Check
注解 ,则进行下边的操作
- 首先校验方法参数里是否有
@CheckProperty
注解,有的话则校验相关参数是否符合要求 - 校验方法上是否有
@CheckProperties
注解,有的话按个校验里边的@CheckProperty
注解判断参数是否符合正则表达式(适用于一个接口有好多个参数的情况) - 如果方法上没有
@CheckProperties
注解,但是有@CheckProperty
注解,校验相关的参数是否符合 - 方法上只有
@Check
注解,但是传入的实体类的属性上有@CheckProperty
挨个校验方法参数是否符合
package net.lesscoding.aop;import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import net.lesscoding.enums.TimeType;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** @author eleven* @date 2022/11/30 11:17* @description*/
@Aspect
@Component
@Slf4j
public class CheckAspect {private long methodIntervalMs = 0L;@Pointcut("@annotation(net.lesscoding.aop.Check)")public void checkPointCut() {}@Before(value = "checkPointCut()")public void test(JoinPoint joinPoint) throws Exception {Check check = getAnnotationCheck(joinPoint);//获取方法,此处可将signature强转为MethodSignatureMethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();checkArgsAnnotation(joinPoint,method);checkOverMethodAnnotation(joinPoint,method);checkInField(joinPoint);}/*** 校验在类属性里的注解* @param joinPoint 切点* @param method 方法*/private void checkInField(JoinPoint joinPoint) {Object[] args = joinPoint.getArgs();for (Object arg : args) {Class<?> argClass = arg.getClass();Field[] declaredFields = argClass.getDeclaredFields();for (int i = 0; i < declaredFields.length; i++) {Field field = declaredFields[i];CheckProperty checkProperty = field.getAnnotation(CheckProperty.class);if (checkProperty == null){continue;}Object argByFiled = getArgByFiled(field, arg);checkArg(checkProperty,arg.getClass(),field.getName(),argByFiled);}}}/*** 根据类定义的field获取值* @param field 类属性* @param arg 参数* @return Object*/protected Object getArgByFiled(Field field,Object arg){try{PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(),arg.getClass());Method readMethod = propertyDescriptor.getReadMethod();Object invoke = readMethod.invoke(arg);return invoke;}catch (Exception e){e.printStackTrace();}return new Object();}/*** 校验方法上的注解* @param joinPoint*/protected void checkOverMethodAnnotation(JoinPoint joinPoint,Method method) {Parameter[] parameters = method.getParameters();Object[] args = joinPoint.getArgs();// 判断多个注解配置CheckProperties checkProperties = method.getAnnotation(CheckProperties.class);if(checkProperties != null){CheckProperty[] checkArr = checkProperties.checks();if(checkArr.length == 0){return;}for (int i = 0; i < checkArr.length; i++) {CheckProperty checkProperty = checkArr[i];int index = checkProperty.index();Parameter parameter = parameters[index != -1 ? index : i];Object arg = args[index != -1 ? index : i];checkArg(checkProperty, parameter, arg);}}// 校验 @CheckProperty 注解CheckProperty checkProperty = method.getAnnotation(CheckProperty.class);if(checkProperty != null){int index = checkProperty.index();Parameter parameter = parameters[index != -1 ? index : 0];Object arg = args[index != -1 ? index : 0];checkArg(checkProperty,parameter,arg);}}/*** 检查方法参数里的注解* @param joinPoint AOP切点*/protected void checkArgsAnnotation(JoinPoint joinPoint, Method method){// 获取参数Parameter[] parameters = method.getParameters();//参数注解,1维是参数,2维是注解Annotation[][] annotations = method.getParameterAnnotations();Object[] args = joinPoint.getArgs();for (int i = 0; i < annotations.length; i++) {Annotation[] annotation = annotations[i];List<Annotation> paramsAnnotation = new ArrayList<>(Arrays.asList(annotation));int finalI = i;paramsAnnotation.stream().forEach(item -> {Class<? extends Annotation> annotationType = item.annotationType();if(annotationType.getName().endsWith(".CheckProperty")){CheckProperty checkProperty = (CheckProperty)item;checkArg(checkProperty,parameters[finalI],args[finalI]);}});}}/*** 校验属性* @param checkProperty 注解内容* @param parameter 参数描述* @param arg 参数*/protected void checkArg(CheckProperty checkProperty,Parameter parameter,Object arg){checkArg(checkProperty,parameter.getType(),parameter.getName(),arg);}/*** 校验属性* @param checkProperty 注解* @param type 参数类型* @param argName 参数名称* @param arg 参数*/protected void checkArg(CheckProperty checkProperty,Class type ,String argName,Object arg){// 获取提示信息String message = checkProperty.message();String regexp = checkProperty.regexp();if(checkProperty.notNull()){if(null == arg ) {throw new IllegalArgumentException(String.format("参数[%s]不能为空: %s", argName, message));}}String argStr = String.valueOf(arg);if(checkProperty.notBlank() && type.equals(String.class)){if(StrUtil.isBlank(argStr)){throw new IllegalArgumentException(String.format("参数[%s]不能为空: %s", argName, message));}}if(StrUtil.isNotBlank(regexp)){if(!ReUtil.isMatch(regexp,argStr)){throw new IllegalArgumentException(String.format("参数 [ %s ] :: [ %s ] 不合法,%s",argName,argStr,checkProperty.message()));}}}/*** 环绕通知,可以用来计算方法耗时* @param joinPoint* @throws Exception*///@Around(value = "checkPointCut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {// 定义返回对象、得到方法需要的参数Object[] args = joinPoint.getArgs();TimeInterval timer = DateUtil.timer();Object obj = joinPoint.proceed(args);MethodSignature signature = (MethodSignature) joinPoint.getSignature();String methodName = signature.getDeclaringTypeName() + "." + signature.getName();log.info("----------{}方法开始执行----------",methodName);// 打印耗时的信息methodIntervalMs = timer.intervalMs();log.info("----------{}执行耗时{}ms----------", methodName, methodIntervalMs);log.info("----------{}返回参数----------\n{}", methodName, new Gson().toJson(obj));return obj;}/*** 是否存在注解,如果存在就获取*/private Check getAnnotationCheck(JoinPoint joinPoint) throws Exception {Signature signature = joinPoint.getSignature();MethodSignature methodSignature = (MethodSignature) signature;Method method = methodSignature.getMethod();if (method != null) {return method.getAnnotation(Check.class);}return null;}
}
3. 通用返回类
package net.lesscoding.common;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @author eleven* @date 2022/11/30 12:12* @description*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {private Integer code;private String message;private Object data;
}
4. 测试类
package net.lesscoding.entity;import lombok.Data;
import net.lesscoding.aop.CheckProperty;/*** @author eleven* @date 2022/11/30 10:34* @description*/
@Data
public class TestEntity {@CheckProperty(message = "超过字符串最大长度",regexp = "\\w{0,12}")private String strTest;@CheckProperty(message = "超过最大长度4",regexp = "\\d{0,4}")private Integer intTest;private Double doubleTest;}
5. 正则表达式常量类
package net.lesscoding.consts;/*** @author eleven* @date 2022/11/30 10:38* @description* 相关正则表达式可以参考* https://jex.im/regulex/#!flags=&re=%5E(a%7Cb)*%3F%24* https://regexr.com/*/
public class RegexpConst {/*** 最大长度32*/public static final String STR_MAX_LENGTH_32 = "\\w{0,32}";/*** 整数最大长度4*/public static final String INTEGER_MAX_LENGTH_4 = "\\d{0,4}";/*** 无符号整数最大长度4*/public static final String UNSIGNED_INTEGER_MAX_LENGTH_4 = "-*\\d{0,4}";/*** 两位小数*/public static final String DECIMAL_PLACE_2 = "\\d+.\\d{0,2}";}
4. 测试接口
package net.lesscoding.controller;import net.lesscoding.aop.Check;
import net.lesscoding.aop.CheckProperties;
import net.lesscoding.aop.CheckProperty;
import net.lesscoding.common.Result;
import net.lesscoding.consts.RegexpConst;
import net.lesscoding.entity.TestEntity;
import org.springframework.web.bind.annotation.*;/*** @author eleven* @date 2022/11/30 12:09* @description*/
@RestController
@RequestMapping("/check")
public class CheckTestController {@GetMapping("/inArgs")@Checkpublic Result inArgs(@CheckProperty(message = "长度超过32位",regexp = RegexpConst.STR_MAX_LENGTH_32) @RequestParam("str") String str,@CheckProperty(message = "长度超过4位",regexp = RegexpConst.INTEGER_MAX_LENGTH_4) @RequestParam("index") Integer index){return new Result(200,"success",str + " :: " + index);}@GetMapping("/overMethodsMulti")@Check@CheckProperties(checks = {@CheckProperty(message = "长度超过32位",regexp = RegexpConst.STR_MAX_LENGTH_32,index = 0),@CheckProperty(message = "长度超过4位",regexp = RegexpConst.INTEGER_MAX_LENGTH_4,index = 1)})public Result overMethodsMulti( String str, Integer index){return new Result(200,"success",str + " :: " + index);}@GetMapping("/overMethods")@Check@CheckProperty(message = "长度超过32位",regexp = RegexpConst.STR_MAX_LENGTH_32,index = 0)public Result overMethods( String str,Integer index){return new Result(200,"success",str + " :: " + index);}@GetMapping("/inFields")@Checkpublic Result inFields(TestEntity testEntity){return new Result(200,"success",testEntity);}@PostMapping("/inFields")@Checkpublic Result inFieldsPost(@RequestBody TestEntity testEntity){return new Result(200,"success",testEntity);}
}