手機號驗證是表單校驗高頻需求。中國大陸號段頻繁變更,硬編碼正則極易過期。本文提供可維護的驗證方案,涵蓋正則寫法、號段維護策略、單元測試與生產環境建議。
// 僅支援 13/15/18 開頭,已過時
Pattern p = Pattern.compile("^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$");
缺點:漏掉 14、16、17、19 等新號段;[^4,\\D] 寫法易誤解;無法應對未來擴展。
public class MobileValidator {
// 從配置中心/資料庫載入,支援熱更新
private static final Set VALID_PREFIXES = new HashSet<>(Arrays.asList(
"130","131","132","133","134","135","136","137","138","139",
"147","149","150","151","152","153","155","156","157","158","159",
"166","170","171","172","173","174","175","176","177","178",
"180","181","182","183","184","185","186","187","188","189",
"190","191","192","193","195","196","197","198","199"
));
private static final Pattern FORMAT_PATTERN = Pattern.compile("^1\\d{10}$");
public static boolean isValid(String mobile) {
if (mobile == null) return false;
if (!FORMAT_PATTERN.matcher(mobile).matches()) return false;
return VALID_PREFIXES.contains(mobile.substring(0, 3));
}
}
O(1) 查詢前綴。@ParameterizedTest
@CsvSource({
"13800138000, true", // 經典號段
"19988889999, true", // 新號段
"12000000000, false", // 錯誤前綴
"1380013800, false", // 10 位
"138001380000, false", // 12 位
"abcdefghijk, false", // 非數字
", false", // 空字串
"null, false" // null
})
void testMobile(String input, boolean expected) {
assertEquals(expected, MobileValidator.isValid(input));
}
valid.prefixes,應用啟動時載入並監聽變更。@Target({FIELD, METHOD, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = MobileValidator.class)
public @interface ValidMobile {
String message() default "手機號格式不正確";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
public class MobileValidator implements ConstraintValidator {
@Override public boolean isValid(String value, ConstraintValidatorContext context) {
return MobileValidator.isValid(value);
}
}
// 使用
public class UserDTO {
@ValidMobile
private String mobile;
}
libphonenumber 庫:PhoneNumberUtil util = PhoneNumberUtil.getInstance();
PhoneNumber number = util.parse(phone, "CN"); // 或 "HK", "US"
boolean valid = util.isValidNumber(number);
| 錯誤寫法 | 問題 | 修正 |
|---|---|---|
^1[3-9]\\d{9}$ | 過於寬鬆,允許 140、160 等未分配段 | 白名單前綴 |
Pattern.compile(...) 在方法內 | 每次調用編譯正則,效能差 | 靜態常量編譯一次 |
未處理 null/空白 | 拋 NPE 或誤判 | 首行防御性檢查 |
| 只驗證格式不驗證號段 | 偽造號碼通過 | 雙重校驗:格式 + 前綴 |