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

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);
} 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;
@@ -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() {
@@ -134,6 +134,10 @@ public class DriverServiceImpl extends BaseServiceImpl<DriverMapper, Driver> 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);
@@ -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() {