# 一些证件校验规则
# 校验身份证
interface IFormatId {
address: string | null;
birth: string | null;
code: string | null;
}
class IdValidate {
// 每一位的加权数
static powerMap = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
// 余数对应的最后一位校验码
static codeMap = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
id: string;
oldCard: boolean = false;
constructor(id: string) {
this.id = id;
this.normalId();
}
// 兼容15位身份证
normalId() {
// 15位:年份只有后两位,没有校验码
const id = this.id;
if (id.length === 15) {
const id17 = id.substring(0, 6) + '19' + id.substring(6);
this.id = id17 + this.getCode(id17);
this.oldCard = true;
}
}
// 根据前17位算出最后一位
getCode(str: string): string {
let sum = 0;
for (let i = 0; i < 17; i++) {
sum += +str[i]! * IdValidate.powerMap[i]!;
}
return IdValidate.codeMap[sum % 11]!;
}
// 解析身份证号码得到每一块对应的数字
formatId(): IFormatId {
// 18位:省市区6位、年月日8位、随机数3位、校验码1位
const id = this.id;
// eslint-disable-next-line max-len
const re = /^([1-8]\d{5})((?:19|20)\d{2}(?:(?:0[1-9])|(?:1[012]))(?:(?:0[1-9])|(?:[12]\d)|(?:3[01])))\d{3}([\dXx])$/;
const execRes = re.exec(id);
if (execRes) {
return {
address: execRes[1] || null,
birth: execRes[2] || null,
code: execRes[3] || null
};
}
return {
address: null,
birth: null,
code: null
};
}
checkAddress(str: string): boolean {
// 接收前6位的数字,检验前2位
const re = /^1[1-5]|2[123]|3[1-7]|4[1-6]|5[0-4]|6[1-5]|71|81|82$/;
return re.test(str.substring(0, 2));
}
checkBirth(str: string): boolean {
// 小于当前日期,并且日期合法
const now = new Date();
// @ts-ignore
const [, year, month, day] = /(\d{4})(\d{2})(\d{2})/.exec(str);
const realDate = new Date(+year, +month - 1, +day);
const realYear = realDate.getFullYear();
const realMonth = realDate.getMonth() + 1;
const realDay = realDate.getDate();
return realDate < now && +year === realYear && +month === realMonth && +day === realDay;
}
checkCode(code: string): boolean {
// 1代身份证不用校验,因为本来就是手动算出来的
if (this.oldCard) {
return true;
}
const rightCode = this.getCode(this.id);
return code === rightCode;
}
validate() {
const formatRes = this.formatId();
if (formatRes) {
const {address, birth, code} = formatRes;
if (address && birth && code) {
return this.checkAddress(address) && this.checkBirth(birth) && this.checkCode(code);
}
}
return false;
}
}
// 校验身份证号
export function validateCNId(number: string): boolean {
const validate = new IdValidate(number);
return validate.validate();
}
// 校验身份证姓名
export function validateName(name) {
// 所有的汉字及各种点的unicode,1-20个字符
const re = /^[\p{Unified_Ideograph}\u25aa\u2022\u25cf\u00b7\u002e]{1,20}$/u;
return re.test(name);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# 校验港澳台居民往来内地通行证
// http://www.locpg.hk/fuzn/2013-03/05/c_125939266.htm
export function validateHKId(id) {
// HM分别表示香港澳门,8位终身号码,2位换证次数
const re = /^[HM]\d{8}(\d{2})?$/;
// 台湾居民通行证号码为8位阿拉伯数字
const reTW = /^\d{8}$/;
return re.test(id) || reTW.test(id);
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 校验定居国外的中国公民护照
// 第一位为字母,第二位为字母或数字,后7位为数字
export function validatePassportId(id) {
const re = /^[a-zA-Z][a-zA-Z0-9]\d{7}$/;
return re.test(id);
}
1
2
3
4
5
2
3
4
5
# 港澳台居民大陆居住证号码正则表达式
class HkRpIdValidate {
// 每一位的加权数
static powerMap = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
// 余数对应的最后一位校验码
static codeMap = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
id: string;
oldCard: boolean = false;
constructor(id: string) {
this.id = id;
this.normalId();
}
// 兼容15位身份证
normalId() {
// 15位:年份只有后两位,没有校验码
const id = this.id;
if (id.length === 15) {
const id17 = id.substring(0, 6) + '19' + id.substring(6);
this.id = id17 + this.getCode(id17);
this.oldCard = true;
}
}
// 根据前17位算出最后一位
getCode(str: string): string {
let sum = 0;
for (let i = 0; i < 17; i++) {
// @ts-ignore
sum += +str[i] * powerMap[i];
}
return codeMap[sum % 11] || '';
}
// 解析身份证号码得到每一块对应的数字
formatId() {
// 18位:省市区6位、年月日8位、随机数3位、校验码1位
const id = this.id;
const re = /^(8[123]0000)((?:19|20)\d{2}(?:(?:0[1-9])|(?:1[012]))(?:(?:0[1-9])|(?:[12]\d)|(?:3[01])))\d{3}([\dXx])$/;
const execRes = re.exec(id);
if (execRes) {
return {
address: execRes[1],
birth: execRes[2],
code: execRes[3]
};
}
return null;
}
checkAddress(str: string): boolean {
// 接收前6位的数字,检验前2位
const re = /^1[1-5]|2[123]|3[1-7]|4[1-6]|5[0-4]|6[1-5]|71|81|82|83$/;
return re.test(str.substring(0, 2));
}
checkBirth(str: string): boolean {
// 小于当前日期,并且日期合法
const now = new Date();
// @ts-ignore
const [_, year, month, day] = /(\d{4})(\d{2})(\d{2})/.exec(str);
const realDate = new Date(+year, +month - 1, +day);
const realYear = realDate.getFullYear();
const realMonth = realDate.getMonth() + 1;
const realDay = realDate.getDate();
return realDate < now && +year === realYear && +month === realMonth && +day === realDay;
}
checkCode(code: string): boolean {
// 1代身份证不用校验,因为本来就是手动算出来的
if (this.oldCard) {
return true;
}
const rightCode = this.getCode(this.id);
return code === rightCode;
}
validate() {
const formatRes = this.formatId();
if (formatRes) {
const {address = '', birth = '', code = ''} = formatRes;
return this.checkAddress(address) && this.checkBirth(birth) && this.checkCode(code);
}
return false;
}
}
// 港澳台居民大陆居住证号码正则表达式
export function validateHkRpId(id) {
const validator = new HkRpIdValidate(id);
return validator.validate();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# 校验外国人永久居留证
跟身份证规则差不多, 首位数字固定为“9”,为外国人标识码
interface IFormatForeignId {
identificationCode: string | null;
address: string | null;
nationality: string | null;
birth: string | null;
code: string | null;
}
class ForeignIdValidate {
// 每一位的加权数
static powerMap = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
// 余数对应的最后一位校验码
static codeMap = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
id: string;
oldCard: boolean = false;
constructor(id: string) {
this.id = id;
this.normalForeignId();
}
// 兼容15位外国人永久居留证
normalForeignId() {
const id = this.id;
if (id.length === 15) {
this.oldCard = true;
}
}
// 根据前17位算出最后一位
getCode(str: string): string {
let sum = 0;
for (let i = 0; i < 17; i++) {
sum += +str[i]! * ForeignIdValidate.powerMap[i]!;
}
return ForeignIdValidate.codeMap[sum % 11]!;
}
// 解析身份证号码得到每一块对应的数字
formatId(): IFormatForeignId {
// 18位:第一位固定是9、申领地2位、国籍国代码3位、年月日8位、顺序码3位、校验码1位
const id = this.id;
// eslint-disable-next-line max-len
const re = /^([9])([1-8]\d{1})([0-9]\d{2})((?:19|20)\d{2}(?:(?:0[1-9])|(?:1[012]))(?:(?:0[1-9])|(?:[12]\d)|(?:3[01])))\d{3}([\dXx])$/;
const execRes = re.exec(id);
if (execRes) {
return {
identificationCode: execRes[1] || null,
address: execRes[2] || null,
nationality: execRes[3] || null,
birth: execRes[4] || null,
code: execRes[5] || null
};
}
return {
identificationCode: null,
address: null,
nationality: null,
birth: null,
code: null
};
}
checkIdentificationCode(str: string): boolean {
// 接收第一位标识码 判断是否固定为9
return str === '9';
}
checkAddress(str: string): boolean {
// 接收申领地2位数字 即中国省级行政区划代码
const re = /^1[1-5]|2[123]|3[1-7]|4[1-6]|5[0-4]|6[1-5]|71|81|82$/;
return re.test(str);
}
checkBirth(str: string): boolean {
// 小于当前日期,并且日期合法
const now = new Date();
// @ts-ignore
const [, year, month, day] = /(\d{4})(\d{2})(\d{2})/.exec(str);
const realDate = new Date(+year, +month - 1, +day);
const realYear = realDate.getFullYear();
const realMonth = realDate.getMonth() + 1;
const realDay = realDate.getDate();
return realDate < now && +year === realYear && +month === realMonth && +day === realDay;
}
checkCode(code: string): boolean {
const rightCode = this.getCode(this.id);
return code === rightCode;
}
validate() {
if (this.oldCard) {
// 旧版按旧版规则走
// 15位,前三位为国籍缩写,后12位为阿拉伯数字
const re = /^[a-zA-Z]{3}\d{12}$/;
return re.test(this.id);
}
const formatRes = this.formatId();
if (formatRes) {
const {identificationCode, address, birth, code} = formatRes;
if (identificationCode && address && birth && code) {
return this.checkIdentificationCode(identificationCode)
&& this.checkAddress(address) && this.checkBirth(birth) && this.checkCode(code);
}
}
return false;
}
}
// 校验外国人永久居留证
export function validateForeignId(id: string): boolean {
const validate = new ForeignIdValidate(id);
return validate.validate();
}
// 校验外国人永久居留证姓名
// 英文,支持逗号(英文)
export function validateForeignName(name) {
const re = /^[a-zA-Z]+,?[a-zA-Z]+$/;
return re.test(name);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125