diff --git a/blade-auth/src/main/java/org/springblade/auth/support/MpLoginSupport.java b/blade-auth/src/main/java/org/springblade/auth/support/MpLoginSupport.java index 1a48b334..ab28cd7f 100644 --- a/blade-auth/src/main/java/org/springblade/auth/support/MpLoginSupport.java +++ b/blade-auth/src/main/java/org/springblade/auth/support/MpLoginSupport.java @@ -98,30 +98,30 @@ public class MpLoginSupport { account = ensureUser(tenantId, phone, openid); } else { account = openidAccount; - if (!samePhone(openidAccount.getPhone(), phone)) { - MpUser phoneAccount = findByPhone(tenantId, phone); - if (phoneAccount != null && !Objects.equals(phoneAccount.getId(), openidAccount.getId())) { - // ①-撞键:落到手机号账号,不迁移 openid - account = phoneAccount; - loginNotice = NOTICE_PHONE_OCCUPIED; - openidOccupied = true; - log.warn("微信换号撞键,登录落到手机号账号:租户={} openid账号={} 手机号账号={}", - tenantId, openidAccount.getId(), phoneAccount.getId()); - } else { - account = movePhone(openidAccount, phone); - if (account == null) { - // 更新手机号失败:并发时可能是手机号刚被别的账号占用,重查一次再决定落点 - MpUser concurrent = findByPhone(tenantId, phone); - if (concurrent != null && !Objects.equals(concurrent.getId(), openidAccount.getId())) { - account = concurrent; - loginNotice = NOTICE_PHONE_OCCUPIED; - openidOccupied = true; - log.warn("更新手机号失败后命中手机号账号:租户={} openid账号={} 手机号账号={}", - tenantId, openidAccount.getId(), concurrent.getId()); - } else { - // 仍然用 openid 账号登录:手机号未能更新不影响身份识别,下次登录再试 - account = openidAccount; - } + // 账号 phone 经 feign 下发时是脱敏值(MpUser.phone 标了 @Sensitive),不能拿来比较。 + // 改按「该手机号是否已有账号」判断:命中自己是同一个人、命中别人是撞键、没命中就换号。 + MpUser phoneAccount = findByPhone(tenantId, phone); + if (phoneAccount != null && !Objects.equals(phoneAccount.getId(), openidAccount.getId())) { + // ①-撞键:落到手机号账号,不迁移 openid + account = phoneAccount; + loginNotice = NOTICE_PHONE_OCCUPIED; + openidOccupied = true; + log.warn("微信换号撞键,登录落到手机号账号:租户={} openid账号={} 手机号账号={}", + tenantId, openidAccount.getId(), phoneAccount.getId()); + } else if (phoneAccount == null) { + account = movePhone(openidAccount, phone); + if (account == null) { + // 更新手机号失败:并发时可能是手机号刚被别的账号占用,重查一次再决定落点 + MpUser concurrent = findByPhone(tenantId, phone); + if (concurrent != null && !Objects.equals(concurrent.getId(), openidAccount.getId())) { + account = concurrent; + loginNotice = NOTICE_PHONE_OCCUPIED; + openidOccupied = true; + log.warn("更新手机号失败后命中手机号账号:租户={} openid账号={} 手机号账号={}", + tenantId, openidAccount.getId(), concurrent.getId()); + } else { + // 仍然用 openid 账号登录:手机号未能更新不影响身份识别,下次登录再试 + account = openidAccount; } } } @@ -227,10 +227,14 @@ public class MpLoginSupport { if (account == null || !MpUserIdentity.has(account.getIdentities(), MpUserIdentity.DRIVER)) { return null; } - // 租户与手机号取账号上的值:档案必须与账号落同一租户(唯一键对 NULL 不去重); - // 账号手机号未能更新时,不使用入参手机号建档 + // 租户取账号上的值(档案必须与账号同租户,唯一键对 NULL 不去重); + // 手机号只能取登录入参:账号 phone 经 feign 下发时是脱敏值,写进档案会让按手机号搜不到司机 String tenantId = Func.toStr(account.getTenantId(), requestedTenantId); - String phone = Func.toStr(account.getPhone(), requestedPhone); + String phone = trimToNull(requestedPhone); + if (phone == null) { + log.warn("登录自动建档跳过:登录手机号为空,租户={} 账号={}", tenantId, account.getId()); + return null; + } DriverEnsureProfileDTO param = new DriverEnsureProfileDTO(); param.setTenantId(tenantId); param.setMpUserId(account.getId()); @@ -316,10 +320,6 @@ public class MpLoginSupport { userInfo.setDetail(detail); } - private boolean samePhone(String left, String right) { - return Objects.equals(trimToNull(left), trimToNull(right)); - } - private String trimToNull(String value) { if (value == null) { return null; diff --git a/blade-auth/src/test/java/org/springblade/auth/support/MpLoginSupportTest.java b/blade-auth/src/test/java/org/springblade/auth/support/MpLoginSupportTest.java index 4518e344..6d09b316 100644 --- a/blade-auth/src/test/java/org/springblade/auth/support/MpLoginSupportTest.java +++ b/blade-auth/src/test/java/org/springblade/auth/support/MpLoginSupportTest.java @@ -59,7 +59,8 @@ class MpLoginSupportTest { @Test @DisplayName("新手机号微信登录:建号 + 建档,登录成功且无提示") void newPhoneCreatesAccountAndProfile() { - MpUser created = mpUser(11L, PHONE, OPENID, "driver"); + // feign 下发时 phone 是脱敏值(MpUser.phone 标了 @Sensitive),建档必须仍用登录入参的真实号 + MpUser created = mpUser(11L, "138****0001", OPENID, "driver"); when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(null)); when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(created)); when(mpUserClient.userInfoById(11L)).thenReturn(R.data(userInfo(created, "driver"))); @@ -154,7 +155,7 @@ class MpLoginSupportTest { @Test @DisplayName("openid 命中且手机号不同但新号空闲:更新账号手机号") void openidHitWithFreePhoneUpdatesPhone() { - MpUser openidAccount = mpUser(31L, "13900000000", OPENID, "driver"); + MpUser openidAccount = mpUser(31L, "139****0000", OPENID, "driver"); when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(openidAccount)); when(mpUserClient.userByPhone(TENANT_ID, NEW_PHONE)).thenReturn(R.data(null)); when(mpUserClient.updateUser(any(MpUser.class))).thenReturn(R.data(Boolean.TRUE)); @@ -171,6 +172,22 @@ class MpLoginSupportTest { verify(mpUserClient, never()).ensureUser(any(MpUserEnsureDTO.class)); } + @Test + @DisplayName("openid 命中且该手机号就是本账号:不重复更新手机号") + void openidHitSameAccountDoesNotUpdatePhone() { + MpUser openidAccount = mpUser(32L, "138****0001", OPENID, "driver"); + when(mpUserClient.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(openidAccount)); + when(mpUserClient.userByPhone(TENANT_ID, PHONE)).thenReturn(R.data(openidAccount)); + when(mpUserClient.userInfoById(32L)).thenReturn(R.data(userInfo(openidAccount, "driver"))); + when(driverClient.ensureProfile(any(DriverEnsureProfileDTO.class))).thenReturn(R.data(profileResult())); + + OAuth2User user = mpLoginSupport.ensureLoginAccount(TENANT_ID, PHONE, OPENID); + + assertEquals("32", user.getUserId()); + verify(mpUserClient, never()).updateUser(any(MpUser.class)); + verify(mpUserClient, never()).ensureUser(any(MpUserEnsureDTO.class)); + } + @Test @DisplayName("纯 admin 身份:不建司机档案") void adminOnlyDoesNotCreateProfile() { 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 bd200eb8..2ca4447d 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 @@ -134,6 +134,10 @@ public class DriverServiceImpl extends BaseServiceImpl imp if (mobile == null) { throw new ServiceException("手机号不能为空"); } + // 脱敏手机号(如 155****2748)不是可用的业务数据:写进档案后按手机号查不到司机 + if (mobile.contains("*")) { + throw new ServiceException("手机号无效,不能写入司机档案"); + } // 查找顺序固定为 user_id → phone:user_id 是账号的稳定标识, // phone 会被运营商二次放号 Driver bound = findDriverByUserId(mpUserId); diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverEnsureProfileTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverEnsureProfileTest.java index 4c013e4d..d029c31b 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverEnsureProfileTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverEnsureProfileTest.java @@ -214,6 +214,14 @@ class DriverEnsureProfileTest { // ------------------------------------------------------------ 入参兜底 + @Test + @DisplayName("脱敏手机号:拒绝写库,不新建档案") + void rejectsMaskedMobile() { + assertThrows(ServiceException.class, () -> service.ensureProfile(TENANT_ID, MP_USER_ID, "155****2748")); + verify(driverMapper, never()).insert(any(Driver.class)); + verify(driverMapper, never()).updateById(any(Driver.class)); + } + @Test @DisplayName("租户为空且当前上下文也没有租户:拒绝写库") void rejectsBlankTenant() {