还在用if else进行数据校验?一招教你轻松搞定后端数据校验(对象、集合)
1、为什么要做数据校验?
对于任何一个应用而言,客户端做的数据有效性验证都不是安全有效的,而数据验证又是一个企业级项目架构上最为基础的功能模块,这时候就要求我们在服务端接收到数据的时候也对数据的有效性进行验证。为什么这么说呢?往往我们在编写程序的时候都会感觉后台的验证无关紧要,毕竟客户端已经做过验证了,后端没必要在浪费资源对数据进行验证了,但恰恰是这种思维最为容易被别人钻空子。毕竟只要有点开发经验的都知道,我们完全可以模拟 HTTP 请求到后台地址,模拟请求过程中发送一些涉及系统安全的数据到后台,后果可想而知…
2、如何利用SpringBoot如何轻松搞定数据校验
1、导入依赖(在 pom.xml 中添加上 spring-boot-starter-web 的依赖即可)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2、改造实体类(验证的属性上加上验证规则注解)
public class WfylTemplate extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键 */
private Long id;
/** 模板名称 */
@NotEmpty(message = "模板名称不能为空")
@Size(min = 1, max = 100,message = "长度在20-100")
private String templateName;
/** 表名 */
@NotEmpty(message = "表名称不能为空")
@Size(min = 1, max = 30,message = "长度在1-30")
@Pattern(regexp = "(^_([a-zA-Z0-9]_?)*$)|(^[a-zA-Z](_?[a-zA-Z0-9])*_?$)", message = "表名格式错误")
private String tableName;
}
3、改造控制器(需要验证的类上加上注解:@Validated)
BusinessType.INSERT)
@PostMapping
public AjaxResult add( @RequestBody @Validated WfylTemplate wfylTemplate)
{
return toAjax(wfylTemplateService.insertWfylTemplate(wfylTemplate));
}
4、全局异常捕获器
@RestControllerAdvice
public class GlobalExceptionHandler
{
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(BindException.class)
public AjaxResult validatedBindException(BindException e)
{
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return AjaxResult.error(message);
}
@ExceptionHandler(value = ConstraintViolationException.class)
public AjaxResult handleMethodArgumentNotValidException(ConstraintViolationException ex) {
Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
for (ConstraintViolation<?> constraintViolation : constraintViolations) {
PathImpl pathImpl = (PathImpl) constraintViolation.getPropertyPath();
// 读取参数字段,constraintViolation.getMessage() 读取验证注解中的message值
String paramName = pathImpl.getLeafNode().getName();
String message = "参数{".concat(paramName).concat("}").concat(constraintViolation.getMessage());
return AjaxResult.error(message);
}
return AjaxResult.error(ex.getMessage());
}
}
5、接口与测试
如何对集合里面的对象进行校验?
踩坑了,踩坑了,按照上面的方式对集合里面的对象进行验证,不用多想,肯定是不行的,不信?你们自己踩坑去。问题来了,怎么解锁验证的新姿势?
1、实体类改造(加上验证注解)
public class WfylTemplateFiled extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键 */
private Long id;
/** 模板id */
private Long templateId;
/** 字段名称 */
@NotEmpty(message = "表名称不能为空")
@Pattern(regexp = "(^_([a-zA-Z0-9]_?)*$)|(^[a-zA-Z](_?[a-zA-Z0-9])*_?$)", message = "字段名称格式错误")
private String filedName;
/** 字段描述 */
private String filedDesc;
/** 字段类型 */
@NotEmpty(message = "字段类型不能为空")
private String filedType;
/** 字段长度 */
@Min(1)
@Max(255)
private Integer filedLength;
}
2、新增VO类,在集合上加上注解:@Valid
public class WfylTemplateVO implements Serializable
{
@Valid
private List<WfylTemplateFiled> wfylTemplateFileds ;
}
3、改造Controller
@PostMapping("{templateId}")
public AjaxResult add(@RequestBody @Validated WfylTemplateVO wfylTemplateFileds, @PathVariable Long templateId)
{
return toAjax(wfylTemplateFiledService.insertWfylTemplateFiledList(wfylTemplateFileds.getWfylTemplateFileds(),templateId));
}
4、测试
{
"wfylTemplateFileds":[
{
"filedName":"",
"filedDesc":"名称",
"filedType":"varchar",
"filedLength":255,
"isList":0,
"isQuery":0,
"isDetail":0,
"isAdd":0,
"queryType":"like",
"htmlType":0,
"dictType":"",
"sort":0
},
{
"filedName":"sex",
"filedDesc":"性别",
"filedType":"varchar",
"filedLength":1,
"isList":0,
"isQuery":0,
"isDetail":0,
"isAdd":0,
"queryType":"=",
"htmlType":0,
"dictType":"",
"sort":2
},{
"filedName":"phone",
"filedDesc":"手机号",
"filedType":"bigint",
"filedLength":11,
"isList":0,
"isQuery":0,
"isDetail":0,
"isAdd":0,
"queryType":"=",
"htmlType":0,
"dictType":"",
"sort":3
},
{
"filedName":"number",
"filedDesc":"数量",
"filedType":"int",
"filedLength":8,
"isList":0,
"isQuery":0,
"isDetail":0,
"isAdd":0,
"queryType":"between",
"htmlType":0,
"dictType":"",
"sort":4
},
{
"filedName":"test",
"filedDesc":"篮球",
"filedType":"int",
"filedLength":8,
"isList":0,
"isQuery":0,
"isDetail":0,
"isAdd":0,
"queryType":"!=",
"htmlType":0,
"dictType":"",
"sort":4
},
{
"filedName":"test1",
"filedDesc":"篮球",
"filedType":"int",
"filedLength":8,
"isList":0,
"isQuery":0,
"isDetail":0,
"isAdd":0,
"queryType":">",
"htmlType":0,
"dictType":"",
"sort":4
},
{
"filedName":"test2",
"filedDesc":"篮球",
"filedType":"int",
"filedLength":8,
"isList":0,
"isQuery":0,
"isDetail":0,
"isAdd":0,
"queryType":">=",
"htmlType":0,
"dictType":"",
"sort":4
},
{
"filedName":"test3",
"filedDesc":"篮球",
"filedType":"int",
"filedLength":8,
"isList":0,
"isQuery":0,
"isDetail":0,
"isAdd":0,
"queryType":"<",
"htmlType":0,
"dictType":"",
"sort":4
},
{
"filedName":"test4",
"filedDesc":"篮球",
"filedType":"int",
"filedLength":8,
"isList":0,
"isQuery":0,
"isDetail":0,
"isAdd":0,
"queryType":"<=",
"htmlType":0,
"dictType":"",
"sort":4
}
]
}
小插曲-在捕获异常的时候发生了栈溢出???
/*异常信息*/
14:02:52.329 [http-nio-8080-exec-5] ERROR c.r.f.w.e.GlobalExceptionHandler - [handleException,83] - Handler dispatch failed; nested exception is java.lang.StackOverflowError
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.StackOverflowError
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1053)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:124)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.ruoyi.common.filter.RepeatableFilter.doFilter(RepeatableFilter.java:39)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter.doFilterInternal(JwtAuthenticationTokenFilter.java:42)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:96)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:74)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:791)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1417)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.StackOverflowError: null
at com.alibaba.fastjson.serializer.SerializeConfig.getObjectWriter(SerializeConfig.java:446)
at com.alibaba.fastjson.serializer.SerializeConfig.getObjectWriter(SerializeConfig.java:442)
at com.alibaba.fastjson.JSON.toJSON(JSON.java:1180)
at com.alibaba.fastjson.JSON.toJSON(JSON.java:1188)
at com.alibaba.fastjson.JSON.toJSON(JSON.java:1131)
at com.alibaba.fastjson.JSON.toJSON(JSON.java:1188)
at com.alibaba.fastjson.JSON.toJSON(JSON.java:1131)
at com.alibaba.fastjson.JSON.toJSON(JSON.java:1188)
at com.alibaba.fastjson.JSON.toJSON(JSON.java:1131)
at com.alibaba.fastjson.JSON.toJSON(JSON.java:1188)
at com.alibaba.fastjson.JSON.toJSON(JSON.java:1131)
at com.alibaba.fastjson.JSON.toJSON(JSON.java:1188)
at com.alibaba.fastjson.JSON.toJSON(JSON.java:1131)
at com.alibaba.fastjson.JSON.toJSON(JSON.java:1188)
/**出事代码*/
@PostMapping
public AjaxResult add( @RequestBody @Validated WfylTemplate wfylTemplate,BindingResult bindingResult)
{
if (bindingResult.hasErrors()){
return AjaxResult.error();
}
return toAjax(wfylTemplateService.insertWfylTemplate(wfylTemplate));
}
Bug还原:之前的代码,我在控制器上注入了BindingResult ,获取异常信息,然后就发生解析Json栈溢出,我用debug 跟踪toJson方法,发现一直循环解析wfylTemplate和bindingResult,一脸的疑惑,百度也没有出现类似的问题,难道是框架之前兼容性的问题??
后面发现,我在全局异常里面已经捕获了BindException.class(没注意到),随后,我移除了一个,解析成功,没有溢出。草率了,粗心大意惹的祸。
hibernate validator还有很多好玩的,比如分组校验,这些还需要再慢慢学习摸索,和大家一起进步吧!