全球邮政编码校验规则详解:正则、边界情况与过度校验的代价
邮编校验是最容易写错的一段代码
邮政编码看起来是一个再简单不过的字段:一串数字,配个正则就完事了。 但只要业务跨出一个国家,这个字段就会变成表单里报错率最高的地方。 原因在于「邮编」这个词在不同国家指代的东西完全不同—— 美国的 ZIP 指向一个投递区,爱尔兰的 Eircode 指向一栋具体的建筑, 荷兰的 postcode 精确到一段街道的一侧,而香港和阿联酋根本没有这套体系。
更麻烦的是,邮编不是静态数据。ZIP+4 的后四位随邮路调整而变动, 新建楼盘会拿到全新的编码,行政区划调整也会引发批量改号。 一份三年前抓下来的邮编列表,今天用来做「白名单校验」,一定会误杀真实用户。
一、主要国家的邮编结构对照
下表列出十个常见市场的邮编结构。正则一列仅表达格式约束, 并不保证该编码真实存在,实际使用时请统一转大写、去掉首尾空白后再匹配。
| 国家 | 格式约束 | 示例 | 说明 |
|---|---|---|---|
| 美国 | ^\d{5}(-\d{4})?$ | 94107 / 94107-1234 | 后四位为 ZIP+4,标识投递区段,会随邮路调整而变化 |
| 加拿大 | ^[ABCEGHJ-NPRSTVXY]\d[A-Z] ?\d[A-Z]\d$ | K1A 0T6 | D F I O Q U 从不出现;W Z 不作首字母 |
| 英国 | 结构为 外码 + 空格 + 内码 | SW1A 1AA / M1 1AE | 外码长度 2-4 位不等,另有 GIR 0AA 等特例 |
| 日本 | ^\d{3}-\d{4}$ | 100-0001 | 1998 年由 3/5 位扩展为 7 位,可定位到町域 |
| 德国 | ^\d{5}$ | 10115 | 允许前导 0,务必按字符串而非整数存储 |
| 荷兰 | ^\d{4} ?[A-Z]{2}$ | 1012 AB | 首位数字不为 0;部分字母组合出于历史原因不启用 |
| 爱尔兰 | ^[A-Z]\d{2} ?[0-9A-Z]{4}$ | D02 X285 | 前 3 位路由键,后 4 位随机唯一标识,不含层级信息 |
| 巴西 | ^\d{5}-?\d{3}$ | 01310-100 | CEP,八位数字,连字符可省略 |
| 印度 | ^[1-9]\d{5}$ | 110001 | PIN 码六位,首位不为 0 |
| 澳大利亚 | ^\d{4}$ | 2000 | 四位数字,存在 0800 这类以 0 开头的编码 |
二、三个值得细看的体系
加拿大:被排除的八个字母
加拿大邮编是 A1A 1A1 形态的六位字母数字混合码, 前三位是前分拣区(Forward Sortation Area,FSA),后三位是投递单元。 关键细节是字符集:D、F、I、O、Q、U 六个字母在任何位置都不使用, 因为它们在光学识别时容易与 0、E、1、0、0、V 混淆;此外 W 和 Z 目前不作首字母,留作扩展。 因此可用首字母只有 18 个。
// 先归一化,再匹配 const normalize = (s) => s.toUpperCase().replace(/\s+/g, " ").trim() const CA = /^[ABCEGHJ-NPRSTVXY]\d[ABCEGHJ-NPRSTV-Z] ?\d[ABCEGHJ-NPRSTV-Z]\d$/
英国:没有「一个正则」能干净地搞定
英国邮编由外码(outward code)与内码(inward code)组成,中间用空格分隔, 例如 SW1A 1AA 中 SW1A 是外码、1AA 是内码。外码长度在 2 到 4 位之间浮动, 可能是 M1、B33、CR2、DN55、SW1A 等多种形态,因此 GOV.UK 给出的参考正则相当冗长, 其中还专门为 GIR 0AA 这个历史遗留编码开了一个分支。
两个实践建议:第一,接受用户不打空格的输入(SW1A1AA), 在后端补上空格再存储;第二,小心正则回溯——嵌套的可选分组容易写出灾难性回溯的模式, 在极端输入下会造成 ReDoS。若担心性能,可以先用长度和字符集做粗筛,再走完整匹配。
需要注意的是,格式正确不代表编码存在。要判断真实性需要查询官方数据源, 例如英国国家统计局按季度发布的邮编目录,或 Royal Mail 的 PAF 地址文件。
爱尔兰:故意设计成猜不出来的编码
爱尔兰在 2015 年 7 月启用 Eircode,它和其他国家的邮编逻辑截然不同: 七位字符分为三位「路由键」和四位「唯一标识符」,前者对应邮件分拨的主要镇区, 后者则精确到单个投递点。唯一标识符是随机生成、不含地理层级信息的, 字符集限定在数字与 A、C、D、E、F、H、K、N、P、R、T、V、W、X、Y 这十五个字母之内。 这种设计让人无法从一个 Eircode 推断出隔壁那栋楼的 Eircode, 也意味着「前缀相同 = 地理相邻」这条在别国成立的假设,在爱尔兰完全不成立。
三、约四十个国家和地区根本没有邮编
万国邮联的资料显示,大约四十个国家和地区没有建立邮政编码体系。 香港是最常被开发者忽略的一个:香港邮政认为本地无需邮编, 官方建议在必须填写的表单中留空。阿联酋、澳门、卡塔尔等地也属于这一类, 它们依靠详尽的门牌/建筑名或邮政信箱完成投递。
工程处理方式
- 在国家元数据里维护一个
hasPostalCode标志,为 false 时直接隐藏字段 - 数据库该列允许 NULL,而不是强制写入空字符串或 00000
- 若下游系统(如某些物流 API)强制要求非空,在适配层填充而非在用户表单层强求
四、严格校验为什么会误伤真实用户
数据滞后
新建成的住宅区、重新划分的邮路、合并的行政区,都会产生新编码。 本地白名单不可能实时同步,住在新小区的用户会被判定为「邮编不存在」。
前导零被吃掉
把邮编按整数存储或经由某些表格软件中转,01234 会变成 1234。 这类问题在美国东北部(麻省、新泽西等地大量 0 开头的 ZIP)和澳大利亚尤为常见。 邮编永远应当按字符串处理。
空格与连字符的宽容度不足
加拿大用户可能输入 K1A0T6,巴西用户可能输入 01310100, 日本用户可能输入 1000001。这些都是同一个编码的合理写法, 应当在归一化阶段吸收掉,而不是弹一个红色报错。
邮编与城市的交叉校验太死
同一个邮编常常对应多个可接受的城市名(USPS 就区分「首选城市名」与「可接受城市名」), 跨邮编的城市边界也普遍存在。用「邮编必须唯一对应城市」的规则去拦截,会产生大量假阳性。
推荐的分层策略:第一层用宽松正则做格式提示(不阻断提交); 第二层在提交时调用官方或商业地址校验服务,返回「已验证/已修正/无法验证」三态; 第三层对「无法验证」的地址不拒收,而是标记出来,由人工或后续物流环节确认。 把校验结果当成一个置信度信号,而不是一道门禁。
五、一个可维护的实现骨架
type PostalRule = {
required: boolean
pattern?: RegExp
normalize?: (raw: string) => string
hint: string
}
const RULES: Record<string, PostalRule> = {
US: {
required: true,
pattern: /^\d{5}(-\d{4})?$/,
normalize: (s) => s.trim(),
hint: "5 位数字,或 5+4 位(12345-6789)",
},
CA: {
required: true,
pattern: /^[ABCEGHJ-NPRSTVXY]\d[A-Z] ?\d[A-Z]\d$/,
normalize: (s) =>
s.toUpperCase().replace(/\s+/g, "").replace(/^(.{3})(.{3})$/, "$1 $2"),
hint: "A1A 1A1",
},
HK: { required: false, hint: "香港不使用邮政编码,可留空" },
AE: { required: false, hint: "阿联酋无统一邮编,可留空" },
}
export function checkPostalCode(country: string, raw: string) {
const rule = RULES[country]
if (!rule) return { level: "unknown" as const }
const value = rule.normalize ? rule.normalize(raw) : raw.trim()
if (!value) {
return rule.required
? { level: "error" as const, message: "请填写邮政编码", value }
: { level: "ok" as const, value: null }
}
if (rule.pattern && !rule.pattern.test(value)) {
// 注意:warning 而非 error,不阻断提交
return { level: "warning" as const, message: `格式似乎不对,期望:${rule.hint}`, value }
}
return { level: "ok" as const, value }
}要点有三:把规则数据化而不是散落在 if-else 里;归一化与校验分开, 存进数据库的永远是归一化后的值;格式不符返回 warning 而不是 error, 把最终决定权交给业务层。
小结
正则能回答的问题只有一个:「这串字符看起来像不像该国的邮编」。 它无法回答「这个编码是否存在」,更无法回答「这个编码是否与这条街道匹配」。 把这三个问题分开,用不同强度的手段处理,是邮编校验唯一可持续的做法。 如果需要为各国邮编规则准备回归测试用例, 可以用本站工具批量生成符合各国格式的样例数据,覆盖边界写法。