加入收藏 | 设为首页 | 会员中心 | 我要投稿 常州站长网 (https://www.0519zz.cn/)- 云渲染、网络安全、数据安全、数据分析、人体识别!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

PHP校验15位和18位身份证号的类封装

发布时间:2022-06-20 09:06:36 所属栏目:PHP教程 来源:互联网
导读:新公司框架源码的时候,发现了这个功能,于是搜索一番并封装了一下身份证号校验的类。 目前大家的身份证号大多是 18 位的,当然,也不排除有些老人的身份证号是 15 位的。 如果强制要求是 18 位的话,会比较好,因为 15 位的身份证号没有校验码,可以说,只
  新公司框架源码的时候,发现了这个功能,于是搜索一番并封装了一下身份证号校验的类。
 
  目前大家的身份证号大多是 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);
  }
  完整代码
 
  传送门:IDCardFilter
 
  最后:这个功能最多算是新颖吧,毕竟之前没有接触过。很开心代码片段里又增加了新的成员。

(编辑:常州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读