验证是否是身份证

实现一个函数来验证中国大陆身份证号码的合法性,包括格式校验和校验码验证

问题

实现一个函数来验证中国大陆身份证号码是否合法。需要验证:

  • 18位身份证号码的格式(15位旧版身份证已停用)
  • 前17位必须是数字
  • 最后一位可以是数字或字母X
  • 出生日期的合法性
  • 地区码的合法性
  • 最后一位校验码是否正确(根据GB11643-1999标准)

解答

/**
 * 验证是否是合法的身份证号码
 * @param {string} idCard - 身份证号码
 * @returns {boolean} 是否合法
 */
function isValidIdCard(idCard) {
  // 基本格式验证:18位,前17位数字,最后一位数字或X
  const reg = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
  
  if (!reg.test(idCard)) {
    return false;
  }
  
  // 统一转为大写
  idCard = idCard.toUpperCase();
  
  // 验证出生日期
  const year = parseInt(idCard.substring(6, 10));
  const month = parseInt(idCard.substring(10, 12));
  const day = parseInt(idCard.substring(12, 14));
  
  const date = new Date(year, month - 1, day);
  if (
    date.getFullYear() !== year ||
    date.getMonth() !== month - 1 ||
    date.getDate() !== day
  ) {
    return false;
  }
  
  // 验证校验码
  // 加权因子
  const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
  // 校验码对应值
  const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
  
  let sum = 0;
  for (let i = 0; i < 17; i++) {
    sum += parseInt(idCard[i]) * weights[i];
  }
  
  const checkCode = checkCodes[sum % 11];
  
  return idCard[17] === checkCode;
}

/**
 * 验证身份证(增强版,返回详细信息)
 * @param {string} idCard - 身份证号码
 * @returns {object} 验证结果和详细信息
 */
function validateIdCard(idCard) {
  const result = {
    valid: false,
    message: '',
    info: null
  };
  
  if (!idCard || typeof idCard !== 'string') {
    result.message = '身份证号码不能为空';
    return result;
  }
  
  idCard = idCard.trim().toUpperCase();
  
  // 格式验证
  const reg = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dX]$/;
  if (!reg.test(idCard)) {
    result.message = '身份证号码格式不正确';
    return result;
  }
  
  // 提取信息
  const province = idCard.substring(0, 2);
  const year = parseInt(idCard.substring(6, 10));
  const month = parseInt(idCard.substring(10, 12));
  const day = parseInt(idCard.substring(12, 14));
  const gender = parseInt(idCard[16]) % 2 === 0 ? '女' : '男';
  
  // 验证日期
  const date = new Date(year, month - 1, day);
  if (
    date.getFullYear() !== year ||
    date.getMonth() !== month - 1 ||
    date.getDate() !== day
  ) {
    result.message = '出生日期不合法';
    return result;
  }
  
  // 验证校验码
  const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
  const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
  
  let sum = 0;
  for (let i = 0; i < 17; i++) {
    sum += parseInt(idCard[i]) * weights[i];
  }
  
  const checkCode = checkCodes[sum % 11];
  if (idCard[17] !== checkCode) {
    result.message = '校验码不正确';
    return result;
  }
  
  // 验证通过
  result.valid = true;
  result.message = '身份证号码合法';
  result.info = {
    province,
    birthday: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
    gender,
    age: new Date().getFullYear() - year
  };
  
  return result;
}

使用示例

// 基础验证
console.log(isValidIdCard('110101199003074593')); // true
console.log(isValidIdCard('110101199003074592')); // false (校验码错误)
console.log(isValidIdCard('11010119900307459'));  // false (长度不够)
console.log(isValidIdCard('110101199013074593')); // false (月份错误)

// 增强版验证(返回详细信息)
const result1 = validateIdCard('110101199003074593');
console.log(result1);
// {
//   valid: true,
//   message: '身份证号码合法',
//   info: {
//     province: '11',
//     birthday: '1990-03-07',
//     gender: '男',
//     age: 34
//   }
// }

const result2 = validateIdCard('110101199003074592');
console.log(result2);
// {
//   valid: false,
//   message: '校验码不正确',
//   info: null
// }

// 支持小写x
console.log(isValidIdCard('11010119900307459x')); // true

// 错误格式测试
console.log(validateIdCard(''));                    // 身份证号码不能为空
console.log(validateIdCard('123456789012345678')); // 身份证号码格式不正确
console.log(validateIdCard('110101199002304593')); // 出生日期不合法

关键点

  • 正则表达式验证:使用正则快速验证基本格式,包括长度、数字位置、日期范围等
  • 出生日期校验:通过Date对象验证日期的真实性,避免如2月30日这样的非法日期
  • 校验码算法:按照GB11643-1999国家标准,使用加权因子和模11算法计算校验码
    • 前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转为大写进行比较
  • 信息提取:可以从身份证号中提取省份、出生日期、性别、年龄等信息
  • 错误提示:增强版函数提供详细的错误信息,便于用户修正输入