Java优雅的实现参数校验

参数校验的几种方式

这里推荐几种方式,可用于参数校验,接口鉴权,令牌续命,版本号兼容过滤

一、基于AOP环绕通知

aop/切面类

/**
 * @Description 基于AOP的环绕通知 控制层的方法进行匹配 校验全部参数
 * @author kz
 * @date 2022/10/17
 */
@Component //IOC创建容器
@Aspect  //切面
public class ParamAspect {
    // 切入点                                     类 方法 参数
     语法格式:execution(* 包名.类名.⽅法名(..))
    @Pointcut("execution(* com.auth.api.controller.*.*(..))")
    public void pt(){}

    @Around("pt()")
    public Object handler(ProceedingJoinPoint point)throws Throwable{
        //1.获取当前方法上的参数
        Object[] args = point.getArgs();
        //2.遍历循环,进行校验参数
        for (Object o:args){
            if (Objects.isNull(o)){
                //空参,失败
                return R.fail("参数为空");
            }
        }
        //3.执行原来的方法
        return point.proceed();
    }
}

二、基于注解+拦截器实现

2.0 参数校验

1.annotation/注解类

/**自定义注解 标记是否进行参数校验,修饰方法*/
@Retention(RetentionPolicy.RUNTIME)//定义注解运行时有效性
@Target(value = ElementType.METHOD)//定义注解的使用范围
public @interface CheckParam {
}

2.拦截器

/**
 * 基于注解+拦截器,实现参数的校验
 * @author kz
 * @date 2022/10/17
 */
public class ParamInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取当前执行的方法
        if(handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            //2.获取方法上的自定义注解
            if (method.hasMethodAnnotation(CheckParam.class)) {
                //3.存在 获取参数 进行校验
                Map<String, String[]> params = request.getParameterMap();
                //     该方法返回map中所有key值的列表
                for (String s : params.keySet()) {
                    if (params.get(s)[0] == null) {
                        System.err.println(s);
                        //判断第一个值 就可以 如果为空就拦截处理
                        //校验不通过拦截
                        response.setContentType("application/json;charset=UTF-8");
                        response.getWriter().println(JSON.toJSONString(R.fail()));
                        return false;
                    }
                }
            }
        }
        //校验通过放行
        return true;
    }
}

其中一个属性有值即可放行
image-20230316231406026
一般都会加在消费者的controller层上,只要有该注解,就会校验参数。
注意:添加拦截器 springmvc拦截器

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    /**添加拦截器*/
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new ParamInterceptor()).addPathPatterns("/**");
        registry.addInterceptor(new TokenInterceptor()).addPathPatterns("/**");
    }
}

通过自定义注解进行标记,在拦截器中使用反射获取方法上是否有该注解。

2.1 接口鉴权

/**
 * 基于注解+拦截器,实现登录之后才可访问的接口
 * @author kz
 * @date 2022/10/17
 */
public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取当前执行的方法
        if(handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            //2.获取方法上的自定义注解
            if (method.hasMethodAnnotation(Token.class)) {
                //3.存在注解。获取令牌,进行校验真伪 获取传递的令牌
                String token = request.getHeader(SystemConfig.HEADER_TOKEN);
                System.err.println(token);
                //验证令牌是否粗存在              当时登录传入的令牌
                if (JedisUtil.exists(RedisKeyConfig.LOGIN_PHONE + token)) {
                    //有效放行
                    return true;
                } else {
                    response.setContentType("application/json;charset=UTF-8");
                    response.getWriter().write(JSON.toJSONString(R.fail("亲,登录已失效!")));
                    return false;
                }
            }
        }
        //没有包含指定注解,通过放行
        return true;
    }
}

2.3实现令牌续命

/**
 * 令牌续命
 * @author kz
 * @date 2022/10/17
 */
@Component                        //全局
public class TokenFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request= exchange.getRequest();
        ServerHttpResponse response=exchange.getResponse();
        //1.获取令牌
        List<String> tokens = request.getHeaders().get(SystemConfig.HEADER_TOKEN);
        if (Objects.nonNull(tokens) && tokens.size()>0){
            String token = tokens.get(0);
            //2.验证令牌是否存在
            if (token!=null && token.length()>0){
                if(JedisUtil.exists(RedisKeyConfig.LOGIN_TOKEN+token)){
                    //3.存在,就续命
                    JedisUtil.expire(RedisKeyConfig.LOGIN_TOKEN+token,RedisKeyConfig.LOGIN_PHONE_TIME);
                }
            }
        }
        return chain.filter(exchange);
    }
}

image-20230316231315154

网关中配置filter提高效率

2.4实现版本号的兼容性过滤

/**
 * 接口的版本号自动校验
 * @author kz
 * @date 2022/10/17
 */
@Component //IOC创建对象           Global全局
public class VersionFilter implements GlobalFilter {
    private Logger logger= LoggerFactory.getLogger(VersionFilter.class);
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //实现对接口的请求.do结尾。需要携带请求消息头--版本号,没有或不匹配进行拦截
        //1.获取请求对象和响应对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //2.获取请求路径
        String url = request.getPath().value();
        //3.验证接口是否以.do结尾
        if (url.contains(".do")){
            //4.获取请求的消息头                                   //版本号常量
            List<String> strings = request.getHeaders().get(SystemConfig.HEADER_VERSION);
            if (Objects.nonNull(strings) && strings.size()>0){
                //拿到版本号
                String version = strings.get(0);
                if (Objects.nonNull(version) && version.length()>0){
                //5.验证版本号是否为空,空拦截
                if (JedisUtil.existsSet(RedisKeyConfig.VERSIONS,version)){
                    //有效 放行
                    return chain.filter(exchange);
                }
            }
        }
            //6.拦截请求
            response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
            //拦截并返回自定义内容
            return response.writeWith(
                    Mono.just(response.bufferFactory().
                            wrap(JSON.toJSONString(R.fail("请传递接口的版本号!")).getBytes())));
    }
        return chain.filter(exchange);
    }
}

image-20230316231244317

三、Validator(JSR303校验)

官方文档:https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#validator-gettingstarted-createmodel

3.1 JSR303介绍

JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。 JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint(约束) 的实现,除此之外还有一些附加的 constraint。

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

实际上,spring-boot-starter-validation依赖主要是为了引入下面这个依赖:

<dependency>
  <groupId>org.hibernate.validator</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>6.2.4.Final</version>
  <scope>compile</scope>
</dependency>

3.2 常用注解

空检查**** ****
@Null验证对象是否为null
@NotNull验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty检查约束元素是否为NULL或者是EMPTY.
Booelan检查**** ****
@AssertTrue验证 Boolean 对象是否为 true
@AssertFalse验证 Boolean 对象是否为 false
长度检查**** ****
@Size(min=, max=)验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=)Validates that the annotated string is between min and max included.
日期检查**** ****
@Past验证 Date 和 Calendar 对象是否在当前时间之前
@Future验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern验证 String 对象是否符合正则表达式的规则
数值检查建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min验证 Number 和 String 对象是否大等于指定的值
@Max验证 Number 和 String 对象是否小等于指定的值
@DecimalMax被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=)验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=)检查数字是否介于min和max之间.
@Range(min=10000,max=50000,message=“range.bean.wage”)private BigDecimal wage;
@Valid递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)

3.3 基本演示

对实体类增加注解

@Data
public class UserDetailUpdateDto {
    @NotBlank  //非空
    private String nickname;
    private Date birthday;
    @Range(min = 1,max = 2) //数值
    private int sex;
    @NotBlank //非空
    private String info;
}

控制层参数添加 @Validated 注解,实现参数校验

    /**修改用户详情*/
    @PostMapping("update.do")                                                   //   全局异常处理器 jsr303校验
    public R update(@RequestBody @Validated UserDetailUpdateDto dto, HttpServletRequest request, BindingResult result){
//        //原来
//        if(result.getFieldErrorCount()>0){
//            //参数有误
//            return R.fail(result.getFieldErrors());
//        }
        //统一异常处理
       return service.update(dto,request.getIntHeader(SystemConfig.HEADER_TOKEN));
    }

统⼀异常处理器捕获异常

//全局异常处理器 配合jsr303校验                       //直接对controller层进行jsr303校验
@RestControllerAdvice(basePackages = "com.kou.user.controller")
public class ControllerExceptionAdvice {
      private Logger logger= LoggerFactory.getLogger(ControllerExceptionAdvice.class);
      //具体的某个异常类型处理
       @ExceptionHandler(MethodArgumentNotValidException.class)
       public R handleValidException(MethodArgumentNotValidException e){
       logger.error("数据异常:{},异常类型:{}",e.getMessage(),e.getClass());
       BindingResult result=e.getBindingResult();
       Map<String,String> map=new HashMap();
       for (FieldError fieldError:result.getFieldErrors()){
           map.put(fieldError.getDefaultMessage(),fieldError.getField());
       }
       return R.fail(map);
    }
    //抛出的所有异常类型处理
    @ExceptionHandler(Throwable.class)
    public R handleException(Throwable e){
           e.printStackTrace();
           //TODO 处理逻辑
        return R.fail();
    }
}

3.4 嵌套问题

It appears that you are having an issue with JSR303 validation not working in your code. From the code block you provided, it seems that you are using the @NotNull annotation to validate certain fields in your MatiMasEntity class. However, it’s unclear from the code block whether you have properly configured JSR303 validation in your project.

To enable JSR303 validation, you need to make sure that you have the necessary dependencies in your project and that you have configured a validator. In a Spring Boot project, for example, you can simply add the spring-boot-starter-validation dependency to your pom.xml file and Spring Boot will automatically configure a validator for you.

Assuming that you have properly configured JSR303 validation in your project, it’s possible that the validation is failing because the @Valid annotation is missing from the MatiDto class. The @Valid annotation is used to validate nested objects, so if you don’t include it, the validation for the MatiMasEntity object may not be triggered.

Here’s an example of how you can use the @Valid annotation to validate the MatiMasEntity object in your MatiDto class:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MatiDto {
@Valid
private MatiMasEntity matiMasEntity;
private List<MatiItemEntity> itemEntityList;
}

By adding the @Valid annotation to the matiMasEntity field, you are telling JSR303 to validate the MatiMasEntity object when validating the MatiDto object.

四、El表达式(扩展)

<!--        el 表达式 juel-->
        <dependency>
            <groupId>de.odysseus.juel</groupId>
            <artifactId>juel</artifactId>
            <version>2.1.3</version>
        </dependency>
package com.kw.springcodesudy.util;
import de.odysseus.el.util.SimpleContext;
import org.apache.el.ExpressionFactoryImpl;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import java.util.HashMap;
import java.util.Map;

public class ConditionElUtil  {

    public static Boolean checkFormDataByRuleEl(String el, Map<String, Object> formData) throws Exception {
        ExpressionFactory factory = new ExpressionFactoryImpl();
        SimpleContext context = new SimpleContext();
        for (Object k : formData.keySet()) {
            if (formData.get(k) != null) {
                context.setVariable(k.toString(), factory.createValueExpression(formData.get(k), formData.get(k).getClass()));
            }
        }
        ValueExpression e = factory.createValueExpression(context, el, Boolean.class);
        return (Boolean) e.getValue(context);
    }

    public static void main(String[] args) throws Exception {
        String el = "${请假天数>3.5 && 部门 == '产品部'}";
        Map<String, Object> formData = new HashMap<>();
        formData.put("请假天数",3.6);
        formData.put("部门", "产品部");
        System.out.println(checkFormDataByRuleEl(el, formData));
    }
}

  • 31
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java中的自定义注解是一种给代码提供额外信息的方式,可以在运行时通过反射机制获取注解的信息。通过自定义注解,我们可以实现参数校验,提高代码的健壮性和可维护性。 首先,我们需要定义一个注解类,用于定义参数校验的规则。比如,我们可以定义一个注解叫做@ParamCheck,用于对方法的参数进行校验。 ```java @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface ParamCheck { String value(); } ``` 接着,在需要进行参数校验的方法上使用@ParamCheck注解,并给注解传入校验规则的表达式。比如,我们可以给一个名为checkNumber的方法的参数添加校验注解。 ```java public void checkNumber(@ParamCheck("number > 0") int number) { // ... } ``` 然后,在方法内部,通过反射机制获取参数的注解信息,并根据注解中定义的校验规则对参数进行校验。 ```java public void checkNumber(int number) { Parameter parameter = ...; // 获取方法的参数信息 ParamCheck paramCheckAnnotation = parameter.getAnnotation(ParamCheck.class); if (paramCheckAnnotation != null) { String expression = paramCheckAnnotation.value(); // 根据expression对number进行校验 // ... } } ``` 最后,我们可以在调用checkNumber方法时传入一个不满足校验规则的参数,比如-10,当方法内部进行参数校验时,可以捕获到校验失败的情况,并进行相应处理。 ```java checkNumber(-10); // 参数校验失败,抛出异常或者进行其他处理 ``` 通过自定义注解实现参数校验可以方便地对代码进行统一的校验规则管理,提高代码的可维护性和可读性。同时,由于注解是在运行时通过反射获取,可以对代码进行动态改变和扩展,使得我们可以更加灵活地进行参数校验
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kkkouz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值