validator參數驗證restful自定義錯誤碼響應
關于spring web應用中關于如何使用 Bean Validation API和hibernate-validator的文章已經很多,本文就不再重復敘述,今天要介紹的重點是在SpringBoot restful服務中如何根據不同驗證錯誤響應不同的自定義錯誤碼。下面直接上代碼。
一、定義restful統一結果返回
阿里java開發手冊中定義的一段參考【“對于公司外的 http/api 開放接口必須使用“錯誤碼”; 而應用內部推薦異常拋出;跨應用間 RPC 調用優先考慮使用 Result 方式,封裝 isSuccess()方法、 “錯誤碼”、“錯誤簡短信息”。】。因此這里也定義個返回結構。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
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來配置更好,此處為了簡單直接采用枚舉定義。這里定義的錯誤僅供參考,不同公司每個應用在實際情況下可能都不大一樣。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
/** * 錯誤代碼枚舉類 * */ 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主要是方便在項目中快速根據邏輯寫返回結果代碼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
/** * 公共響應結果成功失敗的靜態方法調用 * */ 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來處理驗證錯誤自定義錯誤碼返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
/** * 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
/** * 關于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代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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類型的對象,并且這個類被定義成
1
2
3
4
5
6
7
|
@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層的代碼為
1
2
3
4
5
6
7
8
|
@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層進行驗證就可以了。
1
2
3
4
5
6
7
8
9
10
11
|
@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
1
2
3
4
|
@Bean public MethodValidationPostProcessor methodValidationPostProcessor() { return new MethodValidationPostProcessor(); } |
然后在類方法上面加上注解@Validated
1
2
3
4
5
6
|
@RestController @RequestMapping ( "/spring-boot/classroom" ) @Validated public class ClassroomController { ... } |
然后之前沒有生效的注解@Range、@Min、@Max等validator包里面提供的注解就可以生效了。
三、估計到了這里又會有人問
如果validator包里面注解不能滿足我們的需求,我們是否可以自己定義參數驗證的邏輯。答案是肯定的,我們可以利用
1
2
3
4
5
6
7
8
9
|
@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 {}; } |
和
1
2
3
|
public class Validator implements ConstraintValidator<ParamValidator, Object> { ... } |
組合進行自定義,具體的例子網上其他文章就很多了,這里就不進行詳細的例子了,但是最終使用的時候就是
1
2
3
4
5
6
7
8
9
10
|
@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框架的話,可能一些細節方面需要調整, 但是這三種方案應該都是可以兼容的。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/shalousun/article/details/80960835