邮政编码看起来很简单——五位数字,有时再加四位——但这一系统比看上去更有结构,而把校验与地理映射规则搞错会引发真实 bug。本指南涵盖美国邮政编码的结构、第一位数字如何编码大致地理区域、ZIP+4 如何工作、JavaScript/Python/正则中最实用的校验模式,以及你的代码应当优雅处理的边界情况。
简史
Zone Improvement Plan(ZIP)由美国邮政署(USPS)于 1963 年推出,以加快邮件分拣。原始的 5 位编码定义了一个投递区。1983 年新增的 ZIP+4 用于标识一个街区或一个高业务量的收件单位(如企业或政府机关)。大多数面向用户的代码只需 5 位版本,但完全忽略 ZIP+4 是最常见的校验 bug。
5 位邮编的结构
标准邮编的五位数字并不是随机的。它们是一段层级化的地址:
- 第 1 位——国家级区域:九大宽广地理区域之一。东北从 0 起,西海岸以 9 收尾。
- 第 2–3 位——分区中心设施(SCF):服务于一批邮局的处理中心。
- 第 4–5 位——投递区:SCF 内某个具体邮局或投递单位。
仅凭第一位就能判断大致区域。前三位(“邮编前缀”)能可靠地映射到一个州——这正是我们的 邮编查询工具用来识别邮编所属州的依据。
第 1 位对应的区域
| 第一位 | 区域 | 示例州 |
|---|---|---|
| 0 | 新英格兰与波多黎各 / 美属维尔京 | CT, MA, ME, NH, NJ, RI, VT, PR |
| 1 | 中大西洋地区 | DE, NY, PA, 华盛顿特区 |
| 2 | 东南 / 阿巴拉契亚 | NC, SC, VA, WV, MD, DC |
| 3 | 东南 | AL, FL, GA, MS, TN, KY |
| 4 | 中西部 / 五大湖 | OH, IN, MI, WI, IL |
| 5 | 上中西部 / 平原 | IA, MN, MT, ND, SD, NE, KS, MO, WI |
| 6 | 中南部 | AR, LA, OK, TX, NM |
| 7 | 南部 / 中南部 | OK, TX, AR, LA |
| 8 | 山地西部 | AZ, CO, ID, NM, NV, UT, WY |
| 9 | 西海岸及太平洋属地 | CA, OR, WA, AK, HI, GU, AS |
注意,有些州的邮编前缀会横跨区域边界——比如肯塔基既有 4 开头也有其他。“这个邮编是否属于某州”的可靠信号仍是前三位,而不是仅看第一位。
ZIP+4——多出的四位意味着什么
ZIP+4 在 5 位邮编后加一个连字符再加四位数字:
- 多 2 位:具体某个城市街区或一组街道
- 多 2 位:该街区的具体某段,或某个大型收件单位
大多数现代地址表单同时接受 5 位或完整的 ZIP+4。USPS 更偏好 ZIP+4,因为这让自动化分拣机可以直接把邮件投到投递员路线;但传统的 5 位邮编仍然普遍有效。
用正则校验
最简单稳健的模式允许 5 位数字,可选地后接一个连字符和 4 位数字:
^\d{5}(?:-\d{4})?$
该模式接受 90210 和 90210-1234,同时拒绝空格、填充和含奇怪字符的邮编。一个常见错误是强制要求 ZIP+4,这会破坏每一个被 Chrome 保存的地址自动填充的注册表单。
用 JavaScript 校验
const ZIP_RE = /^\d{5}(?:-\d{4})?$/;
function normalizeZip(input) {
const zip = String(input).trim();
if (!ZIP_RE.test(zip)) return null;
return zip.length === 10 ? zip : zip.slice(0, 5);
}
normalizeZip(" 90210-1234 "); // "90210-1234"
normalizeZip("90210"); // "90210"
normalizeZip("9021"); // null
normalizeZip("90210 1234"); // null(空格不被允许)
normalizeZip 辅助函数要么返回规范格式,要么返回 null,让你在 UI 中分流转很方便:返回 null 时显示行内错误;否则把规范值传给你的 API。
用 Python 校验
import re
ZIP_RE = re.compile(r"^\d{5}(?:-\d{4})?$")
def normalize_zip(value: str) -> str | None:
zip_code = value.strip()
if not ZIP_RE.match(zip_code):
return None
return zip_code[:5] if len(zip_code) == 5 else zip_code
若你需要一次性完成校验与地理编码,USA Data Tools 地址 API 会为任意格式合法的邮编返回对应州,让你无需自带前缀表就能拒绝不匹配任何州的邮编。
把邮编映射到州
前三位映射到一个 USPS SCF,而该 SCF 又映射到一个或多个州。实用的查找表相当大,你不该手维护——要么用 邮编查询工具,要么用地址 API:
async function stateForZip(zip) {
const r = await fetch(
`/us-address/api/v1/zip-lookup?zip=${encodeURIComponent(zip)}`,
{ headers: { Authorization: `Bearer ${KEY}` } }
);
if (!r.ok) return null;
const { state } = await r.json();
return state;
}
值得了解的边界情况:少数邮编确实存在歧义——例如联邦联合摄影邮编、以及跨州边界的大型企业邮编。在表单设计中,最好同时索取邮编和州,再校验二者一致。这样能同时抓住拼写错误和罕见的歧义情况。
军事与属地邮编
你的校验器应当接受以下这些看起来不同寻常的邮编:
- 美国军方 APO/FPO/DPO:340xx 区间的邮编,州缩写为
AA、AE或AP。这些是经 USPS 投递的海外军方地址。 - 波多黎各:006xx–009xx 区间,州缩写
PR。有时仅接受 PR 而非拼写全称“Puerto Rico”。 - 美属维尔京群岛:008xx,州
VI。 - 关岛、美属萨摩亚、北马里亚纳:969xx 区间,州
GU、AS或MP。
拒收这些邮编的配送逻辑是电商中最常见也最容易修的 bug 之一。
常见校验错误
- 未去除首尾空白。移动端自动填充经常在前或后加空格——校验前先 trim。
- 用整数类型存储。把邮编存成整数会丢失前导 0,导致每个新罕布什尔(030xx)和波多黎各(006xx)邮编都被破坏。邮编永远是字符串。
- 强制 ZIP+4。在注册表单上要求扩展码是徒增摩擦收益甚微。
- 不拒绝伪造的测试邮编,如
00000和12345。某些邮编是真实的(12345 是纽约斯克内克塔迪通用电气的邮编),有些看似有效却未分配——用查找表或 API 拒绝未分配的。 - 只按第 1 位路由而不是前缀。第 1 位无法唯一标识一个州。
提示:生成假地址时,请确保邮编与州的配对内部一致。随机 5 位数字配一个随机州会在你自己的下游校验里失败。我们的地址生成器从按州维护的前缀列表中选取邮编,这就是为什么生成的“加利福尼亚”地址总是带 90xxx–96xxx 邮编。
代码评审可直接粘贴的速查表
// 接受:5 位数字,可选 ZIP+4
const ZIP_RE = /^\d{5}(?:-\d{4})?$/;
// 绝不要:把邮编存成整数
// 绝不要:input 的 maxlength=5(会破坏 ZIP+4 的粘贴)
// 绝不要:在公开表单强制 ZIP+4
// 一定要:trim 空白、按字符串存储、按州校验
总结
邮政编码是一小块规则却微妙的数据。对大多数应用,5 位版本就够了;ZIP+4 是锦上添花。前三位是能可靠映射到州的最小前缀。先在本地校验格式,并在信任该值进入下游前——最好用一张维护中的查找表或 USA Data Tools API——校验邮编与州的对应关系。把这些做好,你的地址表单就能少出边界类 bug。