您现在的位置是:首页 > 文章详情

springboot统一表单数据校验

日期:2018-08-28点击:454

统一表单数据校验

在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有两个麻烦:

  • 验证代码繁琐,重复劳动
  • 方法内代码显得冗长
  • 每次要看哪些参数验证是否完整,需要去翻阅验证逻辑代码

hibernate validator(官方文档)提供了一套比较完善、便捷的验证实现方式。

spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖。

于是编写了一套校验工具类,并配置了切面做统一的校验,无需在每次手动调用校验方法。

校验工具类 ValidatorUtil

import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.groups.Default; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * @author lism * @date 2018年8月29日10:25:34 * bean 校验工具类 */ public class ValidatorUtil { private static Validator validator = Validation.buildDefaultValidatorFactory() .getValidator(); /** * 校验bean * @param bean * @param <T> * @return */ public static <T> List<ValidateBean> validate(T bean) { Set<ConstraintViolation<T>> constraintViolations = validator.validate(bean, Default.class); return errors2ValidateBeanList(constraintViolations); } /** * 校验属性 * @param bean * @param property * @param <T> * @return */ public static <T> List<ValidateBean> validateProperty(T bean, String property) { Set<ConstraintViolation<T>> constraintViolations = validator.validateProperty(bean, property, Default.class); return errors2ValidateBeanList(constraintViolations); } /** * 校验属性值 * @param bean * @param property * @param propertyValue * @param <T> * @return */ public static <T> List<ValidateBean> validateValue(T bean, String property, Object propertyValue) { Set<? extends ConstraintViolation<?>> constraintViolations = validator.validateValue(bean.getClass(), property, propertyValue, Default.class); return errors2ValidateBeanList(constraintViolations); } private static <T> List<ValidateBean> errors2ValidateBeanList(Set<? extends ConstraintViolation<?>> errors) { List<ValidateBean> validateBeans = new ArrayList<>(); if (errors != null && errors.size() > 0) { for (ConstraintViolation<?> cv : errors) { //这里循环获取错误信息,可以自定义格式 String property = cv.getPropertyPath().toString(); String message = cv.getMessage(); validateBeans.add(new ValidateBean(property, message)); } } return validateBeans; } } 

校验结果辅助类 ValidateBean

import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class ValidateBean { private String property; private String message; @Override public String toString() { final StringBuilder sb = new StringBuilder("{"); sb.append("property='").append(property).append('\''); sb.append(", message='").append(message).append('\''); sb.append('}'); return sb.toString(); } } 

统一数据校验和异常处理切面

import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Arrays; import java.util.List; import java.util.Optional; /** * 统一数据校验和异常处理 * 控制层无需再进行数据校验和异常捕获 * @author lism */ @Aspect @Component @Slf4j @Order(1) public class ValidateBeanAspect { /** * 定义一个切入点 */ @Pointcut("execution(* org.lism..controller..*.*(..))") private void anyMethod() { } @Around("anyMethod()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); if (args.length > 0) { Optional<List<ValidateBean>> optional = Arrays.stream(args).filter(arg -> { return !(arg == null ||arg instanceof HttpServletRequest || arg instanceof HttpServletResponse); }).map(arg -> { return ValidatorUtil.validate(arg); }).filter(validateBeans -> { return validateBeans.size() > 0; }).findFirst(); if (optional.isPresent()) { return new ResponseBean(RTCodeEnum.CODE_FAIL.getCode(), optional.get().toString()); } } try { Object proceed = pjp.proceed(args); if (proceed instanceof ResponseBean) { return proceed; } else { return new ResponseBean(proceed, RTCodeEnum.CODE_200); } } catch (BaseException e) { RequestContextUtil.writeToResponse(new ResponseBean<>(e.getCode(), e.getMessage()).toString()); log.error(e.getMessage()); // return new ResponseBean<>(e.getCode(), e.getMessage()); return null; } catch (Exception e) { log.error(e.getMessage()); RequestContextUtil.writeToResponse(new ResponseBean<>(RTCodeEnum.CODE_FAIL.getCode(), e.getMessage())); // return new ResponseBean<>(RTCodeEnum.CODE_FAIL.getCode(), e.getMessage()); return null; } } @Before("anyMethod()") public void doBefore(JoinPoint pjp) throws Throwable { } @AfterReturning("anyMethod()") public void doAfterReturning(JoinPoint pjp) throws Throwable { } } 

RequestContextUtil工具类

 import com.alibaba.fastjson.JSON; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * RequestContext工具类 * @Author lism * @Date 2018/8/29 17:04 */ public class RequestContextUtil { public static ServletRequestAttributes getRequestAttributes() { return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); } /** * 获取Request * @return */ public static HttpServletRequest getRequest() { //TODO:单元测试的时候,还是会得到requestAttributes,不等于null ServletRequestAttributes requestAttributes = getRequestAttributes(); if (requestAttributes != null) { return requestAttributes.getRequest(); } else { return null; } } /** * 获取Response * @return */ public static HttpServletResponse getResponse() { //TODO:单元测试的时候,还是会得到requestAttributes,不等于null ServletRequestAttributes requestAttributes = getRequestAttributes(); if (requestAttributes != null) { return requestAttributes.getResponse(); } else { return null; } } /** * 获取SessionId * @return */ public static String getSessionId() { //TODO:单元测试的时候,还是会得到requestAttributes,不等于null ServletRequestAttributes requestAttributes = getRequestAttributes(); if (requestAttributes != null) { return requestAttributes.getSessionId(); } else { return null; } } /** * 往前端写数据 * @param object */ public static void writeToResponse(Object object) { PrintWriter writer = null; try { HttpServletResponse response = RequestContextUtil.getResponse(); response.setCharacterEncoding("utf-8"); response.setHeader("Content-type", "text/html;charset=utf-8"); writer = response.getWriter(); writer.write(JSON.toJSONString(object)); writer.flush(); } catch (IOException e) { e.printStackTrace(); }finally { writer.close(); } } } 

返回code 枚举类

import com.alibaba.fastjson.JSONObject; /** */ public enum RTCodeEnum { CODE_OK(0, "OK"), // CODE_DONE(1, "Done"), // CODE_FAIL(-1, "Failed"), CODE_PARAM_ERROR(300, "Input Param Error"), CODE_TOKEN_ERROR(301, "Token Validation Error"), CODE_CAPTCHA_ERROR(302, "验证码错误,请重试"), CODE_DATA_VALIDATE_FAILED(303, "数据校验未通过"), CODE_STATE_EXIST(305, "请勿重复请求"), // Data Issue: 4** CODE_400(400, "服务404"), CODE_DATA_ERROR_PAGETIME_EXPIRE(401, "页面超时不可用,请刷新重试"), // System Service Issue: 5** CODE_SERVICE_NOT_AVAILABLE(500, "系统服务不可用,请联系管理员"), CODE_200(200,"成功"), CODE_401(401,"未登录,需要登录"), CODE_405(405, "权限不足"), CODE_406(406,"客户端请求接口参数不正确或缺少参数"), CODE_501(501,"服务器接口错误"), CODE_999(999,"保留码"); private int code; private String desc; RTCodeEnum(int code, String desc) { this.code = code; this.desc = desc; } public JSONObject toJSON() { JSONObject jsonObject = new JSONObject(); jsonObject.put("code", code); jsonObject.put("desc", desc); return jsonObject; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } } 

ResponseBean工具类

 import com.alibaba.fastjson.annotation.JSONField; import com.google.common.base.MoreObjects; import java.util.Date; /** * Created by lism 2018/8/23. */ public class ResponseBean<T> { private int code = 200; private String msg; private T data; @JSONField(format="yyyy-MM-dd HH:mm:ss") private Date date = new Date(); public static ResponseBean me(Object data){ return new ResponseBean(data); } public ResponseBean(T data) { this.data = data; } public ResponseBean(T data, RTCodeEnum rtCodeEnum) { this.data = data; this.msg = rtCodeEnum.getDesc(); this.code = rtCodeEnum.getCode(); } public ResponseBean(RTCodeEnum rtCodeEnum) { this.msg = rtCodeEnum.getDesc(); this.code = rtCodeEnum.getCode(); } public ResponseBean(T data, int code, String msg) { this.data = data; this.code = code; this.msg = msg; } public ResponseBean(int code, String msg) { this.code = code; this.msg = msg; } public ResponseBean() { } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public T getData() { return data; } public void setData(T data) { this.data = data; } public Date getDate() { return date; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("code", code) .add("msg", msg) .add("data", data) .toString(); } } 

1.校验实体类型参数

编写测试实体

import lombok.Data; import org.hibernate.validator.constraints.Length; import javax.persistence.Entity; import javax.persistence.Table; import javax.validation.constraints.NotEmpty; @Entity @Table(name = "t_subject") @Data public class Subject extends BaseEntityModel { @NotEmpty(message = "专题名不能为空") @Length(min = 1, max = 20, message = "专题名1-20个字符之间") private String name; private String keyword; private String[] docType; private String startTime; private String endTime; } 

测试控制器

@RestController @RequestMapping(value = "/subject") public class SubjectController { @RequestMapping(value = "/", method = RequestMethod.POST, produces = "application/json", consumes = "application/json") @ResponseBody @Override public ResponseBean create(@RequestBody Subject model) { return super.create(model); } 

输入一个空的名子进行测试,可以看到返回结果


v2.png

2.校验RequestParam 类型请求参数

ValidatorUtil方式无法校验RequestParam 请求方法,在BaseController 加上@Validated注解

@RestController @Validated public class BaseController { } 

在子控制器写测试方法

@RestController @RequestMapping(value = "/subject") public class SubjectController extends BaseController { /** * test4 * @return */ @RequestMapping(value = "/test3/", method = RequestMethod.GET, produces = "application/json") @ResponseBody public void test3(HttpServletRequest request, HttpServletResponse response, @Length(min = 1, max = 20, message = "专题名1-20个字符之间") @RequestParam String name) { log.info("test4"); } 

通过测试看到:验证不通过时,抛出了ConstraintViolationException异常,所以我们在ValidateBeanAspect中使用统一的捕获异常是可以捕获到异常,并调用RequestContextUtil.writeToResponse方法将异常写到前台。
测试结果:

v1.png

参考 https://www.cnblogs.com/mr-yang-localhost/p/7812038.html
https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/

原文链接:https://yq.aliyun.com/articles/633430
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章