JSR303是什么?
JSR303 是一套JavaBean参数校验的标准,它定义了很多常用的校验注解,
我们可以直接将这些注解加在我们JavaBean的属性上面,就可以在需要校验的时候进行校验了。
 <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>
 
@Max(value = 3,message = "超过最大值3")
private Integer appLevel;
 
 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);
    }
}
 
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, "未知错误");
        }
    }
}
 
@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
 
采用一个切面来且所有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;
    }
}
 
拦截器使用request.getReader()获取到请求参数,做校验,但是这种方式获取只能获取一次,所以必须把流再塞回去,否则controller获取不到数据。
流程是:定义拦截器—->注册拦截器—->重新获取数据流—->把数据流塞回请求request
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();
                }
            }
        }
    }
}
 
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new KmsParamValidateInterceptor()).addPathPatterns("/**").order(10);
        super.addInterceptors(registry);
    }
}
 
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()));
    }
}
 
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);
        }
    }
}