QinGang_interested/lib/tools/id_cart_util.dart

356 lines
8.4 KiB
Dart
Raw Normal View History

2025-12-12 09:11:30 +08:00
import 'dart:core';
class IDCardInfo {
final String raw;
final bool isValid;
final String? error;
2026-04-13 08:59:45 +08:00
final String? id18;
final String? addressCode;
2025-12-12 09:11:30 +08:00
final String? provinceCode;
final String? province;
final DateTime? birthDate;
2026-04-13 08:59:45 +08:00
final String? birth;
2025-12-12 09:11:30 +08:00
final int? age;
2026-04-13 08:59:45 +08:00
final String? gender;
2025-12-12 09:11:30 +08:00
final bool checksumValid;
2026-04-13 08:59:45 +08:00
final String? constellation;
final String? zodiac;
2025-12-12 09:11:30 +08:00
IDCardInfo({
required this.raw,
required this.isValid,
this.error,
this.id18,
2026-04-13 08:59:45 +08:00
this.addressCode,
2025-12-12 09:11:30 +08:00
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,
2026-04-13 08:59:45 +08:00
'addressCode': addressCode,
2025-12-12 09:11:30 +08:00
'provinceCode': provinceCode,
'province': province,
'birth': birth,
'age': age,
'gender': gender,
'checksumValid': checksumValid,
'constellation': constellation,
'zodiac': zodiac,
};
@override
String toString() => toJson().toString();
}
2026-04-13 08:59:45 +08:00
/// 只校验 18 位身份证号
IDCardInfo parseChineseIDCard(String? id) {
final raw = _normalizeId(id);
2025-12-12 09:11:30 +08:00
if (raw.isEmpty) {
2026-04-13 08:59:45 +08:00
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,
);
2025-12-12 09:11:30 +08:00
}
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': '澳门特别行政区',
2026-04-13 08:59:45 +08:00
'91': '国外',
2025-12-12 09:11:30 +08:00
};
2026-04-13 08:59:45 +08:00
final addressCode = raw.substring(0, 6);
final provinceCode = raw.substring(0, 2);
final province = provinceMap[provinceCode];
if (province == null) {
2025-12-12 09:11:30 +08:00
return IDCardInfo(
raw: raw,
isValid: false,
2026-04-13 08:59:45 +08:00
error: '地址码不合法:省份码错误',
id18: raw,
addressCode: addressCode,
provinceCode: provinceCode,
2025-12-12 09:11:30 +08:00
checksumValid: false,
);
}
2026-04-13 08:59:45 +08:00
final yearStr = raw.substring(6, 10);
final monthStr = raw.substring(10, 12);
final dayStr = raw.substring(12, 14);
2025-12-12 09:11:30 +08:00
2026-04-13 08:59:45 +08:00
if (!RegExp(r'^(18|19|20)\d{2}$').hasMatch(yearStr)) {
2025-12-12 09:11:30 +08:00
return IDCardInfo(
raw: raw,
isValid: false,
2026-04-13 08:59:45 +08:00
error: '年份不合法必须以18、19或20开头',
id18: raw,
addressCode: addressCode,
provinceCode: provinceCode,
province: province,
checksumValid: false,
2025-12-12 09:11:30 +08:00
);
}
2026-04-13 08:59:45 +08:00
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);
2025-12-12 09:11:30 +08:00
DateTime? birthDate;
try {
2026-04-13 08:59:45 +08:00
final dt = DateTime(year, month, day);
if (dt.year == year && dt.month == month && dt.day == day) {
birthDate = dt;
2025-12-12 09:11:30 +08:00
}
2026-04-13 08:59:45 +08:00
} catch (_) {
2025-12-12 09:11:30 +08:00
birthDate = null;
}
if (birthDate == null) {
return IDCardInfo(
raw: raw,
isValid: false,
2026-04-13 08:59:45 +08:00
error: '出生日期无效:请检查年月日是否真实存在',
id18: raw,
addressCode: addressCode,
provinceCode: provinceCode,
province: province,
checksumValid: false,
2025-12-12 09:11:30 +08:00
);
}
2026-04-13 08:59:45 +08:00
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,
);
2025-12-12 09:11:30 +08:00
}
2026-04-13 08:59:45 +08:00
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,
);
}
2025-12-12 09:11:30 +08:00
2026-04-13 08:59:45 +08:00
final gender = (seqNum % 2 == 1) ? '' : '';
2025-12-12 09:11:30 +08:00
2026-04-13 08:59:45 +08:00
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),
);
}
2025-12-12 09:11:30 +08:00
return IDCardInfo(
raw: raw,
isValid: true,
2026-04-13 08:59:45 +08:00
id18: raw,
addressCode: addressCode,
2025-12-12 09:11:30 +08:00
provinceCode: provinceCode,
province: province,
birthDate: birthDate,
2026-04-13 08:59:45 +08:00
birth: _formatDate(birthDate),
age: _calcAge(birthDate),
2025-12-12 09:11:30 +08:00
gender: gender,
2026-04-13 08:59:45 +08:00
checksumValid: true,
constellation: _calcConstellation(birthDate.month, birthDate.day),
zodiac: _calcChineseZodiac(birthDate.year),
2025-12-12 09:11:30 +08:00
);
}
2026-04-13 08:59:45 +08:00
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;
}
2025-12-12 09:11:30 +08:00
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++) {
2026-04-13 08:59:45 +08:00
final n = int.parse(id17[i]);
2025-12-12 09:11:30 +08:00
sum += n * weights[i];
}
2026-04-13 08:59:45 +08:00
return checkMap[sum % 11];
2025-12-12 09:11:30 +08:00
}
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 = [
2026-04-13 08:59:45 +08:00
'摩羯座',
'水瓶座',
'双鱼座',
'白羊座',
'金牛座',
'双子座',
'巨蟹座',
'狮子座',
'处女座',
'天秤座',
'天蝎座',
'射手座'
2025-12-12 09:11:30 +08:00
];
const startDays = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22];
2026-04-13 08:59:45 +08:00
final idx = month - 1;
2025-12-12 09:11:30 +08:00
if (day < startDays[idx]) {
return names[(idx + 11) % 12];
} else {
return names[idx];
}
}
String _calcChineseZodiac(int year) {
2026-04-13 08:59:45 +08:00
const zodiacs = ['', '', '', '', '', '', '', '', '', '', '', ''];
2025-12-12 09:11:30 +08:00
final idx = (year - 1900) % 12;
final i = idx < 0 ? (idx + 12) % 12 : idx;
return zodiacs[i];
2026-04-13 08:59:45 +08:00
}