Browse Source

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

Sisyphus Agent 3 days ago
parent
commit
ced94deee3

+ 54 - 0
docs/superpowers/specs/2026-10-10-address-contact-backfill-realname-design.md

@@ -0,0 +1,54 @@
+# 地址联系人回填真实姓名 — 设计
+
+记录日期: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 已是两个 Service 的方法参数)。
+
+## 实现要点
+
+1. 新增私有方法 `backfillRealNameIfEmpty(Long userId, String contactName)`:
+   - `contactName` 空白 → 直接返回(不回填)
+   - 查询用户,`realName == null || trim().isEmpty()` → `setRealName(contactName.trim())` + `updateById`
+2. `UserAddressService.create/update`、`ConsigneeService.save`(新增与编辑两支)在保存成功后调用。
+3. 风格参照现有 `ConsigneeService.syncUserRegion()`:回填失败 try-catch 静默,**不阻塞地址保存主流程**。
+4. 零 schema 变更(`users.real_name` 列已存在);零前端改动;无新接口(不新增路由,无需更新 API_REFERENCE)。
+
+## 测试(按 tests/AGENTS.md 分层)
+
+- `UserAddressService`:
+  - realName 为空 + 创建地址 → 回填为 receiverName
+  - realName 为空 + 编辑地址 → 回填
+  - realName 已有 → 不覆盖
+  - 联系人空白 → 跳过不回填
+- `ConsigneeService`:新增、编辑两支,用例同上。
+
+## 明确不做
+
+- 不覆盖已填写的真实姓名
+- 不改前端任何页面
+- 家庭地址页不回填(无联系人字段)
+- 不做反向同步(realName → 地址联系人)
+
+## 风险
+
+- 若收货人是替他人下单(如给家人购物),他人姓名会被写入登录用户的真实姓名。按需求"无论在哪儿输入"接受此行为。