博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

java 请求参数自定义校验注解版

Posted on 2023-03-24 16:54  zachry-r  阅读(994)  评论(0)    收藏  举报

1.为什么使用自定义参数校验

使用自定义参数校验的主要原因是确保应用程序接收到有效和合法的输入数据。在现实世界中,不可避免地会发生输入数据不一致、无效或恶意篡改的情况,这可能会导致应用程序崩溃、缺陷或安全漏洞。
通过使用自定义参数校验,您可以验证应用程序接收到的输入是否符合特定的规则和条件。这样可以防止非法数据进入您的应用程序,减少应用程序崩溃或故障的风险,同时提高应用程序的可靠性和安全性。
此外,自定义参数校验还可以提高应用程序的可维护性。通过在应用程序中定义参数校验规则,您可以轻松地管理和更新这些规则,而无需在每个使用这些规则的地方进行手动检查和处理。
总之,使用自定义参数校验可以提高应用程序的可靠性、安全性和可维护性,从而为最终用户提供更好的体验和价值。

2.参数校验最基本使用

// 验证手机号
public static boolean validatePhone(String phone) {
    if (phone == null || "".equals(phone)) {
        return false;
    }
    return phone.matches("^1[3-9]\\d{9}$");
}

// 验证身份证号
public static boolean validateIDCard(String idCard) {
    if (idCard == null || "".equals(idCard)) {
        return false;
    }
    return idCard.matches("(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)");
}

// 验证验证码
public static boolean validateVerifyCode(String verifyCode) {
    if (verifyCode == null || "".equals(verifyCode)) {
        return false;
    }
    return verifyCode.matches("^\\d{6}$");
}

上述代码中,使用了正则表达式来对手机号、身份证号和验证码进行校验。如果参数为空或不符合正则表达式的格式,则返回false;否则返回true。使用上述方法时,您需要传入要验证的参数,并在返回值为true时表示验证通过,返回值为false时表示验证失败。

使用if进行参数校验的主要缺点是:

  1. 代码重复。在每个需要对参数进行校验的地方都需要编写类似的if语句,这会导致代码冗余和可读性降低。
  2. 难以维护。如果需要对参数校验规则进行修改,那么需要修改多个地方的代码。这会增加维护的难度,并且容易出错。
  3. 风格不统一。使用if进行参数校验的代码风格不统一,可能导致在不同的地方出现不同的参数校验规则,从而影响代码的可维护性和可读性。
  4. 可能会漏掉某些异常情况。如果编写的if语句不全面或有漏洞,可能会导致一些异常情况未被检测到,从而导致安全漏洞或错误发生。

相比而言,使用Java的参数校验框架(例如Bean Validation)可以有效地解决上述问题。使用框架可以提供更为简洁和统一的代码风格,并且能够实现更全面、更精确的参数校验,提高代码的可维护性和可读性。

但是对于像手机号,验证码,邮箱。需要单独验证的时候。难道还要一个个的去写if判断吗?

3.采用注解方式实现请求参数的校验

1. 定义自定义注解

在需要进行参数校验的方法上定义注解,并设置需要校验的参数类型、校验策略等属性,例如:

package com.hmdp.annotation;

import java.lang.annotation.*;

/**
 * The interface General constraints.
 *
 * @author zzz
 * @pack_name com.demo.annotation
 * @date 2023 /3/24 09:03:59
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface GeneralConstraints {
    String value() default "";
}

value 为校验参数的名字

2. 定义校验策略接口

定义一个校验策略接口,该接口定义了参数校验的方法:

package com.hmdp.annotation.validator;


/**
 * @author zzz
 * @date 2023/3/24 10:03:03
 */
public interface ParamCheckStrategy<T>   {
    boolean isValid(String value);


}

3. 实现校验策略

实现校验策略接口,并实现校验方法,例如:

  1. 正则匹配规则
package com.demo.utils;

/**
 */
public abstract class RegexPatterns {
    /**
     * 手机号正则
     */
    public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
    /**
     * 邮箱正则
     */
    public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";
    /**
     * 密码正则。4~32位的字母、数字、下划线
     */
    public static final String PASSWORD_REGEX = "^\\w{4,32}$";
    /**
     * 验证码正则, 6位数字或字母
     */
    public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$";

    /**
     * 身份证正则, 18位数字或字母
     */
    public static final String VERIFY_CARD_REGEX = "^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$|^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)$";

}

  1. 正则匹配规则
package com.demo.utils;

import cn.hutool.core.util.StrUtil;

/**
 * @author
 */
public class RegexUtils {
    /**
     * 是否是无效手机格式
     * @param phone 要校验的手机号
     * @return true:符合,false:不符合
     */
    public static boolean isPhoneInvalid(String phone){
        return mismatch(phone, RegexPatterns.PHONE_REGEX);
    }
    /**
     * 是否是无效邮箱格式
     * @param email 要校验的邮箱
     * @return true:符合,false:不符合
     */
    public static boolean isEmailInvalid(String email){
        return mismatch(email, RegexPatterns.EMAIL_REGEX);
    }

    /**
     * 是否是无效验证码格式
     * @param code 要校验的验证码
     * @return true:符合,false:不符合
     */
    public static boolean isCodeInvalid(String code){
        return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);
    }

    public static boolean isCardInvalid(String code){
        return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);
    }

    // 校验是否不符合正则格式
    private static boolean mismatch(String str, String regex){
        if (StrUtil.isBlank(str)) {
            return false;
        }
        return str.matches(regex);
    }
}

不同校验规则实现

package com.demo.annotation.validator.impl;

import com.demo.annotation.validator.ParamCheckStrategy;
import com.demo.utils.RegexUtils;

/**
 * @author zzz
 * @description 手机号校验
 * @date 2023/3/24 11:03:34
 */
public class PhoneNumberValidator implements ParamCheckStrategy<String> {
    @Override
    public boolean isValid(String param) {
        return RegexUtils.isPhoneInvalid(param);
    }
}

package com.demo.annotation.validator.impl;

import com.demo.annotation.validator.ParamCheckStrategy;
import com.demo.utils.RegexUtils;

/**
 * @author zzz
 * 身份证校验
 * @date 2023/3/24 11:03:34
 */
public class IdCardValidator implements ParamCheckStrategy<String> {
    @Override
    public boolean isValid(String input) {
        return RegexUtils.isCardInvalid(input);
    }
}


package com.demo.annotation.validator.impl;

import com.demo.annotation.validator.ParamCheckStrategy;
import com.demo.utils.RegexUtils;

/**
 * @author zzz
 * 验证码校验
 * @date 2023/3/24 11:03:34
 */
public class CodeNumberValidator implements ParamCheckStrategy<String> {
    @Override
    public boolean isValid(String input) {
        return RegexUtils.isCodeInvalid(input);
    }
}


4. 定义切面

定义一个切面,使用 @Around 注解拦截带有 @GeneralConstraints� 注解的方法,对其参数进行校验,例如:

package com.demo.annotation.aspect;

import com.demo.annotation.ParamCheckUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author zzz
 * @pack_name com.hmdp.annotation.aspect
 * @project_name hm-dianping
 * @date 2023/3/24 15:03:59
 */
@Aspect
@Component
public class ParamCheckAspect {

    //定义切点
    @Pointcut("execution(* com.hmdp.controller.*.*(..))")
    public void checkPointcut(){}

    // 定义环绕通知
    @Around("checkPointcut()")
    public Object checkParam(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        ParamCheckUtil.check(args, method);
        return joinPoint.proceed();
    }
}

参数检查工具类

package com.demo.annotation;

import com.demo.annotation.exception.ParamVerifyException;
import com.demo.annotation.validator.ParamCheckStrategy;
import com.demo.annotation.validator.impl.CodeNumberValidator;
import com.demo.annotation.validator.impl.IdCardValidator;
import com.demo.annotation.validator.impl.PhoneNumberValidator;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @author zzz
 * @date 2023/3/24 15:03:38
 */
public class ParamCheckUtil {
    private static final Map<String, ParamCheckStrategy<?>> STRATEGY_MAP = new HashMap<>();

    static{
        // 注册手机号校验策略
        STRATEGY_MAP.put("phone", new PhoneNumberValidator());
        // 注册身份证号校验策略
        STRATEGY_MAP.put("idCard", new IdCardValidator());
        // 注册验证码校验策略
        STRATEGY_MAP.put("code", new CodeNumberValidator());
    }

    public static void check(Object[] args, Method method){
        Annotation[][] annotations = method.getParameterAnnotations();
        for (int i = 0; i < annotations.length; i++) {
            Annotation[] annotation = annotations[i];
            for (Annotation annotation1 : annotation) {
                if (annotation1 instanceof GeneralConstraints){
                    GeneralConstraints generalConstraints = (GeneralConstraints) annotation1;
                    String strategyName = generalConstraints.value();

                    ParamCheckStrategy<?> strategy = STRATEGY_MAP.get(strategyName);
                    if (strategy == null) {
                        throw new ParamVerifyException("Parameter unexpected: " + strategyName);
                    }
                    Object arg = args[i];
                    if (!strategy.isValid(arg.toString())){
                        throw new ParamVerifyException(strategyName +"Parameter Verify Fail: " + arg);
                    }
                }
            }
        }
    }
}

此处的 ParamVerifyException 为自定义异常类

package com.demo.annotation.exception;

/**
 * @author zzz
 * @Description 参数校验异常
 * @date 2023/3/24 13:03:47
 */
public class ParamVerifyException extends RuntimeException {

    public ParamVerifyException(String msg){
        super(msg);
    }
}

5. 在方法上添加注解

在需要校验参数的方法的参数上添加 @GeneralConstraints 注解,例如:

@PostMapping("code")
    public Result sendCode( @RequestParam("phone")  @GeneralConstraints(value = "phone") String phone,
                           HttpSession session) throws NoSuchMethodException {
        //  发送短信验证码并保存验证码

        return Result.fail("功能未完成");
    }

4 .另外使用 切面技术需要导入并开启切面功能

  1. 在spingboot上开启切面功能

@EnableAspectJAutoProxy�

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

到此一个自定义的参数校验就完成了,下面是测试结果

  1. 当输入手机号为空,或者 不满足手机号校验规则时

image.png
image.png

  1. 手机号输入正确则没有异常,校验通过