|
|
@@ -0,0 +1,528 @@
|
|
|
+# 地址联系人回填真实姓名 实现计划
|
|
|
+
|
|
|
+> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
|
|
|
+
|
|
|
+**目标:** 用户保存商城收货地址或收货人时,若其 `users.real_name` 为空,则用联系人姓名自动回填真实姓名(幂等,仅首次生效)。
|
|
|
+
|
|
|
+**架构:** 新增独立组件 `RealNameBackfillService`(依赖 `UserMapper`,提供 `backfill(userId, contactName)`);在 `UserAddressService.create/update` 与 `ConsigneeService.save`(新增/编辑两支)保存成功后调用。回填失败 try-catch 静默,不阻塞地址保存主流程(风格参照 `ConsigneeService.syncUserRegion()`)。零 schema 变更、零前端改动、无新接口。
|
|
|
+
|
|
|
+**技术栈:** Java 8 / Spring Boot 2.7.18 / MyBatis-Plus / JUnit 5 + Mockito(纯单测,无 Spring 上下文、无 MySQL 连接)
|
|
|
+
|
|
|
+**规格:** `docs/superpowers/specs/2026-10-10-address-contact-backfill-realname-design.md`
|
|
|
+
|
|
|
+**⚠️ 工作区约束:** 当前工作区有与本任务无关的未提交改动(`ProductRecommendationController.java`、`ProductRecommendationService.java`、若干根目录临时文件等)。所有 `git add` 必须精确指定本任务文件,**禁止 `git add -A` / `git add .`**。
|
|
|
+
|
|
|
+**⚠️ 测试运行约束:** `cfc-backend/pom.xml:28` 有 `<skipTests>true</skipTests>`,跑单测必须显式 `-DskipTests=false`,否则输出 `Tests are skipped` 是假通过。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 文件结构
|
|
|
+
|
|
|
+| 文件 | 动作 | 职责 |
|
|
|
+|---|---|---|
|
|
|
+| `cfc-backend/src/main/java/com/etotem/cfc/service/RealNameBackfillService.java` | 创建 | 真实姓名回填守卫:realName 空白才写入,幂等,失败静默 |
|
|
|
+| `cfc-backend/src/main/java/com/etotem/cfc/service/UserAddressService.java` | 修改 | `create`/`update` 保存成功后调用 backfill |
|
|
|
+| `cfc-backend/src/main/java/com/etotem/cfc/service/ConsigneeService.java` | 修改 | `save` 新增/编辑两支保存成功后调用 backfill |
|
|
|
+| `cfc-backend/src/test/java/com/etotem/cfc/service/RealNameBackfillServiceTest.java` | 创建 | 回填守卫逻辑单测(8 用例) |
|
|
|
+| `cfc-backend/src/test/java/com/etotem/cfc/service/UserAddressServiceBackfillTest.java` | 创建 | UserAddressService 接线单测(2 用例) |
|
|
|
+| `cfc-backend/src/test/java/com/etotem/cfc/service/ConsigneeServiceBackfillTest.java` | 创建 | ConsigneeService 接线单测(2 用例) |
|
|
|
+
|
|
|
+**关键顺序决策(任务 3):** `ConsigneeService.save` 中 backfill 必须放在 `syncUserRegion(...)` **之后**。`syncUserRegion` 在 realName 为空时回退用 nickname 匹配联系人姓名;若先回填 realName 会改变其匹配结果,破坏既有地区同步行为。放后面可完全保持既有行为不变。
|
|
|
+
|
|
|
+任务分解:
|
|
|
+- 任务 1:`RealNameBackfillService`(TDD:测试→失败→实现→通过→commit)
|
|
|
+- 任务 2:`UserAddressService` 接线(TDD)
|
|
|
+- 任务 3:`ConsigneeService` 接线(TDD)
|
|
|
+- 任务 4:整体验证(compile + 三测试类 12 用例全绿)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 任务 1:RealNameBackfillService 回填组件
|
|
|
+
|
|
|
+**文件:**
|
|
|
+- 创建:`cfc-backend/src/main/java/com/etotem/cfc/service/RealNameBackfillService.java`
|
|
|
+- 测试:`cfc-backend/src/test/java/com/etotem/cfc/service/RealNameBackfillServiceTest.java`
|
|
|
+
|
|
|
+- [ ] **步骤 1:编写失败的测试**
|
|
|
+
|
|
|
+创建 `cfc-backend/src/test/java/com/etotem/cfc/service/RealNameBackfillServiceTest.java`:
|
|
|
+
|
|
|
+```java
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.etotem.cfc.entity.User;
|
|
|
+import com.etotem.cfc.mapper.UserMapper;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.junit.jupiter.api.extension.ExtendWith;
|
|
|
+import org.mockito.InjectMocks;
|
|
|
+import org.mockito.Mock;
|
|
|
+import org.mockito.junit.jupiter.MockitoExtension;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.*;
|
|
|
+import static org.mockito.Mockito.*;
|
|
|
+
|
|
|
+@ExtendWith(MockitoExtension.class)
|
|
|
+class RealNameBackfillServiceTest {
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private UserMapper userMapper;
|
|
|
+
|
|
|
+ @InjectMocks
|
|
|
+ private RealNameBackfillService realNameBackfillService;
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void backfill_真实姓名为null时回填联系人姓名() {
|
|
|
+ User user = new User();
|
|
|
+ user.setRealName(null);
|
|
|
+ when(userMapper.selectById(1L)).thenReturn(user);
|
|
|
+
|
|
|
+ realNameBackfillService.backfill(1L, "张三");
|
|
|
+
|
|
|
+ assertEquals("张三", user.getRealName());
|
|
|
+ verify(userMapper).updateById(user);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void backfill_真实姓名为空白时回填联系人姓名() {
|
|
|
+ User user = new User();
|
|
|
+ user.setRealName(" ");
|
|
|
+ when(userMapper.selectById(1L)).thenReturn(user);
|
|
|
+
|
|
|
+ realNameBackfillService.backfill(1L, "李四");
|
|
|
+
|
|
|
+ assertEquals("李四", user.getRealName());
|
|
|
+ verify(userMapper).updateById(user);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void backfill_联系人姓名去除首尾空白后回填() {
|
|
|
+ User user = new User();
|
|
|
+ user.setRealName(null);
|
|
|
+ when(userMapper.selectById(1L)).thenReturn(user);
|
|
|
+
|
|
|
+ realNameBackfillService.backfill(1L, " 王五 ");
|
|
|
+
|
|
|
+ assertEquals("王五", user.getRealName());
|
|
|
+ verify(userMapper).updateById(user);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void backfill_真实姓名已有值时不覆盖() {
|
|
|
+ User user = new User();
|
|
|
+ user.setRealName("原有姓名");
|
|
|
+ when(userMapper.selectById(1L)).thenReturn(user);
|
|
|
+
|
|
|
+ realNameBackfillService.backfill(1L, "张三");
|
|
|
+
|
|
|
+ assertEquals("原有姓名", user.getRealName());
|
|
|
+ verify(userMapper, never()).updateById(any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void backfill_联系人姓名为null时跳过() {
|
|
|
+ realNameBackfillService.backfill(1L, null);
|
|
|
+
|
|
|
+ verifyNoInteractions(userMapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void backfill_联系人姓名为空白时跳过() {
|
|
|
+ realNameBackfillService.backfill(1L, " ");
|
|
|
+
|
|
|
+ verifyNoInteractions(userMapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void backfill_userId为null时跳过() {
|
|
|
+ realNameBackfillService.backfill(null, "张三");
|
|
|
+
|
|
|
+ verifyNoInteractions(userMapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void backfill_用户不存在时跳过() {
|
|
|
+ when(userMapper.selectById(99L)).thenReturn(null);
|
|
|
+
|
|
|
+ realNameBackfillService.backfill(99L, "张三");
|
|
|
+
|
|
|
+ verify(userMapper, never()).updateById(any());
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **步骤 2:运行测试验证失败**
|
|
|
+
|
|
|
+运行(PowerShell,仓库根目录):
|
|
|
+
|
|
|
+```powershell
|
|
|
+cd cfc-backend
|
|
|
+mvn test -DskipTests=false -Dtest=RealNameBackfillServiceTest
|
|
|
+```
|
|
|
+
|
|
|
+预期:COMPILATION ERROR — 找不到符号 `RealNameBackfillService`(类不存在,这就是本任务的红灯)。
|
|
|
+
|
|
|
+- [ ] **步骤 3:编写最少实现代码**
|
|
|
+
|
|
|
+创建 `cfc-backend/src/main/java/com/etotem/cfc/service/RealNameBackfillService.java`:
|
|
|
+
|
|
|
+```java
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.etotem.cfc.entity.User;
|
|
|
+import com.etotem.cfc.mapper.UserMapper;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 真实姓名回填:用户第一次输入地址/收货人时,若 users.real_name 尚未填写,
|
|
|
+ * 用联系人姓名回填(规格:docs/superpowers/specs/2026-10-10-address-contact-backfill-realname-design.md)。
|
|
|
+ * 幂等:回填一次后 realName 非空,后续保存不再触发。
|
|
|
+ * 风格参照 ConsigneeService.syncUserRegion():失败静默,不阻塞主流程。
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class RealNameBackfillService {
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(RealNameBackfillService.class);
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private UserMapper userMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 若 userId 对应用户的 realName 为空白,则用 contactName(去首尾空白后)回填。
|
|
|
+ * contactName 空白、userId 为空、用户不存在时直接跳过。
|
|
|
+ */
|
|
|
+ public void backfill(Long userId, String contactName) {
|
|
|
+ try {
|
|
|
+ if (userId == null) return;
|
|
|
+ if (contactName == null || contactName.trim().isEmpty()) return;
|
|
|
+ User user = userMapper.selectById(userId);
|
|
|
+ if (user == null) return;
|
|
|
+ String realName = user.getRealName();
|
|
|
+ if (realName != null && !realName.trim().isEmpty()) return;
|
|
|
+ user.setRealName(contactName.trim());
|
|
|
+ userMapper.updateById(user);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("[RealNameBackfill] 回填失败 userId={} : {}", userId, e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **步骤 4:运行测试验证通过**
|
|
|
+
|
|
|
+运行(同步骤 2):
|
|
|
+
|
|
|
+```powershell
|
|
|
+mvn test -DskipTests=false -Dtest=RealNameBackfillServiceTest
|
|
|
+```
|
|
|
+
|
|
|
+预期:`Tests run: 8, Failures: 0, Errors: 0, Skipped: 0`,BUILD SUCCESS。
|
|
|
+
|
|
|
+- [ ] **步骤 5:Commit**
|
|
|
+
|
|
|
+```powershell
|
|
|
+git add cfc-backend/src/main/java/com/etotem/cfc/service/RealNameBackfillService.java cfc-backend/src/test/java/com/etotem/cfc/service/RealNameBackfillServiceTest.java
|
|
|
+git commit -m "feat(realname): 新增 RealNameBackfillService 真实姓名回填组件"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 任务 2:UserAddressService 接线
|
|
|
+
|
|
|
+**文件:**
|
|
|
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/UserAddressService.java`(字段区 17-18 行、`create` 37-49 行、`update` 51-63 行)
|
|
|
+- 测试:`cfc-backend/src/test/java/com/etotem/cfc/service/UserAddressServiceBackfillTest.java`
|
|
|
+
|
|
|
+- [ ] **步骤 1:编写失败的测试**
|
|
|
+
|
|
|
+创建 `cfc-backend/src/test/java/com/etotem/cfc/service/UserAddressServiceBackfillTest.java`:
|
|
|
+
|
|
|
+```java
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.etotem.cfc.entity.UserAddress;
|
|
|
+import com.etotem.cfc.mapper.UserAddressMapper;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.junit.jupiter.api.extension.ExtendWith;
|
|
|
+import org.mockito.InjectMocks;
|
|
|
+import org.mockito.Mock;
|
|
|
+import org.mockito.junit.jupiter.MockitoExtension;
|
|
|
+
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
+import static org.mockito.Mockito.*;
|
|
|
+
|
|
|
+@ExtendWith(MockitoExtension.class)
|
|
|
+class UserAddressServiceBackfillTest {
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private UserAddressMapper userAddressMapper;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private RealNameBackfillService realNameBackfillService;
|
|
|
+
|
|
|
+ @InjectMocks
|
|
|
+ private UserAddressService userAddressService;
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void create_保存成功后以收货人姓名回填真实姓名() {
|
|
|
+ UserAddress address = new UserAddress();
|
|
|
+ address.setReceiverName("张三");
|
|
|
+ address.setIsDefault(0);
|
|
|
+
|
|
|
+ userAddressService.create(address, 1L);
|
|
|
+
|
|
|
+ verify(userAddressMapper).insert(address);
|
|
|
+ verify(realNameBackfillService).backfill(1L, "张三");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void update_保存成功后以收货人姓名回填真实姓名() {
|
|
|
+ UserAddress existing = new UserAddress();
|
|
|
+ existing.setId(10L);
|
|
|
+ existing.setUserId(1L);
|
|
|
+ when(userAddressMapper.selectOne(any())).thenReturn(existing);
|
|
|
+
|
|
|
+ UserAddress address = new UserAddress();
|
|
|
+ address.setId(10L);
|
|
|
+ address.setReceiverName("李四");
|
|
|
+ address.setIsDefault(0);
|
|
|
+
|
|
|
+ userAddressService.update(address, 1L);
|
|
|
+
|
|
|
+ verify(userAddressMapper).updateById(address);
|
|
|
+ verify(realNameBackfillService).backfill(1L, "李四");
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+说明:`update` 走到 `updateById` 前会经 `get()` → `SortUtil.applySort`(`SortContext` 无值时为 no-op,单测安全)。
|
|
|
+
|
|
|
+- [ ] **步骤 2:运行测试验证失败**
|
|
|
+
|
|
|
+```powershell
|
|
|
+mvn test -DskipTests=false -Dtest=UserAddressServiceBackfillTest
|
|
|
+```
|
|
|
+
|
|
|
+预期:`Tests run: 2, Failures: 2, Errors: 0` — 两个用例均因 `verify` 失败:`Wanted but not invoked: realNameBackfillService.backfill(...)`(此时 `UserAddressService` 还没有回填调用;Mockito 未注入不存在的字段,`verify` 的是 mock 对象本身,断言"未发生"。Mockito 的 `WantedButNotInvoked` 是 `AssertionError` 子类,故计入 Failures)。
|
|
|
+
|
|
|
+- [ ] **步骤 3:编写最少实现代码**
|
|
|
+
|
|
|
+修改 `UserAddressService.java` 三处:
|
|
|
+
|
|
|
+字段区(18 行后)新增:
|
|
|
+
|
|
|
+```java
|
|
|
+ @Resource
|
|
|
+ private RealNameBackfillService realNameBackfillService;
|
|
|
+```
|
|
|
+
|
|
|
+`create` 方法(48 行 `userAddressMapper.insert(address);` 之后)追加一行:
|
|
|
+
|
|
|
+```java
|
|
|
+ userAddressMapper.insert(address);
|
|
|
+ realNameBackfillService.backfill(userId, address.getReceiverName());
|
|
|
+```
|
|
|
+
|
|
|
+`update` 方法(62 行 `userAddressMapper.updateById(address);` 之后)追加一行:
|
|
|
+
|
|
|
+```java
|
|
|
+ userAddressMapper.updateById(address);
|
|
|
+ realNameBackfillService.backfill(userId, address.getReceiverName());
|
|
|
+```
|
|
|
+
|
|
|
+`RealNameBackfillService` 同包,无需新增 import。
|
|
|
+
|
|
|
+- [ ] **步骤 4:运行测试验证通过**
|
|
|
+
|
|
|
+```powershell
|
|
|
+mvn test -DskipTests=false -Dtest=UserAddressServiceBackfillTest
|
|
|
+```
|
|
|
+
|
|
|
+预期:`Tests run: 2, Failures: 0, Errors: 0`,BUILD SUCCESS。
|
|
|
+
|
|
|
+- [ ] **步骤 5:Commit**
|
|
|
+
|
|
|
+```powershell
|
|
|
+git add cfc-backend/src/main/java/com/etotem/cfc/service/UserAddressService.java cfc-backend/src/test/java/com/etotem/cfc/service/UserAddressServiceBackfillTest.java
|
|
|
+git commit -m "feat(realname): 商城地址保存后回填用户真实姓名"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 任务 3:ConsigneeService 接线
|
|
|
+
|
|
|
+**文件:**
|
|
|
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/ConsigneeService.java`(字段区 18-22 行、`save` 41-76 行)
|
|
|
+- 测试:`cfc-backend/src/test/java/com/etotem/cfc/service/ConsigneeServiceBackfillTest.java`
|
|
|
+
|
|
|
+- [ ] **步骤 1:编写失败的测试**
|
|
|
+
|
|
|
+创建 `cfc-backend/src/test/java/com/etotem/cfc/service/ConsigneeServiceBackfillTest.java`:
|
|
|
+
|
|
|
+```java
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.etotem.cfc.entity.Consignee;
|
|
|
+import com.etotem.cfc.mapper.ConsigneeMapper;
|
|
|
+import com.etotem.cfc.mapper.UserMapper;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.junit.jupiter.api.extension.ExtendWith;
|
|
|
+import org.mockito.InjectMocks;
|
|
|
+import org.mockito.Mock;
|
|
|
+import org.mockito.junit.jupiter.MockitoExtension;
|
|
|
+
|
|
|
+import static org.mockito.Mockito.*;
|
|
|
+
|
|
|
+@ExtendWith(MockitoExtension.class)
|
|
|
+class ConsigneeServiceBackfillTest {
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private ConsigneeMapper consigneeMapper;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private UserMapper userMapper;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private RealNameBackfillService realNameBackfillService;
|
|
|
+
|
|
|
+ @InjectMocks
|
|
|
+ private ConsigneeService consigneeService;
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void save_新增分支保存成功后以联系人姓名回填真实姓名() {
|
|
|
+ Consignee consignee = new Consignee();
|
|
|
+ consignee.setUserId(1L);
|
|
|
+ consignee.setName("张三");
|
|
|
+ consignee.setIsDefault(0);
|
|
|
+
|
|
|
+ consigneeService.save(consignee);
|
|
|
+
|
|
|
+ verify(consigneeMapper).insert(consignee);
|
|
|
+ verify(realNameBackfillService).backfill(1L, "张三");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void save_编辑分支保存成功后以联系人姓名回填真实姓名() {
|
|
|
+ Consignee existing = new Consignee();
|
|
|
+ existing.setId(10L);
|
|
|
+ existing.setUserId(1L);
|
|
|
+ when(consigneeMapper.selectById(10L)).thenReturn(existing);
|
|
|
+
|
|
|
+ Consignee consignee = new Consignee();
|
|
|
+ consignee.setId(10L);
|
|
|
+ consignee.setName("李四");
|
|
|
+ consignee.setIsDefault(0);
|
|
|
+
|
|
|
+ consigneeService.save(consignee);
|
|
|
+
|
|
|
+ verify(consigneeMapper).updateById(consignee);
|
|
|
+ verify(realNameBackfillService).backfill(1L, "李四");
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+说明:`save` 内部 `syncUserRegion` 会调 `userMapper.selectById`(mock 默认返回 null → 其内部 early-return),`@Mock UserMapper` 仅为满足字段注入。
|
|
|
+
|
|
|
+- [ ] **步骤 2:运行测试验证失败**
|
|
|
+
|
|
|
+```powershell
|
|
|
+mvn test -DskipTests=false -Dtest=ConsigneeServiceBackfillTest
|
|
|
+```
|
|
|
+
|
|
|
+预期:`Tests run: 2, Failures: 2, Errors: 0` — `Wanted but not invoked: realNameBackfillService.backfill(...)`(`WantedButNotInvoked` 是 `AssertionError` 子类,计入 Failures)。
|
|
|
+
|
|
|
+- [ ] **步骤 3:编写最少实现代码**
|
|
|
+
|
|
|
+修改 `ConsigneeService.java` 三处:
|
|
|
+
|
|
|
+字段区(22 行后)新增:
|
|
|
+
|
|
|
+```java
|
|
|
+ @Resource
|
|
|
+ private RealNameBackfillService realNameBackfillService;
|
|
|
+```
|
|
|
+
|
|
|
+`save` 编辑分支(61 行 `syncUserRegion(existing.getUserId(), consignee);` 之后、`return` 之前)追加:
|
|
|
+
|
|
|
+```java
|
|
|
+ syncUserRegion(existing.getUserId(), consignee);
|
|
|
+ // 回填写在 syncUserRegion 之后:保持既有同步逻辑看到回填前的 realName,行为不变
|
|
|
+ realNameBackfillService.backfill(existing.getUserId(), consignee.getName());
|
|
|
+ return Result.success(consignee.getId());
|
|
|
+```
|
|
|
+
|
|
|
+`save` 新增分支(73 行 `syncUserRegion(consignee.getUserId(), consignee);` 之后、`return` 之前)追加:
|
|
|
+
|
|
|
+```java
|
|
|
+ syncUserRegion(consignee.getUserId(), consignee);
|
|
|
+ realNameBackfillService.backfill(consignee.getUserId(), consignee.getName());
|
|
|
+ return Result.success(consignee.getId());
|
|
|
+```
|
|
|
+
|
|
|
+`RealNameBackfillService` 同包,无需新增 import。
|
|
|
+
|
|
|
+- [ ] **步骤 4:运行测试验证通过**
|
|
|
+
|
|
|
+```powershell
|
|
|
+mvn test -DskipTests=false -Dtest=ConsigneeServiceBackfillTest
|
|
|
+```
|
|
|
+
|
|
|
+预期:`Tests run: 2, Failures: 0, Errors: 0`,BUILD SUCCESS。
|
|
|
+
|
|
|
+- [ ] **步骤 5:Commit**
|
|
|
+
|
|
|
+```powershell
|
|
|
+git add cfc-backend/src/main/java/com/etotem/cfc/service/ConsigneeService.java cfc-backend/src/test/java/com/etotem/cfc/service/ConsigneeServiceBackfillTest.java
|
|
|
+git commit -m "feat(realname): 收货人保存后回填用户真实姓名"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 任务 4:整体验证
|
|
|
+
|
|
|
+**文件:** 无新改动,仅验证。
|
|
|
+
|
|
|
+- [ ] **步骤 1:全量编译**
|
|
|
+
|
|
|
+```powershell
|
|
|
+cd cfc-backend
|
|
|
+mvn clean compile
|
|
|
+```
|
|
|
+
|
|
|
+预期:BUILD SUCCESS,无编译错误。
|
|
|
+
|
|
|
+- [ ] **步骤 2:运行本任务全部 12 个单测**
|
|
|
+
|
|
|
+```powershell
|
|
|
+mvn test -DskipTests=false -Dtest="RealNameBackfillServiceTest,UserAddressServiceBackfillTest,ConsigneeServiceBackfillTest"
|
|
|
+```
|
|
|
+
|
|
|
+预期:`Tests run: 12, Failures: 0, Errors: 0, Skipped: 0`,BUILD SUCCESS。
|
|
|
+
|
|
|
+- [ ] **步骤 3:核对提交记录**
|
|
|
+
|
|
|
+```powershell
|
|
|
+git log --oneline -4
|
|
|
+```
|
|
|
+
|
|
|
+预期:看到 3 个 feat 提交 + 之前的 docs 提交,且 `git status` 中本任务涉及的 6 个文件全部干净(无关的既有改动仍在,属正常)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 测试矩阵 ↔ 用例映射(规格覆盖度自证)
|
|
|
+
|
|
|
+| 规格测试要求 | 承载用例 |
|
|
|
+|---|---|
|
|
|
+| realName 为空 + 创建地址 → 回填 | `UserAddressServiceBackfillTest.create_保存成功后以收货人姓名回填真实姓名` |
|
|
|
+| realName 为空 + 编辑地址 → 回填 | `UserAddressServiceBackfillTest.update_保存成功后以收货人姓名回填真实姓名` |
|
|
|
+| realName 已有 → 不覆盖 | `RealNameBackfillServiceTest.backfill_真实姓名已有值时不覆盖` |
|
|
|
+| 联系人空白 → 跳过 | `RealNameBackfillServiceTest.backfill_联系人姓名为null时跳过` / `backfill_联系人姓名为空白时跳过` |
|
|
|
+| ConsigneeService 新增、编辑两支 | `ConsigneeServiceBackfillTest` 两个用例 |
|
|
|
+| 空白 realName(空串/纯空格)也回填 | `RealNameBackfillServiceTest.backfill_真实姓名为空白时回填联系人姓名` |
|
|
|
+| trim 后回填 | `RealNameBackfillServiceTest.backfill_联系人姓名去除首尾空白后回填` |
|
|
|
+| 防御路径(userId null / 用户不存在) | `backfill_userId为null时跳过` / `backfill_用户不存在时跳过` |
|
|
|
+
|
|
|
+守卫逻辑集中在 `RealNameBackfillServiceTest`(8 用例),两个接线测试只验证"保存成功后以正确参数调用 backfill"——合起来完整覆盖规格的测试矩阵,避免在接线层重复 mock 同一守卫逻辑。
|