Spring Validated 引數校驗
阿新 • • 發佈:2020-09-08
預定義物件的說明
Result結果
Restful介面統一返回Result格式的結果:
package com.qiankai.valid.common; import lombok.Data; import lombok.experimental.Accessors; /** * 返回結果 * * @author kai_qian * @version v1.0 * @since 2020/09/08 10:38 */ @Data @Accessors(chain = true) public class Result<T> { private int code; private String message; private T data; public boolean ok() { return this.code == 0; } public static <T> Result<T> success() { return new Result<T>().setCode(0).setMessage("成功"); } public static <T> Result<T> success(T data) { return new Result<T>().setCode(0).setMessage("成功").setData(data); } public static <T> Result<T> failure() { return new Result<T>().setCode(-1).setMessage("失敗"); } public static <T> Result<T> failure(int code, String msg) { return new Result<T>().setCode(code).setMessage(msg); } public static <T> Result<T> failure(int code, String msg, T data) { return new Result<T>().setCode(-1).setMessage("失敗").setData(data); } }
ErrorCode
全域性異常錯誤碼,後面統一處理異常會用到:
一般為了更好的處理全域性異常,使用的錯誤碼都是定義成列舉型別(包含錯誤碼和錯誤描述),我這邊方便演示就隨便定義了一個類
package com.qiankai.valid.common; /** * 錯誤碼 * * @author kai_qian * @version v1.0 * @since 2020/09/08 11:08 */ public final class ErrorCode { /** * 引數校驗失敗錯誤碼 */ public static final int ARGUMENT_VALID_FAILURE = -2; }
常用引數校驗
在DTO上添加註解,實現引數校驗,假設存在 UserDTO 如下:
@Data public class UserDTO { private Long userId; @NotNull @Length(min = 2, max = 10) private String userName; @NotNull @Length(min = 6, max = 20) private String account; @NotNull @Length(min = 6, max = 20) private String password; }
RequestBody校驗
package com.qiankai.valid.controller;
import com.qiankai.valid.common.Result;
import com.qiankai.valid.dto.UserDTO;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
/**
* RequestBody 引數校驗
* 校驗失敗會丟擲 MethodArgumentNotValidException 異常
*
* @author kai_qian
* @version v1.0
* @since 2020/09/08 10:37
*/
@RequestMapping("/api/user01")
@RestController
public class User01Controller {
/**
* RequestBody 引數校驗
* 使用 @Valid 和 @Validated 都可以
*/
@PostMapping("/save/1")
public Result saveUser(@RequestBody @Validated UserDTO userDTO) {
return Result.success();
}
@PostMapping("/save/2")
public Result save2User(@RequestBody @Valid UserDTO userDTO) {
return Result.success();
}
}
RequestParam / PathVariable 校驗
package com.qiankai.valid.controller;
import com.qiankai.valid.common.Result;
import com.qiankai.valid.dto.UserDTO;
import org.hibernate.validator.constraints.Length;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
* RequestMapping / PathVariable 引數校驗
* 校驗失敗會丟擲 ConstraintViolationException 異常
*
* @author kai_qian
* @version v1.0
* @since 2020/09/08 10:57
*/
@RequestMapping("/api/user02")
@RestController
@Validated
public class User02Controller {
/**
* 此時必須在Controller上標註 @Validated 註解,並在入參上宣告約束註解
*/
/**
* 路徑變數
* 新增約束註解 @Min
*/
@GetMapping("{userId}")
public Result detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {
// 校驗通過,才會執行業務邏輯處理
UserDTO userDTO = new UserDTO();
userDTO.setUserId(userId);
userDTO.setAccount("11111111111111111");
userDTO.setUserName("xixi");
userDTO.setAccount("11111111111111111");
return Result.success(userDTO);
}
/**
* 查詢引數
* 新增約束註解 @Length @NotNull
*/
@GetMapping("getByAccount")
public Result getByAccount(@Length(min = 6, max = 20) @NotNull String account) {
// 校驗通過,才會執行業務邏輯處理
UserDTO userDTO = new UserDTO();
userDTO.setUserId(10000000000000003L);
userDTO.setAccount(account);
userDTO.setUserName("xixi");
userDTO.setAccount("11111111111111111");
return Result.success(userDTO);
}
}
全域性異常處理
上面如果校驗失敗,會丟擲 MethodArgumentNotValidException
或者 ConstraintViolationException
異常。
在實際專案開發中,通常會用統一異常處理來返回一個更友好的提示。
比如我們系統要求無論傳送什麼異常,http的狀態碼必須返回200,由業務碼去區分系統的異常情況。
package com.qiankai.valid.exception;
import com.qiankai.valid.common.ErrorCode;
import com.qiankai.valid.common.Result;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
/**
* 統一異常處理
*
* @author kai_qian
* @version v1.0
* @since 2020/09/08 11:05
*/
@RestControllerAdvice
public class CommonExceptionHandler {
@ExceptionHandler({MethodArgumentNotValidException.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
StringBuilder sb = new StringBuilder("校驗失敗:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sb.toString();
return Result.failure(ErrorCode.ARGUMENT_VALID_FAILURE, msg);
}
@ExceptionHandler({ConstraintViolationException.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Result handleConstraintViolationException(ConstraintViolationException ex) {
return Result.failure(ErrorCode.ARGUMENT_VALID_FAILURE, ex.getMessage());
}
}
使用全域性異常前
使用全域性異常後
分組校驗
有時候,為了區分業務場景,對於不同場景下的資料驗證規則可能不一樣(例如新增時可以不用傳遞 ID,而修改時必須傳遞ID),可以使用分組校驗。
程式碼示例
DTO 如下:
package com.qiankai.valid.dto;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
* 分組校驗
*
* @author kai_qian
* @version v1.0
* @since 2020/09/08 11:12
*/
@Data
public class UserGroupValidDTO {
@NotNull(groups = Update.class)
@Min(value = 10000000000000000L, groups = Update.class)
private Long userId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String userName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String account;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String password;
/**
* 儲存的時候校驗分組
*/
public interface Save {
}
/**
* 更新的時候校驗分組
*/
public interface Update {
}
}
Controller 如下:
package com.qiankai.valid.controller;
import com.qiankai.valid.common.Result;
import com.qiankai.valid.dto.UserGroupValidDTO;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 分組校驗
*
* @author kai_qian
* @version v1.0
* @since 2020/09/08 11:19
*/
@RestController
@RequestMapping("/api/user_group_valid")
public class UserGroupValidController {
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated(UserGroupValidDTO.Save.class) UserGroupValidDTO userDTO) {
// 校驗通過,才會執行業務邏輯處理
return Result.success();
}
@PostMapping("/update")
public Result updateUser(@RequestBody @Validated(UserGroupValidDTO.Update.class) UserGroupValidDTO userDTO) {
// 校驗通過,才會執行業務邏輯處理
return Result.success();
}
}
巢狀校驗
上面的校驗主要是針對基本型別進行了校驗,如果DTO中包含了自定義的實體類,就需要用到巢狀校驗。
程式碼示例
DTO 如下:
package com.qiankai.valid.dto;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
* 巢狀校驗
* DTO中的某個欄位也是一個物件,這種情況下,可以使用巢狀校驗
*
* @author kai_qian
* @version v1.0
* @since 2020/09/08 12:21
*/
@Data
public class UserNestedValidDTO {
@Min(value = 10000000000000000L, groups = Update.class)
private Long userId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String userName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String account;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String password;
/**
* 此時DTO類的對應欄位必須標記@Valid註解
*/
@Valid
@NotNull(groups = {Save.class, Update.class})
private Job job;
@Data
public static class Job {
@NotNull(groups = {Update.class})
@Min(value = 1, groups = Update.class)
private Long jobId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String jobName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String position;
}
/**
* 儲存的時候校驗分組
*/
public interface Save {
}
/**
* 更新的時候校驗分組
*/
public interface Update {
}
}
Controller 如下:
package com.qiankai.valid.controller;
import com.qiankai.valid.common.Result;
import com.qiankai.valid.dto.UserNestedValidDTO;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 巢狀校驗
*
* @author kai_qian
* @version v1.0
* @since 2020/09/08 13:31
*/
@RestController
@RequestMapping("/api/user_nested_valid")
public class UserNestedValidController {
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated(UserNestedValidDTO.Save.class) UserNestedValidDTO userDTO) {
// 校驗通過,才會執行業務邏輯處理
return Result.success();
}
@PostMapping("/update")
public Result updateUser(@RequestBody @Validated(UserNestedValidDTO.Update.class) UserNestedValidDTO userDTO) {
// 校驗通過,才會執行業務邏輯處理
return Result.success();
}
}
介面呼叫示例
根據DTO以及Controller中的校驗規則,在update時,如果不傳 jobId
巢狀校驗就會報錯,如下: