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强转为MethodSignature
MethodSignature 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")
@Check
public 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")
@Check
public Result inFields(TestEntity testEntity){
return new Result(200,"success",testEntity);
}
@PostMapping("/inFields")
@Check
public Result inFieldsPost(@RequestBody TestEntity testEntity){
return new Result(200,"success",testEntity);
}
}