跳转到主要内容

人名、地名里的生僻字:系统怎么做

适合谁看:开发会员、订单、政务、金融等要录入人名和地址的系统,不想让生僻字用户「改名」的开发者。

常见的坑

环节 常见问题 正确做法
数据库 MySQL 用 utf8(即 utf8mb3),存不下 4 字节字符,扩展区的字被截断或报错 库、表、字段、连接全部用 utf8mb4[1]
输入校验 用 [\u4e00-\u9fa5] 判断「是不是汉字」,把扩展区的字当成非法字符 用 Unicode 属性判断,如 JavaScript 的 /\p{Script=Han}/u[2][3]
长度限制 按 UTF-16 单元计算长度,一个扩展区汉字算 2 个字符,名字被判「太长」 按码位或「字素」计算长度[4]
截断显示 截断时把代理对、变体选择符切开,出现乱码 按码位或字素截断
旧接口 和外部系统用 GBK 交换数据,扩展区的字变问号 用 UTF-8;必须用国标编码时用 GB 18030
显示 字体没有这个字,显示方框 字体栈加补字字体,见《网页和系统里怎么显示生僻字》
打印、证件 打印机或 PDF 没有嵌入字体 生成 PDF 时嵌入有这个字的字体

代码示例

JavaScript 判断是否全是汉字(包括扩展区):

const isHan = (s) => /^\p{Script=Han}+$/u.test(s);
isHan("𠮷"); // true(U+20BB7,扩展 B)

按码位计算长度:

[..."𠮷野"].length; // 2,而 "𠮷野".length 是 3

MySQL 建表:

CREATE TABLE person (
  name VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci
);

连接时也要指定 utf8mb4,否则数据在传输中就会出错。

测试清单

上线前,至少用下面几类字测一遍:

  • 扩展 A 的字(基本平面,但很多字体没有);
  • 扩展 B 及以后的字(4 字节);
  • 带变体选择符的字,见《异体字、变体选择符和汉字描述序列》;
  • 输入、保存、搜索、导出 Excel、打印、短信、对接第三方,每个环节都要过。

合规要求

用于政务服务和公共服务的产品,GB 18030-2022 要求满足实现级别 3,见《GB 18030:中文编码的国家标准》。

参考资料(4)

参考资料

    MySQL 文档:utf8mb4 字符集(英文),Oracle(MySQL) MDN:正则表达式 Unicode 属性转义 Unicode 标准附录 #29:文本分段(字素),Unicode 联盟 MDN:String length 与 UTF-16