QinGang_interested/lib/tools/id_cart_util.dart

356 lines
8.4 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'dart:core';
class IDCardInfo {
final String raw;
final bool isValid;
final String? error;
final String? id18;
final String? addressCode;
final String? provinceCode;
final String? province;
final DateTime? birthDate;
final String? birth;
final int? age;
final String? gender;
final bool checksumValid;
final String? constellation;
final String? zodiac;
IDCardInfo({
required this.raw,
required this.isValid,
this.error,
this.id18,
this.addressCode,
this.provinceCode,
this.province,
this.birthDate,
this.birth,
this.age,
this.gender,
required this.checksumValid,
this.constellation,
this.zodiac,
});
Map<String, dynamic> toJson() => {
'raw': raw,
'isValid': isValid,
'error': error,
'id18': id18,
'addressCode': addressCode,
'provinceCode': provinceCode,
'province': province,
'birth': birth,
'age': age,
'gender': gender,
'checksumValid': checksumValid,
'constellation': constellation,
'zodiac': zodiac,
};
@override
String toString() => toJson().toString();
}
/// 只校验 18 位身份证号
IDCardInfo parseChineseIDCard(String? id) {
final raw = _normalizeId(id);
if (raw.isEmpty) {
return IDCardInfo(
raw: raw,
isValid: false,
error: '身份证号不能为空',
checksumValid: false,
);
}
if (raw.length != 18) {
return IDCardInfo(
raw: raw,
isValid: false,
error: '身份证号长度不正确应为18位',
checksumValid: false,
);
}
if (!RegExp(r'^\d{17}[\dX]$').hasMatch(raw)) {
return IDCardInfo(
raw: raw,
isValid: false,
error: '身份证号格式不正确前17位必须是数字最后一位必须是数字或X',
checksumValid: false,
);
}
const provinceMap = <String, String>{
'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': '国外',
};
final addressCode = raw.substring(0, 6);
final provinceCode = raw.substring(0, 2);
final province = provinceMap[provinceCode];
if (province == null) {
return IDCardInfo(
raw: raw,
isValid: false,
error: '地址码不合法:省份码错误',
id18: raw,
addressCode: addressCode,
provinceCode: provinceCode,
checksumValid: false,
);
}
final yearStr = raw.substring(6, 10);
final monthStr = raw.substring(10, 12);
final dayStr = raw.substring(12, 14);
if (!RegExp(r'^(18|19|20)\d{2}$').hasMatch(yearStr)) {
return IDCardInfo(
raw: raw,
isValid: false,
error: '年份不合法必须以18、19或20开头',
id18: raw,
addressCode: addressCode,
provinceCode: provinceCode,
province: province,
checksumValid: false,
);
}
if (!RegExp(r'^(0[1-9]|1[0-2])$').hasMatch(monthStr)) {
return IDCardInfo(
raw: raw,
isValid: false,
error: '月份不合法必须在01到12之间',
id18: raw,
addressCode: addressCode,
provinceCode: provinceCode,
province: province,
checksumValid: false,
);
}
if (!RegExp(r'^(0[1-9]|[12]\d|3[01])$').hasMatch(dayStr)) {
return IDCardInfo(
raw: raw,
isValid: false,
error: '日期不合法必须在01到31之间',
id18: raw,
addressCode: addressCode,
provinceCode: provinceCode,
province: province,
checksumValid: false,
);
}
final year = int.parse(yearStr);
final month = int.parse(monthStr);
final day = int.parse(dayStr);
DateTime? birthDate;
try {
final dt = DateTime(year, month, day);
if (dt.year == year && dt.month == month && dt.day == day) {
birthDate = dt;
}
} catch (_) {
birthDate = null;
}
if (birthDate == null) {
return IDCardInfo(
raw: raw,
isValid: false,
error: '出生日期无效:请检查年月日是否真实存在',
id18: raw,
addressCode: addressCode,
provinceCode: provinceCode,
province: province,
checksumValid: false,
);
}
final seqCode = raw.substring(14, 17);
if (!RegExp(r'^\d{3}$').hasMatch(seqCode)) {
return IDCardInfo(
raw: raw,
isValid: false,
error: '顺序码不合法第15到17位必须全部为数字',
id18: raw,
addressCode: addressCode,
provinceCode: provinceCode,
province: province,
birthDate: birthDate,
birth: _formatDate(birthDate),
checksumValid: false,
);
}
final seqNum = int.parse(seqCode);
if (seqNum == 0) {
return IDCardInfo(
raw: raw,
isValid: false,
error: '顺序码不合法不能为000',
id18: raw,
addressCode: addressCode,
provinceCode: provinceCode,
province: province,
birthDate: birthDate,
birth: _formatDate(birthDate),
checksumValid: false,
);
}
final gender = (seqNum % 2 == 1) ? '' : '';
final checksumValid = _verifyCheck(raw);
if (!checksumValid) {
return IDCardInfo(
raw: raw,
isValid: false,
error: '校验位不正确',
id18: raw,
addressCode: addressCode,
provinceCode: provinceCode,
province: province,
birthDate: birthDate,
birth: _formatDate(birthDate),
age: _calcAge(birthDate),
gender: gender,
checksumValid: false,
constellation: _calcConstellation(birthDate.month, birthDate.day),
zodiac: _calcChineseZodiac(birthDate.year),
);
}
return IDCardInfo(
raw: raw,
isValid: true,
id18: raw,
addressCode: addressCode,
provinceCode: provinceCode,
province: province,
birthDate: birthDate,
birth: _formatDate(birthDate),
age: _calcAge(birthDate),
gender: gender,
checksumValid: true,
constellation: _calcConstellation(birthDate.month, birthDate.day),
zodiac: _calcChineseZodiac(birthDate.year),
);
}
String _normalizeId(String? id) {
return (id ?? '')
.trim()
.replaceAll(' ', '')
.replaceAll(' ', '')
.replaceAll('', 'X')
.toUpperCase();
}
String _formatDate(DateTime d) {
return '${d.year.toString().padLeft(4, '0')}-'
'${d.month.toString().padLeft(2, '0')}-'
'${d.day.toString().padLeft(2, '0')}';
}
int _calcAge(DateTime birthDate) {
final now = DateTime.now();
var age = now.year - birthDate.year;
if (now.month < birthDate.month ||
(now.month == birthDate.month && now.day < birthDate.day)) {
age -= 1;
}
return age;
}
String _calcCheckChar(String id17) {
const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
const checkMap = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
int sum = 0;
for (var i = 0; i < 17; i++) {
final n = int.parse(id17[i]);
sum += n * weights[i];
}
return checkMap[sum % 11];
}
bool _verifyCheck(String id18) {
if (id18.length != 18) return false;
final id17 = id18.substring(0, 17);
final expected = _calcCheckChar(id17);
final actual = id18[17].toUpperCase();
return expected == actual;
}
String _calcConstellation(int month, int day) {
const names = [
'摩羯座',
'水瓶座',
'双鱼座',
'白羊座',
'金牛座',
'双子座',
'巨蟹座',
'狮子座',
'处女座',
'天秤座',
'天蝎座',
'射手座'
];
const startDays = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22];
final idx = month - 1;
if (day < startDays[idx]) {
return names[(idx + 11) % 12];
} else {
return names[idx];
}
}
String _calcChineseZodiac(int year) {
const zodiacs = ['', '', '', '', '', '', '', '', '', '', '', ''];
final idx = (year - 1900) % 12;
final i = idx < 0 ? (idx + 12) % 12 : idx;
return zodiacs[i];
}