手机号校验的宽严取舍
手机号规则会随时间变化:号段持续新增,携号转网让运营商与号段不再对应,虚拟运营商与物联网卡又带来新的形态。把号段写死在正则里,短则几个月就要改一次。更稳的做法是:只校验「长度加首位数字」这一层形状,把输入先归一化,复杂判断交给专门的号码库。
基本信息
| 内地手机号 | 11 位数字,以 1 开头,第二位常见为 3 到 9 |
|---|---|
| 国际格式 | E.164 规定总长不超过 15 位,以加号加国家码开头 |
| 归一化 | 去掉空格、连字符、括号与斜杠,把国家码前缀统一为加号加国家码 |
| 不可用正则判断 | 号码归属地、运营商、是否在用、是否可接通 |
典型使用场景
- 注册与登录表单:提交前挡掉位数或字符明显错误的输入,降低无效短信成本
- 通讯录导入:清洗历史数据时先归一化再判断格式,把异常记录单独列出
- 数据脱敏:识别号码位置后做中间四位掩码,日志与导出文件一并处理
- 风控特征:把格式异常与频繁重试作为特征之一,而不是唯一的拦截依据
- 多地区业务:按用户所在地区选择规则,避免用同一套规则处理所有国家
校验与归一化写法
校验与归一化写法
# 保守写法:只限制位数与首位,号段变化时不需改模式
^1[3-9]\d{9}$
# 允许国家码前缀:加号可省,国家码前后可带空格或连字符
^(?:\+?86[ -]?)?1[3-9]\d{9}$
# 国际通用形状:E.164 总长限制,具体国家规则交给号码库
^\+[1-9]\d{6,14}$
# 归一化:先去分隔符,再统一国家码写法
digits = re.sub(r'[\s()\-]', '', raw) # 去掉常见分隔符
if digits.startswith('0086'): digits = '+86' + digits[4:]校验尺度与代价
| 尺度 | 模式 | 代价 |
|---|---|---|
| 最宽 | 只判断长度与首位 | 几乎不改,但会放过少量无效号码 |
| 中等 | 限制第二位数字范围 | 号段新增时可能需要微调 |
| 最严 | 逐个列举号段 | 需频繁维护,漏更新会拒掉真实用户 |
| 按国别 | 分地区规则或号码库 | 实现成本高,但准确率最好 |
如果业务确实要求校验到号段,把号段清单放到配置或数据库里,做成可按期更新的数据,而不是写死在代码或模式中;这样一次更新能覆盖所有使用方。
手机号是个人敏感信息:采集需说明用途,展示与日志应脱敏,导出文件要控制范围;把完整号码写入普通日志是常见但代价很高的合规问题。