From 4a4b69024fc637d9fc09fb5c77c12cc3887aa03c Mon Sep 17 00:00:00 2001 From: weicw <594098497@qq.com> Date: Tue, 27 Oct 2026 20:29:47 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E6=96=B0=E5=A2=9E=E6=89=8B?= =?UTF-8?q?=E6=9C=BA=E5=8F=B7=E5=94=AF=E4=B8=80=E5=8C=B9=E9=85=8D=E5=8F=B8?= =?UTF-8?q?=E6=9C=BA=E6=A1=A3=E6=A1=88=20ID=20=E7=9A=84=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/service/IDriverService.java | 12 ++ .../service/impl/DriverServiceImpl.java | 26 ++++ .../service/impl/DriverResolveMobileTest.java | 130 ++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverResolveMobileTest.java diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IDriverService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IDriverService.java index 73924e3e..d2962801 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IDriverService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IDriverService.java @@ -111,4 +111,16 @@ public interface IDriverService extends BaseService { */ List exportDriver(Wrapper queryWrapper); + /** + * 按手机号解析司机档案 ID + *

+ * 归属主键是档案 ID,手机号只是派单时的号码快照:只有租户内唯一命中才认, + * 0 条(承运商司机、未登录过小程序的司机)与多条(历史脏数据)都返回 null,不抛异常。 + * + * @param tenantId 租户ID,为空时取当前登录租户 + * @param mobile 手机号 + * @return 司机档案ID;无法唯一确定时为 null + */ + Long resolveDriverIdByMobile(String tenantId, String mobile); + } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverServiceImpl.java index 2d4dc598..fa3a1c80 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverServiceImpl.java @@ -25,6 +25,7 @@ */ package org.springblade.transport.service.impl; +import cn.hutool.core.util.DesensitizedUtil; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -171,6 +172,31 @@ public class DriverServiceImpl extends BaseServiceImpl imp return bindExistingProfile(existing.getId(), mpUserId); } + @Override + public Long resolveDriverIdByMobile(String tenantId, String mobile) { + String resolvedTenantId = trimToNull(tenantId); + if (resolvedTenantId == null) { + resolvedTenantId = trimToNull(AuthUtil.getTenantId()); + } + String mobileSnapshot = trimToNull(mobile); + // 脱敏手机号(如 138****8000)与空号都不是可用业务数据,查不到也不该查 + if (resolvedTenantId == null || mobileSnapshot == null || mobileSnapshot.contains("*")) { + return null; + } + // 手机号会被二次放号,同租户内可能残留多条历史档案:只有唯一命中才认,不猜 + List matchedDrivers = list(Wrappers.lambdaQuery() + .select(Driver::getId) + .eq(Driver::getTenantId, resolvedTenantId) + .eq(Driver::getMobile, mobileSnapshot)); + if (matchedDrivers.size() != 1) { + log.warn("司机档案解析未唯一命中,driver_id 留空。租户={} 手机号={} 命中={} 条", + resolvedTenantId, DesensitizedUtil.mobilePhone(mobileSnapshot), matchedDrivers.size()); + return null; + } + Driver matchedDriver = matchedDrivers.get(0); + return matchedDriver == null ? null : matchedDriver.getId(); + } + @Override @Transactional(rollbackFor = Exception.class) public DriverSyncMobileVO syncMobile(String tenantId, Long mpUserId, String oldPhone, String newPhone) { diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverResolveMobileTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverResolveMobileTest.java new file mode 100644 index 00000000..01db1969 --- /dev/null +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverResolveMobileTest.java @@ -0,0 +1,130 @@ +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.springblade.system.feign.IMpUserClient; +import org.springblade.system.feign.ISysClient; +import org.springblade.transport.mapper.DriverMapper; +import org.springblade.transport.pojo.entity.Driver; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * 手机号 -> 司机档案 ID 解析入口的测试。 + *

+ * 归属主键是档案 ID,手机号只是派单时的号码快照:只有租户内唯一命中才认,0 条与多条都不认。 + * 解析结果是"能不能写归属"的判断,不是业务校验,因此任何入参都只返回 null,不抛异常。 + */ +class DriverResolveMobileTest { + + private static final String TENANT_ID = "000000"; + private static final String MOBILE = "13800138000"; + + 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); + } + + @Test + @DisplayName("租户内唯一命中:返回档案 ID") + void returnsProfileIdWhenUnique() { + when(driverMapper.selectList(any())).thenReturn(List.of(profile(9001L))); + + assertEquals(9001L, service.resolveDriverIdByMobile(TENANT_ID, MOBILE)); + } + + @Test + @DisplayName("租户内没有档案:返回 null") + void returnsNullWhenAbsent() { + when(driverMapper.selectList(any())).thenReturn(Collections.emptyList()); + + assertNull(service.resolveDriverIdByMobile(TENANT_ID, MOBILE)); + } + + @Test + @DisplayName("租户内多条档案(历史同号):返回 null,不猜") + void returnsNullWhenMultiple() { + List profiles = new ArrayList<>(); + profiles.add(profile(9001L)); + profiles.add(profile(9002L)); + when(driverMapper.selectList(any())).thenReturn(profiles); + + assertNull(service.resolveDriverIdByMobile(TENANT_ID, MOBILE)); + } + + @Test + @DisplayName("手机号为空或脱敏串:返回 null,且不查库") + void returnsNullWithoutQueryForBlankOrMaskedMobile() { + assertNull(service.resolveDriverIdByMobile(TENANT_ID, null)); + assertNull(service.resolveDriverIdByMobile(TENANT_ID, " ")); + assertNull(service.resolveDriverIdByMobile(TENANT_ID, "138****8000")); + verify(driverMapper, never()).selectList(any()); + } + + @Test + @DisplayName("租户为空:取当前登录租户,仍不抛异常") + void fallsBackToCurrentTenant() { + when(driverMapper.selectList(any())).thenReturn(List.of(profile(9001L))); + + Long driverId = null; + try (var ignored = org.mockito.Mockito.mockStatic(org.springblade.core.secure.utils.AuthUtil.class)) { + ignored.when(org.springblade.core.secure.utils.AuthUtil::getTenantId).thenReturn(TENANT_ID); + driverId = service.resolveDriverIdByMobile(null, MOBILE); + } + assertEquals(9001L, driverId); + } + + private Driver profile(Long id) { + Driver profile = new Driver(); + profile.setId(id); + profile.setIsDeleted(0); + profile.setTenantId(TENANT_ID); + profile.setMobile(MOBILE); + return profile; + } + + 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); + } + +}