2026-10-10-address-contact-backfill-realname-design.md 3.0 KB

地址联系人回填真实姓名 — 设计

记录日期:2026-10-10

需求

用户第一次输入地址时,无论在哪个入口输入,如果 users.real_name(真实姓名)还没填,则用地址里的联系人姓名回填真实姓名。

决策

  • 方案:后端统一回填(方案1 + 子决策 b)
  • 触发规则:每次保存地址/收货人成功后(新增 和 编辑都触发),若当前用户 realName 为空 → 用联系人姓名回填。
    • 该规则天然幂等:回填一次后 realName 非空,后续保存不再触发,效果等价于"第一次输入地址时"。
    • 用户先编辑已有地址、realName 仍为空的情况也能覆盖。
  • 否决的方案:前端各入口分别处理(漏一个入口就破功,且多一次网络请求);仅改 ConsigneeService(漏掉商城收货地址入口)。

覆盖入口

入口 联系人字段 改动点
商城收货地址 /api/user/address/create + /update receiverName UserAddressService.create() / update()
收货人管理 /api/consignee/save(新增 + 编辑) name ConsigneeService.save()
  • 家庭地址页(pages/family/address-edit)无联系人字段,不涉及。
  • 回填目标始终为当前登录用户的 realName。userId 来源:UserAddressService.create/update 由方法参数传入;ConsigneeService.save 在新增分支取 consignee.getUserId()、编辑分支取 existing.getUserId()。

实现要点

  1. 新增独立组件 RealNameBackfillService(@Service,依赖 UserMapper),提供方法 backfill(Long userId, String contactName):
    • contactName 空白 → 直接返回(不回填)
    • 查询用户,realName == null || trim().isEmpty() → setRealName(contactName.trim()) + updateById
    • 用独立组件而非私有方法:UserAddressService 与 ConsigneeService 两个类都要调用,且避免 UserService 双向依赖
  2. UserAddressService.create/update、ConsigneeService.save(新增与编辑两支)在保存成功后注入并调用 RealNameBackfillService#backfill。
  3. 风格参照现有 ConsigneeService.syncUserRegion():回填失败 try-catch 静默,不阻塞地址保存主流程。
  4. 零 schema 变更(users.real_name 列已存在);零前端改动;无新接口(不新增路由,无需更新 API_REFERENCE)。

测试(按 tests/AGENTS.md 分层)

  • UserAddressService:
    • realName 为空 + 创建地址 → 回填为 receiverName
    • realName 为空 + 编辑地址 → 回填
    • realName 已有 → 不覆盖
    • 联系人空白 → 跳过不回填
  • ConsigneeService:新增、编辑两支,用例同上。

明确不做

  • 不覆盖已填写的真实姓名
  • 不改前端任何页面
  • 家庭地址页不回填(无联系人字段)
  • 不做反向同步(realName → 地址联系人)

风险

  • 若收货人是替他人下单(如给家人购物),他人姓名会被写入登录用户的真实姓名。按需求"无论在哪儿输入"接受此行为。