自定义正则表达式校验注解

21 篇文章 1 订阅
15 篇文章 1 订阅

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注解 ,则进行下边的操作

  1. 首先校验方法参数里是否有@CheckProperty注解,有的话则校验相关参数是否符合要求
  2. 校验方法上是否有@CheckProperties注解,有的话按个校验里边的@CheckProperty注解判断参数是否符合正则表达式(适用于一个接口有好多个参数的情况
  3. 如果方法上没有@CheckProperties注解,但是有@CheckProperty注解,校验相关的参数是否符合
  4. 方法上只有@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);
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值