固定电话验证详解:区号、号码、分机号的完整验证

6,754 阅读9分钟

固定电话验证详解:区号、号码、分机号的完整验证

📋 目录


引言

移动互联网时代,表单里还有多少地方要填固定电话?

企业通讯录、客服回访、政务办事、银行开户、公司招聘……"座机"字段依然无处不在。它比手机号更难校验——手机号规则简单统一(11 位、1 开头),而固定电话要同时面对三件不确定的事:

组成变化
区号带不带 0?3 位还是 4 位?
号码7 位还是 8 位?大城市小城市不一样
分机有没有?几位?用什么分隔符?

手写正则时,这三件事任意一个考虑不周就会误杀合法号码或放行垃圾输入。ValidX 把这条规则封装成了 @ChineseLandline,本文拆解它背后的完整规则,并对比 @ChinesePhone@ChinesePhoneOrLandline@PhoneNumber 的选型边界。


一、固定电话的三段式结构

先看一段真实的中国大陆固话号码:

010 - 12345678 - 1234
└┬─┘  └──┬───┘  └┬─┘
区号    号码     分机(可选)
  • 区号:以 0 开头(长途冠码)+ 城市代码,共 3~4 位
  • 号码:本地局号 + 用户号,共 7~8 位
  • 分机:企业内部话务台转接,1~6 位,可选

这三段拼在一起,对应 ValidX ChineseLandlineValidator 里的核心正则:

^(0\d{2,3}[-\s]?)?\d{7,8}([-\s]\d{1,6})?$

逐段拆开看:

正则片段含义真实对应
(0\d{2,3}[-\s]?)?区号,整体可选(支持不填区号的本地号码)010-0755-0755
\d{7,8}本地号码 7~8 位大城市 8 位、中小城市 7 位
([-\s]\d{1,6})?分机号 1~6 位,整体可选-1234 100

正则还容忍两类分隔符:- 和空格([-\s]),且区号与号码之间允许没有分隔符——01012345678 这种紧凑写法也能通过。


二、区号、号码、分机号的真实规则

① 区号:0 是长途冠码,不是区号的一部分

很多人把"区号"理解成 0100755 整体,但电信标准里 0 是长途冠码,真正的城市代码是它后面的 1~3 位:

城市完整区号拆解位数
北京0100 + 103 位
上海0210 + 213 位
广州0200 + 203 位
深圳07550 + 7554 位
成都0280 + 283 位
长沙07310 + 7314 位

对应正则 0\d{2,3}第一位必须是 0,后面跟 2~3 位城市代码。这就是为什么 400-xxx-xxxx800-xxx-xxxx 这类客服号码会被拒绝——它们不以 0 开头,不是区号体系。

② 本地号码:7 位还是 8 位,随城市容量走

本地号码由"局号 + 用户号"组成:直辖市、省会等大容量城市升到 8 位,中小城市多为 7 位。正则用 \d{7,8} 同时容纳两者——这也是很多手写正则写死 \d{8} 后误杀小城市电话的原因。

③ 分机号:企业内部编号,1~6 位

分机是 PBX(用户交换机)内部的分支编号,形如 -1234 或空格分隔的 100。ValidX 分机规则是 \d{1,6},最长 6 位,前缀 - 或空格。

④ 分隔符:- / 空格 / 无

区号与号码之间三种写法都合法:

010-12345678   0755 12345678   01012345678    都通过

三、最容易写错的"伪正则"

网上流传的固话正则,几乎每条都踩过下面某个坑:

常见写法问题后果
\d{3,4}-\d{7,8}区号不强制 0 开头400-123-456780012345678 全被放行
0\d{3}-\d{8}锁死 4+8 结构北京 010 + 7 位号码被误杀,小城市 7 位号全灭
0\d{2,3}-\d{7,8}不收分机、不收空格/紧凑写法带分机的公司电话、无分隔符的输入被拒
0\d{2,3}-\d{7,8}(-\d+)?分机位数无上限-9999999999 也能过
^0\d{2,3}-?\d{7,8}$只认区号,不认本地号码内线直拨的 7 位号被拒

一条合格的中国大陆固话规则,至少要同时满足:区号可选且 0 开头 34 位、本地号码 78 位、分机可选 1~6 位、分隔符为 -/空格/无——这正是 @ChineseLandline 的完整语义。


四、@ChineseLandline:开箱即用的固话验证

公司通讯录场景,验证一条联系人记录:

public class ContactDTO {

    @NotNull
    private String name;

    @NotBlank
    @ChineseLandline                    // 固定电话号码格式不正确
    private String officePhone;         // 公司座机:010-12345678 / 010-12345678-1234

    @NotBlank
    @ChinesePhone                      // 手机号码格式不正确
    private String mobile;
}

@ChineseLandline 与其它 ValidX 注解一致:

  • 注解位于 io.github.vipxieliang.validx.annotations,验证器为 ChineseLandlineValidator
  • 默认消息 {io.github.vipxieliang.validx.annotation.chinese.landline} → "固定电话号码格式不正确"
  • 中文、英文、日文等 9 种语言消息文件内置,无需手动配 i18n
  • 支持 groupsmessage 覆盖:@ChineseLandline(message = "座机号格式不对,示例:010-12345678")

实测通过的样例:

输入结果
010-12345678(北京,8 位号)
021-1234567(上海,7 位号)
0755-12345678(深圳,4 位区号)
01012345678(无分隔符)
010-12345678-1234(带分机)
010 12345678 100(空格分隔 + 分机)
12345678(无区号的本地号码)
13812345678(手机号)
400-123-4567(客服号码,非区号体系)
(010)12345678(括号,不支持的格式)
138-1234-5678(手机号加分隔符)

五、场景选型:固话 / 手机 / 二选一

很多字段是"不知道用户填手机还是座机",直接上 @ChineseLandline 会误伤手机号。ValidX 三个注解覆盖三种诉求:

注解放行适用场景
@ChinesePhone仅手机号明确要求手机:验证码、登录绑定
@ChineseLandline仅固定电话明确要求座机:公司电话、政务专线
@ChinesePhoneOrLandline手机或座机都行"联系电话"等开放字段

"收货人联系电话"这类字段是典型的二选一,两个正则都会写错的人尤其多——ValidX 直接提供组合注解:

@NotBlank
@ChinesePhoneOrLandline   // 手机号码或者固定电话号码格式不正确
private String contactPhone;

ChinesePhoneOrLandlineValidator 的实现就是"手机验证器 OR 固话验证器",职责清晰、互不干扰:

return phoneValidator.isValid(value, null) || landlineValidator.isValid(value, null);

六、@PhoneNumber:面向国际的分机号支持

如果业务面向外贸、跨国企业(联系人留 +1-555-123-4567),@PhoneNumber 是更合适的注解,它把"分机号"当一等公民支持:

参数默认作用
countryCode""(不限国家)限定国家代码,如 "+86""+1"
allowExtensiontrue是否允许分机号
strictfalsetrue 时强制 + 开头的国际格式

分机关键字支持 ext.extx#extension

@PhoneNumber(allowExtension = true)
private String phone;   // 合法示例:+1-555-123-4567 ext. 100

用法对比——区分"中国区号固话"与"国际电话":

// 外贸联系人:必须 + 开头、带美国区号、不要分机
@PhoneNumber(countryCode = "+1", strict = true, allowExtension = false)
private String usPhone;

// 国内联系人:严格按中国区号 + 固话结构
@ChineseLandline
private String cnPhone;

选型建议:字段面向中国大陆(表单、电商、政务)用 @ChineseLandline / @ChinesePhoneOrLandline;面向国际客户、需要 + 国家代码与分机关键字的场景才用 @PhoneNumber。两者一"地"一"洋",别混用。


七、链式 API:不写 DTO 也能验

没有 DTO 的接口(比如校验一个前端随手传来的查询参数),可以走链式 API,规则与注解完全等价:

ValidX v = ValidX.init();

// 单个固话校验
v.isChineseLandline(officePhone)
 .isChinesePhoneOrLandline(contactPhone);   // 手机/座机二选一

// 国际电话:指定国家代码 + 关闭分机
v.isPhoneNumber(usPhone, "+1", false, true);

if (v.passed()) {
    contactService.save(officePhone, contactPhone, usPhone);
} else {
    // v.getErrors() 收集全部失败原因
    throw new BusinessException(v.getErrors().get(0));
}

链式方法签名与注解参数一一对应:

注解链式方法
@ChineseLandlineisChineseLandline(Object)
@ChinesePhoneOrLandlineisChinesePhoneOrLandline(Object)
@PhoneNumberisPhoneNumber(Object) / isPhoneNumber(value, countryCode) / (value, countryCode, allowExtension) / (value, countryCode, allowExtension, strict)

八、源码解读:宽松背后的两条路径

ChineseLandlineValidatorisValid 只做了两件事,先看核心正则:

private static final Pattern LANDLINE_PATTERN = Pattern.compile(
    "^(0\\d{2,3}[-\\s]?)?\\d{7,8}([-\\s]\\d{1,6})?$"
);

public boolean isValid(String value, ConstraintValidatorContext context) {
    if (value == null || value.isEmpty()) {
        return true; // 空值处理交给@NotNull等其他注解处理
    }

    // 去除空格和横线后进行验证
    String cleanValue = value.replaceAll("[\\s-]", "");

    // 检查是否包含非数字字符(除了开头的0)
    if (!cleanValue.matches("^0?\\d+$")) {
        return false;
    }

    // 对于固定电话,检查长度是否符合要求
    if (cleanValue.length() >= 10 && cleanValue.length() <= 13 && cleanValue.startsWith("0")) {
        // 如果去除分隔符后符合固定电话长度要求,认为是有效的
        return true;
    }

    // 验证固定电话(使用原始值,但允许空格和横线)
    return LANDLINE_PATTERN.matcher(value).matches();
}

判断分两条路径:

  1. 宽松路径(无分隔符输入):先把 -/空格全删掉,若剩下的是 0 开头、10~13 位的纯数字,直接放行——目的是容忍"用户没加分隔符"的输入(如 01012345678),不必再精确切分哪几位是区号。
  2. 正则路径(原始输入):走 LANDLINE_PATTERN,严格匹配"区号 + 号码(+ 分机)"结构。

两条路径分工的结果是:带分隔符的输入交给正则精判,不带分隔符的输入交给长度粗判


九、边界与坑

1. 宽松路径比正则有更宽的误放

长度粗判存在两个"其实不该过"的样例(实测确认):

0755-123456         号码只有 6 位,但因去分隔后为 10 位且 0 开头被放行
0101234567890       0 开头 13 位任意数字也被放行(真实区号+号码最长 12 位)

如果你对格式要求苛刻(如银行开户号码必须严格符合区号长度表),可以另加一层业务校验兜底,或与服务端"区号-城市"对照表交叉核对。

2. Javadoc 示例言过其实:双分机其实不支持

ChineseLandline 注解的 Javadoc 声称支持 010-12345678-1234-1234(两段分机),但真实正则 ([-\s]\d{1,6})? 只允许一段分机,实测 010-12345678-1234-1234 被拒绝。企业跨多级总机的两段分机需求目前需自行扩展(如去掉末尾 - 后分段校验),这是注解文档与实现不一致的一处遗留问题,使用时以本文实测为准。

3. 本地号码不以 0/1 开头?真实正则不保证

不少资料称"本地号码不以 0、1 开头",但 \d{7,8} 并不排除这种局向号——ValidX 选择宽松策略,接受此类输入。极端业务要求(局号白名单)需另配服务端校验。

4. 空值放行,必填交给 @NotBlank

与 ValidX 所有注解一致,@ChineseLandlinenull/空串返回合法——"是否必填"是 @NotBlank/@NotNull 的职责,两者分开才是声明式校验的正确姿势。

5. 客服号码、手机号不会误入

400/800 开头(非 0 区号体系)、手机号(1 开头 11 位)都被正确拒绝。需要"手机或座机都行"请用 @ChinesePhoneOrLandline,而不是放宽成"11 位数字都过"的自写正则。

6. 存库前统一格式

固话允许 -/空格/无分隔三种写法,同一号码可能以三种形态入库——与银行卡号同理,存库前统一为一种格式(如 010-12345678),并建议把分机号拆成独立字段,避免"格式化整串"时误伤分机。


总结

场景推荐注解要点
明确座机@ChineseLandline区号可选、0 开头 34 位;号码 78 位;分机可选 1~6 位
手机或座机@ChinesePhoneOrLandline手机验证器 OR 固话验证器
国际电话@PhoneNumbercountryCode / allowExtension / strict,分机支持 ext./x/#
无 DTO 场景isChineseLandline / isPhoneNumber(...)链式 API 与注解等价

固定电话验证的复杂度不在"写正则",而在记住每个不确定点:区号带不带 0、3 位还是 4 位,号码 7 位还是 8 位,分机有没有、用 - 还是空格、最多几位。把这套规则交给 @ChineseLandline,把"手滑多写一位区号""漏了分机段"这类 bug 从业务代码里彻底移除——这也是 ValidX 把规则标准化、沉淀成注解的意义所在。


项目地址