美文网首页
并不简单的中文姓名校验

并不简单的中文姓名校验

作者: 前端C罗 | 来源:发表于2018-12-28 20:08 被阅读40次

谈到中文姓名校验,大家是既熟悉又陌生,茫茫然中使用面向百度编程大法找到一个正则表达式,放到项目中。输入张三,验证通过,完美!这就是我要的校验啦(😀)。
各位请跟C罗一起来看这个看似简单的问题,首先,下面给出一个目前项目中的场景,非常常见,基本满大街的项目可能都会遇到的。

姓名校验的表单
姓名校验的核心代码如下
const validChineseName = (name) => {
    return /^[\u3400-\u9fa5]$/g.test(name)
}

问题的转折点往往从但是开始的,上面简洁的实现貌似并不完美,有一天,来了一位名字为𥖄(xian)的用户,验证无法通过(😂)。咱们的故事由此开始讲起,整个故事中涉及到2个关键的知识点:

  • Unicode及其编码算法的一些知识
  • JS正则中对于字符相关的规则判断编写

先学习以下几个基本概念

Coded Character Set: 编码字符集,给字符表里的抽象字符编上一个数字,这些数字对跟字符集中的字符一一映射。Unicode字符集是一种编码字符集。

Character encoding form:,字符编码表,将编码字符集中的字符对应的码点转换成一定长度的二进制序列,便于计算机处理,此二进制序列与码点的映射关系称为字符编码表。我们经常提到的utf8、utf16都是指字符编码表的不同算法。

Code point:,码点,一个字符集一般 可以用一张或多张由多个行和多个列所构成的二位表来表示。二维表中的行和列的交叉点,称之为码点,码点拥有一个唯一的编号,称之为码点值或码点编号。

它们之间的关系可用下图来笼统地表达

字符编码表与编码字符集的关系
经常会有同学跟我讨论的时候把utf8utf16unicode混为一谈,结合上文的几个概念和示意图,不难发现,所谓的utf8只是基于unicode字符集及其码点的概念,提供一个码点寻址的算法,将其转换为计算机理解的二进制串,划一下重点。

Unicode对于互联网的巨大意义不言而喻,堪称信息互联的基石。Unicode流行起来之前,很多非英文字符国家会使用自己的一套玩法。比如GB2312,如果你没有安装相应的解码器,对不起,只能欣赏艺术感极强的乱码符号。

回到上面说到的生僻字校验的问题,任何一家尊重用户的企业,对于自己忠实的客户都不能将之拒之门外,哪怕这样的用户在巨大的群体中零星的存在。

初始时,考虑到以下2个问题

  • 用户姓名的生僻字很难枚举,不确定边界
  • 生僻字和emoji表情均是使用高、低代理区的方式表示
    基于以上问题,考虑使用用户客诉后收集生僻字构建平台自有的特殊字符集的方案。对于发生客诉后,强烈要求系统解决校验问题的客户,我们认为是忠诚度或者信任度较高的用户,构建此方案是利于我们留存这些用户,虽然体验不是那么完美,但至少能让此类用户有一个途径进一步触达产品的其他层面。
在配置平台拉取生僻字符集

此方案的核心代码如下

const validChineseName = (n) => {
    const excludedChars = ["𥖄","𤰉"];
    const excludedCharsStr = excludedChars.join('');
    const reg = new RegExp(`^([\u3400-\u9fa5${excludedCharsStr}]){2,15}$`, 'g');
    return reg.test(n);
};

使用mocha做一下单元测试,验证一下校验方法

// 功能示例代码
const validChineseName = (n) => {
    const excludedChars = ["𥖄","𤰉"];
    const excludedCharsStr = excludedChars.join('');
    const reg = new RegExp(`^([\u3400-\u9fa5${excludedCharsStr}]){2,15}$`, 'g');
    return reg.test(n);
};

module.exports = {
    validChineseName
};
// 单元测试示例代码
const expect = require('chai').expect;
const mocha = require('mocha');
const validator = require('./validator');

describe('中文姓名校验', function() {
  it('罗 应该是 false', function() {
    expect(validator.validChineseName('罗')).to.be.equal(false);
  });
});

describe('中文姓名校验', function() {
  it('𥖄 应该是 false', function() {
    expect(validator.validChineseName('𥖄')).to.be.equal(false);
  });
});

describe('中文姓名校验', function() {
  it('罗𥖄 应该是 true', function() {
    expect(validator.validChineseName('罗𥖄')).to.be.equal(true);
  });
});

describe('中文姓名校验', function() {
  it('罗超 应该是 true', function() {
    expect(validator.validChineseName('超')).to.be.equal(true);
  });
});

第一轮单元测试的结果截图如下


单元测试结果截图

第二个用例未通过单元测试,从上面的代码看出来,第二个只有一个生僻字𥖄,理论上我们预期它的校验结果应该是false,但实际这一个生僻字校验的结果居然是true,something went wrong。

字符的正则校验,本质上可以理解为使用unicode来做匹配,因此,通过线上unicode与汉字的转算工具,查看𥖄对应的unicode\ud855\udd84。问题看来是出在高低代理对上。

为了验证我们的猜测,可以使用如下的正则来类比,本质上是一致的。

/[ab]{2,10}/g.test('ab')
// true

也就是说,在正则匹配的时候底层会把字符转换为unicode的utf-16的编码,然后进行匹配

定位到问题后,只需要把连续的生僻字的高低代理队结合起来再动态构造正则表达式,JavaScript中字符串提供了一个方法charCodeAt,对于我们处理这个问题是一个很重要的函数。

The charCodeAt() method returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index.
The UTF-16 code unit matches the Unicode code point for code points that can be represented in a single UTF-16 code unit. If the Unicode code point cannot be represented in a single UTF-16 code unit (because its value is greater than 0x10000) then the code unit returned will be the first part of a surrogate pair for the code point. If you want the entire code point value, use codePointAt().
通过调用charCodeAt可以获取对应utf16编码,其中原则如下

  • 可以用1个编码单元表示的时候直接用1个编码单元来表示字符的码点
  • 如果1个编码单元无法表示的时候,使用2个编码单元,构成高低代理位的形式,通过一定的算法来表示字符的码点
    可以用代码和相应的结果来直观感受
const aCharCode = 'a'.charCodeAt(0).toString(16).padStart(4, '0')
// aCharCode = \u0061
const hCode = '𥖄'.charCodeAt(0).toString(16).padStart(4, '0')
const lCode = '𥖄'.charCodeAt(1).toString(16).padStart(4, '0')
const code = `\\u${hCode}\\u${lCode}`
// code = \ud855\udd84

经过万般折腾后,调整后的校验示例代码如下

// 获取给定字符的utf-16编码
const getCharCode = (c) => {
    const h = c.charCodeAt(0);
    const l = c.charCodeAt(1);
    let hStr = '', lStr = '';
    if (h) {
        let hCode = h.toString(16).padStart(4, '0');
        hStr = `\\u${hCode}`;
    }
    if (l) {
        let lCode = l.toString(16).padStart(4, '0');
        lStr = `\\u${lCode}`;
    }
    const charCode = `${hStr}${lStr}`;
    return charCode;
}
// 校验
const validChineseName = (n) => {
    const excludedChars = ["𥖄","𤰉"];
    const charCodeArr = excludedChars.map(c => {
        return getCharCode(c)
    });
    const excludedCharsStr = charCodeArr.join(')|(');
    const reg = new RegExp(`^([\u3400-\u9fa5]|(${excludedCharsStr})){2,15}`, 'g');
    return reg.test(n);
};

运行单元测试,用例结果如下图所示


单元测试结果截图

到此,一个前端兼容生僻字的方案有了初步的实现,方案不完美,还有以下问题需要同步考虑

欢迎留言讨论 (by 前端cluo

相关文章

  • 并不简单的中文姓名校验

    谈到中文姓名校验,大家是既熟悉又陌生,茫茫然中使用面向百度编程大法找到一个正则表达式,放到项目中。输入张三,验证通...

  • JavaScript-简单的页面输入控制

    1.姓名只能是汉字 2.简单的身份证校验(校验内容包括数字,位数,以及末尾的x,X) 3.简单的手机号码校验(号码...

  • leveldb源码学习--crc32

    CRC原理 基本概念 CRC(Cyclic Redundancy Check)中文名是循环冗余校验,其计算简单,被...

  • ant-design vue组件库使用后总结

    1、form组件 自定义校验 template: methods: (1)校验不含中文字符 (2)校验金额 2、...

  • UITextField文字字数检验方法

    邮箱校验 或者 中文检验 只允许输入字母数字 中文输入时字数限制 添加监听 实现方法 移除监听 字数检验 谓词校验...

  • 输入框校验:只能输入中文

    输入框校验:只能输入中文

  • 常用的正则校验

    1-255整数正则 姓名(纯汉字)校验 [\u4E00-\u9Fa5]表示中文汉字。以下代码匹配2-10个汉字,如...

  • js工具方法

    1 数字转中文 常用正则校验和部分工具方法

  • LaTeX:拥有(中文)姓名

    现在很多国外期刊都允许让中国、日本和韩国的研究者在自己的英文名后面附上自己的母语姓名,我们CJK大家族终于拥有姓名...

  • 常用正则表达式

    1、校验密码强度 2、校验中文 3、由数字、26个英文字母或下划线组成的字符串 4、校验E-Mail 地址 5、校...

网友评论

      本文标题:并不简单的中文姓名校验

      本文链接:https://www.haomeiwen.com/subject/sjvmkqtx.html