@hzab/utils 1.0.10 → 1.0.11-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +4 -0
- package/package.json +1 -1
- package/src/idCardVerify.ts +238 -0
package/changelog.md
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 身份证号码合法性校验
|
|
3
|
+
* @param {string} idCard 身份证号码
|
|
4
|
+
* @param {Object} opt 校验选项
|
|
5
|
+
* @param {boolean} opt.validBirthDate 是否校验出生日期
|
|
6
|
+
* @returns {boolean} 是否合法
|
|
7
|
+
*/
|
|
8
|
+
export function isValidIdCard(idCard, opt?) {
|
|
9
|
+
// 空值校验
|
|
10
|
+
if (!idCard) return false;
|
|
11
|
+
|
|
12
|
+
// 去除空格
|
|
13
|
+
idCard = idCard.trim().toUpperCase();
|
|
14
|
+
|
|
15
|
+
// 长度校验
|
|
16
|
+
const len = idCard.length;
|
|
17
|
+
if (len !== 15 && len !== 18) return false;
|
|
18
|
+
|
|
19
|
+
// 15位身份证正则(仅数字,出生日期格式为YYMMDD)
|
|
20
|
+
const reg15 = /^[1-9]\d{7}((0[1-9])|(1[0-2]))((0[1-9])|([1-2]\d)|(3[0-1]))\d{3}$/;
|
|
21
|
+
// 18位身份证正则(前17位数字,最后一位可能是数字或X)
|
|
22
|
+
const reg18 = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))((0[1-9])|([1-2]\d)|(3[0-1]))\d{3}[\dX]$/;
|
|
23
|
+
|
|
24
|
+
// 格式校验
|
|
25
|
+
if (len === 15 && !reg15.test(idCard)) return false;
|
|
26
|
+
if (len === 18 && !reg18.test(idCard)) return false;
|
|
27
|
+
|
|
28
|
+
// 15位转18位后校验校验码
|
|
29
|
+
let id18 = idCard;
|
|
30
|
+
if (len === 15) {
|
|
31
|
+
id18 = convert15to18(idCard);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 校验地址码(省级+市级)
|
|
35
|
+
if (!isValidAddressCode(id18)) return false;
|
|
36
|
+
|
|
37
|
+
// 校验出生日期是否在合理范围内(1900年-当前年份)
|
|
38
|
+
if (opt?.validBirthDate && !isValidBirthDate(id18)) return false;
|
|
39
|
+
|
|
40
|
+
// 校验最后一位校验码
|
|
41
|
+
return verifyCheckCode(id18);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 校验地址码(前6位)
|
|
46
|
+
* 规则校验:省级代码合法 + 市级代码格式合法
|
|
47
|
+
*/
|
|
48
|
+
export function isValidAddressCode(id18) {
|
|
49
|
+
const addrCode = id18.substring(0, 6);
|
|
50
|
+
|
|
51
|
+
const provinceCode = addrCode.substring(0, 2);
|
|
52
|
+
const cityCode = addrCode.substring(2, 4);
|
|
53
|
+
|
|
54
|
+
// 1. 校验省级代码(第1-2位)
|
|
55
|
+
if (!isValidProvinceCode(provinceCode)) return false;
|
|
56
|
+
|
|
57
|
+
// 2. 校验市级代码(第3-4位)
|
|
58
|
+
if (!isValidCityCode(cityCode)) return false;
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 校验省级代码(第1-2位)
|
|
65
|
+
* 合法范围:11-15, 21-23, 31-37, 41-46, 50-54, 61-65, 71, 81, 82
|
|
66
|
+
*/
|
|
67
|
+
function isValidProvinceCode(code) {
|
|
68
|
+
const pattern = /^(1[1-5]|2[1-3]|3[1-7]|4[1-3]|4[4-6]|5[0-4]|6[1-5]|71|81|82)$/;
|
|
69
|
+
return pattern.test(code);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 校验市级代码(第3-4位)
|
|
74
|
+
* 规则:
|
|
75
|
+
* - 01-20:常规地级市、自治州、地区、盟
|
|
76
|
+
* - 51-59:地区(历史遗留)
|
|
77
|
+
* - 90:省直辖县级市/县
|
|
78
|
+
* - 不能是00
|
|
79
|
+
*/
|
|
80
|
+
function isValidCityCode(code) {
|
|
81
|
+
const pattern = /^(0[1-9]|1[0-9]|20|5[1-9]|90)$/;
|
|
82
|
+
return pattern.test(code);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 校验出生日期是否合法且在一定范围内
|
|
87
|
+
* @param {string} id18 18位身份证号码
|
|
88
|
+
* @returns {boolean} 出生日期是否合法
|
|
89
|
+
*/
|
|
90
|
+
export function isValidBirthDate(id18) {
|
|
91
|
+
// 提取出生日期信息
|
|
92
|
+
const birthYear = parseInt(id18.substring(6, 10));
|
|
93
|
+
const birthMonth = parseInt(id18.substring(10, 12));
|
|
94
|
+
const birthDay = parseInt(id18.substring(12, 14));
|
|
95
|
+
|
|
96
|
+
const currentYear = new Date().getFullYear();
|
|
97
|
+
|
|
98
|
+
// 年份范围校验:1900年到当前年份(包含)
|
|
99
|
+
if (birthYear < 1900 || birthYear > currentYear) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 月份范围校验
|
|
104
|
+
if (birthMonth < 1 || birthMonth > 12) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 日期范围校验(考虑闰年)
|
|
109
|
+
const daysInMonth = getDaysInMonth(birthYear, birthMonth);
|
|
110
|
+
|
|
111
|
+
if (birthDay < 1 || birthDay > daysInMonth) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 获取指定月份的天数
|
|
120
|
+
* @param {number} year 年份
|
|
121
|
+
* @param {number} month 月份(1-12)
|
|
122
|
+
* @returns {number} 该月的天数
|
|
123
|
+
*/
|
|
124
|
+
export function getDaysInMonth(year, month) {
|
|
125
|
+
// 闰年判断:能被4整除但不能被100整除,或者能被400整除
|
|
126
|
+
const isLeapYear = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
127
|
+
|
|
128
|
+
const daysInMonth = [31, isLeapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
129
|
+
return daysInMonth[month - 1];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 15位身份证转18位
|
|
134
|
+
* @param {string} id15 15位身份证号码
|
|
135
|
+
* @returns {string} 18位身份证号码
|
|
136
|
+
*/
|
|
137
|
+
export function convert15to18(id15) {
|
|
138
|
+
// 提取15位中的年份(两位)
|
|
139
|
+
const yearTwoDigits = parseInt(id15.substring(6, 8));
|
|
140
|
+
// 年份范围判断:如果两位年份大于当前年份的后两位,则属于1900年代,否则属于2000年代
|
|
141
|
+
const currentYear = new Date().getFullYear();
|
|
142
|
+
const currentYearTwoDigits = currentYear % 100;
|
|
143
|
+
|
|
144
|
+
let fullYear;
|
|
145
|
+
if (yearTwoDigits > currentYearTwoDigits) {
|
|
146
|
+
fullYear = 1900 + yearTwoDigits;
|
|
147
|
+
} else {
|
|
148
|
+
fullYear = 2000 + yearTwoDigits;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 年份前加完整的四位年份
|
|
152
|
+
let id17 = id15.substring(0, 6) + fullYear + id15.substring(8);
|
|
153
|
+
// 计算校验码
|
|
154
|
+
const checkCode = calculateCheckCode(id17);
|
|
155
|
+
return id17 + checkCode;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 计算18位身份证的校验码
|
|
160
|
+
* @param {string} id17 身份证前17位
|
|
161
|
+
* @returns {string} 校验码(0-9或X)
|
|
162
|
+
*/
|
|
163
|
+
export function calculateCheckCode(id17) {
|
|
164
|
+
// 加权因子
|
|
165
|
+
const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
|
|
166
|
+
// 校验码对应值
|
|
167
|
+
const checkCodes = ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"];
|
|
168
|
+
|
|
169
|
+
let sum = 0;
|
|
170
|
+
for (let i = 0; i < 17; i++) {
|
|
171
|
+
sum += parseInt(id17[i]) * weights[i];
|
|
172
|
+
}
|
|
173
|
+
const mod = sum % 11;
|
|
174
|
+
return checkCodes[mod];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 校验18位身份证的校验码
|
|
179
|
+
* @param {string} id18 18位身份证号码
|
|
180
|
+
* @returns {boolean} 校验码是否正确
|
|
181
|
+
*/
|
|
182
|
+
export function verifyCheckCode(id18) {
|
|
183
|
+
const id17 = id18.substring(0, 17);
|
|
184
|
+
const providedCode = id18[17];
|
|
185
|
+
const calculatedCode = calculateCheckCode(id17);
|
|
186
|
+
return providedCode === calculatedCode;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 计算年龄
|
|
191
|
+
* @param {number} year 出生年份
|
|
192
|
+
* @param {number} month 出生月份
|
|
193
|
+
* @param {number} day 出生日期
|
|
194
|
+
* @returns {number} 年龄
|
|
195
|
+
*/
|
|
196
|
+
export function calculateAge(year, month, day) {
|
|
197
|
+
const today = new Date();
|
|
198
|
+
let age = today.getFullYear() - year;
|
|
199
|
+
const monthDiff = today.getMonth() + 1 - month;
|
|
200
|
+
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < day)) {
|
|
201
|
+
age--;
|
|
202
|
+
}
|
|
203
|
+
return age;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 获取身份证详细信息
|
|
208
|
+
* @param {string} idCard 身份证号码
|
|
209
|
+
* @returns {object|null} 详细信息对象,校验失败返回null
|
|
210
|
+
*/
|
|
211
|
+
export function getIdCardInfo(idCard) {
|
|
212
|
+
if (!isValidIdCard(idCard)) return null;
|
|
213
|
+
|
|
214
|
+
let id18 = idCard.trim().toUpperCase();
|
|
215
|
+
if (id18.length === 15) {
|
|
216
|
+
id18 = convert15to18(id18);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 解析信息
|
|
220
|
+
const provinceCode = id18.substring(0, 2);
|
|
221
|
+
const birthYear = parseInt(id18.substring(6, 10));
|
|
222
|
+
const birthMonth = parseInt(id18.substring(10, 12));
|
|
223
|
+
const birthDay = parseInt(id18.substring(12, 14));
|
|
224
|
+
const genderCode = parseInt(id18.substring(16, 17));
|
|
225
|
+
|
|
226
|
+
const birthDate = new Date(birthYear, birthMonth - 1, birthDay);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
province: provinceCode,
|
|
230
|
+
birthDate: birthDate,
|
|
231
|
+
birthYear: birthYear,
|
|
232
|
+
birthMonth: birthMonth,
|
|
233
|
+
birthDay: birthDay,
|
|
234
|
+
age: calculateAge(birthYear, birthMonth, birthDay),
|
|
235
|
+
gender: genderCode % 2 === 1 ? "男" : "女",
|
|
236
|
+
isValid: true,
|
|
237
|
+
};
|
|
238
|
+
}
|