本文要点

  • 保留 rawAddress,不要只存解析后字段
  • 邮编和门牌号使用文本类型
  • 行政区域使用可选多层字段而非固定 State
  • 订单应保存下单时的地址快照
作者
Random Address Generator 编辑团队
审核
数据模型与 QA 审核组
最近实质更新
从国际化、原始值保留、邮编类型和地址版本化角度重写。

先区分用户输入、解析值和展示值

用户输入是原始证据,解析字段用于搜索和业务逻辑,展示值则按国家顺序组合。只保存其中一种会让后续无法核对自动修改,也无法在解析器升级后重新处理。

建议保留 rawAddressLines、parsedComponents、normalizedAddress 和 normalizationProvider/version。归一化不成功也应能保存原始输入,而不是把整条记录丢弃。

字段类型目的
countryCodeCHAR(2)ISO 国家代码
addressLinesJSON / TEXT[]保留可变行数
adminArea1..3TEXT nullable多级行政区域
localityTEXT nullable城市、suburb 或邮政城镇
postalCodeTEXT nullable保留前导零和字母
rawAddressJSON审计和重新解析

不要把看起来像数字的字段存成数字

ZIP Code、Postcode、PLZ 和门牌号是标识符。它们可以有前导零、字母、空格、连字符或范围。用整数类型会丢失信息,并且让不同国家需要不同补丁。

所有文本字段应使用 Unicode 编码,长度限制来自真实数据和业务边界,而不是美文样本的平均长度。

type PostalAddress = {
  countryCode: string;
  addressLines: string[];
  adminArea1?: string;
  adminArea2?: string;
  adminArea3?: string;
  locality?: string;
  postalCode?: string;
  rawAddress: unknown;
};

空字符串、null 和未提供不要混用

Address Line 2 可以没有值。在 API 契约中明确它应被省略、传 null 还是传空字符串,并在入库层统一。否则查询、去重和导出会将同一状态当成三种值。

不要用 N/A 或横线填充缺失地址行;这些占位符会进入标签和数据导出。

用快照保护订单和审计记录

用户的地址簿可以继续更新,但已创建订单、发票和发货记录应保留当时的地址快照。如果订单只引用当前 addressId,用户修改地址后历史记录也会改变。

快照应包含展示行、结构化字段、当时验证状态和时间,而不要只保存一条格式化字符串。

  • 订单地址不随地址簿更新
  • 记录归一化提供者与版本
  • 验证状态含时间戳
  • 敏感日志不输出完整真实地址

用跨国固定样本验证模式迁移

每次数据库迁移都用包含英国房屋名、加拿大字母邮编、德语字符、日文原文和前导零的固定样本做往返测试。迁移前后比较语义字段和原始 JSON,而不是只比较行数。

迁移预演还要记录被截断、无法转码或从空字符串变为 null 的记录数量。如果数据库提示迁移成功,但应用读回时无法重建原始行,这次迁移仍然失败。在上线前抽取新旧版本的结构化差异,并为不可逆更改准备恢复或重放方案。

  • 比较迁移前后的 Unicode 字符串和 JSON 结构
  • 统计截断、编码失败和 null 转换
  • 从应用层重新读取而非只查数据库成功状态
  • 为已保存的 rawAddress 准备重放工具

常见问题

地址表应该有固定的 Address Line 1、2、3 吗?

前端可以展示固定数量,但底层使用 addressLines 数组更能容纳不同国家的行数。

Postal Code 应该用 VARCHAR 吗?

是的。邮编可有字母、空格、连字符和前导零,不适合数值类型。

为什么要保留 rawAddress?

它用于审计自动修改、排查解析错误,并在解析器升级后重新处理。

来源与进一步阅读

  1. MDN:地址层级与自动填充字段
  2. W3C:国际化地址模式讨论