瞧瞧別人家的參數校驗,那叫一個優雅!
前言
對于開發人員來說,對用戶輸入的參數或者系統參數做校驗,是日常工作之一。
很多小伙伴在寫接口的時候,可能都會碰到一個問題:參數校驗應該怎么寫?
比如,開發一個用戶注冊接口,需要校驗以下條件:
- 用戶名不能為空,長度在 3 到 20 個字符之間;
- 密碼不能為空,長度至少為 8 個字符;
- 年齡必須是正整數,不能超過 120;
- 郵箱必須符合標準格式。
乍一看,這種校驗邏輯看起來很簡單嘛,直接寫幾個 if 就完事了。
但真的這么簡單嗎?
接下來我們就從傳統的參數校驗入手,看看問題出在哪,然后再聊聊 Spring Boot 中如何優雅地實現參數校驗,希望對你會有所幫助。
一、傳統參數校驗的問題
很多人可能會直接在 Controller 里手寫校驗邏輯,比如下面這個代碼:
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping("/register")
public ResponseEntity<String> register(@RequestBody Map<String, Object> request) {
String username = (String) request.get("username");
if (username == null || username.length() < 3 || username.length() > 20) {
return ResponseEntity.badRequest().body("用戶名不能為空,且長度必須在3到20之間");
}
String password = (String) request.get("password");
if (password == null || password.length() < 8) {
return ResponseEntity.badRequest().body("密碼不能為空,且長度至少為8個字符");
}
Integer age = (Integer) request.get("age");
if (age == null || age <= 0 || age > 120) {
return ResponseEntity.badRequest().body("年齡必須是正整數,且不能超過120");
}
return ResponseEntity.ok("注冊成功!");
}
}
這段代碼乍一看沒什么問題,但如果仔細分析,會發現一堆隱患:
- 代碼冗余:校驗邏輯散落在 Controller 里,寫起來麻煩,后期維護更是災難。
- 重復勞動:類似的校驗邏輯可能會出現在多個接口里,導致代碼重復度極高。
- 用戶體驗差:返回的錯誤信息不統一、不規范,前端開發還得猜用戶輸入到底哪兒錯了。
- 擴展性差:萬一某天需要加新的校驗規則,你可能要到處改代碼。
所以,這種手寫參數校驗的方式,在簡單場景下勉強能用,但如果業務變復雜,問題會越來越多。
那么問題來了,那有沒有更優雅的方式來處理這些問題呢?
答:當然是有的。
二、Spring Boot 的參數校驗機制
在 Spring Boot 中,我們可以使用 Hibernate Validator(Bean Validation 的參考實現)來實現參數校驗。
它的核心思路是:把校驗邏輯從業務代碼里抽離出來,用注解的方式聲明校驗規則。
接下來我們一步步來看怎么實現。
1. 使用注解進行參數校驗
首先,定義一個用于接收用戶注冊參數的 DTO 對象:
@Data
public class UserRegistrationRequest {
@NotNull(message = "用戶名不能為空")
@Size(min = 3, max = 20, message = "用戶名長度必須在3到20之間")
private String username;
@NotNull(message = "密碼不能為空")
@Size(min = 8, message = "密碼長度至少為8個字符")
private String password;
@NotNull(message = "年齡不能為空")
@Min(value = 1, message = "年齡必須是正整數")
@Max(value = 120, message = "年齡不能超過120")
private Integer age;
@Email(message = "郵箱格式不正確")
private String email;
}
這里我們用了幾個常見的校驗注解:
- @NotNull:字段不能為空;
- @Size:限制字符串長度;
- @Min 和 @Max:限制數值范圍;
- @Email:校驗郵箱格式。
這些注解由 Hibernate Validator 提供,基本涵蓋了日常開發中的大部分校驗需求。
然后,在 Controller 中這樣寫:
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping("/register")
public ResponseEntity<String> register(@Valid @RequestBody UserRegistrationRequest request) {
return ResponseEntity.ok("注冊成功!");
}
}
注意這里的 @Valid 注解,它的作用是告訴 Spring:對請求參數進行校驗。
2. 統一處理校驗錯誤
如果前端傳的參數不合法,Spring 會拋出一個 MethodArgumentNotValidException 異常。默認情況下,這個異常返回的信息不太友好,可能是這樣的:
{
"timestamp": "2024-01-01T12:00:00.000+00:00",
"status": 400,
"error": "Bad Request",
"message": "Validation failed for object='userRegistrationRequest'. Error count: 2",
"path": "/api/users/register"
}
為了提升用戶體驗,我們可以用全局異常處理器來統一格式化錯誤信息:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationException(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error -> {
errors.put(error.getField(), error.getDefaultMessage());
});
return ResponseEntity.badRequest().body(errors);
}
}
現在,當參數校驗失敗時,返回的錯誤信息會變成這樣:
{
"username": "用戶名長度必須在3到20之間",
"password": "密碼不能為空"
}
清晰又直觀,用戶一看就明白自己錯在哪兒了。
三、應對復雜場景的高級技巧
1. 分組校驗
有些場景下,不同的接口對參數的校驗規則是不一樣的,比如:
- 注冊接口要求 username 和 password 是必填項;
- 更新接口只需要校驗 email 和 age。
這種情況下,可以用 分組校驗 來解決。
定義校驗分組
public interface RegisterGroup {}
public interface UpdateGroup {}
在字段上指定分組
public class UserRequest {
@NotNull(groups = RegisterGroup.class, message = "用戶名不能為空")
@Size(min = 3, max = 20, groups = RegisterGroup.class, message = "用戶名長度必須在3到20之間")
private String username;
@NotNull(groups = RegisterGroup.class, message = "密碼不能為空")
private String password;
@Email(groups = UpdateGroup.class, message = "郵箱格式不正確")
private String email;
@Min(value = 1, groups = UpdateGroup.class, message = "年齡必須是正整數")
private Integer age;
}
在 Controller 中指定分組
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping("/register")
public ResponseEntity<String> register(@Validated(RegisterGroup.class) @RequestBody UserRequest request) {
return ResponseEntity.ok("注冊成功!");
}
@PutMapping("/update")
public ResponseEntity<String> update(@Validated(UpdateGroup.class) @RequestBody UserRequest request) {
return ResponseEntity.ok("更新成功!");
}
}
2. 自定義校驗注解
如果 Hibernate Validator 提供的注解不能滿足需求,還可以自定義校驗注解。例如,校驗手機號格式。
定義注解
@Documented
@Constraint(validatedBy = PhoneValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPhone {
String message() default "手機號格式不正確";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
實現校驗邏輯
public class PhoneValidator implements ConstraintValidator<ValidPhone, String> {
private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && value.matches(PHONE_REGEX);
}
}
使用自定義注解
public class UserRequest {
@ValidPhone
private String phone;
}
四、總結
優雅的參數校驗不僅能提高代碼的可維護性,還能顯著提升用戶體驗。
在 Spring Boot 中,通過使用 Hibernate Validator 提供的注解,配合分組校驗、自定義校驗和統一異常處理。
我們可以輕松實現簡潔、高效、可擴展的參數校驗機制。
優雅的參數校驗的秘籍是:
- 注解優先:能用注解解決的校驗,就不要手寫邏輯代碼。
- 分離校驗邏輯:參數校驗應該集中在 DTO 層,避免散落在業務代碼中。
- 全局統一異常處理:確保錯誤信息規范化、友好化。
- 合理使用分組校驗:根據接口需求靈活調整校驗規則。
- 覆蓋邊界條件:通過單元測試驗證校驗邏輯,確保沒有漏網之魚。