前言
看新公司框架源碼的時候,發(fā)現(xiàn)了這個功能,于是搜索一番并封裝了一下身份證號校驗的類互拾。
目前大家的身份證號大多是 18
位的,當然嚎幸,也不排除有些老人的身份證號是 15
位的颜矿。
如果強制要求是 18
位的話,會比較好嫉晶,因為 15
位的身份證號沒有校驗碼骑疆,可以說,只要了解大概結(jié)構(gòu)替废,隨手都可以造出一系列身份證號碼來箍铭。
當然,如果只是單純的程序校驗椎镣,18
位的身份證號碼也可以偽造诈火,就是需要偽造者花點心思。
最好的還是調(diào)用相關(guān)部門給的接口状答,進行校驗冷守。
本文所編寫的身份證號碼校驗,只是針對相關(guān)規(guī)則下的計算惊科,是調(diào)用接口前能做的事情拍摇。
身份證號規(guī)則
15位: 省份(2位) + 地級市(2位) + 縣級市(2位) + 出生年(2位) + 出生月(2位) + 出生日(2位) + 順序號(3位)
18位: 省份(2位) + 地級市(2位) + 縣級市(2位) + 出生年(4位) + 出生月(2位) + 出生日(2位) + 順序號(3位) + 校驗位(1位)
相比之下,18位
比 15位
多出生年 2位
馆截、校驗位 1位
授翻。
其中,順序號如果是偶數(shù)孙咪,則說明是女生堪唐,順序號是奇數(shù),則說明是男生翎蹈。
校驗位的計算:
有17位數(shù)字淮菠,分別是:
7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2
分別用身份證的前 17 位乘以上面相應位置的數(shù)字,然后相加荤堪。
接著用相加的和對 11 取模合陵。
用獲得的值在下面 11 個字符里查找對應位置的字符枢赔,這個字符就是校驗位。
'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'
15位轉(zhuǎn)18位:
從上述的分析中拥知,可以知道踏拜,只要補充上年分和校驗位就可以了。
一般情況下年份補充都是加上 19
就可以了低剔。
校驗類的實現(xiàn)
通過分析身份證號的規(guī)則速梗,了解到,有幾點是可以做的:
- 檢查身份是否正確(一般不會變化襟齿,而且省份不多)
- 檢查地級市和縣級市(如果有這方面的資源姻锁,可以考慮,不過一般不建議)
- 檢查年月日
- 檢查校驗碼
當然猜欺,因為可能部分人用的是 15位
的身份證號位隶,所以需要一個轉(zhuǎn)換的方法,不過开皿,這里還是建議限制需要 18位
的身份證號涧黄。
下面開始實現(xiàn):
初始化:
class IDCardFilter
{
/**
* 身份證號碼校驗
*
* @param string $idCard
* @return bool
*/
public function vaild($idCard)
{
// 基礎(chǔ)的校驗,校驗身份證格式是否正確
if (!$this->isCardNumber($idCard)) {
return false;
}
// 將 15 位轉(zhuǎn)換成 18 位
$idCard = $this->fifteen2Eighteen($idCard);
// 檢查省是否存在
if (!$this->checkProvince($idCard)) {
return false;
}
// 檢查生日是否正確
if (!$this->checkBirthday($idCard)) {
return false;
}
// 檢查校驗碼
return $this->checkCode($idCard);
}
}
上面已經(jīng)實現(xiàn)了一個校驗的方法赋荆,里面調(diào)用了類里的很多方法弓熏,下面一一實現(xiàn)。
檢測是否是身份證號碼:
這一塊的處理比較簡單糠睡,一個正則表達式搞定了挽鞠。
其中,(^\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位轉(zhuǎn)18位:
邏輯不復雜,先判斷是否是15位均抽,然后判斷需要添加的年份嫁赏,最終生成校驗碼拼接返回就OK了。
/**
* 15位轉(zhuǎn)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);
}
校驗碼的生成:
詳細計算規(guī)則見上面潦蝇,這里就不做重復的闡述了。
/**
* 生成校驗碼
*
* @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 => "內(nèi)蒙古",
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位
轉(zhuǎn) 18位
的都完全不用考慮這個方法了惋鹅。
/**
* 校驗碼比對
*
* @param string $idCard
* @return void
*/
public function checkCode($idCard)
{
$idCardBase = substr($idCard, 0, 17);
$code = $this->genCode($idCardBase);
return $idCard == ($idCardBase . $code);
}
完整代碼
傳送門:IDCardFilter
最后
這個功能最多算是新穎吧则酝,畢竟之前沒有接觸過。很開心代碼片段里又增加了新的成員闰集。