不能直接用一个正则匹配所有身份证号,因15位(纯数字)与18位(含x校验码、权重算法等)规则差异大,正则无法可靠处理校验逻辑、年份范围、性别推断等业务约束。

为什么不能直接用一个正则匹配所有身份证号
15位身份证是旧规则,纯数字;18位包含末位校验码(可能是X),且前17位有明确的地区、出生年月、顺序码结构。两者规则差异大,强行合并正则会牺牲可读性、可维护性,还容易漏掉校验逻辑——比如18位末位X不区分大小写、出生年份必须在1900–2099之间、顺序码奇偶性隐含性别这些都不是正则能可靠处理的。
推荐分两步:先长度分流,再分别校验
实际项目中更稳妥的做法是先判断长度,再走对应分支。15位只需检查是否全数字+地区码有效;18位必须做完整校验(包括加权求和与模11校验)。
- 15位:用
^\d{15}$快速初筛,再查substr($id, 0, 2)是否在公安部公布的省份代码表中(如"11"北京、"31"上海) - 18位:先用
^\d{17}[\dXx]$匹配格式,再提取前17位转整数数组,按权重[7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2]加权求和,对11取模后对照校验码表['1','0','X','9','8','7','6','5','4','3','2'] - 别忘了统一转大写处理
X:strtoupper(substr($id, -1)) === $check_codes[$mod]
常见错误:把校验码逻辑塞进正则里
有人试图写类似^\d{17}[\dXx]$加后行断言验证模运算,这在PHP PCRE中不可行——正则引擎无法执行算术。即使硬套,也会导致:
-
preg_match返回true但实际校验失败(比如110101199003072718末位应为1却写了8) - 替换
X时忘记strtolower/strtoupper,导致x被当成非法字符 - 忽略15位中出生年份是两位(如
90代表1990),误判为2090年
一个轻量但完整的校验函数示例function validateIdCard($id) {
$id = trim($id);
if (strlen($id) === 15) {
return ctype_digit($id)
&& in_array(substr($id, 0, 2), ['11','12','13','21','22','23','31','32','33','34','35','36','37','41','42','43','44','45','46','50','51','52','53','54','61','62','63','64','65','71','81','82']);
}
if (strlen($id) === 18) {
if (!preg_match('/^\d{17}[\dXx]$/', $id)) return false;
$weights = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2];
$check_codes = ['1','0','X','9','8','7','6','5','4','3','2'];
$sum = 0;
for ($i = 0; $i <p>注意:地区码校验建议从权威源加载为数组,而不是硬编码全部34个;15位身份证已停发,生产环境可考虑直接拒绝。</p>
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











