|
|
@@ -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 → 地址联系人)
|
|
|
+
|
|
|
+## 风险
|
|
|
+
|
|
|
+- 若收货人是替他人下单(如给家人购物),他人姓名会被写入登录用户的真实姓名。按需求"无论在哪儿输入"接受此行为。
|