您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

Spring Boot 优雅地实现接口参数校验

时间:2020-11-19 11:02:16  来源:  作者:

今天继续为大家分享在工作中如何优雅的校验接口的参数的合法性以及如何统一处理接口返回的json格式。每个字都是干货,原创不易,分享不易。

Spring Boot 优雅地实现接口参数校验

 

validation主要是校验用户提交的数据的合法性,比如是否为空,密码是否符合规则,邮箱格式是否正确等等,校验框架比较多,用的比较多的是hibernate-validator, 也支持国际化,也可以自定义校验类型的注解,这里只是简单地演示校验框架在Spring Boot中的简单集成,要想了解更多可以参考 hibernate-validator。

1. pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2. dto

public class UserInfoIDto {

    private Long id;

    @NotBlank
    @Length(min=3, max=10)
    private String username;

    @NotBlank
    @Email
    private String email;

    @NotBlank
    @Pattern(regexp="^((13[0-9])|(15[^4,\D])|(18[0,3-9]))\d{8}$", message="手机号格式不正确")
    private String phone;

    @Min(value=18)
    @Max(value = 200)
    private int age;

    @NotBlank
    @Length(min=6, max=12, message="昵称长度为6到12位")
    private String nickname;

     // Getter & Setter
}

3. controller

import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;

@RestController
public class SimpleController {

    @PostMApping("/users")
    public String register(@Valid @RequestBody UserInfoIDto userInfoIDto, BindingResult result){
        if (result.hasErrors()) {
            FieldError fieldError = result.getFieldError();
            String field = fieldError.getField();
            String msg = fieldError.getDefaultMessage();

            return field + ":" + msg;
        }
        System.out.println("开始注册用户...");

        return "success";
    }
}
Spring Boot 优雅地实现接口参数校验

 

4. 去掉BindingResult参数

每个接口都需要BindingResult参数,而且每个接口都需要处理错误信息,这样增加一个参数也不优雅,处理错误信息代码量也很重复。如果去掉BindingResult参数,系统就会报错MethodArgumentNotValidException,我们只需要使用全局异常来捕获该错误,处理一下就可以省略传BindingResult参数了。

@RestController
public class SimpleController {

    @PostMapping("/users")
    public String register(@Valid @RequestBody UserInfoIDto userInfoIDto){
        System.out.println("开始注册用户...");
        return "success";
    }
}

@RestControllerAdvice 用于拦截所有的@RestController

@RestControllerAdvice
public class GlobalExceptionHandlerAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public String methodArgumentNotValidException(MethodArgumentNotValidException e) {
        // 从异常对象中拿到ObjectError对象
        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
        // 然后提取错误提示信息进行返回
        return objectError.getDefaultMessage();
    }
}
Spring Boot 优雅地实现接口参数校验

 

5. 统一返回格式

错误码枚举

@Getter
public enum ErrorCodeEnum {
    SUCCESS(1000, "成功"),
    FAILED(1001, "响应失败"),
    VALIDATE_FAILED(1002, "参数校验失败"),
    ERROR(5000, "未知错误");

    private Integer code;
    private String msg;

    ErrorCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

自定义异常。

@Getter
public class APIException extends RuntimeException {
    private int code;
    private String msg;

    public APIException(ErrorCodeEnum errorCodeEnum) {
        super(errorCodeEnum.getMsg());
        this.code = errorCodeEnum.getCode();
        this.msg = errorCodeEnum.getMsg();
    }
}

定义返回格式。

@Getter
public class Response<T> {
    /**
     * 状态码,比如1000代表响应成功
     */
    private int code;

    /**
     * 响应信息,用来说明响应情况
     */
    private String msg;

    /**
     * 响应的具体数据
     */
    private T data;


    public Response(T data) {
        this.code = ErrorCodeEnum.SUCCESS.getCode();
        this.msg = ErrorCodeEnum.SUCCESS.getMsg();
        this.data = data;
    }

    public Response(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

全局异常处理器增加对APIException的拦截,并修改异常时返回的数据格式。

@RestControllerAdvice
public class GlobalExceptionHandlerAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Response<String> methodArgumentNotValidException(MethodArgumentNotValidException e) {
        // 从异常对象中拿到ObjectError对象
        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
        // 然后提取错误提示信息进行返回
        return new Response<>(ErrorCodeEnum.VALIDATE_FAILED.getCode(), objectError.getDefaultMessage());
    }


    @ExceptionHandler(APIException.class)
    public Response<String> APIExceptionHandler(APIException e) {
        return new Response<>(e.getCode(), e.getMsg());
    }
}

SimpleController 增加一个抛出异常的方法。

@RestController
public class SimpleController {

    @PostMapping("/users")
    public String register(@Valid @RequestBody UserInfoIDto userInfoIDto){
        System.out.println("开始注册用户...");
        return "success";
    }

    @GetMapping("/users")
    public Response<UserInfoIDto> list() {
        UserInfoIDto userInfoIDto = new UserInfoIDto();
        userInfoIDto.setUsername("monday");
        userInfoIDto.setAge(30);
        userInfoIDto.setPhone("123456789");
        if (true) {
            throw new APIException(ErrorCodeEnum.ERROR);
        }
        // 为了保持数据格式统一,必须使用Response包装一下
        return new Response<>(userInfoIDto);
    }
}
Spring Boot 优雅地实现接口参数校验

 

报错返回的格式。

Spring Boot 优雅地实现接口参数校验

 

不报错,返回的格式。

Spring Boot 优雅地实现接口参数校验

 

6. 去掉接口中的Response包装

@RestControllerAdvice既可以全局拦截异常也可拦截指定包下正常的返回值,可以对返回值进行修改。

@RestControllerAdvice(basePackages = {"com.example.validator.controller"})
public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> {

    /**
     * 对那些方法需要包装,如果接口直接返回Response就没有必要再包装了
     *
     * @param returnType
     * @param aClass
     * @return 如果为true才会执行beforeBodyWrite
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> aClass) {
        return !returnType.getParameterType().equals(Response.class);
    }


    @Override
    public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {
        // String类型不能直接包装,所以要进行些特别的处理
        if (returnType.getGenericParameterType().equals(String.class)) {
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                // 将数据包装在Response里后,再转换为json字符串响应给前端
                return objectMapper.writeValueAsString(new Response<>(data));
            } catch (JsonProcessingException e) {
                throw new APIException(ErrorCodeEnum.ERROR);
            }
        }
        // 这里统一包装
        return new Response<>(data);
    }
}
@RestController
public class SimpleController {

    @GetMapping("/users")
    public UserInfoIDto list() {
        UserInfoIDto userInfoIDto = new UserInfoIDto();
        userInfoIDto.setUsername("monday");
        userInfoIDto.setAge(30);
        userInfoIDto.setPhone("123456789");
        // 直接返回值,不需要再使用Response包装
        return userInfoIDto;
    }
}
Spring Boot 优雅地实现接口参数校验

 

7. 每个校验错误都对应不同的错误码

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ValidateErrorCode {
    /** 校验错误码 code */
    int value() default 100000;
}
@Data
public class UserInfoIDto {
    @NotBlank
    @Email
    @ValidateErrorCode(value = 20000)
    private String email;

    @NotBlank
    @Pattern(regexp="^((13[0-9])|(15[^4,\D])|(18[0,3-9]))\d{8}$", message="手机号格式不正确")
    @ValidateErrorCode(value = 30000)
    private String phone;
}

校验异常获取注解中的错误码。

@ExceptionHandler(MethodArgumentNotValidException.class)
    public Response<String> methodArgumentNotValidException(MethodArgumentNotValidException e) throws NoSuchFieldException {
        // 从异常对象中拿到ObjectError对象
        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);

        // 参数的Class对象,等下好通过字段名称获取Field对象
        Class<?> parameterType = e.getParameter().getParameterType();
        // 拿到错误的字段名称
        String fieldName = e.getBindingResult().getFieldError().getField();
        Field field = parameterType.getDeclaredField(fieldName);
        // 获取Field对象上的自定义注解
        ValidateErrorCode annotation = field.getAnnotation(ValidateErrorCode.class);
        if (annotation != null) {
            return new Response<>(annotation.value(),objectError.getDefaultMessage());
        }

        // 然后提取错误提示信息进行返回
        return new Response<>(ErrorCodeEnum.VALIDATE_FAILED.getCode(), objectError.getDefaultMessage());
    }
Spring Boot 优雅地实现接口参数校验

 


Spring Boot 优雅地实现接口参数校验

 

8. 个别接口不统一包装响应

有时候第三方接口回调我们的接口,我们的接口必须按照第三方定义的返回格式来,此时第三方不一定和我们自己的返回格式一样,所以要提供一种可以绕过统一包装的方式。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface NotResponseWrap {
}
@RestController
public class SimpleController {

    @NotResponseWrap
    @PostMapping("/users")
    public String register(@Valid @RequestBody UserInfoIDto userInfoIDto){
        System.out.println("开始注册用户...");
        return "success";
    }
}

ResponseControllerAdvice 增加一个不包装的条件,配置了@NotResponseWrap注解就跳过包装。

@Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> aClass) {
        return !(returnType.getParameterType().equals(Response.class) || returnType.hasMethodAnnotation(NotResponseWrap.class));
    }
Spring Boot 优雅地实现接口参数校验


Tags:Spring Boot   点击:()  评论:()
声明:本站部分内容来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除,谢谢。
▌相关评论
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
▌相关推荐
今天继续为大家分享在工作中如何优雅的校验接口的参数的合法性以及如何统一处理接口返回的json格式。每个字都是干货,原创不易,分享不易。 validation主要是校验用户提交的数...【详细内容】
2020-11-19   Spring Boot  点击:(0)  评论:(0)  加入收藏
最近发现 Spring Boot 本地不能 Debug 调试了,原来 Spring Boot 升级后,对应插件的命令参数都变了,故本文做一个升级。背景:Spring Boot 项目在使用 Spring Boot Maven 插件执行...【详细内容】
2020-11-11   Spring Boot  点击:(4)  评论:(0)  加入收藏
Kafka集群安装、配置和启动Kafka需要依赖zookeeper,并且自身集成了zookeeper,zookeeper至少需要3个节点保证集群高可用,下面是在单机linux下创建kafka3个节点伪集群模式。1、下...【详细内容】
2020-11-10   Spring Boot  点击:(2)  评论:(0)  加入收藏
建立多语言网站,可以获得更多用户流量。多语言网站被称为国际化(i18n),与本地化相反。你比如京东、淘宝这类电商网站就是国际化的电商网站,它支持多国语言。注意: 国际化是一个由...【详细内容】
2020-09-03   Spring Boot  点击:(6)  评论:(0)  加入收藏
松哥最近在研究 Spring Security 源码,发现了很多好玩的代码,抽空写几篇文章和小伙伴们分享一下。很多人吐槽 Spring Security 比 Shiro 重量级,这个重量级不是凭空来的,重量有...【详细内容】
2020-09-02   Spring Boot  点击:(6)  评论:(0)  加入收藏
SpringBoot 有两个关键元素:@SpringBootApplicationSpringApplication 以及 run() 方法 SpringApplication 这个类应该算是 Spring Boot 框架的“创新”产物了,原始的 Spring...【详细内容】
2020-08-12   Spring Boot  点击:(4)  评论:(0)  加入收藏
只要你用Springboot,一定会用到各种spring-boot-starter。其实写一个spring-boot-starter...【详细内容】
2020-08-10   Spring Boot  点击:(8)  评论:(0)  加入收藏
我们学会了如何使用Spring Boot使用进程内缓存在加速数据访问。可能大家会问,那我们在Spring Boot中到底使用了什么缓存呢?在Spring Boot中通过@EnableCaching注解自动化配置...【详细内容】
2020-07-16   Spring Boot  点击:(11)  评论:(0)  加入收藏
Spring Boot 配置文件加解密原理就这么简单背景接上文《失踪人口回归,mybatis-plus 3.3.2 发布》[1] ,提供了一个非常实用的功能 「数据安全保护」 功能,不仅支持数据源的配置...【详细内容】
2020-06-02   Spring Boot  点击:(8)  评论:(0)  加入收藏
找不到配置?配置不对?配置被覆盖?Spring Boot 配置加载过程解析:1、Spring Boot 配置的加载有着约定俗成的步骤: 从 resources 目录下加载 application.properties/applicatio...【详细内容】
2020-05-25   Spring Boot  点击:(20)  评论:(0)  加入收藏
前后端分离已经在慢慢走进各公司的技术栈,根据松哥了解到的消息,不少公司都已经切换到这个技术栈上面了。即使贵司目前没有切换到这个技术栈上面,松哥也非常建议大家学习一下前...【详细内容】
2020-03-31   Spring Boot  点击:(7)  评论:(0)  加入收藏
Spring Boot 算是目前 Java 领域最火的技术栈了,除了书呢?当然就是开源项目了,今天整理15个开源领域非常不错的Spring Boot项目供大家学习,参考。高富帅的路上只能帮你到这里了,...【详细内容】
2020-03-31   Spring Boot  点击:(7)  评论:(0)  加入收藏
在我们实际工作中,总会遇到这样需求,在项目启动的时候需要做一些初始化的操作,比如初始化线程池,提前加载好加密证书等。今天就给大家介绍一个 Spring Boot 神器,专门帮助大家解...【详细内容】
2019-12-26   Spring Boot  点击:(39)  评论:(0)  加入收藏
如果这两天登录 https://start.spring.io/ 就会发现,Spring Boot 默认版本已经升到了 2.1.0。这是因为 Spring Boot 刚刚发布了 2.1.0 版本,我们来看下 Spring Boot 2 发布以...【详细内容】
2019-12-26   Spring Boot  点击:(26)  评论:(0)  加入收藏
这篇文章介绍如何使用 Jpa 和 Thymeleaf 做一个增删改查的示例。先和大家聊聊我为什么喜欢写这种脚手架的项目,在我学习一门新技术的时候,总是想快速的搭建起一个 Demo 来试试...【详细内容】
2019-12-25   Spring Boot  点击:(35)  评论:(0)  加入收藏
使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供参考 一,技术要点:springboot的基本知识,redis基本操作,...【详细内容】
2019-12-25   Spring Boot  点击:(25)  评论:(0)  加入收藏
1.环境目前开发的项目使用的spring boot(2.1.4.RELEASE)+ssm2. 需求现在有一个数据处理任务的接口,在spring boot项目启动后,可以手动的去启动任务,但是这样比较麻烦,每次项...【详细内容】
2019-10-10   Spring Boot  点击:(46)  评论:(0)  加入收藏
1.什么是 Spring Boot ?解释一下:Spring Boot 可以构建一切。Spring Boot 设计之初就是为了最少的配置,最快的速度来启动和运行 Spring 项目。Spring Boot 使用特定的配置来构...【详细内容】
2019-10-08   Spring Boot  点击:(20)  评论:(0)  加入收藏
前言在现今Spring boot 流行年代,相信大部人都非常喜欢Spring boot简洁而非常有效的配置,让我们从庞大xml配置解放出来,让每个框架都需要手动配置集成的劳动中解放出来。这一...【详细内容】
2019-10-08   Spring Boot  点击:(30)  评论:(0)  加入收藏
其实前后端分离本身并不难,后段提供接口,前端做数据展示,关键是这种思想。很多人做惯了前后端不分的开发,在做前后端分离的时候,很容易带进来一些前后端不分时候的开发思路,结果做...【详细内容】
2019-09-27   Spring Boot  点击:(26)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条