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

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

## 常见的坑

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

## 代码示例

JavaScript 判断是否全是汉字（包括扩展区）：

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

按码位计算长度：

```js
[..."𠮷野"].length; // 2，而 "𠮷野".length 是 3
```

MySQL 建表：

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

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

## 测试清单

上线前，至少用下面几类字测一遍：

- 扩展 A 的字（基本平面，但很多字体没有）；
- 扩展 B 及以后的字（4 字节）；
- 带变体选择符的字，见[《异体字、变体选择符和汉字描述序列》](https://docs.cocobook.com/link/1283)；
- 输入、保存、搜索、导出 Excel、打印、短信、对接第三方，每个环节都要过。

## 合规要求

用于政务服务和公共服务的产品，GB 18030-2022 要求满足实现级别 3，见[《GB 18030：中文编码的国家标准》](https://docs.cocobook.com/link/1280)。

<p style="margin-top:2.5em;padding-top:.6em;border-top:1px solid #e6e6e6;font-size:.85em;color:#8a8a8a"><a href="https://docs.cocobook.com/link/1307#bkmrk-src-e09-systems" style="color:inherit;text-decoration:none;border-bottom:1px dotted #bbb">参考资料</a></p>