
本文介绍如何根据 `contacttype` 枚举值(如 `email` 或 `phonenumber`)对 `contactvalue` 字段执行差异化校验:手机号仅允许数字,邮箱则需支持更复杂的字符组合,并提供可扩展的正则驱动校验方案。
在构建领域模型(如 ContactDTO)时,常需根据业务类型动态约束字段格式。例如,当 contacttype 为 PHONENUMBER 时,contactvalue 必须纯数字;而为 EMAIL 时,则需兼容 @、.、下划线、连字符等合法邮箱字符——此时简单的 isAlphanumeric() 校验会误判(如 "user.name@domain.co.uk" 含点号和 @,非纯字母数字)。
✅ 推荐方案:枚举内聚校验逻辑(高可维护性)
将校验规则封装进 ContactType 枚举中,既保证类型安全,又便于后续新增类型(如 WECHAT_ID、QQ_NUMBER):
import java.util.regex.Pattern;
public enum ContactType {
PHONENUMBER("^\\d+$"), // 纯数字,至少一位
EMAIL("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"); // RFC 5322 简化版
private final Pattern pattern;
ContactType(String regex) {
this.pattern = Pattern.compile(regex);
}
public void validate(String input) {
if (input == null || input.trim().isEmpty()) {
throw new IllegalArgumentException(name() + " value cannot be null or empty");
}
if (!pattern.matcher(input.trim()).matches()) {
throw new IllegalArgumentException(
String.format("%s value '%s' does not match required format", name(), input)
);
}
}
}对应 DTO 中的设置方法简洁清晰:
public class ContactDTO {
private ContactType contactType;
private String contactValue;
private Long studentId;
public void setContact(ContactType type, String value) {
type.validate(value); // ✅ 统一入口,自动路由到对应规则
this.contactType = type;
this.contactValue = value.trim();
}
// getter/setter 省略...
}⚠️ 注意事项与最佳实践
- 邮箱校验勿过度简化:避免使用 StringUtils.isAlphanumeric(),它会拒绝所有合法邮箱中的 @、.、+、-、_ 等字符;
- 空值与空白处理:validate() 方法应主动检查 null 和空白字符串,防止后续 NPE 或脏数据;
- 正则性能足够:单次匹配开销极小,且 Pattern 在枚举构造时已编译复用,无需担心性能损耗;
-
测试覆盖建议:为每种 ContactType 编写边界用例,例如:
- PHONENUMBER: "1234567890" ✅,"123abc" ❌,"" ❌
- EMAIL: "test@example.com" ✅,"invalid@.com" ❌,"user+tag@domain.co.uk" ✅
? 总结
通过将格式规则内聚于枚举,不仅消除了外部 switch 的重复校验逻辑,还提升了代码可读性与可扩展性。当业务需要新增联系方式类型时,只需扩展枚举并提供对应正则,无需修改任何 DTO 或服务层代码——真正实现“开闭原则”。











