博主
258
258
258
258
专辑

第三十二节 SpringBoot参数校验的三种方式

亮子 2022-09-20 08:38:52 981 0 0 0

1、JSR303校验

  • JSR303是什么?

    JSR303 是一套JavaBean参数校验的标准,它定义了很多常用的校验注解,
    我们可以直接将这些注解加在我们JavaBean的属性上面,就可以在需要校验的时候进行校验了。

1)、导包

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

如果是非SpringBoot项目,需添加下面依赖:

        <!--参数校验-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>2.4.2</version>
        </dependency>

2)、在要校验的字段上加上校验注解

@Max(value = 3,message = "超过最大值3")
private Integer appLevel;

3)、在请求参数处要加@Valid

 public RespResult<Void> add(@RequestBody @Valid AppInfoDTO appInfoDTO){
        return idsAppInfoService.add(appInfoDTO);
    }

使用@Validated注解也是可以的。

package com.shenmazg6.controller;


import com.shenmazg6.service.TbUserService;
import com.shenmazg6.utils.ResultResponse;
import com.shenmazg6.vo.LoginInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;


@RestController
@Slf4j
@RequestMapping(value = "/user")
public class TbUserController {

    @Autowired
    TbUserService tbUserService;


    @PostMapping(value = "/login1")
    public ResultResponse login1(@RequestBody @Valid LoginInfoVo loginInfoVo) {
        return tbUserService.login(loginInfoVo);
    }

    @PostMapping(value = "/login2")
    public ResultResponse login2(@RequestBody @Validated LoginInfoVo loginInfoVo) {
        return tbUserService.login(loginInfoVo);
    }
}

4)、自定义异常捕获

JSR303抛出的异常是MethodArgumentNotValidException,为了格式复合代码风格,自己定义抛出的异常格式
ResultResponse是自己项目的公共返回类(略)

package com.shenma.user.error;

import com.shenma.comms.domain.ResultResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @author 军哥
 * @version 1.0
 * @description: 全局统一异常
 * @date 2022/9/20 17:00
 */

@RestControllerAdvice
@Slf4j
public class GlobalExceptionAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultResponse handleError(MethodArgumentNotValidException e){
        log.warn("Method Argument Not Valid",e);
        BindingResult bindingResult = e.getBindingResult();
        FieldError fieldError = bindingResult.getFieldError();
        String message = String.format("%s",fieldError.getDefaultMessage());
        return ResultResponse.FAILED(400, message);
    }
}
  • 显示多个异常
package com.shenma.user.error;

import com.alibaba.druid.support.json.JSONUtils;
import com.shenma.comms.domain.ResultResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.List;

/**
 * @author 军哥
 * @version 1.0
 * @description: 全局统一异常
 * @date 2022/9/20 17:00
 */

@RestControllerAdvice
@Slf4j
public class GlobalExceptionAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultResponse handleError(MethodArgumentNotValidException e){
        log.warn("Method Argument Not Valid",e);
        BindingResult bindingResult = e.getBindingResult();

        //得到所有错误信息计数
        int errorCount = bindingResult.getErrorCount();
        HashMap<String, String> map = new HashMap<>();
        //错误数大于0
        if (errorCount>0){
            //得到所有错误
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            //迭代错误
            fieldErrors.forEach((fieldError)->{
                //错误信息
                String field = fieldError.getField();
                log.debug("属性:{},传来的值是:{},出错的提示消息:{}",
                        field,fieldError.getRejectedValue(),fieldError.getDefaultMessage());

                map.put(field,fieldError.getDefaultMessage());
            });

            return ResultResponse.FAILED(400, JSONUtils.toJSONString(map));
        }else{
            return ResultResponse.FAILED(500, "未知错误");
        }
    }
}

5)、JSR303参数校验常用注解

@NotNull    	注解元素必须是非空
@Null       	注解元素必须是空
@Digits     	验证数字构成是否合法
@Future     	验证是否在当前系统时间之后
@Past       	验证是否在当前系统时间之前
@Max        	验证值是否小于等于最大指定整数值
@Min        	验证值是否大于等于最小指定整数值
@Pattern    	验证字符串是否匹配指定的正则表达式
@Size       	验证元素大小是否在指定范围内
@DecimalMax  	验证值是否小于等于最大指定小数值
@DecimalMin 	验证值是否大于等于最小指定小数值
@AssertTrue		被注释的元素必须为true
@AssertFalse	被注释的元素必须为false

@Null			用于验证对象为null
@NotNull		用于对象不能为null,无法查检长度为0的字符串
@NotBlank		只用于String类型上,不能为null且trim()之后的size>0
@NotEmpty		用于集合类、String类不能为null,且size>0。但是带有空格的字符串校验不出来
@Size			用于对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length			用于String对象的大小必须在指定的范围内
@Pattern		用于String对象是否符合正则表达式的规则
@Email			用于String对象是否符合邮箱格式
@Min			用于Number和String对象是否大等于指定的值
@Max			用于Number和String对象是否小等于指定的值
@AssertTrue		用于Boolean对象是否为true
@AssertFalse	用于Boolean对象是否为false

2、AOP切面

采用一个切面来且所有controller,这样就可以拿到请求参数,做校验,这里没有定义注解,直接且所有controller

import com.alibaba.fastjson.JSON;
import com.atpingan.sunflower.commonutils.exception.IdaasException;
import com.atpingan.sunflower.commonutils.pingan.enums.ComErrorCode;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import java.util.Map;

@Slf4j
@Aspect
@Component
public class ReqParamValidateAop {

    @Pointcut("execution(* com.atpingan.sunflower.idaas.controller..*.*(..))")
    public void executeControllerAop(){

    }
    @Around("executeControllerAop()")
    public Object doExecuteControllerAop(ProceedingJoinPoint joinPoint) throws Throwable {
        Map<String,Object> map = JSON.parseObject(JSON.toJSONString(joinPoint.getArgs()[0]),Map.class);
        System.out.println(map);
        Integer appLevel = map.get("appLevel")==null?null:(Integer) map.get("appLevel");
        if(appLevel>5){
            throw new IdaasException(ComErrorCode.PARAM_VALID_ERROR.getCode(),ComErrorCode.PARAM_VALID_ERROR.getMessage());
        }
        Object proceed = joinPoint.proceed();
        return proceed;
    }
}

3、拦截器

拦截器使用request.getReader()获取到请求参数,做校验,但是这种方式获取只能获取一次,所以必须把流再塞回去,否则controller获取不到数据。
流程是:定义拦截器—->注册拦截器—->重新获取数据流—->把数据流塞回请求request

1)、写一个拦截器,获取参数并校验

import com.alibaba.fastjson.JSONObject;
import com.atpingan.sunflower.commonutils.exception.IdaasException;
import com.atpingan.sunflower.commonutils.pingan.enums.ComErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Map;

@Component
@Slf4j
public class KmsParamValidateInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Map<String,Object> postReqParam = getPostReqParam(request,Map.class);
        if(null != postReqParam && postReqParam.size() !=0){
        Integer appLevel = postReqParam.get("appLevel")==null?null:(Integer)postReqParam.get("appLevel");
        if(appLevel !=null && appLevel>6){
            throw new IdaasException(ComErrorCode.PARAM_VALID_ERROR.getCode(),ComErrorCode.PARAM_VALID_ERROR.getMessage());
        }
        }
        return true;
    }
    /**
     * 从请求request获取参数
     * @param request
     * @param tClass
     * @param <T>
     * @return
     */
    private <T> T getPostReqParam(HttpServletRequest request, Class<T> tClass) {
        StringBuffer sb = new StringBuffer();
        String line = null;
        BufferedReader br = null;
        try {
            br = request.getReader();
            while (null != (line=br.readLine())){
                sb.append(line);
            }
            String s = sb.toString();
            T t = JSONObject.parseObject(s,tClass);
            return t;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }finally {
            if(br !=null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2)、注册拦截器

@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new KmsParamValidateInterceptor()).addPathPatterns("/**").order(10);
        super.addInterceptors(registry);
    }
}

3)、写一个HttpServletRequestWrapper 获取流

import org.apache.commons.io.IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

public class RequestWrapper extends HttpServletRequestWrapper {

    //参数字节数组
    private byte[] requestBody;
    //Http请求对象
    private HttpServletRequest request;
    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.request = request;
    }
    /**
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        /**
         * 每次调用此方法时将数据流中的数据读取出来,然后再回填到InputStream之中
         * 解决通过@RequestBody和@RequestParam(POST方式)读取一次后控制器拿不到参数问题
         */
        if (null == this.requestBody) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            IOUtils.copy(request.getInputStream(), baos);
            this.requestBody = baos.toByteArray();
        }
        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener listener) {

            }
            @Override
            public int read() {
                return bais.read();
            }
        };
    }
    public byte[] getRequestBody() {
        return requestBody;
    }
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
}

4)、写一个过滤器把流塞回去

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@WebFilter(filterName = "HttpServletRequestFilter",urlPatterns = "/")
@Order(100)
public class HttpServletRequestFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        ServletRequest requestWrapper = null;
        if(request instanceof HttpServletRequest){
            requestWrapper = new RequestWrapper(request);
        }
        if(null == requestWrapper){
            filterChain.doFilter(request,response);
        }else {
            filterChain.doFilter(requestWrapper,response);
        }
    }
}

参考文章