郵政編碼看起来很簡单——五位數字,有时再加四位——但这一系统比看上去更有結构,而把校驗與地理映射规则搞錯会引發真實 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。