如何在枚举驱动的 DTO 中实现基于类型的数据格式校验

本文介绍如何根据 `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 或服务层代码——真正实现“开闭原则”。