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);
}
}
}