正則匹配身份證有bug你知道麼?

星光筆發表於2019-01-15

在開發中,我們需要驗證使用者的輸入資訊,多半採用正則驗證,下面就是身份證證號的幾種常用的正規表示式:

var  reg = /(^d{15}$)|(^d{18}$)|(^d{17}(d|X|x)$)/;

var reg= /^[1-9]d{7}((0d)|(1[0-2]))(([0|1|2]d)|3[0-1])d{3}$/;

var  reg = /^[1-9]d{5}[1-9]d{3}((0d)|(1[0-2]))(([0|1|2]d)|3[0-1])d{4}$/;

但是這些並不能管用,是不是很氣人?

這是為什麼呢?

下面我們看一下身份證的規則

身份證查詢系統說明:

輸入準確的18位身份證號碼即可查詢身份證號碼歸屬地,年齡,性別,通過身份證號碼查詢姓名。

輸入不合法格式的身份證號碼會提示身份證號碼錯誤,本身份證號碼查詢系統也可作為身份證號碼驗證。

身份證號碼和姓名格式科普:前1-6位為行政區劃程式碼即歸屬地,第7-14位為出生年月日,第15-17位為順序程式碼,在同一個地區出生同一個出生的人通過順序號碼區分,第17位奇數表示男性,偶數表示女性,第18位為校驗碼,用於校驗身份證號碼是否合法

 

很顯然我們正則驗證出錯的原因就是第18位,用於身份證號是否合法驗證的校驗

 

這是為什麼呢?是不是很詭異?按照道理講,不應該不符合就是false?但是返回的都是true;

因為是這樣的,我們使用的正規表示式 reg.test(“),當我們使用test其實就是通過我們寫的正規表示式去動態匹配我們輸入的字串是否符合我們表示式的要求,如果符合就會返回Boolean值

 

這就是為什麼我們身份證驗證會出錯,因為我們正則只是匹配了形式,並沒有按照身份證的規則去動態驗證,沒有權重

那麼正確的驗證如下

 1 /**
 2  * @params  idCard  string
 3  * @outParams res
 4  *  status : boolean
 5  *  msg : string
 6  *
 7  */
 8 function checkIdcard(idCard) {
 9   idCard = idCard.toString();
10   var city = {
11     11: "北京",
12     12: "天津",
13     13: "河北",
14     14: "山西",
15     15: "內蒙古",
16     21: "遼寧",
17     22: "吉林",
18     23: "黑龍江 ",
19     31: "上海",
20     32: "江蘇",
21     33: "浙江",
22     34: "安徽",
23     35: "福建",
24     36: "江西",
25     37: "山東",
26     41: "河南",
27     42: "湖北 ",
28     43: "湖南",
29     44: "廣東",
30     45: "廣西",
31     46: "海南",
32     50: "重慶",
33     51: "四川",
34     52: "貴州",
35     53: "雲南",
36     54: "西藏 ",
37     61: "陝西",
38     62: "甘肅",
39     63: "青海",
40     64: "寧夏",
41     65: "新疆",
42     71: "臺灣",
43     81: "香港",
44     82: "澳門",
45     91: "國外 "
46   };
47   var tip = "";
48   var pass = true;
49 
50   if (
51     !idCard ||
52     !/^d{6}(18|19|20)?d{2}(0[1-9]|1[012])(0[1-9]|[12]d|3[01])d{3}(d|X)$/i.test(
53       idCard
54     )
55   ) {
56     tip = "身份證號格式錯誤";
57     pass = false;
58   } else if (!city[idCard.substr(0, 2)]) {
59     tip = "地址編碼錯誤";
60     pass = false;
61   } else {
62     //18位身份證需要驗證最後一位校驗位
63     if (idCard.length == 18) {
64       idCard = idCard.split("");
65       //∑(ai×Wi)(mod 11)
66       //加權因子
67       var factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
68       //校驗位
69       var parity = [1, 0, "X", 9, 8, 7, 6, 5, 4, 3, 2];
70       var sum = 0;
71       var ai = 0;
72       var wi = 0;
73       for (var i = 0; i < 17; i++) {
74         ai = idCard[i];
75         wi = factor[i];
76         sum += ai * wi;
77       }
78       var last = parity[sum % 11];
79       if (parity[sum % 11] != idCard[17]) {
80         tip = "校驗位錯誤";
81         pass = false;
82       }
83     }
84   }
85   var obj = {
86     status: pass,
87     msg: tip
88   };
89   if (!pass) {
90     return obj;
91   }
92 
93   return obj;
94 }

寫法注意事項:省區編碼,我們可以寫成陣列的形式,但是需要去迴圈操作,會比較消耗效能:如果是陣列,新手是這麼寫的

function checkIdcard(idCard) {
  idCard = idCard.toString();
  var city = [
    11,
    12,
    13,
    14,
    15,
    21,
    22,
    23,
    31,
    33,
    34,
    35,
    36,
    37,
    41,
    42,
    43,
    44,
    45,
    46,
    50,
    51,
    52,
    54,
    61,
    62,
    63,
    64,
    65,
    71,
    81,
    91
  ];
  var tip = "";
  var pass = true;
  var Flag = true;
  for(var i=0;i<city.length;i++){
    if (city[i] == idCard.substr(0, 2)) Falg= false;
  }
  if (
    !idCard ||
    !/^d{6}(18|19|20)?d{2}(0[1-9]|1[012])(0[1-9]|[12]d|3[01])d{3}(d|X)$/i.test(
      idCard
    )
  ) {
    tip = "身份證號格式錯誤";
    pass = false;
  } else if (!Flag) {
           tip = "地址編碼錯誤";
           pass = false;
         } else {
           //18位身份證需要驗證最後一位校驗位
           if (idCard.length == 18) {
             idCard = idCard.split("");
             //∑(ai×Wi)(mod 11)
             //加權因子
             var factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
             //校驗位
             var parity = [1, 0, "X", 9, 8, 7, 6, 5, 4, 3, 2];
             var sum = 0;
             var ai = 0;
             var wi = 0;
             for (var i = 0; i < 17; i++) {
               ai = idCard[i];
               wi = factor[i];
               sum += ai * wi;
             }
             var last = parity[sum % 11];
             if (parity[sum % 11] != idCard[17]) {
               tip = "校驗位錯誤";
               pass = false;
             }
           }
         }
  var obj = {
    status: pass,
    msg: tip
  };
  if (!pass) {
    return obj;
  }

  return obj;
}

  優化上面的程式碼

/**
 * @params  idCard  string
 * @outParams res
 *  status : boolean
 *  msg : string
 *
 */
function checkIdcard(idCard) {
  idCard = idCard.toString();
  var city = [
    11,
    12,
    13,
    14,
    15,
    21,
    22,
    23,
    31,
    33,
    34,
    35,
    36,
    37,
    41,
    42,
    43,
    44,
    45,
    46,
    50,
    51,
    52,
    54,
    61,
    62,
    63,
    64,
    65,
    71,
    81,
    91
  ];
  var tip = "";
  var pass = true;

  if (
    !idCard ||
    !/^d{6}(18|19|20)?d{2}(0[1-9]|1[012])(0[1-9]|[12]d|3[01])d{3}(d|X)$/i.test(
      idCard
    )
  ) {
    tip = "身份證號格式錯誤";
    pass = false;
  } else if (city.indexOf(idCard.substr(0, 2)) < 0) {
    tip = "地址編碼錯誤";
    pass = false;
  } else {
    //18位身份證需要驗證最後一位校驗位
    if (idCard.length == 18) {
      idCard = idCard.split("");
      //∑(ai×Wi)(mod 11)
      //加權因子
      var factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
      //校驗位
      var parity = [1, 0, "X", 9, 8, 7, 6, 5, 4, 3, 2];
      var sum = 0;
      var ai = 0;
      var wi = 0;
      for (var i = 0; i < 17; i++) {
        ai = idCard[i];
        wi = factor[i];
        sum += ai * wi;
      }
      var last = parity[sum % 11];
      if (parity[sum % 11] != idCard[17]) {
        tip = "校驗位錯誤";
        pass = false;
      }
    }
  }
  var obj = {
    status: pass,
    msg: tip
  };
  if (!pass) {
    return obj;
  }

  return obj;
}

注意一下寫法,儘量減少迴圈的操作,推薦使用第一種和第三種用法;如果你是es6使用者,那麼建議var 修改成 let 

最後基於模組的思想,建議搭建可以在實際開發中,把驗證的方法統一一個物件去處理,

然後哪裡需要哪裡引入。

參考博文:https://blog.csdn.net/a632202838/article/details/44827427

線上身份查詢:http://sfz.diqibu.com/

相關文章