🐛 修复登录自动建档把脱敏手机号写入司机档案

This commit is contained in:
2026-10-23 18:05:20 +08:00
parent e54359c113
commit 7fa20747ab
4 changed files with 62 additions and 33 deletions
@@ -98,30 +98,30 @@ public class MpLoginSupport {
account = ensureUser(tenantId, phone, openid); account = ensureUser(tenantId, phone, openid);
} else { } else {
account = openidAccount; account = openidAccount;
if (!samePhone(openidAccount.getPhone(), phone)) { // 账号 phone 经 feign 下发时是脱敏值(MpUser.phone 标了 @Sensitive),不能拿来比较。
MpUser phoneAccount = findByPhone(tenantId, phone); // 改按「该手机号是否已有账号」判断:命中自己是同一个人、命中别人是撞键、没命中就换号。
if (phoneAccount != null && !Objects.equals(phoneAccount.getId(), openidAccount.getId())) { MpUser phoneAccount = findByPhone(tenantId, phone);
// ①-撞键:落到手机号账号,不迁移 openid if (phoneAccount != null && !Objects.equals(phoneAccount.getId(), openidAccount.getId())) {
account = phoneAccount; // ①-撞键:落到手机号账号,不迁移 openid
loginNotice = NOTICE_PHONE_OCCUPIED; account = phoneAccount;
openidOccupied = true; loginNotice = NOTICE_PHONE_OCCUPIED;
log.warn("微信换号撞键,登录落到手机号账号:租户={} openid账号={} 手机号账号={}", openidOccupied = true;
tenantId, openidAccount.getId(), phoneAccount.getId()); log.warn("微信换号撞键,登录落到手机号账号:租户={} openid账号={} 手机号账号={}",
} else { tenantId, openidAccount.getId(), phoneAccount.getId());
account = movePhone(openidAccount, phone); } else if (phoneAccount == null) {
if (account == null) { account = movePhone(openidAccount, phone);
// 更新手机号失败:并发时可能是手机号刚被别的账号占用,重查一次再决定落点 if (account == null) {
MpUser concurrent = findByPhone(tenantId, phone); // 更新手机号失败:并发时可能是手机号刚被别的账号占用,重查一次再决定落点
if (concurrent != null && !Objects.equals(concurrent.getId(), openidAccount.getId())) { MpUser concurrent = findByPhone(tenantId, phone);
account = concurrent; if (concurrent != null && !Objects.equals(concurrent.getId(), openidAccount.getId())) {
loginNotice = NOTICE_PHONE_OCCUPIED; account = concurrent;
openidOccupied = true; loginNotice = NOTICE_PHONE_OCCUPIED;
log.warn("更新手机号失败后命中手机号账号:租户={} openid账号={} 手机号账号={}", openidOccupied = true;
tenantId, openidAccount.getId(), concurrent.getId()); log.warn("更新手机号失败后命中手机号账号:租户={} openid账号={} 手机号账号={}",
} else { tenantId, openidAccount.getId(), concurrent.getId());
// 仍然用 openid 账号登录:手机号未能更新不影响身份识别,下次登录再试 } else {
account = openidAccount; // 仍然用 openid 账号登录:手机号未能更新不影响身份识别,下次登录再试
} account = openidAccount;
} }
} }
} }
@@ -227,10 +227,14 @@ public class MpLoginSupport {
if (account == null || !MpUserIdentity.has(account.getIdentities(), MpUserIdentity.DRIVER)) { if (account == null || !MpUserIdentity.has(account.getIdentities(), MpUserIdentity.DRIVER)) {
return null; return null;
} }
// 租户与手机号取账号上的值:档案必须与账号落同一租户(唯一键对 NULL 不去重); // 租户取账号上的值(档案必须与账号同租户,唯一键对 NULL 不去重);
// 账号手机号未能更新时,不使用入参手机号建档 // 手机号只能取登录入参:账号 phone 经 feign 下发时是脱敏值,写进档案会让按手机号搜不到司机
String tenantId = Func.toStr(account.getTenantId(), requestedTenantId); 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(); DriverEnsureProfileDTO param = new DriverEnsureProfileDTO();
param.setTenantId(tenantId); param.setTenantId(tenantId);
param.setMpUserId(account.getId()); param.setMpUserId(account.getId());
@@ -316,10 +320,6 @@ public class MpLoginSupport {
userInfo.setDetail(detail); userInfo.setDetail(detail);
} }
private boolean samePhone(String left, String right) {
return Objects.equals(trimToNull(left), trimToNull(right));
}
private String trimToNull(String value) { private String trimToNull(String value) {
if (value == null) { if (value == null) {
return null; return null;
@@ -59,7 +59,8 @@ class MpLoginSupportTest {
@Test @Test
@DisplayName("新手机号微信登录:建号 + 建档,登录成功且无提示") @DisplayName("新手机号微信登录:建号 + 建档,登录成功且无提示")
void newPhoneCreatesAccountAndProfile() { 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.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(null));
when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(created)); when(mpUserClient.ensureUser(any(MpUserEnsureDTO.class))).thenReturn(R.data(created));
when(mpUserClient.userInfoById(11L)).thenReturn(R.data(userInfo(created, "driver"))); when(mpUserClient.userInfoById(11L)).thenReturn(R.data(userInfo(created, "driver")));
@@ -154,7 +155,7 @@ class MpLoginSupportTest {
@Test @Test
@DisplayName("openid 命中且手机号不同但新号空闲:更新账号手机号") @DisplayName("openid 命中且手机号不同但新号空闲:更新账号手机号")
void openidHitWithFreePhoneUpdatesPhone() { 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.userByOpenid(TENANT_ID, OPENID)).thenReturn(R.data(openidAccount));
when(mpUserClient.userByPhone(TENANT_ID, NEW_PHONE)).thenReturn(R.data(null)); when(mpUserClient.userByPhone(TENANT_ID, NEW_PHONE)).thenReturn(R.data(null));
when(mpUserClient.updateUser(any(MpUser.class))).thenReturn(R.data(Boolean.TRUE)); when(mpUserClient.updateUser(any(MpUser.class))).thenReturn(R.data(Boolean.TRUE));
@@ -171,6 +172,22 @@ class MpLoginSupportTest {
verify(mpUserClient, never()).ensureUser(any(MpUserEnsureDTO.class)); 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 @Test
@DisplayName("纯 admin 身份:不建司机档案") @DisplayName("纯 admin 身份:不建司机档案")
void adminOnlyDoesNotCreateProfile() { void adminOnlyDoesNotCreateProfile() {
@@ -134,6 +134,10 @@ public class DriverServiceImpl extends BaseServiceImpl<DriverMapper, Driver> imp
if (mobile == null) { if (mobile == null) {
throw new ServiceException("手机号不能为空"); throw new ServiceException("手机号不能为空");
} }
// 脱敏手机号(如 155****2748)不是可用的业务数据:写进档案后按手机号查不到司机
if (mobile.contains("*")) {
throw new ServiceException("手机号无效,不能写入司机档案");
}
// 查找顺序固定为 user_id → phone:user_id 是账号的稳定标识, // 查找顺序固定为 user_id → phone:user_id 是账号的稳定标识,
// phone 会被运营商二次放号 // phone 会被运营商二次放号
Driver bound = findDriverByUserId(mpUserId); Driver bound = findDriverByUserId(mpUserId);
@@ -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 @Test
@DisplayName("租户为空且当前上下文也没有租户:拒绝写库") @DisplayName("租户为空且当前上下文也没有租户:拒绝写库")
void rejectsBlankTenant() { void rejectsBlankTenant() {