新公司框架源码的时候, 发现了这个功能, 于是搜索一番并封装了一下身份证号校验的类.
目前大家的身份证号大多是 18 位的, 当然, 也不排除有些老人的身份证号是 15 位的.
如果强制要求是 18 位的话, 会比较好, 因为 15 位的身份证号没有校验码, 可以说, 只要了解大概结构, 随手都可以造出一系列身份证号码来.
当然, 如果只是单纯的程序校验, 18 位的身份证号码也可以伪造, 就是需要伪造者花点心思.
最好的还是调用相关部门给的接口, 进行校验.
本文所编写的身份证号码校验, 只是针对相关规则下的计算, 是调用接口前能做的事情.
身份证号规则
15 位: 省份 (2 位) + 地级市 (2 位) + 县级市 (2 位) + 出生年 (2 位) + 出生月 (2 位) + 出生日 (2 位) + 顺序号 (3 位)
18 位: 省份 (2 位) + 地级市 (2 位) + 县级市 (2 位) + 出生年 (4 位) + 出生月 (2 位) + 出生日 (2 位) + 顺序号 (3 位) + 校验位 (1 位)
相比之下, 18 位 比 15 位 多出生年 2 位 , 校验位 1 位 .
其中, 顺序号如果是偶数, 则说明是女生, 顺序号是奇数, 则说明是男生.
校验位的计算:
有 17 位数字, 分别是:
7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2
分别用身份证的前 17 位乘以上面相应位置的数字, 然后相加.
接着用相加的和对 11 取模.
用获得的值在下面 11 个字符里查找对应位置的字符, 这个字符就是校验位.
'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'
15 位转 18 位:
从上述的分析中, 可以知道, 只要补充上年分和校验位就可以了.
一般情况下年份补充都是加上 19 就可以了.
校验类的实现
通过分析身份证号的规则, 了解到, 有几点是可以做的:
检查身份是否正确 (一般不会变化, 而且省份不多)
检查地级市和县级市 (如果有这方面的资源, 可以考虑, 不过一般不建议)
检查年月日
检查校验码
当然, 因为可能部分人用的是 15 位 的身份证号, 所以需要一个转换的方法, 不过, 这里还是建议限制需要 18 位 的身份证号.
下面开始实现:
初始化:
- class IDCardFilter
- {
- /**
- * 身份证号码校验
- *
- * @param string $idCard
- * @return bool
- */
- public function vaild($idCard)
- {
- // 基础的校验, 校验身份证格式是否正确
- if (!$this->isCardNumber($idCard)) {
- return false;
- }
- // 将 15 位转换成 18 位
- $idCard = $this->fifteen2Eighteen($idCard);
- // 检查省是否存在
- if (!$this->checkProvince($idCard)) {
- return false;
- }
- // 检查生日是否正确
- if (!$this->checkBirthday($idCard)) {
- return false;
- }
- // 检查校验码
- return $this->checkCode($idCard);
- }
- }
上面已经实现了一个校验的方法, 里面调用了类里的很多方法, 下面一一实现.
检测是否是身份证号码:
这一块的处理比较简单, 一个正则表达式搞定了.
其中, (^\d{15}$) 用于匹配 15 位 身份证号的情况; (^\d{17}(\d|X)$) 用于匹配 18 位 身份证号的情况.
- const REGX = '#(^\d{15}$)|(^\d{17}(\d|X)$)#';
- /**
- * 检测是否是身份证号码
- *
- * @param string $idCard
- * @return boolean
- */
- public function isCardNumber($idCard)
- {
- return preg_match(self::REGX, $idCard);
- }
15 位转 18 位:
逻辑不复杂, 先判断是否是 15 位, 然后判断需要添加的年份, 最终生成校验码拼接返回就 OK 了.
- /**
- * 15 位转 18 位
- *
- * @param string $idCard
- * @return void
- */
- public function fifteen2Eighteen($idCard)
- {
- if (strlen($idCard) != 15) {
- return $idCard;
- }
- // 如果身份证顺序码是 996 997 998 999, 这些是为百岁以上老人的特殊编码
- // $code = array_search(substr($idCard, 12, 3), [996, 997, 998, 999]) !== false ? '18' : '19';
- // 一般 19 就够了
- $code = '19';
- $idCardBase = substr($idCard, 0, 6) . $code . substr($idCard, 6, 9);
- return $idCardBase . $this->genCode($idCardBase);
- }
校验码的生成:
详细计算规则见上面, 这里就不做重复的阐述了.
- /**
- * 生成校验码
- *
- * @param string $idCardBase
- * @return void
- */
- final protected function genCode($idCardBase)
- {
- $idCardLength = strlen($idCardBase);
- if ($idCardLength != 17) {
- return false;
- }
- $factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
- $verifyNumbers = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
- $sum = 0;
- for ($i = 0; $i <$idCardLength; $i++) {
- $sum += substr($idCardBase, $i, 1) * $factor[$i];
- }
- $index = $sum % 11;
- return $verifyNumbers[$index];
- }
检查省份是否正确:
- protected $provinces = [
- 11 => "北京", 12 => "天津", 13 => "河北", 14 => "山西", 15 => "内蒙古",
- 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 => "澳门", 91 => "国外"
- ];
- /**
- * 检查省份是否正确
- *
- * @param string $idCard
- * @return void
- */
- public function checkProvince($idCard)
- {
- $provinceNumber = substr($idCard, 0, 2);
- return isset($this->provinces[$provinceNumber]);
- }
检测生日是否正确:
这里也是用正则匹配, 匹配出年月日的.
- /**
- * 检测生日是否正确
- *
- * @param string $idCard
- * @return void
- */
- public function checkBirthday($idCard)
- {
- $regx = '#^\d{6}(\d{4})(\d{2})(\d{2})\d{3}[0-9X]$#';
- if (!preg_match($regx, $idCard, $matches)) {
- return false;
- }
- array_shift($matches);
- list($year, $month, $day) = $matches;
- return checkdate($month, $day, $year);
- }
校验码比对:
话说, 15 位 转 18 位 的都完全不用考虑这个方法了.
- /**
- * 校验码比对
- *
- * @param string $idCard
- * @return void
- */
- public function checkCode($idCard)
- {
- $idCardBase = substr($idCard, 0, 17);
- $code = $this->genCode($idCardBase);
- return $idCard == ($idCardBase . $code);
- }
完整代码
传送门:
最后
这个功能最多算是新颖吧, 毕竟之前没有接触过. 很开心代码片段里又增加了新的成员.
来源: https://www.jb51.net/article/150338.htm