SpringBoot validator參數(shù)驗證restful自定義錯誤碼響應(yīng)方式
validator參數(shù)驗證restful自定義錯誤碼響應(yīng)
關(guān)于spring web應(yīng)用中關(guān)于如何使用 Bean Validation API和hibernate-validator的文章已經(jīng)很多,本文就不再重復(fù)敘述,今天要介紹的重點是在SpringBoot restful服務(wù)中如何根據(jù)不同驗證錯誤響應(yīng)不同的自定義錯誤碼。下面直接上代碼。
一、定義restful統(tǒng)一結(jié)果返回
阿里java開發(fā)手冊中定義的一段參考【“對于公司外的 http/api 開放接口必須使用“錯誤碼”; 而應(yīng)用內(nèi)部推薦異常拋出;跨應(yīng)用間 RPC 調(diào)用優(yōu)先考慮使用 Result 方式,封裝 isSuccess()方法、 “錯誤碼”、“錯誤簡短信息”?!俊R虼诉@里也定義個返回結(jié)構(gòu)。
public class CommonResult<T> implements Serializable {
/**
* serialVersionUID:.
*/
private static final long serialVersionUID = -7268040542410707954L;
/**
* 是否成功
*/
private boolean success = false;
/**
* 返回信息
*/
private String message;
/**
* 裝在數(shù)據(jù)
*/
private T data;
/**
* 錯誤代碼
*/
private String code;
/**
* 默認(rèn)構(gòu)造器
*/
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
* 數(shù)據(jù)
*/
public CommonResult(boolean success, String message, T data){
this.success = success;
this.message = message;
this.data = data;
}
//省略get set
}
二、定義一個錯誤碼枚舉
在有需要國際化的項目,當(dāng)然選擇通過i18n來配置更好,此處為了簡單直接采用枚舉定義。這里定義的錯誤僅供參考,不同公司每個應(yīng)用在實際情況下可能都不大一樣。
/**
* 錯誤代碼枚舉類
*
*/
public enum ErrorCodeEnum {
SUCCESS("0000", "success"),
PARAM_EMPTY("1001", "必選參數(shù)為空"),
PARAM_ERROR("1002", "參數(shù)格式錯誤"),
UNKNOWN_ERROR("9999", "系統(tǒng)繁忙,請稍后再試....");
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;
}
}
三、靜態(tài)封裝CommonResult
靜態(tài)封裝CommonResult主要是方便在項目中快速根據(jù)邏輯寫返回結(jié)果代碼。
/**
* 公共響應(yīng)結(jié)果成功失敗的靜態(tài)方法調(diào)用
*
*/
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("");
}
/**
* 根據(jù)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);
}
}
}
五、驗證實例
這里直接給出一個簡單的參數(shù)驗證例子。
Controller繼承上面寫的BaseController
/**
* 關(guān)于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("");
}
}
入?yún)ο驦eader代碼
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
}
這時項目已經(jīng)已經(jīng)完全可以根據(jù)驗證錯誤來返回自定義的錯誤碼和提示了。
本例所涉及源代碼:https://github.com/shalousun/api-doc-test
小結(jié)一下
在一些對外服務(wù)提供restful的應(yīng)用中,根據(jù)不同的驗證錯誤返回其實是避免不了。當(dāng)然實現(xiàn)的方式可以有種,而本文所采用的方式相對來說簡單易懂。
使用validator-api驗證springboot的參數(shù)
作為服務(wù)端開發(fā),驗證前端傳入的參數(shù)的合法性是一個必不可少的步驟,但是驗證參數(shù)是一個基本上是一個體力活,而且冗余代碼繁多,也影響代碼的可閱讀性,所以有沒有一個比較優(yōu)雅的方式來解決這個問題?
這么簡單的問題當(dāng)然早就有大神遇到并且解決了,這一篇文章主要講一下解決基于spring-boot的驗證參數(shù)的比較好的方法:利用validator-api來進(jìn)行驗證參數(shù)。
在spring-boot-starter-web包里面有hibernate-validator包,它提供了一系列驗證各種參數(shù)的方法,所以說spring-boot已經(jīng)幫我們想好要怎么解決這個問題了。
這篇文章針對spring-boot里面的spring-mvc介紹三種方式來驗證參數(shù)。
一、這個方法在網(wǎng)上大部分都可以查到
先假設(shè)我們的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,就可以表示參數(shù)的范圍和出錯時候的提示信息。還有很多其他注解,這里就不一一列出
然后我們的Controller層的代碼為
@RequestMapping(value = "/paramErrorTest", method = RequestMethod.GET)
public String paramErrorTest(
@Valid
@ModelAttribute
GradeAndClassroomModel gradeAndClassroomModel,
BindingResult result) {
return classroomService.getTeacherName(gradeAndClassroomModel.getGrade(), gradeAndClassroomModel.getClassroomNumber());
}
其中如果驗證出錯,result對象里面就會有錯誤信息,然后可以自己進(jìn)行處理。
二、針對上面的例子
會有人說,就兩個參數(shù),為什么要作為對象呢?會不會太麻煩?確實,如果只有少數(shù)對象,直接把參數(shù)寫到Controller層,然后在Controller層進(jìn)行驗證就可以了。
@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提供的注解移除來寫到請求參數(shù)上面的話是不是就可以了呢?答案是錯,為什么這樣不能成功的驗證參數(shù)呢?具體原因大家可以參考官方文檔:
上面的文檔已經(jīng)說的很清楚了,所以我們需要創(chuàng)建一個Bean
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
然后在類方法上面加上注解@Validated
@RestController
@RequestMapping("/spring-boot/classroom")
@Validated
public class ClassroomController {
...
}
然后之前沒有生效的注解@Range、@Min、@Max等validator包里面提供的注解就可以生效了。
三、估計到了這里又會有人問
如果validator包里面注解不能滿足我們的需求,我們是否可以自己定義參數(shù)驗證的邏輯。答案是肯定的,我們可以利用
@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> {
...
}
組合進(jìn)行自定義,具體的例子網(wǎng)上其他文章就很多了,這里就不進(jìn)行詳細(xì)的例子了,但是最終使用的時候就是
@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,自定義的驗證方法也不會執(zhí)行。驗證邏輯會失效。
是不是通過這樣寫注解的方式來驗證進(jìn)行請求的參數(shù),代碼邏輯更佳清晰和優(yōu)雅?表達(dá)的含義也會更佳清楚?并且沒有了大量重復(fù)的類似的驗證代碼。
Ps:這里的代碼都是基于spring-mvc框架來試驗的,如果有人并沒有使用spring-mvc作為rest框架,而是使用jersey來作為rest框架的話,可能一些細(xì)節(jié)方面需要調(diào)整, 但是這三種方案應(yīng)該都是可以兼容的。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
spring boot+mybatis 多數(shù)據(jù)源切換(實例講解)
下面小編就為大家?guī)硪黄猻pring boot+mybatis 多數(shù)據(jù)源切換(實例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09
java調(diào)用chatgpt接口來實現(xiàn)專屬于自己的人工智能助手
這篇文章主要介紹了用java來調(diào)用chatget的接口,實現(xiàn)自己的聊天機器人,對人工智能感興趣的小伙伴可以參考閱讀2023-03-03
Java中BigDecimal,DateFormatter?和迭代器的"陷阱"
這篇文章主要介紹了Java中BigDecimal,DateFormatter?和迭代器的"陷阱",文章圍繞主題展開詳細(xì)的內(nèi)容介紹,感興趣的小伙伴可以參考一下2022-06-06
Java?synchronized輕量級鎖實現(xiàn)過程淺析
這篇文章主要介紹了Java synchronized輕量級鎖實現(xiàn)過程,synchronized是Java里的一個關(guān)鍵字,起到的一個效果是"監(jiān)視器鎖",它的功能就是保證操作的原子性,同時禁止指令重排序和保證內(nèi)存的可見性2023-02-02

