Pattern pattern = Pattern.compile(regex); // 編譯正則(可複用、執行緒安全)
Matcher matcher = pattern.matcher(input); // 創建匹配器
boolean matches = matcher.matches(); // 整體匹配
完整 RFC 5322 正則超過 6000 字元,**生產環境不建議直接使用**。
/**
* 實用郵箱正則:
* - 用戶名:字母數字、點、底線、減號、百分號、加號
* - 域名:字母數字、減號、點,每段不以減號開頭結尾
* - 頂級域:2-63 字母
*/
private static final String EMAIL_REGEX =
"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,63}$";
// HTML5 表單驗證使用的正則
private static final String EMAIL_REGEX_HTML5 =
"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$";
/**
* 中國大陸手機號正則(2024 最新號段)
* 13x, 14[5-9], 15[0-3,5-9], 16[6,7], 17[0-8], 18x, 19[1,3,8,9]
*/
private static final String MOBILE_REGEX =
"^(?:13\\d|14[5-9]|15[0-35-9]|16[67]|17[0-8]|18\\d|19[1389])\\d{8}$";
package com.example.util;
import java.util.regex.Pattern;
public final class ValidatorUtils {
private ValidatorUtils() {}
// 預編譯 Pattern(執行緒安全、效能最優)
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,63}$");
private static final Pattern MOBILE_PATTERN =
Pattern.compile("^(?:13\\d|14[5-9]|15[0-35-9]|16[67]|17[0-8]|18\\d|19[1389])\\d{8}$");
private static final Pattern ID_CARD_PATTERN =
Pattern.compile("^\\d{17}[\\dXx]$"); // 簡版,嚴格版需校驗碼算法
public static boolean isEmail(String email) {
return email != null && EMAIL_PATTERN.matcher(email).matches();
}
public static boolean isMobile(String mobile) {
return mobile != null && MOBILE_PATTERN.matcher(mobile).matches();
}
public static boolean isIdCard(String idCard) {
return idCard != null && ID_CARD_PATTERN.matcher(idCard).matches();
}
// 通用正則驗證
public static boolean matches(String input, String regex) {
return input != null && Pattern.matches(regex, input);
}
// 提取匹配內容
public static String extract(String input, String regex, int group) {
if (input == null) return null;
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
return m.find() ? m.group(group) : null;
}
}
@Test
void testEmailValidation() {
assertTrue(ValidatorUtils.isEmail("user@example.com"));
assertTrue(ValidatorUtils.isEmail("user.name+tag@sub.domain.co.uk"));
assertFalse(ValidatorUtils.isEmail("invalid"));
assertFalse(ValidatorUtils.isEmail("@domain.com"));
assertFalse(ValidatorUtils.isEmail("user@"));
}
@Test
void testMobileValidation() {
assertTrue(ValidatorUtils.isMobile("13800138000"));
assertTrue(ValidatorUtils.isMobile("15912345678"));
assertTrue(ValidatorUtils.isMobile("19988888888"));
assertFalse(ValidatorUtils.isMobile("12345678901")); // 無效號段
assertFalse(ValidatorUtils.isMobile("1380013800")); // 位數不足
}
| 陷阱 | 錯誤做法 | 正確做法 |
|---|---|---|
| 重複編譯 Pattern | 方法內 `Pattern.compile()` | 靜態常量預編譯 |
| 用 `matches()` 做部分匹配 | `matcher.matches()` 要求整體匹配 | 用 `matcher.find()` 找子串 |
| 忽略 null 輸入 | 直接傳給 matcher 拋 NPE | 先判 null 返回 false |
| 正則注入風險 | 用戶輸入直接拼入正則 | 嚴格白名單、或用 `Pattern.quote()` |
| 手機號段過期 | 寫死舊號段 | 定期更新、或接入號段查詢 API |
// 自定義註解
@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 ValidatorUtils.isMobile(value);
}
}
// 使用
public class UserDTO {
@ValidMobile
private String phone;
}
正則驗證核心在於:**預編譯 Pattern、處理 null、定期更新業務規則(特別是手機號段)**。郵箱驗證建議採用「簡單正則 + 發送驗證碼」雙重保障;手機號驗證需緊跟運營商號段變更。將驗證邏輯封裝為工具類並整合 Bean Validation,能實現「定義一次、到處生效」的乾淨架構。