手机号验证技术方案:号段规则与正则表达式
引言
手机号是当下互联网产品最重要的用户标识:注册、登录、营销触达、风控、消息通知......几乎每个业务系统都要处理手机号。也正因为如此,手机号验证是一道几乎天天要写的代码。
但大多数项目的手机号校验长这样:
java
if (!phone.matches("^1[3-9]\\d{9}$")) {
throw new IllegalArgumentException("手机号格式不正确");
}
几行代码,看似能跑。但你真的了解背后的问题吗:为什么是 11 位?为什么一条正则就够用了?"校验通过"和"号码真实存在"是一回事吗?
本文从号段规则出发,逐层拆解中国手机号验证的技术方案,并给出在 ValidX 中的落地实践。
一、手机号码的结构:11 位的"密码"
1.1 三段式结构
一个标准的中国大陆手机号码是 11 位数字,可以拆成三段:
1 3 8 0 0 1 3 8 0 0 0
│ └──┬──┘ └────────┬────────┘
│ │ └── 用户号码(8 位)
│ └── 号段(3 位)
└── 第一位固定为 1
- 第一位 :固定为
1,这是工信部为移动通信分配的前缀; - 号段(第 2-4 位):标识运营商与号段归属;
- 用户号码(第 5-11 位):由运营商按序分配。
以国际视角看,手机号在国际格式下写作 +86 138 0013 8000,其中 86 是国际区号。国内存储一般只存 11 位本号。
1.2 为什么是 11 位?
第一位固定为 1,意味着号码空间主要取决于后 10 位。若号段按"第二位 + 第三位"组合(13x、15x 这种),理论容量为:
10(第2位)× 10(第3位)× 10^8(后8位)= 100 亿
减去第一位 1 的约束,实际可分配空间依然非常充裕。但随着物联网卡、虚拟运营商、广电入局,号段从最初几个增长到现在的上百个,11 位仍然够用,但号段枚举表已经复杂到不该手写维护。
二、号段规则全解析
2.1 号段归属总表
截至 2025 年,主要运营商的公众移动通信号段大致如下(部分细节会随运营商调整):
| 运营商 | 号段 |
|---|---|
| 中国移动 | 134-139、147、148、150-152、157-159、172、178、182-184、187-188、195、197、198 |
| 中国联通 | 130-132、145、146、155-156、166、171、175-176、185-186、196 |
| 中国电信 | 133、149、153、173、174、177、180-181、189、190、191、193、199 |
| 中国广电 | 192 |
| 虚拟运营商 | 162、165、167、170、171(部分) |
2.2 号段分配的内在规律
仔细看这张表,能总结出几条规律:
- 第二位从 3 到 9 全覆盖 :
13x、14x、15x、16x、17x、18x、19x均已开放,这也是宽松正则1[3-9]的由来------但"全覆盖"不代表"全号段",16x只开放了其中几个。 - 早期号段靠"细分"扩展 :当年号段紧张,
135-139给了移动后,联通拿不到独立第二位,只能复用130-132,电信用133。号段归属看的是第二、三、四位组合。 - 新号段按"整段"放量 :
166(联通)、198(移动)、199(电信)、192(广电)是近年新增的整段号段,规则简单、好记。 14x与物联网 :145/146/147/149等部分14x号段早期用于数据卡/物联网卡,普通语音卡号码在14段中并非全部开放。
2.3 为什么不能只信"号段表"
号段表是动态的。今天不存在的号段,下个月可能就放号了;反之,号段属于某运营商,不代表该号段 100% 全部在用(有些号段仅部分放号)。
所以设计验证方案时,第一原则是:
号段校验只保证"格式上像手机号",不保证"这个号码真实存在、可接通"。
真实性与可用性,必须由短信验证码、运营商接口(实名校验、在网状态查询)来保证。
三、验证正则:一条正则就够
ValidX 的 ChinesePhoneValidator 采用宽松格式校验,正则只有一条:
java
String regex = "^1[3-9]\\d{9}$"; // 1 开头,第二位 3-9,共 11 位
它是当下主流平台(阿里、腾讯等)的通行做法,只表达三件事:11 位、1 开头、第二位在 3-9 之间。
3.1 正则逐段拆解
| 片段 | 含义 |
|---|---|
^ / $ |
锚点:必须完全匹配整串,防止部分匹配 |
1 |
第一位固定为 1(移动通信前缀) |
[3-9] |
第二位 3-9,覆盖 13x~19x 全部已开放的第二位 |
\d{9} |
剩余 9 位数字,与前面合计 11 位 |
整条正则的判定逻辑:
text
1 + 第二位(3-9) + 9 位数字 = 11 位
3.2 为什么不枚举号段?
有人会问:号段归属表那么详细,为什么不把已开放号段逐一写进正则?原因有三:
- 号段是动态的 :今天枚举的号段表,明天就可能过时。历史上
199、192刚放号时,不少项目因为正则里没有这些号段而误杀真实用户; - 格式层不该越权:格式校验只回答"长得像不像手机号","号码是否真实存在、能否接通"由短信验证码与运营商接口回答;
- 误杀比误收更可怕:误收一个假号码,最终会被短信验证码拦下;误杀一个真号码,用户直接流失。
因此 ValidX 明确不做号段枚举 。154、194 这类尚未放号的号段也能通过格式层------这是有意为之:格式层放宽,真实校验下沉。任何"长得像手机号"的号码先进来,再由短信验证码这道关卡决定它是否真实存在。
四、写手机号正则的常见陷阱
4.1 丢失锚点:matches 与 find 的错位
正则里的 ^ 和 $ 不可省略。Java 的 String.matches() 本身就是全匹配,但若用 Pattern.find() 或前端 test() 之外的方式做部分匹配,12345678901 里也能"找到"一段 1380013800。锚点不是装饰,是语义的一部分。
4.2 "假严谨":把格式校验当实名校验
13800138000 这种测试号、以及随便凑的 11 位数字,格式校验全部通过,但它不代表一个真实用户。格式校验、短信验证、运营商实名核验是三道不同强度的关卡,别指望第一道关卡解决全部问题。
4.3 前后端规则不一致
前端一套正则、后端又一套正则,两边规则悄悄漂移,最终表现为"前端提示通过、后端拒绝提交"的诡异 bug。最佳实践是前后端共用一份规则清单,或在后端维护权威规则、前端只做轻量兜底。
五、ValidX 中的落地实践
ValidX 将上述规则封装为注解与链式 API 两种用法,业务代码无需再手写正则。
5.1 注解方式
java
@Data
public class RegisterRequest {
@NotBlank(message = "手机号不能为空")
@ChinesePhone(message = "手机号格式不正确")
private String phone;
}
@ChinesePhone 内置的就是第 3 节所述的正则,语义与规则分离。新号段放号时无需升级依赖------宽松格式天然兼容未来号段。
5.2 链式 API 方式
java
List<String> errors = ValidX.init()
.isChinesePhone(phone)
.isEmail(email)
.getErrors();
5.3 更多电话场景
| 场景 | 注解 | 链式 API | 说明 |
|---|---|---|---|
| 中国手机号 | @ChinesePhone |
isChinesePhone(...) |
11 位,第二位 3-9(宽松格式) |
| 手机号或固定电话 | @ChinesePhoneOrLandline |
isChinesePhoneOrLandline(...) |
二选一通过即可 |
| 中国固定电话 | @ChineseLandline |
isChineseLandline(...) |
区号 + 7/8 位号码 |
| 国际电话(E.164) | @PhoneNumber(countryCode = "+86", strict = true) |
isPhoneNumber(value, "+86", true, true) |
支持国际区号、分机号 |
5.4 测试用例参考
| 输入 | 结果 | 说明 |
|---|---|---|
13800138000 |
通过 | 官方测试号段 |
19912345678 |
通过 | 电信新号段 |
16612345678 |
通过 | 联通新号段 |
19212345678 |
通过 | 广电号段 |
15412345678 |
通过 | 格式层放行,真实性由短信验证码把关 |
19412345678 |
通过 | 格式层放行,真实性由短信验证码把关 |
12345 |
拒绝 | 位数不足 |
23800138000 |
拒绝 | 首位不是 1 |
1380013800 |
拒绝 | 只有 10 位 |
138001380000 |
拒绝 | 12 位 |
138a0138000 |
拒绝 | 含非数字字符 |
注:ValidX 对
null与空串返回"通过",把"必填"的职责交给@NotBlank/@NotNull,职责单一、组合灵活。
六、校验边界:号段 ≠ 真实
最后必须强调验证方案的分层设计:
| 层级 | 手段 | 能保证什么 |
|---|---|---|
| 第一层:格式 | 宽松格式正则 | 长得像手机号 |
| 第二层:拥有 | 短信验证码 | 号码真实可接收短信 |
| 第三层:实名 | 运营商实名校验 | 号码与身份信息匹配 |
在注册、登录等关键路径上,格式校验只是入场券,业务安全水位由后两层决定。把正则写成"无所不能"是常见的过度设计------它既挡不住真人恶意注册,又可能误杀新号段用户。
结语
中国手机号验证看似简单,实则隐含号段演进的动态规则。正确的姿势是:
- 格式校验宜宽不宜严 :
^1[3-9]\d{9}$足够,不必枚举号段; - 正则要带锚点,避免部分匹配;
- 规则要可演进,新号段放号时不改业务代码;
- 格式校验只做第一道关卡,真实性交给短信与运营商接口。
ValidX 把号段规则封装成了开箱即用的 @ChinesePhone,把复杂度挡在业务之外。如果你正在维护一套手写正则的手机号校验,不妨试试用注解替代------少一份维护负担,少一类"误杀新号段"的事故。
ValidX 是面向中国业务场景的 Java 验证库,基于 Jakarta Bean Validation 规范,注解与链式 API 双模式,内置身份证、手机号、邮箱、姓名等 100+ 验证规则。项目地址:
github.com/vipxieliang/validx