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

该模式接受 9021090210-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 区间的郵遞區號,州缩写為 AAAEAP。这些是经 USPS 投遞的海外军方地址。
  • 波多黎各:006xx–009xx 区间,州缩写 PR。有时仅接受 PR 而非拼写全称“Puerto Rico”。
  • 美屬维尔京群岛:008xx,州 VI
  • 關岛、美屬萨摩亚、北马里亚纳:969xx 区间,州 GUASMP

拒收这些郵遞區號的配送逻辑是電商中最常见也最容易修的 bug 之一。

常见校驗錯誤

  1. 未去除首尾空白。移动端自动填充经常在前或后加空格——校驗前先 trim。
  2. 用整数类型儲存。把郵遞區號存成整数会丢失前导 0,导致每個新罕布什爾(030xx)和波多黎各(006xx)郵遞區號都被破坏。郵遞區號永远是字符串。
  3. 强制 ZIP+4。在註冊表单上要求扩展碼是徒增摩擦收益甚微。
  4. 不拒绝伪造的測試郵遞區號,如 0000012345。某些郵遞區號是真實的(12345 是纽约斯克内克塔迪通用電气的郵遞區號),有些看似有效却未分配——用查找表或 API 拒绝未分配的。
  5. 只按第 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。