|
|
@@ -90,6 +90,8 @@ public class InfoUserController extends BaseController {
|
|
|
@Autowired
|
|
|
private IInfoUserService infoUserService;
|
|
|
@Autowired
|
|
|
+ private UserCancellationService userCancellationService;
|
|
|
+ @Autowired
|
|
|
private ImAccountService imAccountService;
|
|
|
@Autowired
|
|
|
private InfoUserMapper infoUserMapper;
|
|
|
@@ -127,6 +129,7 @@ public class InfoUserController extends BaseController {
|
|
|
private static final String OAUTH_TEMP_PREFIX = "oauth:bind:";
|
|
|
/** 设备确认临时凭证 Redis 前缀:值为 deviceId,与 OAUTH_TEMP_PREFIX 同 TTL 成对写入(029) */
|
|
|
private static final String OAUTH_TEMP_DEVICE_PREFIX = "oauth:bind:dev:";
|
|
|
+ private static final String OAUTH_TEMP_USER_PREFIX = "oauth:bind:user:";
|
|
|
|
|
|
|
|
|
@Anonymous
|
|
|
@@ -276,6 +279,8 @@ public class InfoUserController extends BaseController {
|
|
|
*/
|
|
|
@Anonymous
|
|
|
@GetMapping("/deleuser")
|
|
|
+ @Auth
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
public AjaxResult deleuser(@RequestHeader String token) {
|
|
|
JwtUtil jwtUtil = new JwtUtil();
|
|
|
String id = jwtUtil.getusid(token);
|
|
|
@@ -289,6 +294,10 @@ public class InfoUserController extends BaseController {
|
|
|
if (exist) {
|
|
|
throw new ServiceException("抱歉,您還有未完成的訂單,無法刪除帳號");
|
|
|
}
|
|
|
+ InfoUser account = infoUserService.getById(userId);
|
|
|
+ if (account != null && "0".equals(account.getUserType())) {
|
|
|
+ return success(userCancellationService.request(userId));
|
|
|
+ }
|
|
|
return toAjax(infoUserService.deleteInfoUserByUserId(userId));
|
|
|
}
|
|
|
|
|
|
@@ -953,6 +962,7 @@ public class InfoUserController extends BaseController {
|
|
|
@Anonymous
|
|
|
@Auth
|
|
|
@GetMapping("/gettoken")
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
public AjaxResult gettoken(@RequestHeader String token) {
|
|
|
JwtUtil jwtUtil = new JwtUtil();
|
|
|
String id = jwtUtil.getusid(token);
|
|
|
@@ -960,6 +970,8 @@ public class InfoUserController extends BaseController {
|
|
|
wrapper.eq("user_id", id);
|
|
|
wrapper.eq("status", 0);
|
|
|
wrapper.eq("del_flag", "0");
|
|
|
+ // 刷新会话也与注销共用账号锁,避免注销撤销后又签发新会话。
|
|
|
+ wrapper.last("FOR UPDATE");
|
|
|
InfoUser user = infoUserService.getOne(wrapper);
|
|
|
if (user == null) {
|
|
|
return error(MessageUtils.message("no.user.stop"));
|
|
|
@@ -989,8 +1001,9 @@ public class InfoUserController extends BaseController {
|
|
|
private InfoUser findPhoneLoginUser(String phone) {
|
|
|
List<InfoUser> matches = infoUserService.list(new LambdaQueryWrapper<InfoUser>()
|
|
|
.eq(InfoUser::getPhone, phone)
|
|
|
- .eq(InfoUser::getDelFlag, "0")
|
|
|
- .eq(InfoUser::getUserType, "0"));
|
|
|
+ .and(w -> w.eq(InfoUser::getDelFlag, "0").or(pending -> pending
|
|
|
+ .eq(InfoUser::getDelFlag, "1").gt(InfoUser::getCancellationDeadlineAt, new Date())))
|
|
|
+ .eq(InfoUser::getUserType, "0").last("FOR UPDATE"));
|
|
|
if (matches.size() > 1) {
|
|
|
log.warn("[Login] 手机号关联多个账号 phone={}", maskPhone(phone));
|
|
|
throw new ServiceException(MessageUtils.message("no.user.phone.duplicate"));
|
|
|
@@ -1006,6 +1019,7 @@ public class InfoUserController extends BaseController {
|
|
|
*/
|
|
|
@Anonymous
|
|
|
@PostMapping("/lodeing")
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
public AjaxResult lodeing(@RequestBody UserDTO userDTO) {
|
|
|
JwtUtil tokn = new JwtUtil();
|
|
|
String xcode = redisCache.getCacheObject(userDTO.getPhone().trim().replaceAll("\\+", ""));
|
|
|
@@ -1063,6 +1077,7 @@ public class InfoUserController extends BaseController {
|
|
|
userDto.setProvider("phone");
|
|
|
fillLoginUserInfo(userDto);
|
|
|
String token = JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, userDto);
|
|
|
+ if (token == null || token.isBlank()) throw new ServiceException(MessageUtils.message("no.system.error"));
|
|
|
return success(MessageUtils.message("no.user.login.success"), inus, token);
|
|
|
}
|
|
|
|
|
|
@@ -1073,6 +1088,8 @@ public class InfoUserController extends BaseController {
|
|
|
* @return
|
|
|
*/
|
|
|
public AjaxResult userLoginUpdateInfo(UserDTO userDTO, InfoUser infoUser) {
|
|
|
+ // 仅在短信验证成功后恢复;普通用户的行锁由 lodeing 外层事务持有至新会话签发。
|
|
|
+ infoUser = recoverCancelledUser(infoUser);
|
|
|
DateUtil date = new DateUtil();
|
|
|
QueryWrapper<InfoUser> query = new QueryWrapper<>();
|
|
|
query.eq("user_id", infoUser.getUserId());
|
|
|
@@ -1109,6 +1126,7 @@ public class InfoUserController extends BaseController {
|
|
|
userDto.setProvider("phone");
|
|
|
fillLoginUserInfo(userDto);
|
|
|
String token = JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, userDto);
|
|
|
+ if (token == null || token.isBlank()) throw new ServiceException(MessageUtils.message("no.system.error"));
|
|
|
return success(MessageUtils.message("no.user.login.success"), user, token);
|
|
|
}
|
|
|
|
|
|
@@ -1119,6 +1137,7 @@ public class InfoUserController extends BaseController {
|
|
|
*/
|
|
|
@Anonymous
|
|
|
@PostMapping("/oauthLogin")
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
public AjaxResult oauthLogin(@RequestBody OAuthLoginDto dto) {
|
|
|
if (dto == null || dto.getProvider() == null || dto.getProvider().isEmpty()) {
|
|
|
return error(MessageUtils.message("no.oauth.provider.blank"));
|
|
|
@@ -1146,8 +1165,17 @@ public class InfoUserController extends BaseController {
|
|
|
// 已绑定:校验用户正常后直接登录
|
|
|
log.info("[OAuth] 已绑定 provider={}, userId={}", dto.getProvider(), bind.getUserId());
|
|
|
QueryWrapper<InfoUser> q = new QueryWrapper<>();
|
|
|
- q.eq("user_id", bind.getUserId()).eq("status", 0).eq("del_flag", "0");
|
|
|
+ q.eq("user_id", bind.getUserId()).eq("status", 0);
|
|
|
+ if (accountType == null || accountType == OAuthAccountType.USER) {
|
|
|
+ q.eq("user_type", "0").and(w -> w.eq("del_flag", "0").or(pending -> pending
|
|
|
+ .eq("del_flag", "1").gt("cancellation_deadline_at", new Date())));
|
|
|
+ } else {
|
|
|
+ q.eq("del_flag", "0");
|
|
|
+ }
|
|
|
+ if (accountType == null || accountType == OAuthAccountType.USER) q.last("FOR UPDATE");
|
|
|
InfoUser u = infoUserService.getOne(q);
|
|
|
+ if (u != null && (accountType == null || accountType == OAuthAccountType.USER))
|
|
|
+ u = recoverCancelledUser(u);
|
|
|
if (u == null) {
|
|
|
log.warn("[OAuth] 已绑定但账号已停用 userId={}", bind.getUserId());
|
|
|
return error(MessageUtils.message("no.user.stop"));
|
|
|
@@ -1160,7 +1188,9 @@ public class InfoUserController extends BaseController {
|
|
|
u.setDeviceToken(dto.getDeviceToken());
|
|
|
u.setVoIPToken(dto.getVoIPToken());
|
|
|
infoUserService.saveOrUpdate(u);
|
|
|
- deviceTrustService.recordLogin(dto.getDeviceId(), u.getPhone(), u.getUserId());
|
|
|
+ if (accountType == null || accountType == OAuthAccountType.USER) {
|
|
|
+ deviceTrustService.recordOauthLogin(dto.getDeviceId(), u.getPhone(), u.getUserId(), bind);
|
|
|
+ }
|
|
|
refreshOauthDisplayName(bind, displayName);
|
|
|
return issueOauthToken(u, dto.getProvider(), providerUid);
|
|
|
}
|
|
|
@@ -1175,10 +1205,13 @@ public class InfoUserController extends BaseController {
|
|
|
: dto.getProvider() + "@" + providerUid + "@" + displayName;
|
|
|
redisCache.setCacheObject(OAUTH_TEMP_PREFIX + tempKey, tempValue, 5, TimeUnit.MINUTES);
|
|
|
// 029:设备信任免短信确认仅用户端(会员)适用;业务账号绑定必须真实短信,不走免确认
|
|
|
- String trustedPhone = accountType == null || accountType == OAuthAccountType.USER
|
|
|
- ? deviceTrustService.getTrustedPhone(dto.getDeviceId()) : null;
|
|
|
+ InfoUserDevice trustedDevice = accountType == null || accountType == OAuthAccountType.USER
|
|
|
+ ? deviceTrustService.getTrust(dto.getDeviceId()) : null;
|
|
|
+ String trustedPhone = trustedDevice == null ? null : trustedDevice.getPhone();
|
|
|
if (trustedPhone != null) {
|
|
|
redisCache.setCacheObject(OAUTH_TEMP_DEVICE_PREFIX + tempKey, dto.getDeviceId(), 5, TimeUnit.MINUTES);
|
|
|
+ redisCache.setCacheObject(OAUTH_TEMP_USER_PREFIX + tempKey,
|
|
|
+ String.valueOf(trustedDevice.getUserId()), 5, TimeUnit.MINUTES);
|
|
|
log.info("[OAuth] 设备已信任,返回 deviceConfirm provider={}, deviceId={}, phone={}",
|
|
|
dto.getProvider(), dto.getDeviceId(), DeviceTrustService.maskPhone(trustedPhone));
|
|
|
Map<String, Object> data = new HashMap<>();
|
|
|
@@ -1221,22 +1254,20 @@ public class InfoUserController extends BaseController {
|
|
|
OAuthAccountType accountType = OAuthAccountType.forProvider(provider);
|
|
|
log.info("[OAuth] 绑定流程 provider={}", provider);
|
|
|
|
|
|
- // 容错:若期间已被绑定,直接登录
|
|
|
- InfoUserOauth exist = infoUserOauthMapper.selectOne(
|
|
|
- new LambdaQueryWrapper<InfoUserOauth>()
|
|
|
- .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(provider))
|
|
|
- .eq(InfoUserOauth::getProviderUid, providerUid));
|
|
|
- if (exist != null) {
|
|
|
- log.info("[OAuth] 绑定期间已被绑定,直接登录 userId={}", exist.getUserId());
|
|
|
- InfoUser u = infoUserService.getById(exist.getUserId());
|
|
|
- if (accountType != null && !canOauthLogin(accountType, u)) {
|
|
|
- return error(MessageUtils.message("no.user.stop"));
|
|
|
+ // 期间已有绑定也必须验证本次手机号/业务账号,不能提前签发旧账号 token。
|
|
|
+ boolean businessAccount = accountType != null && !accountType.allowCreate();
|
|
|
+ // 本轮仅改变用户端换号;业务端原有绑定容错及角色校验保持原规则。
|
|
|
+ if (businessAccount) {
|
|
|
+ InfoUserOauth existing = infoUserOauthMapper.selectOne(new LambdaQueryWrapper<InfoUserOauth>()
|
|
|
+ .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(provider))
|
|
|
+ .eq(InfoUserOauth::getProviderUid, providerUid));
|
|
|
+ if (existing != null) {
|
|
|
+ InfoUser account = infoUserService.getById(existing.getUserId());
|
|
|
+ if (!canOauthLogin(accountType, account)) return error(MessageUtils.message("no.user.stop"));
|
|
|
+ refreshOauthDisplayName(existing, displayName);
|
|
|
+ return issueOauthToken(account, provider, providerUid);
|
|
|
}
|
|
|
- refreshOauthDisplayName(exist, displayName);
|
|
|
- return issueOauthToken(u, provider, providerUid);
|
|
|
}
|
|
|
-
|
|
|
- boolean businessAccount = accountType != null && !accountType.allowCreate();
|
|
|
InfoUser user;
|
|
|
if (businessAccount) {
|
|
|
// 017 2026-10-08 增量:业务端(骑手/商家/子账号)改用账号密码绑定。
|
|
|
@@ -1295,6 +1326,7 @@ public class InfoUserController extends BaseController {
|
|
|
// getuser 不带 userType 过滤,同手机号命中商家/骑手账号会错绑或多行异常。
|
|
|
OAuthAccountType bindingChannel = accountType != null ? accountType : OAuthAccountType.USER;
|
|
|
user = findOauthBindingUser(bindingChannel, phone);
|
|
|
+ if (user != null) user = recoverCancelledUser(user);
|
|
|
if (user != null && !"0".equals(user.getStatus())) {
|
|
|
// 停用账号不允许绑定三方(与 lodeing 一致,防止绕过停用)
|
|
|
log.warn("[OAuth] 账号已停用 phone={}", maskPhone(phone));
|
|
|
@@ -1332,24 +1364,12 @@ public class InfoUserController extends BaseController {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 写三方绑定
|
|
|
- InfoUserOauth bind = new InfoUserOauth();
|
|
|
- bind.setUserId(user.getUserId());
|
|
|
- bind.setProvider("line".equals(provider) ? "line_user" : provider);
|
|
|
- bind.setProviderUid(providerUid);
|
|
|
- bind.setProviderDisplayName(displayName != null && !displayName.isBlank() ? displayName : null);
|
|
|
- bind.setCreateTime(new Date());
|
|
|
- if (infoUserOauthMapper.insert(bind) != 1) {
|
|
|
- throw new ServiceException(MessageUtils.message("no.system.error"));
|
|
|
- }
|
|
|
- log.info("[OAuth] 写入绑定 provider={}, userId={}", provider, user.getUserId());
|
|
|
-
|
|
|
- redisCache.deleteObject(OAUTH_TEMP_PREFIX + dto.getTempKey());
|
|
|
- // 029 设备信任仅用户端适用:业务端绑定只验了密码(共享凭证场景)、未验手机号,
|
|
|
- // 写信任会让共享设备后续经用户端 deviceConfirm 冒用登记手机号,故不记录
|
|
|
- if (!businessAccount) {
|
|
|
- deviceTrustService.recordLogin(dto.getDeviceId(), dto.getPhone(), user.getUserId());
|
|
|
- }
|
|
|
+ // 先解绑旧用户,再写本次新绑定,避免误删新手机号的绑定。
|
|
|
+ if (!businessAccount) deviceTrustService.recordLogin(dto.getDeviceId(), user.getPhone(), user.getUserId());
|
|
|
+ InfoUserOauth bind = bindVerifiedIdentity(user, provider, providerUid, displayName);
|
|
|
+ if (!businessAccount) deviceTrustService.recordOauthLogin(dto.getDeviceId(), user.getPhone(), user.getUserId(), bind);
|
|
|
+ redisCache.deleteObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey());
|
|
|
+ redisCache.deleteObject(OAUTH_TEMP_USER_PREFIX + dto.getTempKey());
|
|
|
return issueOauthToken(user, provider, providerUid);
|
|
|
}
|
|
|
|
|
|
@@ -1359,6 +1379,7 @@ public class InfoUserController extends BaseController {
|
|
|
*/
|
|
|
@Anonymous
|
|
|
@PostMapping("/oauthDeviceConfirm")
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
public AjaxResult oauthDeviceConfirm(@RequestBody OAuthDeviceConfirmDto dto) {
|
|
|
if (dto == null || dto.getTempKey() == null || dto.getTempKey().isEmpty()) {
|
|
|
log.warn("[OAuth] oauthDeviceConfirm 缺 tempKey");
|
|
|
@@ -1376,73 +1397,55 @@ public class InfoUserController extends BaseController {
|
|
|
// 凭证与设备必须匹配:dev 键由 oauthLogin 的 deviceConfirm 分支写入,换设备重放即拒绝
|
|
|
String boundDevice = redisCache.getCacheObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey());
|
|
|
InfoUserDevice trust = deviceTrustService.getTrust(dto.getDeviceId());
|
|
|
- if (boundDevice == null || !boundDevice.equals(dto.getDeviceId()) || trust == null) {
|
|
|
- log.warn("[OAuth] 设备确认凭证与设备不匹配 tempKey={}, deviceId={}", dto.getTempKey(), dto.getDeviceId());
|
|
|
+ Object expectedUser = redisCache.getCacheObject(OAUTH_TEMP_USER_PREFIX + dto.getTempKey());
|
|
|
+ if (boundDevice == null || !boundDevice.equals(dto.getDeviceId()) || trust == null
|
|
|
+ || expectedUser == null || !String.valueOf(trust.getUserId()).equals(String.valueOf(expectedUser)))
|
|
|
return error(MessageUtils.message("no.oauth.device.mismatch"));
|
|
|
- }
|
|
|
String[] cachedParts = cached.split("@", 3);
|
|
|
+ if (cachedParts.length < 2) return error(MessageUtils.message("no.oauth.tempkey.expired"));
|
|
|
String provider = cachedParts[0];
|
|
|
String providerUid = cachedParts[1];
|
|
|
String displayName = cachedParts.length > 2 ? cachedParts[2] : null;
|
|
|
OAuthAccountType accountType = OAuthAccountType.forProvider(provider);
|
|
|
- if (accountType != null && accountType != OAuthAccountType.USER) {
|
|
|
- // 业务账号绑定必须真实短信验证,设备信任免确认不适用
|
|
|
- log.warn("[OAuth] 设备确认不支持业务渠道 provider={}", provider);
|
|
|
+ if (accountType != null && accountType != OAuthAccountType.USER)
|
|
|
return error(MessageUtils.message("no.oauth.device.mismatch"));
|
|
|
- }
|
|
|
- log.info("[OAuth] 设备确认流程 provider={}, providerUid={}, deviceId={}", provider, providerUid, dto.getDeviceId());
|
|
|
|
|
|
- // 容错:若期间已被绑定,直接登录(与 oauthBindPhone 一致)
|
|
|
- InfoUserOauth exist = infoUserOauthMapper.selectOne(
|
|
|
- new LambdaQueryWrapper<InfoUserOauth>()
|
|
|
- .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(provider))
|
|
|
- .eq(InfoUserOauth::getProviderUid, providerUid));
|
|
|
- if (exist != null) {
|
|
|
- log.info("[OAuth] 设备确认期间已被绑定,直接登录 userId={}", exist.getUserId());
|
|
|
- InfoUser u = infoUserService.getById(exist.getUserId());
|
|
|
- if (accountType != null && !canOauthLogin(accountType, u)) {
|
|
|
- return error(MessageUtils.message("no.user.stop"));
|
|
|
- }
|
|
|
- refreshOauthDisplayName(exist, displayName);
|
|
|
- return issueOauthToken(u, provider, providerUid);
|
|
|
- }
|
|
|
+ // 用户行锁→设备行锁→绑定行锁;旧确认凭证不能绑定到新手机号,也不复建已消失的信任账号。
|
|
|
+ InfoUser user = findOauthBindingUser(OAuthAccountType.USER, trust.getPhone());
|
|
|
+ if (user == null || !java.util.Objects.equals(user.getUserId(), trust.getUserId()))
|
|
|
+ return error(MessageUtils.message("no.oauth.device.mismatch"));
|
|
|
+ if (!"0".equals(user.getStatus())) return error(MessageUtils.message("no.user.stop"));
|
|
|
+ user = recoverCancelledUser(user);
|
|
|
+ if (!canOauthLogin(OAuthAccountType.USER, user)) throw new ServiceException(MessageUtils.message("no.user.stop"));
|
|
|
+ deviceTrustService.requireConfirmation(dto.getDeviceId(), user.getUserId());
|
|
|
+ if (!redisCache.deleteObject(OAUTH_TEMP_PREFIX + dto.getTempKey()))
|
|
|
+ throw new ServiceException(MessageUtils.message("no.oauth.tempkey.expired"));
|
|
|
+ InfoUserOauth bind = bindVerifiedIdentity(user, provider, providerUid, displayName);
|
|
|
+ deviceTrustService.recordOauthLogin(dto.getDeviceId(), user.getPhone(), user.getUserId(), bind);
|
|
|
+ redisCache.deleteObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey());
|
|
|
+ redisCache.deleteObject(OAUTH_TEMP_USER_PREFIX + dto.getTempKey());
|
|
|
+ return issueOauthToken(user, provider, providerUid);
|
|
|
+ }
|
|
|
|
|
|
- // 信任手机号即账号主键:已注册校验停用、未注册自动创建(与 oauthBindPhone 一致,防绕过停用)
|
|
|
- String phone = trust.getPhone();
|
|
|
- InfoUser user = findOauthBindingUser(OAuthAccountType.USER, phone);
|
|
|
- if (user != null && !"0".equals(user.getStatus())) {
|
|
|
- log.warn("[OAuth] 设备确认账号已停用 phone={}", DeviceTrustService.maskPhone(phone));
|
|
|
- return error(MessageUtils.message("no.user.stop"));
|
|
|
- }
|
|
|
- if (user == null) {
|
|
|
- InfoUser info = new InfoUser();
|
|
|
- info.setPhone(phone);
|
|
|
- info.setNickName(phone);
|
|
|
- info.setUserType("0");
|
|
|
- info.setMycode(new UUIDUtil().get8UUID());
|
|
|
- infoUserService.saveOrUpdate(info);
|
|
|
- user = findOauthBindingUser(OAuthAccountType.USER, phone);
|
|
|
- if (user == null) {
|
|
|
- return error(MessageUtils.message("no.user.not.exist"));
|
|
|
- }
|
|
|
- createUserWallet(user.getUserId());
|
|
|
- log.info("[OAuth] 设备确认新建账号 userId={}", user.getUserId());
|
|
|
+ /** 身份与手机号已验证后才调用;并发已有绑定只能复用同一用户,不得返回其他账号。 */
|
|
|
+ private InfoUserOauth bindVerifiedIdentity(InfoUser user, String provider, String providerUid, String displayName) {
|
|
|
+ InfoUserOauth bind = infoUserOauthMapper.selectOne(new LambdaQueryWrapper<InfoUserOauth>()
|
|
|
+ .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(provider))
|
|
|
+ .eq(InfoUserOauth::getProviderUid, providerUid).last("FOR UPDATE"));
|
|
|
+ if (bind != null) {
|
|
|
+ if (!java.util.Objects.equals(bind.getUserId(), user.getUserId()))
|
|
|
+ throw new ServiceException(MessageUtils.message("no.oauth.binding.conflict"));
|
|
|
+ refreshOauthDisplayName(bind, displayName);
|
|
|
+ return bind;
|
|
|
}
|
|
|
-
|
|
|
- // 写三方绑定(一键确认即免短信绑定)
|
|
|
- InfoUserOauth bind = new InfoUserOauth();
|
|
|
+ bind = new InfoUserOauth();
|
|
|
bind.setUserId(user.getUserId());
|
|
|
bind.setProvider("line".equals(provider) ? "line_user" : provider);
|
|
|
bind.setProviderUid(providerUid);
|
|
|
bind.setProviderDisplayName(displayName != null && !displayName.isBlank() ? displayName : null);
|
|
|
bind.setCreateTime(new Date());
|
|
|
- infoUserOauthMapper.insert(bind);
|
|
|
- log.info("[OAuth] 设备确认写入绑定 provider={}, providerUid={}, userId={}", provider, providerUid, user.getUserId());
|
|
|
-
|
|
|
- redisCache.deleteObject(OAUTH_TEMP_PREFIX + dto.getTempKey());
|
|
|
- redisCache.deleteObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey());
|
|
|
- deviceTrustService.recordLogin(dto.getDeviceId(), phone, user.getUserId());
|
|
|
- return issueOauthToken(user, provider, providerUid);
|
|
|
+ if (infoUserOauthMapper.insert(bind) != 1) throw new ServiceException(MessageUtils.message("no.system.error"));
|
|
|
+ return bind;
|
|
|
}
|
|
|
|
|
|
/** 手机号脱敏:保留前 3 后 4,中间 ****(仅日志用)。 */
|
|
|
@@ -1485,10 +1488,17 @@ public class InfoUserController extends BaseController {
|
|
|
dto.setProviderUid(providerUid);
|
|
|
fillLoginUserInfo(dto);
|
|
|
String token = JwtUtil.setToken(tokenKey, dto);
|
|
|
+ if (token == null || token.isBlank()) throw new ServiceException(MessageUtils.message("no.system.error"));
|
|
|
Object loginUser = accountType == OAuthAccountType.MERCHANT ? merchantLoginView(user) : user;
|
|
|
return success(MessageUtils.message("no.user.login.success"), loginUser, token);
|
|
|
}
|
|
|
|
|
|
+ /** 候选查询已锁定账号;只有身份认证成功的用户登录入口调用,刷新token不调用。 */
|
|
|
+ private InfoUser recoverCancelledUser(InfoUser user) {
|
|
|
+ return user != null && "0".equals(user.getUserType()) && "1".equals(user.getDelFlag())
|
|
|
+ ? userCancellationService.authenticatedLogin(user.getUserId()) : user;
|
|
|
+ }
|
|
|
+
|
|
|
private boolean canOauthLogin(OAuthAccountType channel, InfoUser user) {
|
|
|
if (user == null || !"0".equals(user.getStatus()) || !"0".equals(user.getDelFlag())
|
|
|
|| !channel.userTypes().contains(user.getUserType())) {
|
|
|
@@ -1533,8 +1543,13 @@ public class InfoUserController extends BaseController {
|
|
|
LambdaQueryWrapper<InfoUser> query = new LambdaQueryWrapper<InfoUser>()
|
|
|
.eq(channel.allowCreate(), InfoUser::getPhone, phone)
|
|
|
.eq(!channel.allowCreate(), InfoUser::getTelPhone, phone)
|
|
|
- .eq(InfoUser::getDelFlag, "0")
|
|
|
.in(InfoUser::getUserType, channel.userTypes());
|
|
|
+ if (channel == OAuthAccountType.USER) {
|
|
|
+ query.and(w -> w.eq(InfoUser::getDelFlag, "0").or(pending -> pending
|
|
|
+ .eq(InfoUser::getDelFlag, "1").gt(InfoUser::getCancellationDeadlineAt, new Date()))).last("FOR UPDATE");
|
|
|
+ } else {
|
|
|
+ query.eq(InfoUser::getDelFlag, "0");
|
|
|
+ }
|
|
|
List<InfoUser> matches = infoUserService.list(query);
|
|
|
if (matches.size() > 1) {
|
|
|
log.warn("[OAuth] 手机号关联多个账号 channel={}, phone={}", channel, maskPhone(phone));
|