🐛 后台改手机号同步登录账号与司机档案电话
- transport 新增 feign /feign/client/driver/sync-mobile:按 user_id+tenant_id 找档案, 档案 mobile 等于账号旧号时只改 mobile 一列(带 driving_vehicle 原值),已是新号幂等成功, 找不到或判据不满足返回 synced=false 与原因 - blade-system updateUserWithPhoneSync:账号等于旧号时跟随新号、补账号唯一性校验, 本地写库后调 feign 同步档案;同步失败只记日志,不回滚账号更新。登录链路仍走 updateUser - App 内改号改调 updateUserWithPhoneSync - bindOpenId 改为覆盖写:不再拦截已绑定其他 openid 或 openid 属于其它账号
This commit is contained in:
+12
@@ -30,7 +30,9 @@ import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.tenant.annotation.NonDS;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.transport.pojo.dto.DriverEnsureProfileDTO;
|
||||
import org.springblade.transport.pojo.dto.DriverSyncMobileDTO;
|
||||
import org.springblade.transport.pojo.vo.DriverEnsureProfileVO;
|
||||
import org.springblade.transport.pojo.vo.DriverSyncMobileVO;
|
||||
import org.springblade.transport.service.IDriverService;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -58,4 +60,14 @@ public class DriverClient implements IDriverClient {
|
||||
return R.data(driverService.ensureProfile(param.getTenantId(), param.getMpUserId(), param.getPhone()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping(SYNC_MOBILE)
|
||||
public R<DriverSyncMobileVO> syncMobile(@RequestBody DriverSyncMobileDTO param) {
|
||||
if (param == null) {
|
||||
return R.fail("司机档案参数不能为空");
|
||||
}
|
||||
return R.data(driverService.syncMobile(param.getTenantId(), param.getMpUserId(),
|
||||
param.getOldPhone(), param.getNewPhone()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
@@ -31,6 +31,7 @@ import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.transport.excel.DriverExcel;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
import org.springblade.transport.pojo.vo.DriverEnsureProfileVO;
|
||||
import org.springblade.transport.pojo.vo.DriverSyncMobileVO;
|
||||
import org.springblade.transport.pojo.vo.DriverExpiryStatVO;
|
||||
import org.springblade.transport.pojo.vo.DriverVO;
|
||||
|
||||
@@ -70,6 +71,21 @@ public interface IDriverService extends BaseService<Driver> {
|
||||
*/
|
||||
DriverEnsureProfileVO ensureProfile(String tenantId, Long mpUserId, String phone);
|
||||
|
||||
/**
|
||||
* 账号改号后同步司机档案手机号
|
||||
* <p>
|
||||
* 幂等入口:只在档案属于该账号(user_id 命中)且档案 mobile 等于旧号时改这一列,
|
||||
* 档案已是新号时返回成功且不写库;档案找不到或判据不满足时不动档案,通过 message 说明原因。
|
||||
* 参数为空、手机号为脱敏值时抛 ServiceException。
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param mpUserId 小程序用户ID
|
||||
* @param oldPhone 账号改号前的手机号
|
||||
* @param newPhone 账号改号后的手机号
|
||||
* @return 档案ID与本次是否同步
|
||||
*/
|
||||
DriverSyncMobileVO syncMobile(String tenantId, Long mpUserId, String oldPhone, String newPhone);
|
||||
|
||||
/**
|
||||
* 修改状态
|
||||
*
|
||||
|
||||
+60
@@ -48,8 +48,11 @@ import org.springblade.transport.pojo.entity.Driver;
|
||||
import org.springblade.transport.pojo.entity.TransportVehicle;
|
||||
import org.springblade.transport.pojo.vo.DriverEnsureProfileVO;
|
||||
import org.springblade.transport.pojo.vo.DriverExpiryStatVO;
|
||||
import org.springblade.transport.pojo.vo.DriverSyncMobileVO;
|
||||
import org.springblade.transport.pojo.vo.DriverVO;
|
||||
import org.springblade.transport.service.IDriverService;
|
||||
import org.springblade.transport.support.DriverPhoneSyncDecision;
|
||||
import org.springblade.transport.support.DriverPhoneSyncPlanner;
|
||||
import org.springblade.transport.support.VehiclePlateSupport;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -168,6 +171,63 @@ public class DriverServiceImpl extends BaseServiceImpl<DriverMapper, Driver> imp
|
||||
return bindExistingProfile(existing.getId(), mpUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DriverSyncMobileVO syncMobile(String tenantId, Long mpUserId, String oldPhone, String newPhone) {
|
||||
String resolvedTenantId = trimToNull(tenantId);
|
||||
if (resolvedTenantId == null) {
|
||||
resolvedTenantId = trimToNull(AuthUtil.getTenantId());
|
||||
}
|
||||
if (resolvedTenantId == null) {
|
||||
throw new ServiceException("租户信息为空,无法同步司机档案手机号");
|
||||
}
|
||||
if (Func.isEmpty(mpUserId)) {
|
||||
throw new ServiceException("小程序用户ID不能为空");
|
||||
}
|
||||
// 旧号是档案当前值的判据基准;含 * 的脱敏串与空串都不是可用手机号
|
||||
String oldMobile = trimToNull(oldPhone);
|
||||
if (oldMobile == null || oldMobile.contains("*")) {
|
||||
throw new ServiceException("账号原手机号无效,无法同步司机档案");
|
||||
}
|
||||
String newMobile = trimToNull(newPhone);
|
||||
if (newMobile == null || newMobile.contains("*")) {
|
||||
throw new ServiceException("手机号无效,不能写入司机档案");
|
||||
}
|
||||
Driver profile = findDriverByUserId(mpUserId);
|
||||
DriverPhoneSyncDecision decision = DriverPhoneSyncPlanner.decide(
|
||||
resolvedTenantId, mpUserId, profile, oldMobile, newMobile);
|
||||
Long driverId = profile == null ? null : profile.getId();
|
||||
if (decision == DriverPhoneSyncDecision.INVALID_PHONE) {
|
||||
throw new ServiceException("手机号无效,不能写入司机档案");
|
||||
}
|
||||
if (decision == DriverPhoneSyncDecision.NO_PROFILE) {
|
||||
return DriverSyncMobileVO.notSynced(null, "未找到该账号的司机档案");
|
||||
}
|
||||
if (decision == DriverPhoneSyncDecision.TENANT_MISMATCH) {
|
||||
return DriverSyncMobileVO.notSynced(driverId, "司机档案不属于该账号所在租户,未同步");
|
||||
}
|
||||
if (decision == DriverPhoneSyncDecision.USER_MISMATCH) {
|
||||
return DriverSyncMobileVO.notSynced(driverId, "司机档案未绑定该账号,未同步");
|
||||
}
|
||||
if (decision == DriverPhoneSyncDecision.ALREADY_SYNCED) {
|
||||
// 档案已是新号:重复调用(前端重试、日志重放)不应再写 update_time
|
||||
return DriverSyncMobileVO.synced(driverId);
|
||||
}
|
||||
if (decision == DriverPhoneSyncDecision.MOBILE_MISMATCH) {
|
||||
return DriverSyncMobileVO.notSynced(driverId, "司机档案手机号与账号原手机号不一致,未同步");
|
||||
}
|
||||
Driver patch = new Driver();
|
||||
patch.setId(driverId);
|
||||
patch.setMobile(newMobile);
|
||||
// 只改 mobile 一列,但 driving_vehicle 是 FieldStrategy.ALWAYS:不带原值会被写成 NULL
|
||||
carryDrivingVehicle(driverId, patch);
|
||||
if (!updateById(patch)) {
|
||||
// 0 行:档案被逻辑删除或租户条件不命中
|
||||
return DriverSyncMobileVO.notSynced(driverId, "司机档案更新未命中记录,未同步");
|
||||
}
|
||||
return DriverSyncMobileVO.synced(driverId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按小程序账号反查档案;user_id 有全局唯一索引,故不叠加租户条件
|
||||
*/
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support;
|
||||
|
||||
/**
|
||||
* 司机档案手机号同步的判定结果
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public enum DriverPhoneSyncDecision {
|
||||
|
||||
/**
|
||||
* 入参不可用:账号ID为空,或旧号/新号为空、为脱敏值
|
||||
*/
|
||||
INVALID_PHONE,
|
||||
|
||||
/**
|
||||
* 该账号没有司机档案
|
||||
*/
|
||||
NO_PROFILE,
|
||||
|
||||
/**
|
||||
* 档案的 tenant_id 与账号租户不一致
|
||||
*/
|
||||
TENANT_MISMATCH,
|
||||
|
||||
/**
|
||||
* 档案的 user_id 不是该账号
|
||||
*/
|
||||
USER_MISMATCH,
|
||||
|
||||
/**
|
||||
* 档案手机号已是新号:幂等,不写库
|
||||
*/
|
||||
ALREADY_SYNCED,
|
||||
|
||||
/**
|
||||
* 档案手机号与账号旧号不一致:不动档案
|
||||
*/
|
||||
MOBILE_MISMATCH,
|
||||
|
||||
/**
|
||||
* 判据全满足:只改 mobile 一列
|
||||
*/
|
||||
NEEDS_UPDATE
|
||||
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support;
|
||||
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 「账号改号后是否同步司机档案 mobile」的判据
|
||||
* <p>
|
||||
* 纯函数:不读写库、不依赖 Spring 容器。Service 只负责「查档案 → decide → 按结果写库」,
|
||||
* 判据本身可以直接列真值表测试。
|
||||
* <p>
|
||||
* 全部以 trim 后比较;null 与 null 视为相等只用于「都没有值」的情形,不参与业务判据。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public final class DriverPhoneSyncPlanner {
|
||||
|
||||
private DriverPhoneSyncPlanner() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定本次改号是否该改档案的 mobile
|
||||
*
|
||||
* @param tenantId 账号所属租户
|
||||
* @param mpUserId 小程序账号ID
|
||||
* @param profile 按 user_id 查到的司机档案,未查到为 null
|
||||
* @param oldPhone 账号改号前的手机号
|
||||
* @param newPhone 账号改号后的手机号
|
||||
* @return 判定结果
|
||||
*/
|
||||
public static DriverPhoneSyncDecision decide(String tenantId, Long mpUserId, Driver profile,
|
||||
String oldPhone, String newPhone) {
|
||||
String oldMobile = trimToNull(oldPhone);
|
||||
String newMobile = trimToNull(newPhone);
|
||||
// 旧号既是「档案是否仍属于这次改号」的基准,也是唯一能安全替换的值;
|
||||
// 脱敏串与空串写进档案都不可用
|
||||
if (mpUserId == null || oldMobile == null || newMobile == null
|
||||
|| oldMobile.contains("*") || newMobile.contains("*")) {
|
||||
return DriverPhoneSyncDecision.INVALID_PHONE;
|
||||
}
|
||||
if (profile == null) {
|
||||
return DriverPhoneSyncDecision.NO_PROFILE;
|
||||
}
|
||||
String resolvedTenantId = trimToNull(tenantId);
|
||||
String profileTenantId = trimToNull(profile.getTenantId());
|
||||
if (resolvedTenantId != null && profileTenantId != null && !resolvedTenantId.equals(profileTenantId)) {
|
||||
return DriverPhoneSyncDecision.TENANT_MISMATCH;
|
||||
}
|
||||
if (!Objects.equals(profile.getUserId(), mpUserId)) {
|
||||
return DriverPhoneSyncDecision.USER_MISMATCH;
|
||||
}
|
||||
String profileMobile = trimToNull(profile.getMobile());
|
||||
if (Objects.equals(profileMobile, newMobile)) {
|
||||
return DriverPhoneSyncDecision.ALREADY_SYNCED;
|
||||
}
|
||||
if (!Objects.equals(profileMobile, oldMobile)) {
|
||||
return DriverPhoneSyncDecision.MOBILE_MISMATCH;
|
||||
}
|
||||
return DriverPhoneSyncDecision.NEEDS_UPDATE;
|
||||
}
|
||||
|
||||
private static String trimToNull(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = value.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.feign.ISysClient;
|
||||
import org.springblade.transport.mapper.DriverMapper;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
import org.springblade.transport.pojo.vo.DriverSyncMobileVO;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 后台 / App 改号后同步司机档案 phone 的测试。
|
||||
* <p>
|
||||
* 守住四条不变式:只改 mobile 一列的 patch 必须带上 driving_vehicle 原值;
|
||||
* 档案 phone 与账号旧号不一致时不动(那是另一段事实);已是新号时幂等成功;
|
||||
* 找不到档案只返回 synced=false 与可读原因,不抛异常给调用方的账号更新。
|
||||
*/
|
||||
class DriverSyncMobileTest {
|
||||
|
||||
private static final Long MP_USER_ID = 7001L;
|
||||
private static final Long DRIVER_ID = 9001L;
|
||||
private static final String TENANT_ID = "000000";
|
||||
private static final String OLD_PHONE = "15975348524";
|
||||
private static final String NEW_PHONE = "13800138000";
|
||||
private static final String PLATE = "桂A32523";
|
||||
|
||||
private DriverMapper driverMapper;
|
||||
private DriverServiceImpl service;
|
||||
|
||||
@BeforeAll
|
||||
static void initTableInfo() {
|
||||
MybatisConfiguration configuration = new MybatisConfiguration();
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, "");
|
||||
assistant.setCurrentNamespace("test");
|
||||
TableInfoHelper.initTableInfo(assistant, Driver.class);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
driverMapper = mock(DriverMapper.class);
|
||||
service = new DriverServiceImpl(mock(IMpUserClient.class), mock(ISysClient.class));
|
||||
inject("baseMapper", driverMapper);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 前提满足:只改 mobile 一列
|
||||
|
||||
@Test
|
||||
@DisplayName("档案属于该账号且 mobile 等于旧号:只改 mobile,patch 必须带上 driving_vehicle 原值")
|
||||
void syncsMobileAndKeepsDrivingVehicle() {
|
||||
Driver profile = driver(DRIVER_ID, MP_USER_ID, OLD_PHONE, PLATE);
|
||||
stubProfileLookup(profile);
|
||||
when(driverMapper.selectById(DRIVER_ID)).thenReturn(profile);
|
||||
when(driverMapper.updateById(any(Driver.class))).thenReturn(1);
|
||||
|
||||
DriverSyncMobileVO result = service.syncMobile(TENANT_ID, MP_USER_ID, OLD_PHONE, NEW_PHONE);
|
||||
|
||||
assertTrue(result.getSynced());
|
||||
assertEquals(DRIVER_ID, result.getDriverId());
|
||||
assertNull(result.getMessage());
|
||||
|
||||
ArgumentCaptor<Driver> captor = ArgumentCaptor.forClass(Driver.class);
|
||||
verify(driverMapper).updateById(captor.capture());
|
||||
Driver patch = captor.getValue();
|
||||
assertEquals(DRIVER_ID, patch.getId());
|
||||
assertEquals(NEW_PHONE, patch.getMobile());
|
||||
assertEquals(PLATE, patch.getDrivingVehicle(),
|
||||
"patch 不带 driving_vehicle 会把司机的车辆绑定写掉(该列 FieldStrategy.ALWAYS)");
|
||||
assertNull(patch.getTenantId(), "只改 mobile,不得写 tenant_id");
|
||||
assertNull(patch.getUserId(), "只改 mobile,不得写 user_id");
|
||||
verify(driverMapper, never()).insert(any(Driver.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("档案 mobile 已是新号:幂等成功,不再写库")
|
||||
void isIdempotentWhenProfileAlreadyHasNewMobile() {
|
||||
stubProfileLookup(driver(DRIVER_ID, MP_USER_ID, NEW_PHONE, PLATE));
|
||||
|
||||
DriverSyncMobileVO result = service.syncMobile(TENANT_ID, MP_USER_ID, OLD_PHONE, NEW_PHONE);
|
||||
|
||||
assertTrue(result.getSynced());
|
||||
assertEquals(DRIVER_ID, result.getDriverId());
|
||||
assertNull(result.getMessage());
|
||||
verify(driverMapper, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("档案 mobile 与账号旧号不一致:不动档案,返回可读原因(不抛异常)")
|
||||
void skipsWhenProfileMobileDiffersFromOldPhone() {
|
||||
stubProfileLookup(driver(DRIVER_ID, MP_USER_ID, "13512345678", PLATE));
|
||||
|
||||
DriverSyncMobileVO result = service.syncMobile(TENANT_ID, MP_USER_ID, OLD_PHONE, NEW_PHONE);
|
||||
|
||||
assertFalse(result.getSynced());
|
||||
assertEquals(DRIVER_ID, result.getDriverId());
|
||||
assertNotNull(result.getMessage());
|
||||
verify(driverMapper, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("按 user_id + tenant 找不到档案:synced=false 并给出原因,不抛异常")
|
||||
void returnsNotSyncedWhenProfileMissing() {
|
||||
stubProfileLookup(null);
|
||||
|
||||
DriverSyncMobileVO result = service.syncMobile(TENANT_ID, MP_USER_ID, OLD_PHONE, NEW_PHONE);
|
||||
|
||||
assertFalse(result.getSynced());
|
||||
assertNull(result.getDriverId());
|
||||
assertNotNull(result.getMessage());
|
||||
verify(driverMapper, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 入参兜底
|
||||
|
||||
@Test
|
||||
@DisplayName("账号 ID 为空:拒绝,不写库")
|
||||
void rejectsEmptyUserId() {
|
||||
assertThrows(ServiceException.class, () -> service.syncMobile(TENANT_ID, null, OLD_PHONE, NEW_PHONE));
|
||||
verify(driverMapper, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("旧号为空:拒绝,不写库")
|
||||
void rejectsBlankOldPhone() {
|
||||
assertThrows(ServiceException.class, () -> service.syncMobile(TENANT_ID, MP_USER_ID, " ", NEW_PHONE));
|
||||
verify(driverMapper, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("新号为空:拒绝,不写库")
|
||||
void rejectsBlankNewPhone() {
|
||||
assertThrows(ServiceException.class, () -> service.syncMobile(TENANT_ID, MP_USER_ID, OLD_PHONE, null));
|
||||
verify(driverMapper, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("脱敏新号:拒绝写库")
|
||||
void rejectsMaskedNewPhone() {
|
||||
assertThrows(ServiceException.class, () -> service.syncMobile(TENANT_ID, MP_USER_ID, OLD_PHONE, "138****8000"));
|
||||
verify(driverMapper, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 夹具
|
||||
|
||||
private void stubProfileLookup(Driver profile) {
|
||||
when(driverMapper.selectOne(any())).thenReturn(profile);
|
||||
when(driverMapper.selectOne(any(), anyBoolean())).thenReturn(profile);
|
||||
}
|
||||
|
||||
private Driver driver(Long id, Long userId, String mobile, String drivingVehicle) {
|
||||
Driver driver = new Driver();
|
||||
driver.setId(id);
|
||||
driver.setIsDeleted(0);
|
||||
driver.setTenantId(TENANT_ID);
|
||||
driver.setUserId(userId);
|
||||
driver.setMobile(mobile);
|
||||
driver.setDrivingVehicle(drivingVehicle);
|
||||
return driver;
|
||||
}
|
||||
|
||||
private void inject(String field, Object value) throws Exception {
|
||||
Class<?> type = service.getClass();
|
||||
while (type != null) {
|
||||
try {
|
||||
Field declared = type.getDeclaredField(field);
|
||||
declared.setAccessible(true);
|
||||
declared.set(service, value);
|
||||
return;
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
type = type.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("未找到字段:" + field);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user