SpringBoot validator参数验证restful自定义错误码响应方式

目录
  • validator参数验证restful自定义错误码响应
    • 一、定义restful统一结果返回
    • 二、定义一个错误码枚举
    • 三、静态封装CommonResult
    • 四、定义BaseController来处理验证错误自定义错误码返回
    • 五、验证实例
    • 小结一下
  • 使用validator-api验证springboot的参数
    • 一、这个方法在网上大部分都可以查到
    • 二、针对上面的例子
    • 三、估计到了这里又会有人问

validator参数验证restful自定义错误码响应

关于spring web应用中关于如何使用 Bean Validation API和hibernate-validator的文章已经很多,本文就不再重复叙述,今天要介绍的重点是在SpringBoot restful服务中如何根据不同验证错误响应不同的自定义错误码。下面直接上代码。

一、定义restful统一结果返回

阿里java开发手册中定义的一段参考【“对于公司外的 http/api 开放接口必须使用“错误码”; 而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、 “错误码”、“错误简短信息”。】。因此这里也定义个返回结构。

public class CommonResult<T> implements Serializable {

	/**
	 * serialVersionUID:.
	 */
	private static final long serialVersionUID = -7268040542410707954L;

	/**
	 * 是否成功
	 */
	private boolean success = false;

	/**
	 * 返回信息
	 */
	private String message;

	/**
	 * 装在数据
	 */
	private T data;

	/**
	 * 错误代码
	 */
	private String code;

	/**
	 * 默认构造器
	 */
	public CommonResult(){

	}
	/**
	 *
	 * @param success
	 * 			是否成功
	 * @param message
	 * 			返回的消息
	 */
	public CommonResult(boolean success, String message){
		this.success = success;
		this.message = message;
	}
	/**
	 *
	 * @param success
	 * 			是否成功
	 */
	public CommonResult(boolean success){
		this.success = success;
	}

	/**
	 *
	 * @param code error code
	 * @param message success or error messages
	 */
	public CommonResult(String code,String message){
		this.code = code;
		this.message = message;
	}
	/**
	 *
	 * @param success
	 * 			是否成功
	 * @param message
	 * 			消息
	 * @param data
	 * 			数据
	 */
	public CommonResult(boolean success, String message, T data){
		this.success = success;
		this.message = message;
		this.data = data;
	}
    //省略get set
}

二、定义一个错误码枚举

在有需要国际化的项目,当然选择通过i18n来配置更好,此处为了简单直接采用枚举定义。这里定义的错误仅供参考,不同公司每个应用在实际情况下可能都不大一样。

/**
 * 错误代码枚举类
 *
 */
public enum ErrorCodeEnum {
    SUCCESS("0000", "success"),
    PARAM_EMPTY("1001", "必选参数为空"),
    PARAM_ERROR("1002", "参数格式错误"),
    UNKNOWN_ERROR("9999", "系统繁忙,请稍后再试....");
    private String code;
    private String desc;
    ErrorCodeEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public String getCode() {
        return this.code;
    } 

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return "[" + this.code + "]" + this.desc;
    }
}

三、静态封装CommonResult

静态封装CommonResult主要是方便在项目中快速根据逻辑写返回结果代码。

/**
 * 公共响应结果成功失败的静态方法调用
 *
 */
public class ResultUtil {

    /**
     * return success
     *
     * @param data
     * @return
     */
    public static <T> CommonResult<T> returnSuccess(T data) {
        CommonResult<T> result = new CommonResult();
        result.setCode(ErrorCodeEnum.SUCCESS.getCode());
        result.setSuccess(true);
        result.setData(data);
        result.setMessage(ErrorCodeEnum.SUCCESS.getDesc());
        return result;
    }

    /**
     * return error
     *
     * @param code error code
     * @param msg  error message
     * @return
     */
    public static CommonResult returnError(String code, String msg) {
        CommonResult result = new CommonResult();
        result.setCode(code);
        result.setData("");
        result.setMessage(msg);
        return result;
    }

    /**
     * use enum
     *
     * @param status
     * @return
     */
    public static CommonResult returnError(ErrorCodeEnum status) {
        return returnError(status.getCode(), status.getDesc());
    }
}

四、定义BaseController来处理验证错误自定义错误码返回

/**
 * BaseController
 *
 */
public abstract class BaseController {
    private static final Logger LOGGER = LoggerFactory.getLogger(BaseController.class);
    /**
     * validate params
     *
     * @param bindingResult
     * @return
     */
    protected CommonResult validParams(BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();
            return processBindingError(fieldError);
        }
        return ResultUtil.returnSuccess("");
    }

    /**
     * 根据spring binding 错误信息自定义返回错误码和错误信息
     *
     * @param fieldError
     * @return
     */
    private CommonResult processBindingError(FieldError fieldError) {
        String code = fieldError.getCode();
        LOGGER.debug("validator error code: {}", code);
        switch (code) {
            case "NotEmpty":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_EMPTY.getCode(), fieldError.getDefaultMessage());
            case "NotBlank":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_EMPTY.getCode(), fieldError.getDefaultMessage());
            case "NotNull":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_EMPTY.getCode(), fieldError.getDefaultMessage());
            case "Pattern":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Min":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Max":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Length":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Range":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Email":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "DecimalMin":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "DecimalMax":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Size":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Digits":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Past":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Future":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            default:
                return ResultUtil.returnError(ErrorCodeEnum.UNKNOWN_ERROR);
        }
    }
}

五、验证实例

这里直接给出一个简单的参数验证例子。

Controller继承上面写的BaseController

/**
 * 关于Validator使用测试
 *
 */
@RestController
@RequestMapping("validator")
public class ValidatorTestController extends BaseController {
    private static final Logger LOGGER = LoggerFactory.getLogger(ValidatorTestController.class);
    /**
     * validate验证测试
     *
     * @param leader
     * @param bindingResult
     * @return
     */
    @PostMapping("/test")
    public CommonResult testSimpleValidate(@Valid @RequestBody Leader leader, BindingResult bindingResult) {
        LOGGER.debug("ReqParams:{}", JSON.toJSONString(leader));
        CommonResult result = validParams(bindingResult);
        if (!result.isSuccess()) {
            return result;
        }
        return ResultUtil.returnSuccess("");
    }
}

入参对象Leader代码

public class Leader {
    /**
     * 姓名
     */
    @NotEmpty
    private String name;

    /**
     * 生日
     */
    @Pattern(regexp = "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", message = "出生日期格式不正确")
    private String birthday;

    /**
     * 年龄
     */
    @Min(value = 0)
    private Integer age;
    //省略gettes and  setters
}

这时项目已经已经完全可以根据验证错误来返回自定义的错误码和提示了。

本例所涉及源代码:https://github.com/shalousun/api-doc-test

小结一下

在一些对外服务提供restful的应用中,根据不同的验证错误返回其实是避免不了。当然实现的方式可以有种,而本文所采用的方式相对来说简单易懂。

使用validator-api验证springboot的参数

作为服务端开发,验证前端传入的参数的合法性是一个必不可少的步骤,但是验证参数是一个基本上是一个体力活,而且冗余代码繁多,也影响代码的可阅读性,所以有没有一个比较优雅的方式来解决这个问题?

这么简单的问题当然早就有大神遇到并且解决了,这一篇文章主要讲一下解决基于spring-boot的验证参数的比较好的方法:利用validator-api来进行验证参数。

在spring-boot-starter-web包里面有hibernate-validator包,它提供了一系列验证各种参数的方法,所以说spring-boot已经帮我们想好要怎么解决这个问题了。

这篇文章针对spring-boot里面的spring-mvc介绍三种方式来验证参数。

一、这个方法在网上大部分都可以查到

先假设我们的restful的接口接受一个GradeAndClassroomModel类型的对象,并且这个类被定义成

@Data
public class GradeAndClassroomModel {
@Range(min = 1, max = 9, message = "年级只能从1-9")
private int grade;
@Range(min = 1, max = 99, message = "班级只能从1-99")
private int classroomNumber;
}

利用validator提供的一系列注解,比如本例中的@Range,就可以表示参数的范围和出错时候的提示信息。还有很多其他注解,这里就不一一列出

然后我们的Controller层的代码为

@RequestMapping(value = "/paramErrorTest", method = RequestMethod.GET)
public String paramErrorTest(
  @Valid
  @ModelAttribute
  GradeAndClassroomModel gradeAndClassroomModel,
  BindingResult result) {
  return classroomService.getTeacherName(gradeAndClassroomModel.getGrade(), gradeAndClassroomModel.getClassroomNumber());
}

其中如果验证出错,result对象里面就会有错误信息,然后可以自己进行处理。

二、针对上面的例子

会有人说,就两个参数,为什么要作为对象呢?会不会太麻烦?确实,如果只有少数对象,直接把参数写到Controller层,然后在Controller层进行验证就可以了。

@RequestMapping(value = "/teacherName", method = RequestMethod.GET)
public String teacherName(
  @Range(min = 1, max = 9, message = "年级只能从1-9")
  @RequestParam(name = "grade", required = true)
  int grade,
  @Min(value = 1, message = "班级最小只能1")
  @Max(value = 99, message = "班级最大只能99")
  @RequestParam(name = "classroom", required = true)
  int classroom) {
return classroomService.getTeacherName(grade, classroom);
}

如果直接把validator提供的注解移除来写到请求参数上面的话是不是就可以了呢?答案是错,为什么这样不能成功的验证参数呢?具体原因大家可以参考官方文档

上面的文档已经说的很清楚了,所以我们需要创建一个Bean

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
  return new MethodValidationPostProcessor();
}

然后在类方法上面加上注解@Validated

@RestController
@RequestMapping("/spring-boot/classroom")
@Validated
public class ClassroomController {
 ...
}

然后之前没有生效的注解@Range、@Min、@Max等validator包里面提供的注解就可以生效了。

三、估计到了这里又会有人问

如果validator包里面注解不能满足我们的需求,我们是否可以自己定义参数验证的逻辑。答案是肯定的,我们可以利用

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Constraint(validatedBy = {Validator.class})
public @interface ParamValidator {
  String message() default "Parameter error!";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}

public class Validator implements ConstraintValidator<ParamValidator, Object> {
  ...
}

组合进行自定义,具体的例子网上其他文章就很多了,这里就不进行详细的例子了,但是最终使用的时候就是

  @RequestMapping(value = "/paramValidator", method = RequestMethod.GET)
  public String paramValidator(
      @ParamValidator(isRequired = true, desc = "年级", range = "int:1~9", message = "年级只能从1-9")
      @RequestParam(name = "grade", required = true)
      int grade,
      @ParamValidator(isRequired = true, desc = "班级", range = "int:1~99", message = "班级只能从1-99")
      @RequestParam(name = "classroom", required = true)
      int classroom) {
    return classroomService.getTeacherName(grade, classroom);
  }

另外不要忘记方法二里面里面提到的MethodValidationPostProcessor这个bean,如果没有初始化这个bean,自定义的验证方法也不会执行。验证逻辑会失效。

是不是通过这样写注解的方式来验证进行请求的参数,代码逻辑更佳清晰和优雅?表达的含义也会更佳清楚?并且没有了大量重复的类似的验证代码。

Ps:这里的代码都是基于spring-mvc框架来试验的,如果有人并没有使用spring-mvc作为rest框架,而是使用jersey来作为rest框架的话,可能一些细节方面需要调整, 但是这三种方案应该都是可以兼容的。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • SpringBoot + validation 接口参数校验的思路详解

    有参数传递的地方都少不了参数校验.在web开发中,前端的参数校验是为了用户体验,后端的参数校验是为了安全.试想一下,如果在controller层中没有经过任何校验的参数通过service层.dao层一路来到了数据库就可能导致严重的后果,最好的结果是查不出数据,严重一点就是报错,如果这些没有被校验的参数中包含了恶意代码,那就可能导致更严重的后果. 实践 一.引入依赖 <!--引入spring-boot-starter-validation--> <dependency> <gr

  • SpringBoot使用validation做参数校验的实现步骤

    1.添加依赖 直接添加 hibernate-validator <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.2.Final</version> </dependency> 添加spring-boot-starter-validat

  • SpringBoot使用validation-api实现对枚举类参数校验的方法

    前言 之前写了一个博客是关于使用SpringBoot使用validation-api实现参数校验,当时使用的注解都是validation-api自带的注解只能完成对空值.长度等简单的校验,在我们日常的使用当中会遇到对参数是否在枚举值类的校验,针对这种情况我们怎么来实现呢? SpringBoot使用validation-api实现参数校验可参考我的博客:SpringBoot使用validation-api实现参数校验 正文 SpringBoot使用validation-api实现对枚举类参数校验

  • SpringBoot使用validation-api实现参数校验的示例

    我们在开发Java项目的时候,经常需要对参数进行一些必填项.格式.长度等进行校验,如果手写代码对参数校验,每个接口会需要很多低级的代码,这样会降低代码的可读性.那么我们能不能使用一种比较优雅的方式来实现,对请求中的参数进行校验呢? knife4j的安装与使用可参考我的博客:SpringBoot使用knife4j进行在线接口调试 正文 ValidationApi框架就是用来解决参数校验中代码冗余问题,ValidationApi框架提供一些注解用来帮助我们对请求参数进行校验: SpringBoot使

  • SpringBoot validator参数验证restful自定义错误码响应方式

    目录 validator参数验证restful自定义错误码响应 一.定义restful统一结果返回 二.定义一个错误码枚举 三.静态封装CommonResult 四.定义BaseController来处理验证错误自定义错误码返回 五.验证实例 小结一下 使用validator-api验证springboot的参数 一.这个方法在网上大部分都可以查到 二.针对上面的例子 三.估计到了这里又会有人问 validator参数验证restful自定义错误码响应 关于spring web应用中关于如何使用

  • Spring Boot 如何自定义返回错误码错误信息

     说明 •在实际的开发过程中,很多时候要定义符合自己业务的错误码和错误信息,而不是统一的而不是统一的下面这种格式返回到调用端 INTERNAL_SERVER_ERROR(500, "Internal Server Error"), 下面我们来看看如何将我们自定义的错误码和错误信息返回到调用端. 1 自定义错误码 •首先我们要定义一个枚举类 public enum ErrorEnum { /* * 错误信息 * */ E_20011(20011, "缺少必填参数"),

  • springboot接口参数校验JSR303的实现

    目录 一.在controller接口处理校验异常 二.统一异常处理 三.错误码枚举类 四.自定义参数校验注解 在 javax.validation.constraints包中定义了非常多的校验注解,引入依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependenc

  • GoFrame错误处理常用方法及错误码使用示例

    目录 前言 错误创建 New/Newf Wrap/Wrapf NewSkip/NewSkipf 错误码使用 错误码相关方法概览 NewCode/NewCodef 示例代码 WrapCode/WrapCodef 示例代码 NewCodeSkip/NewCodeSkipf 获取error中的错误码接口 总结 前言 这篇文章将为大家介绍:GoFrame 错误处理的常用方法&错误码的使用.如何自定义错误对象.如何忽略部分堆栈信息.如何自定义错误码的返回.如何获取error对象中的错误码. 错误创建 Ne

  • Java Controller实现参数验证与统一异常处理流程详细讲解

    目录 一,前期数据及类准备 1.1 统一状态码 1.2 统一返回格式 1.3 自定义接口API异常类 1.4 参数封装类 二,参数验证 2.1 pom.xml 2.2 修改参数封装类 2.3 controller 2.4 测试 三,统一异常处理 3.1 方法参数验证异常处理 3.2 其他异常处理 最近开发了比较多的接口,因为没有可参考的案例,所以一开始一直按照我的理解进行开发.开发多了发现自己每个结果都写了相同的代码:try() {} catch() {}, 和关于参数判空的:StringUti

  • SpringBoot整合Hibernate Validator实现参数验证功能

    在前后端分离的开发模式中,后端对前端传入的参数的校验成了必不可少的一个环节.但是在多参数的情况下,在controller层加上参数验证,会显得特别臃肿,并且会有许多的重复代码.这里可以引用Hibernate Validator来解决这个问题,直接在实体类进行参数校验,验证失败直接返回错误信息给前端,减少controller层的代码量. 一.xml引入Hibernate Validator <!-- 验证器 --> <dependency> <groupId>org.hi

  • 浅谈validator自定义验证及易错点

    目录 validator自定义验证及易错点 validator自定义验证 易错点 vue里面如何自定义校验 如下 也可以自定义校验 validator自定义验证及易错点 validator自定义验证 element中Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可. 而表单验证功能就包括validator自定义验证,用法如图所示 在验证规则中自定义一个规则,命名随意,再给它一个触发函数,而规则

  • Spring Cloud Feign 自定义配置(重试、拦截与错误码处理) 代码实践

    基于 spring-boot-starter-parent 2.1.9.RELEASE, spring-cloud-openfeign 2.1.3.RELEASE 引子 Feign 是一个声明式.模板化的HTTP客户端,简化了系统发起Http请求.创建它时,只需要创建一个接口,然后加上FeignClient注解,使用它时,就像调用本地方法一样,作为开发者的我们完全感知不到这是在调用远程的方法,也感知不到背后发起了HTTP请求: /** * @author axin * @suammry xx 客

  • golang常用库之字段参数验证库-validator使用详解

    golang常用库:gorilla/mux-http路由库使用 golang常用库:配置文件解析库-viper使用 golang常用库:操作数据库的orm框架-gorm基本使用 golang常用库:字段参数验证库-validator使用 一.背景 在平常开发中,特别是在web应用开发中,为了验证输入字段的合法性,都会做一些验证操作.比如对用户提交的表单字段进行验证,或者对请求的API接口字段进行验证,验证字段的合法性,保证输入字段值的安全,防止用户的恶意请求. 一般的做法是用正则表达式,一个字段

  • SpringBoot项目实用功能之实现自定义参数解析器

    核心点 1.实现接口 org.springframework.web.method.support.HandlerMethodArgumentResolver supportsParameter 方法根据当前方法参数决定是否需要应用当前这个参数解析器 resolveArgument 执行具体的解析过程 2.将自实现的参数解析器类 添加到Spring容器中 3.实现 org.springframework.web.servlet.config.annotation.WebMvcConfigurer

随机推荐