springboot参数校验的三种方式

springboot对请求参数拦截校验,业务代码可保持纯净。

1.JSR303校验
①导包
 <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
 </dependency>
②在要校验的字段上加上校验注解

例如:(其他注解校验会泽参考另一篇博客JSR303参数校验与commons-lang3的常见验证

@Max(value = 3,message = "超过最大值3")
private Integer appLevel;
③在请求参数处要加@Valid
 public RespResult<Void> add(@RequestBody @Valid AppInfoDTO appInfoDTO){
        return idsAppInfoService.add(appInfoDTO);
    }
④自定义异常捕获

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

@RestControllerAdvice
@Slf4j
public class IDAASExceptionAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public RespResult 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 RespResult.error(ComErrorCode.PARAM_VALID_ERROR.getCode(),message);
    }
}
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

①写一个拦截器,获取参数并校验
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);
    }
}
③ 写一个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()));
    }
}
④写一个过滤器把流塞回去
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);
        }
    }
}
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐