邮箱校验正则的边界问题
邮箱格式的完整语法定义在 RFC 5321 与 5322 里,允许引号包裹的本地部分、注释、IP 字面量域名等大量现实中几乎不用的写法。照搬整份语法写出巨型正则既难维护也容易被构造出回溯爆炸。工程上正确的分层是:用一条宽松正则做格式粗筛,再用发确认邮件来确定地址真实可用。
基本信息
| 长度约束 | 本地部分不超过 64 字符,整个地址通常不超过 254 字符 |
|---|---|
| 本地部分 | 常见字符为字母、数字与 . _ % + -,点号不能出现在开头或结尾,也不能连续两个 |
| 域名部分 | 按域名规则:标签由字母数字与连字符组成,不以连字符开头或结尾,至少两个标签 |
| 大小写 | 域名不区分大小写,本地部分理论上区分大小写,实践中都按不区分处理 |
| 推荐做法 | 正则只做粗筛,DNS 与真实投递能力交给后续环节验证 |
典型使用场景
- 表单提交前的即时提示:只挡明显写错的输入,减少无效请求,不承担最终判定责任
- 批量导入前的数据清洗:标记可疑记录供人工复核,而不是直接判定非法
- 日志与数据库中提取地址:用宽松模式抽取候选值,再逐个规范化
- 风控与反垃圾:把明显异常的形状(无域名、超长本地部分)作为特征之一,而非唯一判据
- 对接第三方数据:不同系统对合法地址的判定差异大,粗筛标准要与合作方对齐
实用的分层写法
实用的分层写法
# 第一层:粗筛,只挡明显错误,允许最常见的合法字符集
^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$
# 第二层:把域名规则写细,标签不以连字符开头结尾,禁止连续点号
^[A-Za-z0-9._%+-]+@(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z]{2,}$
# 第三层:在代码里补长度约束,正则不适合表达 64 与 254 这类整体限制
local, _, domain = addr.rpartition('@') # 先拆再分别判断长度
if len(local) > 64 or len(addr) > 254: reject()
# 第四层:DNS 查询与确认邮件,用来判断地址是否真实可用检查项与适合的手段
| 检查项 | 适合的手段 | 说明 |
|---|---|---|
| 只含一个 @ | 正则或代码计数 | 多于一个 @ 的地址在实践中不被接受 |
| 字符集形状 | 正则粗筛 | 宽松即可,过严会误杀合法地址 |
| 域名标签规则 | 正则,或域名校验函数 | 标签长度与连字符位置有明确规则 |
| 长度上限 | 代码判断 | 本地 64、整体 254,用正则表达不直观 |
| 地址是否真实存在 | 确认邮件 | 正则无法验证,只能验证格式 |
允许非 ASCII 地址与国际化域名涉及 SMTPUTF8 等扩展,不同邮件系统支持程度不一,处理这类输入时应先与邮件服务商确认能力,再决定是否在入口放行。
本地部分里的加号常用于子地址(加号后内容被投递到同一邮箱),校验时把它排除会拒绝掉大量正常注册;下划线、百分号、连字符同理,都属于实际常见字符。