參數校驗的六大神功!
新手司機翻車實錄
"哥,注冊接口又被刷爆了!
"某一個周末下午,我接到電話,打開日志一看,NullPointerException
堆棧里有38個不同位置的校驗邏輯。
原來新人小王在Controller里寫滿了這樣的代碼:
// 典型錯誤示范(轉載自某小廠祖傳代碼)
public String register(UserDTO user) {
if (user.getName() == null) {
return"名字不能為空";
}
if (user.getAge() == null) {
return"年齡不能為空";
}
if (user.getAge() < 18) {
return"年齡不能小于18歲";
}
if (!user.getPhone().matches("^1[3-9]\\d{9}$")) {
return"手機號不合法";
}
// ...后續還有20個if...
}
這才是代碼界的"九轉大腸"——每個入口都讓人窒息。
作為一位有很多開發經驗的老司機,今天,老夫帶你修煉參數校驗的6大神功。
圖片
希望對你會有所幫助。
第一重:JSR規范基礎功
1.1 HibernateValidator瞬煉大法
可以使用Hibernate中Validator框架做參數校驗,具體代碼如下:
public class UserDTO {
@NotBlank(message = "名稱要填,皮這一下很開心?")
private String name;
@NotNull
@Min(value = 18, message = "未成年禁止入內")
@Max(60)
private Integer age;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "這手機號是哪國來的?")
private String phone;
}
// Controller層啟用校驗(新手必知第一步)
@PostMapping("/register")
public Result register(@Valid @RequestBody UserDTO user) {
// 業務代碼...
}
技術要點:
- 引入
spring-boot-starter-validation
依賴(調料包記得加) @Valid
注解要放在入參側(別貼在DTO類上)- 錯誤信息會進
BindingResult
(打掃戰場需要手動處理)
第二重:全局異常擒龍手
2.1 統一異常攔截器
我們需要對異常進行統一攔截。
這樣在出現參數校驗異常,比如空指針時,不會把服務的內部錯誤信息直接輸出給用戶。
通過@RestControllerAdvice和@ExceptionHandler注解實現統一異常攔截器的功能。
具體代碼如下:
@RestControllerAdvice
publicclass GlobalExceptionHandler {
// 專治各種不服校驗
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidException(MethodArgumentNotValidException e) {
BindingResult result = e.getBindingResult();
return Result.fail(result.getFieldError().getDefaultMessage());
}
}
// 返回格式規范(示例)
publicclass Result<T> {
private Integer code;
private String msg;
private T data;
publicstatic <T> Result<T> fail(String message) {
returnnew Result<>(500, message, null);
}
}
反爬蟲機制:
- 禁止直接暴露字段名給前端(攻擊者會利用字段名信息)
- 錯誤信息字典化管理(后面會教國際化這招)
第三重:自定義校驗屠龍技
3.1 手機/郵箱二元校驗
有時候,Hibernate Validator框架或者其他校驗框架定義的校驗不滿足需求,我們需要自定義校驗規則。
則可以自定義注解,實現ConstraintValidator接口,來實現具體的自定義的校驗邏輯。
自定義注解@Contact在字段上使用。
具體代碼如下:
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = ContactValidator.class)
public @interface Contact {
String message() default "聯系方式格式錯誤";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// 校驗邏輯實現(不要相信前端的下拉框!)
publicclass ContactValidator implements ConstraintValidator<Contact, String> {
privatestaticfinal Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
privatestaticfinal Pattern EMAIL_PATTERN = Pattern.compile("^\\w+@\\w+\\.\\w+$");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return PHONE_PATTERN.matcher(value).matches()
|| EMAIL_PATTERN.matcher(value).matches();
}
}
六邊形戰士培養計劃:
- 可通過
context.buildConstraintViolationWithTemplate()
動態修改錯誤信息 - 支持DI注入Spring管理的Bean(比如從數據庫加載正則)
第四重:分組校驗北冥功
4.1 增刪改查不同校驗規則
對于增刪改查中,對于實體對象中的同一個參數,在不同的應用場景中需要做不同分組校驗。
具體代碼如下:
// 定義校驗組別(劃分陣營)
publicinterface CreateGroup {}
publicinterface UpdateGroup {}
// DTO根據場景應用分組
publicclass ProductDTO {
@Null(groups = UpdateGroup.class)
@NotNull(groups = CreateGroup.class)
private Long id;
@NotBlank(groups = {CreateGroup.class, UpdateGroup.class})
private String name;
}
// 控制層按需激活校驗組
@PostMapping("/create")
public Result create(@Validated(CreateGroup.class) @RequestBody ProductDTO dto) {
// 創建邏輯
}
多副本作戰手冊:
- Default組始終生效(除非使用
groups
顯式配置) - 妙用
@ConvertGroup
進行分組轉換
第五重:跨界校驗凌波微步
5.1 跨字段關系校驗
如果存在跨字段關系校驗的情況,即組合條件校驗,比如:用戶密碼和確認密碼,可以將自定義注解作用在類上。
具體代碼如下:
@Target(TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordValidator.class)
public @interface PasswordValid {
String message() default "兩次密碼不一致";
// ...
}
publicclass PasswordValidator implements ConstraintValidator<PasswordValid, UserDTO> {
@Override
public boolean isValid(UserDTO user, ConstraintValidatorContext context) {
return user.getPassword().equals(user.getConfirmPassword());
}
}
// 應用到類級別
@PasswordValid
publicclass UserDTO {
private String password;
private String confirmPassword;
}
風控新法:
- 適用于訂單金額與優惠券匹配等業務規則
- DDD值對象的天然場景
第六重:規則引擎之天機策
天機殿的自動化戰場
新來的產品小妹指著參數校驗文檔:"每次改個手機號正則都要等發版?
"我默默掏出了祖傳的規則引擎。
這種政商聯動的需求,是時候施展大型工程的必殺技了!
6.1 規則引擎的三層境界
第一境:硬編碼校驗(青銅段位的if-else)第二境:配置化校驗(黃金段位的數據庫規則表)第三境:熱力場作戰(王者段位的動態規則引擎)
6.2 Drools天機大陣部署實錄
戰場場景:信貸額度動態校驗(每小時調整風控模型) 。
天機規則文件如下:
// 天機規則文件(credit_rule.drl)
rule "白領貸基礎校驗"
when
$req : LoanRequest(
occupation == "白領",
salary > 10000,
age >= 25 && age <= 45
)
then
$req.setRiskScore(-10); //加分項
end
rule "高危行業攔截"
when
$req : LoanRequest(
industry in ("賭博業", "傳銷"),
location.contains("緬甸")
)
then
throw new ValidationException("閣下莫非是緬北戰神?");
end
布陣心法:
圖片
陣法要訣:
- 規則文件按業務線拆分(金融/電商/社交各立山頭)
- 使用kie-maven-plugin自動編譯規則文件
- KieScanner監聽規則變更(天機更新不重啟服務)
6.3 SpringBoot接引天機大陣
法咒集成:
@Configuration
publicclass DroolsConfig {
@Bean
public KieContainer kieContainer() {
KieServices ks = KieServices.Factory.get();
KieFileSystem kfs = ks.newKieFileSystem();
// 加載天機卷軸(規則文件)
Resource resource = new ClassPathResource("rules/credit_rule.drl");
kfs.write(ks.getResources().newInputStreamResource(resource.getInputStream())
.setTargetPath("credit_rule.drl"));
KieBuilder kieBuilder = ks.newKieBuilder(kfs).buildAll();
return ks.newKieContainer(kieBuilder.getKieModule().getReleaseId());
}
}
// Controller層調用天尊之力
@PostMapping("/apply")
public Result applyLoan(@RequestBody LoanRequest request) {
kieSession.insert(request);
kieSession.fireAllRules(); // 執行天機推演
return riskService.process(request);
}
天機沙箱防御:
- 限制規則中eval()的使用次數(防CPU過載)
- 為每個請求創建獨立KieSession(防線程污染)
- 設置規則執行超時熔斷(天機殿也有算不動的時候)
6.4 天機策反制訣竅
某次上線后,規則引擎的神操作:
rule "特殊時段放水"
when
$req : LoanRequest(hour > 2 && hour < 5)
then
$req.setCreditLimit(50000); //給值夜班的兄弟開后門
end
反制方案:
- 規則提交走審批流(太上長老團聯署制)
- 生產環境禁用update/modify關鍵字(防自動奪舍)
- 規則版本回滾機制(祭出玄天寶鏡倒轉時空)
祖師爺級參數校驗綱領
段位 | 招式名稱 | 修煉難度 | 適用場景 | 破壞力 |
青銅 | if-else硬編碼 | ★☆☆ | 小型工具類 | ??? |
白銀 | JSR注解大法 | ★★☆ | 常規CRUD | ?? |
黃金 | 全局異常攔截 | ★★★ | RESTful API | ? |
鉑金 | 定制校驗規則 | ★★★☆ | 復雜業務規則 | ? |
鉆石 | 組合條件校驗 | ★★★★ | 跨字段業務約束 | ? |
王者 | 規則引擎整合 | ★★★★★ | 動態風控場景 | ? |
避坑法門
- 不過三:Controller層校驗不要超過三層(應該轉給Service)
- 見好就收:業務規則校驗與基礎格式校驗分離
- 防君子更防小人:服務端校驗必須存在(前端校驗是防君子用的)
- 語義明確:錯誤提示避免暴露敏感信息(比如"用戶不存在"改為"賬號或密碼錯誤")
最后提醒各位大俠:好的參數校驗就像空氣——你平時感受不到它的存在,但一旦失去它,整個系統瞬間崩塌!(代碼fields正提刀趕來)