Skip to content

Commit

Permalink
Merge pull request baidu#9540 from CheshireJCat/fix-id-card-number-va…
Browse files Browse the repository at this point in the history
…lidate

fix: 完善身份证号码的校验逻辑
  • Loading branch information
hsm-lv committed Jan 30, 2024
2 parents 60d7f4d + 60e7d1c commit fc3396b
Show file tree
Hide file tree
Showing 6 changed files with 303 additions and 33 deletions.
4 changes: 2 additions & 2 deletions docs/zh-CN/components/form/formitem.md
Original file line number Diff line number Diff line change
Expand Up @@ -1272,7 +1272,7 @@ amis 会有默认的报错信息,如果你想自定义校验信息,配置`va
| `isPhoneNumber` | 是否为合法的手机号码 | `(value: any) => boolean` | |
| `isTelNumber` | 是否为合法的电话号码 | `(value: any) => boolean` | |
| `isZipcode` | 是否为邮编号码 | `(value: any) => boolean` | |
| `isId` | 是否为身份证号码,没做校验 | `(value: any) => boolean` |
| `isId` | 是否为身份证号码,支持 18 位和 15 位验证,单个验证请使用 `isId18` / `isId15` | `(value: any) => boolean` |
| `matchRegexp:/foo/` | 必须命中某个正则。 | `(value: any, regexp: string \| RegExp) => boolean` | |
| `matchRegexp${n}:/foo/` | 必须命中某个正则。 设置正则表达式时属性名需以 `matchRegexp` 开头,`n`支持`1-9`,且 `validations``validationsErrors` 中属性名需匹配。 | `(value: any, regexp: string \| RegExp) => boolean` | |
| `isDateTimeSame` | 日期和目标日期相同,支持指定粒度,默认到毫秒 `millisecond` | `(value: any, targetDate: any, granularity?: string) => boolean` | `2.2.0` |
Expand Down Expand Up @@ -1781,7 +1781,7 @@ fillMapping 配置 支持变量取值和表达式;
| description | [模板](../../../docs/concepts/template) | | 表单项描述 |
| placeholder | `string` | | 表单项描述 |
| inline | `boolean` | | 是否为 内联 模式 |
| strictMode | `boolean` | | 通过配置 false 可以及时获取所有表单里面的数据,否则可能会有不同步 |
| strictMode | `boolean` | | 通过配置 false 可以及时获取所有表单里面的数据,否则可能会有不同步 |
| submitOnChange | `boolean` | | 是否该表单项值发生变化时就提交当前表单。 |
| disabled | `boolean` | | 当前表单项是否是禁用状态 |
| disabledOn | [表达式](../../../docs/concepts/expression) | | 当前表单项是否禁用的条件 |
Expand Down
164 changes: 164 additions & 0 deletions packages/amis-core/src/utils/validateId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/**
* 身份证号码校验,(没有校验城市和区县)
*
* 身份证号码是由18位组成,分别表示:
* 第1、2位数字表示:所在省份的代码;
* 第3、4位数字表示:所在城市的代码;
* 第5、6位数字表示:所在区县的代码;
* 第7-14位数字表示:出生年、月、日(其中7、8、9、10位是年,11、12位是月,13、14位是日);
* 第15-17位数字表示:顺序编码,都是同一地址辖区内的,以及同年同月同日出生人的顺序码,同时 第17位兼具性别标识功能,男单女双;
* 第18位是校检码:可以是0-9的数字,有时也用X表示, X来代替10。
*
* 身份证15位时,次序为省(2位)市(2位)区县(2位)年(2位)月(2位)日(2位)顺序编码(3位),皆为数字,没有最后一位校验码
*/
export function isId18(id: string) {
if (!/^\d{17}(\d|X)$/.test(id)) {
return false;
}
const info = getInfoFromId(id);
if (!info) {
return false;
}
const {province, city, country, birthday} = info;
if (!verifyRegion(province, city, country) || !verifyBirthday(birthday)) {
return false;
}
return verifyCheckCode(id);
}
export function isId15(id: string) {
if (!/^\d{15}$/.test(id)) {
return false;
}
const info = getInfoFromId(id);
if (!info) {
return false;
}
const {province, city, country, birthday} = info;
return verifyRegion(province, city, country) && verifyBirthday(birthday);
}
export function isId(id: string) {
return isId18(id) || isId15(id);
}

function getProvInfo(prov: string) {
return {
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: '国外'
}[prov];
}

function getInfoFromId(id: string) {
if (
(id.length === 18 && !/^\d{17}(\d|X)$/.test(id)) ||
(id.length === 15 && !/^\d{15}$/.test(id))
) {
return;
}
let data;
if (id.length == 18) {
data = id.match(
/^(\d{2})(\d{2})(\d{2})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X)$/
);
} else if (id.length == 15) {
data = id.match(/^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{3})$/);
}
if (!data) {
return;
}
const [
_id,
province,
city,
country,
year,
month,
day,
sequenceCode,
checkCode
] = data;
return {
province,
provinceName: getProvInfo(province),

city,
country,
year,
month,
day,
birthday: new Date(year + '/' + month + '/' + day),
sequenceCode,
checkCode,
isMale: +sequenceCode % 2 === 1,
isFemale: +sequenceCode % 2 === 0
};
}

function verifyRegion(province: string, city: string, country: string) {
if (!getProvInfo(province)) {
return false;
}
// 这里暂时不获取市和区的具体信息,因为调用CityDB会把校验函数传染为异步
// city位: 00表示省;01-20,51-70表示省直辖市;21-50表示地区(自治州、盟)
if (!/^(?:[0-6]\d|70)$/.test(city)) {
return false;
}
if (!/^\d\d$/.test(country)) {
return false;
}
return true;
}

function verifyBirthday(birthday: any) {
return !isNaN(+birthday);
}

/**
* 校验码计算方式
* 1、将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2 ;
* 2、将这17位数字和系数相乘的结果相加;
* 3、用加出来和除以11,看余数是多少;
* 4、余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2;
* 5、通过上面得知如果余数是2,余数所对应的最后一位身份证号是X,就会在身份证的第18位数字上出现罗马数字的X。
*/
function verifyCheckCode(id: string) {
const arrInt = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
const arrCh = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
let cardTemp = 0;
for (let i = 0; i < 17; i++) {
cardTemp += +id.slice(i, i + 1) * arrInt[i];
}
return arrCh[cardTemp % 11] === id.slice(17, 18);
}
61 changes: 32 additions & 29 deletions packages/amis-core/src/utils/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const makeRegexp = (reg: string | RegExp) => {
};
import memoize from 'lodash/memoize';
import isPlainObject from 'lodash/isPlainObject';
import {isId, isId15, isId18} from './validateId';

const makeUrlRegexp = memoize(function (options: any) {
options = {
Expand Down Expand Up @@ -99,10 +100,12 @@ export interface ValidateFn {
arg3?: any,
arg4?: any,
arg5?: any
): boolean | {
error: boolean;
msg?: string;
};
):
| boolean
| {
error: boolean;
msg?: string;
};
}

export const validations: {
Expand Down Expand Up @@ -261,13 +264,13 @@ export const validations: {
return !isExisty(value) || isEmpty(value) || /^\d{6}$/.test(value);
},
isId: function (values, value) {
return (
!isExisty(value) ||
isEmpty(value) ||
/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)/.test(
value
)
);
return !isExisty(value) || isEmpty(value) || isId(String(value));
},
isId18: function (values, value) {
return !isExisty(value) || isEmpty(value) || isId18(String(value));
},
isId15: function (values, value) {
return !isExisty(value) || isEmpty(value) || isId15(String(value));
},
notEmptyString: function (values, value) {
return !isExisty(value) || !(String(value) && String(value).trim() === '');
Expand Down Expand Up @@ -488,6 +491,8 @@ export const validateMessages: {
isTelNumber: 'validate.isTelNumber',
isZipcode: 'validate.isZipcode',
isId: 'validate.isId',
isId18: 'validate.isId',
isId15: 'validate.isId',
isDateTimeSame: 'validate.isDateTimeSame',
isDateTimeBefore: 'validate.isDateTimeBefore',
isDateTimeAfter: 'validate.isDateTimeAfter',
Expand Down Expand Up @@ -518,7 +523,6 @@ export function validate(
msg: string;
}> = [];


if (rules) {
const ruleNames = Object.keys(rules);
const length = ruleNames.length;
Expand Down Expand Up @@ -547,10 +551,7 @@ export function validate(
// {error: true, msg: '错误提示'}
// 格式的信息来灵活展示错误
let fnResErrorMsg = '';
if (
typeof validateRes === 'object' &&
validateRes.error === true
) {
if (typeof validateRes === 'object' && validateRes.error === true) {
fnResErrorMsg = validateRes?.msg ?? '';
}

Expand All @@ -560,20 +561,22 @@ export function validate(
msgRuleName = `${ruleName}Array`;
}

return [{
rule: ruleName,
msg: filter(
__(
return [
{
rule: ruleName,
msg: filter(
__(
(messages && messages[ruleName]) ||
fnResErrorMsg ||
validateMessages[msgRuleName] ||
validateMessages[ruleName]
),
{
...[''].concat(args)
}
)
}];
fnResErrorMsg ||
validateMessages[msgRuleName] ||
validateMessages[ruleName]
),
{
...[''].concat(args)
}
)
}
];
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion packages/amis-editor/src/tpl/validations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,13 @@ setSchemaTpl('validations', function () {
value: 'isZipcode'
},
{
label: '身份证号码',
label: '身份证号码(18/15位)',
value: 'isId'
},
{
label: '身份证号码(18位)',
value: 'isId18'
},
{
label: 'JSON格式',
value: 'isJson'
Expand Down
11 changes: 10 additions & 1 deletion packages/amis-editor/src/validator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -367,14 +367,23 @@ registerValidator(
}
},
{
label: '身份证号码',
label: '身份证号码(18/15位)',
name: 'isId',
group: ValidationGroup.Pattern,
message: '请输入合法的身份证号',
tag: {
[ValidatorTag.Text]: ValidTagMatchType.isMore
}
},
{
label: '身份证号码(18位)',
name: 'isId18',
group: ValidationGroup.Pattern,
message: '请输入合法的身份证号',
tag: {
[ValidatorTag.Text]: ValidTagMatchType.isMore
}
},
{
label: 'JSON格式',
name: 'isJson',
Expand Down

0 comments on commit fc3396b

Please sign in to comment.