日常用的邮箱正则
大多数应用其实并不需要"完美"的邮箱校验器。它们要的只是一个能在注册时拦住手滑输错、挡掉空字符串、挡掉多余空格、挡掉漏写 @ 的符号组合。当同事在代码评审里问"来个邮箱正则"的时候,我一般给下面这一行:
^[^\s@]+@[^\s@]+\.[^\s@]{2,}$
它很短,几乎能原样贴进任何正则引擎(PCRE、Python、JavaScript、Go 都吃得下),而且它没有假装自己是一套标准。把它当成一道门,而不是一纸判决书。
这个正则到底匹配了什么
从左到右读,每一段都有存在的理由:
^把匹配锚定在字符串开头,这样合法地址才不会藏在长句子中间蒙混过关。[^\s@]+是本地名。它允许一个或多个既不是空白也不是 @ 的字符,于是user、name.surname+tag、dev.team都过得去。@是那个分隔符,而且必须恰好一个。[^\s@]+是域名,同样拒绝空白和第二个 @。\.匹配一个真正的小数点。注意反斜杠:没转义的点会匹配任意字符,于是a@b_c也会被放进来。[^\s@]{2,}是顶级域,强制两位及以上、且非空白非 @ 的字符。正是它把[email protected]这种一位顶级域挡在门外。$锚定结尾,所以尾随的垃圾会失败。
结果就是:它接受 [email protected] 和 [email protected],拒绝 @nope.com、no-at-sign.com 和 a@b c。
RFC 5322 怎么把"合法"这件事搅黄了
如果你去翻"正确"的邮箱正则,迟早会撞上 RFC 5322。它允许的一些写法,几乎没有任何注册表单应该接受,而一份完全合规的正则长达几百字符,实战里还是错的。随便举几个:
- 引号包裹的本地名里可以有空格、可以有 @、可以有大部分标点:
"john doe"@example.com甚至"a\b@c"@example.com都合法。 - 圆括号注释几乎能插在任何地方:
user(comment)@example.com。 - 域名可以是一个方括号里的 IP 字面量:
user@[192.168.1.1]。 - 国际化邮箱在本地名里用 UTF-8,纯 ASCII 的正则根本描述不了。
写一条把这些全放行的正则,技术上做得到,意义却没有。你会拒绝掉用户真实在用的地址,同时又放行你的邮件服务器会直接弹回的地址。
为什么正则永远给不出正确答案
核心论点很简单:正则只能查"形状",查不了"可达性"。一个满足地球上所有规则的形状,如果没人看那个收件箱,就一文不值。真正算数的校验只有一种——发一封带确认链接的邮件,然后等对方点开。这一步同时证明了三件事,而这是任何正则都做不到的:地址是真的、这个人控制着它、而且他愿意收你的信。
所以正确的架构很无聊:用一条宽松正则在表单层拦掉明显手滑,把过关的存下来,然后发验证邮件。如果点不开,账号就一直是不活跃状态。没有哪条正则能替掉这一步。
把真实地址写残的常见错误
下面这些是我在生产代码里见得最多的坑:
| 坏习惯 | 它害了谁 | 改法 |
|---|---|---|
在本地名里禁掉 + | Gmail 用户失去 name+shop 标签,这是人家依赖的功能 | 放行 + 和多数 ASCII 标点 |
| 拒绝子域名 | [email protected] 合法且常见 | 用 @ 后接一个或多个点分隔标签 |
把 . 写成没转义的 . | 没转义的点匹配任意字符 | 转义它:\. |
| 写死顶级域白名单 | 新顶级域不断冒出来,你会过时 | 改成要求长度 {2,} |
合法与非法,一眼对照
用下面这张表快速测一下你正在考虑的正则。一条靠谱的实用正则应当接受上半组、拒绝下半组。
| 输入 | 期望 | 说明 |
|---|---|---|
[email protected] | 接受 | 最正常的样子 |
[email protected] | 接受 | 加号标签加子域名 |
@nope.com | 拒绝 | 本地名为空 |
spaces [email protected] | 拒绝 | 空格不允许 |
[email protected] | 拒绝 | 一位顶级域 |
比正则有用的:语言内置校验
每个正经平台都自带邮箱检查器,而且几乎都比你手写的强:
- HTML5 直接给了你
<input type="email">,免费套用浏览器自己的规则和原生界面。 - Python 有
email.utils.parseaddr以及email头解析器。 - JavaScript 没有内置校验器,但封装了 RFC 的库一行就能装上。
- 多数后端框架(Django、Rails、Laravel)在模型层就帮你校验了邮箱。
该靠它们就靠它们,自定义正则保持宽松,把严格版本留给日志解析这种只需要快速启发式的地方。
更严格的变体,以及什么时候用它
如果你必须拒绝 ASCII 之外的任何东西,下面这个变体把本地名锁进一个安全字符集,并要求字母顶级域:
^[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$
拿它对付那些一碰到引号或 Unicode 就崩的遗留系统。但别拿它当"地址可用"的证明,也别拿它去挡那些有充分权利使用非 ASCII 邮箱的国际用户。
怎么测你的正则
把你候选的那条连同上面的正常样例和陷阱样例一起丢进实时测试器。重点盯两种失败:一种是放行了 [email protected](顶级域太短),一种是拒绝了 [email protected](禁了加号)。能扛住这两下的,做表单层检查就够用了。记住收尾这条规矩:正则管形状,确认邮件管真相。