美文网首页Spring 学习spring bootSpring Boot
springboot统一表单数据校验

springboot统一表单数据校验

作者: LI木水 | 来源:发表于2018-08-29 18:04 被阅读493次

    统一表单数据校验

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

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

    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/

    相关文章

      网友评论

        本文标题:springboot统一表单数据校验

        本文链接:https://www.haomeiwen.com/subject/zjdkwftx.html