|
@@ -43,6 +43,7 @@ import com.ruoyi.app.user.dto.OAuthBindDto;
|
|
|
import com.ruoyi.app.user.dto.OAuthDeviceConfirmDto;
|
|
import com.ruoyi.app.user.dto.OAuthDeviceConfirmDto;
|
|
|
import com.ruoyi.app.user.dto.OAuthLoginDto;
|
|
import com.ruoyi.app.user.dto.OAuthLoginDto;
|
|
|
import com.ruoyi.app.utils.oauth.OAuthAccountType;
|
|
import com.ruoyi.app.utils.oauth.OAuthAccountType;
|
|
|
|
|
+import com.ruoyi.app.utils.oauth.OAuthIdentity;
|
|
|
import com.ruoyi.app.utils.oauth.LineOAuthStateService;
|
|
import com.ruoyi.app.utils.oauth.LineOAuthStateService;
|
|
|
import com.ruoyi.app.user.service.DeviceTrustService;
|
|
import com.ruoyi.app.user.service.DeviceTrustService;
|
|
|
import com.ruoyi.system.domain.InfoUserDevice;
|
|
import com.ruoyi.system.domain.InfoUserDevice;
|
|
@@ -895,6 +896,26 @@ public class InfoUserController extends BaseController {
|
|
|
} else {
|
|
} else {
|
|
|
user.setPointsWallet(Long.valueOf(userWallet.getPointsWallet().toString()));
|
|
user.setPointsWallet(Long.valueOf(userWallet.getPointsWallet().toString()));
|
|
|
}
|
|
}
|
|
|
|
|
+ // 017 2026-09-24 增量:按本次登录会话回填渠道与第三方名称。
|
|
|
|
|
+ // 渠道来自会话 claim;名称精确按 userId+providerUid 反查绑定,无 providerUid 的旧会话只展示渠道,不任意挑绑定。
|
|
|
|
|
+ Map<String, Object> sessionClaims = JwtUtil.verifyToken(token);
|
|
|
|
|
+ Object providerClaim = sessionClaims.get("provider");
|
|
|
|
|
+ String oauthProvider = providerClaim == null || "null".equals(providerClaim)
|
|
|
|
|
+ ? null : String.valueOf(providerClaim);
|
|
|
|
|
+ if (oauthProvider != null && !"phone".equals(oauthProvider)) {
|
|
|
|
|
+ user.setOauthProvider(oauthProvider);
|
|
|
|
|
+ Object uidClaim = sessionClaims.get("providerUid");
|
|
|
|
|
+ if (uidClaim != null && !"null".equals(uidClaim) && !String.valueOf(uidClaim).isEmpty()) {
|
|
|
|
|
+ InfoUserOauth identity = infoUserOauthMapper.selectOne(
|
|
|
|
|
+ new LambdaQueryWrapper<InfoUserOauth>()
|
|
|
|
|
+ .eq(InfoUserOauth::getUserId, Long.valueOf(id))
|
|
|
|
|
+ .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(oauthProvider))
|
|
|
|
|
+ .eq(InfoUserOauth::getProviderUid, String.valueOf(uidClaim)));
|
|
|
|
|
+ if (identity != null) {
|
|
|
|
|
+ user.setProviderDisplayName(identity.getProviderDisplayName());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
return success(user);
|
|
return success(user);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -946,8 +967,12 @@ public class InfoUserController extends BaseController {
|
|
|
LoginUserDto userDto = new LoginUserDto();
|
|
LoginUserDto userDto = new LoginUserDto();
|
|
|
userDto.setUserId(user.getUserId());
|
|
userDto.setUserId(user.getUserId());
|
|
|
userDto.setUserName(user.getUserName());
|
|
userDto.setUserName(user.getUserName());
|
|
|
- Object provider = JwtUtil.verifyToken(token).get("provider");
|
|
|
|
|
|
|
+ Map<String, Object> renewClaims = JwtUtil.verifyToken(token);
|
|
|
|
|
+ Object provider = renewClaims.get("provider");
|
|
|
userDto.setProvider(provider == null || "null".equals(provider) ? "phone" : String.valueOf(provider));
|
|
userDto.setProvider(provider == null || "null".equals(provider) ? "phone" : String.valueOf(provider));
|
|
|
|
|
+ Object providerUid = renewClaims.get("providerUid");
|
|
|
|
|
+ userDto.setProviderUid(providerUid == null || "null".equals(providerUid)
|
|
|
|
|
+ ? null : String.valueOf(providerUid));
|
|
|
fillLoginUserInfo(userDto);
|
|
fillLoginUserInfo(userDto);
|
|
|
String wtoken = JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, userDto);
|
|
String wtoken = JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, userDto);
|
|
|
return success(MessageUtils.message("no.user.token.success"), wtoken);
|
|
return success(MessageUtils.message("no.user.token.success"), wtoken);
|
|
@@ -1102,7 +1127,14 @@ public class InfoUserController extends BaseController {
|
|
|
lineOAuthStateService.consume(dto.getProvider(), dto.getState());
|
|
lineOAuthStateService.consume(dto.getProvider(), dto.getState());
|
|
|
}
|
|
}
|
|
|
log.info("[OAuth] oauthLogin provider={}", dto.getProvider());
|
|
log.info("[OAuth] oauthLogin provider={}", dto.getProvider());
|
|
|
- String providerUid = oauthVerifyService.verify(dto.getProvider(), dto.getCredential());
|
|
|
|
|
|
|
+ OAuthIdentity identity = oauthVerifyService.verifyIdentity(dto.getProvider(), dto.getCredential());
|
|
|
|
|
+ String providerUid = identity.providerUid();
|
|
|
|
|
+ // 第三方显示名称:LINE 取 profile displayName;Apple 由 App 首次授权提交 fullName(017 2026-09-24 增量)
|
|
|
|
|
+ String displayName = identity.displayName();
|
|
|
|
|
+ if ((displayName == null || displayName.isEmpty()) && dto.getProvider().startsWith("apple")
|
|
|
|
|
+ && dto.getFullName() != null && !dto.getFullName().isBlank()) {
|
|
|
|
|
+ displayName = dto.getFullName().trim();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// 查是否已绑定
|
|
// 查是否已绑定
|
|
|
InfoUserOauth bind = infoUserOauthMapper.selectOne(
|
|
InfoUserOauth bind = infoUserOauthMapper.selectOne(
|
|
@@ -1128,15 +1160,19 @@ public class InfoUserController extends BaseController {
|
|
|
u.setVoIPToken(dto.getVoIPToken());
|
|
u.setVoIPToken(dto.getVoIPToken());
|
|
|
infoUserService.saveOrUpdate(u);
|
|
infoUserService.saveOrUpdate(u);
|
|
|
deviceTrustService.recordLogin(dto.getDeviceId(), u.getPhone(), u.getUserId());
|
|
deviceTrustService.recordLogin(dto.getDeviceId(), u.getPhone(), u.getUserId());
|
|
|
- return issueOauthToken(u, dto.getProvider());
|
|
|
|
|
|
|
+ refreshOauthDisplayName(bind, displayName);
|
|
|
|
|
+ return issueOauthToken(u, dto.getProvider(), providerUid);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 未绑定:缓存 {provider, providerUid},返回 needPhone + tempKey
|
|
// 未绑定:缓存 {provider, providerUid},返回 needPhone + tempKey
|
|
|
log.info("[OAuth] 未绑定,返回 needPhone provider={}", dto.getProvider());
|
|
log.info("[OAuth] 未绑定,返回 needPhone provider={}", dto.getProvider());
|
|
|
// (029:同设备已验证过手机号 → 免短信一键确认 deviceConfirm 分支)
|
|
// (029:同设备已验证过手机号 → 免短信一键确认 deviceConfirm 分支)
|
|
|
String tempKey = UUID.randomUUID().toString().replace("-", "");
|
|
String tempKey = UUID.randomUUID().toString().replace("-", "");
|
|
|
- redisCache.setCacheObject(OAUTH_TEMP_PREFIX + tempKey,
|
|
|
|
|
- dto.getProvider() + "@" + providerUid, 5, TimeUnit.MINUTES);
|
|
|
|
|
|
|
+ // 未绑定时把第三方名称一并带进临时凭证,绑定时落库;名称为空保持两段式(兼容旧缓存值)
|
|
|
|
|
+ String tempValue = displayName == null || displayName.isEmpty()
|
|
|
|
|
+ ? dto.getProvider() + "@" + providerUid
|
|
|
|
|
+ : dto.getProvider() + "@" + providerUid + "@" + displayName;
|
|
|
|
|
+ redisCache.setCacheObject(OAUTH_TEMP_PREFIX + tempKey, tempValue, 5, TimeUnit.MINUTES);
|
|
|
// 029:设备信任免短信确认仅用户端(会员)适用;业务账号绑定必须真实短信,不走免确认
|
|
// 029:设备信任免短信确认仅用户端(会员)适用;业务账号绑定必须真实短信,不走免确认
|
|
|
String trustedPhone = accountType == null || accountType == OAuthAccountType.USER
|
|
String trustedPhone = accountType == null || accountType == OAuthAccountType.USER
|
|
|
? deviceTrustService.getTrustedPhone(dto.getDeviceId()) : null;
|
|
? deviceTrustService.getTrustedPhone(dto.getDeviceId()) : null;
|
|
@@ -1174,12 +1210,13 @@ public class InfoUserController extends BaseController {
|
|
|
log.warn("[OAuth] tempKey 已过期/不存在");
|
|
log.warn("[OAuth] tempKey 已过期/不存在");
|
|
|
return error(MessageUtils.message("no.oauth.tempkey.expired"));
|
|
return error(MessageUtils.message("no.oauth.tempkey.expired"));
|
|
|
}
|
|
}
|
|
|
- int at = cached.indexOf('@');
|
|
|
|
|
- if (at <= 0 || at == cached.length() - 1) {
|
|
|
|
|
|
|
+ String[] cachedParts = cached.split("@", 3);
|
|
|
|
|
+ if (cachedParts.length < 2 || cachedParts[0].isEmpty() || cachedParts[1].isEmpty()) {
|
|
|
return error(MessageUtils.message("no.oauth.tempkey.expired"));
|
|
return error(MessageUtils.message("no.oauth.tempkey.expired"));
|
|
|
}
|
|
}
|
|
|
- String provider = cached.substring(0, at);
|
|
|
|
|
- String providerUid = cached.substring(at + 1);
|
|
|
|
|
|
|
+ String provider = cachedParts[0];
|
|
|
|
|
+ String providerUid = cachedParts[1];
|
|
|
|
|
+ String displayName = cachedParts.length > 2 ? cachedParts[2] : null;
|
|
|
OAuthAccountType accountType = OAuthAccountType.forProvider(provider);
|
|
OAuthAccountType accountType = OAuthAccountType.forProvider(provider);
|
|
|
log.info("[OAuth] 绑定流程 provider={}", provider);
|
|
log.info("[OAuth] 绑定流程 provider={}", provider);
|
|
|
|
|
|
|
@@ -1194,7 +1231,8 @@ public class InfoUserController extends BaseController {
|
|
|
if (accountType != null && !canOauthLogin(accountType, u)) {
|
|
if (accountType != null && !canOauthLogin(accountType, u)) {
|
|
|
return error(MessageUtils.message("no.user.stop"));
|
|
return error(MessageUtils.message("no.user.stop"));
|
|
|
}
|
|
}
|
|
|
- return issueOauthToken(u, provider);
|
|
|
|
|
|
|
+ refreshOauthDisplayName(exist, displayName);
|
|
|
|
|
+ return issueOauthToken(u, provider, providerUid);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 用户端保留既有校验;业务账号绑定必须通过真实短信验证码。
|
|
// 用户端保留既有校验;业务账号绑定必须通过真实短信验证码。
|
|
@@ -1264,6 +1302,7 @@ public class InfoUserController extends BaseController {
|
|
|
bind.setUserId(user.getUserId());
|
|
bind.setUserId(user.getUserId());
|
|
|
bind.setProvider("line".equals(provider) ? "line_user" : provider);
|
|
bind.setProvider("line".equals(provider) ? "line_user" : provider);
|
|
|
bind.setProviderUid(providerUid);
|
|
bind.setProviderUid(providerUid);
|
|
|
|
|
+ bind.setProviderDisplayName(displayName != null && !displayName.isBlank() ? displayName : null);
|
|
|
bind.setCreateTime(new Date());
|
|
bind.setCreateTime(new Date());
|
|
|
if (infoUserOauthMapper.insert(bind) != 1) {
|
|
if (infoUserOauthMapper.insert(bind) != 1) {
|
|
|
throw new ServiceException(MessageUtils.message("no.system.error"));
|
|
throw new ServiceException(MessageUtils.message("no.system.error"));
|
|
@@ -1272,7 +1311,7 @@ public class InfoUserController extends BaseController {
|
|
|
|
|
|
|
|
redisCache.deleteObject(OAUTH_TEMP_PREFIX + dto.getTempKey());
|
|
redisCache.deleteObject(OAUTH_TEMP_PREFIX + dto.getTempKey());
|
|
|
deviceTrustService.recordLogin(dto.getDeviceId(), phone, user.getUserId());
|
|
deviceTrustService.recordLogin(dto.getDeviceId(), phone, user.getUserId());
|
|
|
- return issueOauthToken(user, provider);
|
|
|
|
|
|
|
+ return issueOauthToken(user, provider, providerUid);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -1302,9 +1341,10 @@ public class InfoUserController extends BaseController {
|
|
|
log.warn("[OAuth] 设备确认凭证与设备不匹配 tempKey={}, deviceId={}", dto.getTempKey(), dto.getDeviceId());
|
|
log.warn("[OAuth] 设备确认凭证与设备不匹配 tempKey={}, deviceId={}", dto.getTempKey(), dto.getDeviceId());
|
|
|
return error(MessageUtils.message("no.oauth.device.mismatch"));
|
|
return error(MessageUtils.message("no.oauth.device.mismatch"));
|
|
|
}
|
|
}
|
|
|
- int at = cached.indexOf('@');
|
|
|
|
|
- String provider = cached.substring(0, at);
|
|
|
|
|
- String providerUid = cached.substring(at + 1);
|
|
|
|
|
|
|
+ String[] cachedParts = cached.split("@", 3);
|
|
|
|
|
+ String provider = cachedParts[0];
|
|
|
|
|
+ String providerUid = cachedParts[1];
|
|
|
|
|
+ String displayName = cachedParts.length > 2 ? cachedParts[2] : null;
|
|
|
OAuthAccountType accountType = OAuthAccountType.forProvider(provider);
|
|
OAuthAccountType accountType = OAuthAccountType.forProvider(provider);
|
|
|
if (accountType != null && accountType != OAuthAccountType.USER) {
|
|
if (accountType != null && accountType != OAuthAccountType.USER) {
|
|
|
// 业务账号绑定必须真实短信验证,设备信任免确认不适用
|
|
// 业务账号绑定必须真实短信验证,设备信任免确认不适用
|
|
@@ -1324,7 +1364,8 @@ public class InfoUserController extends BaseController {
|
|
|
if (accountType != null && !canOauthLogin(accountType, u)) {
|
|
if (accountType != null && !canOauthLogin(accountType, u)) {
|
|
|
return error(MessageUtils.message("no.user.stop"));
|
|
return error(MessageUtils.message("no.user.stop"));
|
|
|
}
|
|
}
|
|
|
- return issueOauthToken(u, provider);
|
|
|
|
|
|
|
+ refreshOauthDisplayName(exist, displayName);
|
|
|
|
|
+ return issueOauthToken(u, provider, providerUid);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 信任手机号即账号主键:已注册校验停用、未注册自动创建(与 oauthBindPhone 一致,防绕过停用)
|
|
// 信任手机号即账号主键:已注册校验停用、未注册自动创建(与 oauthBindPhone 一致,防绕过停用)
|
|
@@ -1354,6 +1395,7 @@ public class InfoUserController extends BaseController {
|
|
|
bind.setUserId(user.getUserId());
|
|
bind.setUserId(user.getUserId());
|
|
|
bind.setProvider("line".equals(provider) ? "line_user" : provider);
|
|
bind.setProvider("line".equals(provider) ? "line_user" : provider);
|
|
|
bind.setProviderUid(providerUid);
|
|
bind.setProviderUid(providerUid);
|
|
|
|
|
+ bind.setProviderDisplayName(displayName != null && !displayName.isBlank() ? displayName : null);
|
|
|
bind.setCreateTime(new Date());
|
|
bind.setCreateTime(new Date());
|
|
|
infoUserOauthMapper.insert(bind);
|
|
infoUserOauthMapper.insert(bind);
|
|
|
log.info("[OAuth] 设备确认写入绑定 provider={}, providerUid={}, userId={}", provider, providerUid, user.getUserId());
|
|
log.info("[OAuth] 设备确认写入绑定 provider={}, providerUid={}, userId={}", provider, providerUid, user.getUserId());
|
|
@@ -1361,7 +1403,7 @@ public class InfoUserController extends BaseController {
|
|
|
redisCache.deleteObject(OAUTH_TEMP_PREFIX + dto.getTempKey());
|
|
redisCache.deleteObject(OAUTH_TEMP_PREFIX + dto.getTempKey());
|
|
|
redisCache.deleteObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey());
|
|
redisCache.deleteObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey());
|
|
|
deviceTrustService.recordLogin(dto.getDeviceId(), phone, user.getUserId());
|
|
deviceTrustService.recordLogin(dto.getDeviceId(), phone, user.getUserId());
|
|
|
- return issueOauthToken(user, provider);
|
|
|
|
|
|
|
+ return issueOauthToken(user, provider, providerUid);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** 手机号脱敏:保留前 3 后 4,中间 ****(仅日志用)。 */
|
|
/** 手机号脱敏:保留前 3 后 4,中间 ****(仅日志用)。 */
|
|
@@ -1372,8 +1414,18 @@ public class InfoUserController extends BaseController {
|
|
|
return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
|
|
return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** 三方登录签发 token(claim provider=本次登录渠道),复用现有 JwtUtil + Redis 会话。 */
|
|
|
|
|
- private AjaxResult issueOauthToken(InfoUser user, String provider) {
|
|
|
|
|
|
|
+ /** 017 2026-09-24 增量:登录成功刷新第三方显示名称;名称为空/未变化保留旧值,失败不阻断登录。 */
|
|
|
|
|
+ private void refreshOauthDisplayName(InfoUserOauth bind, String displayName) {
|
|
|
|
|
+ if (bind == null || displayName == null || displayName.isBlank()
|
|
|
|
|
+ || displayName.equals(bind.getProviderDisplayName())) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ bind.setProviderDisplayName(displayName);
|
|
|
|
|
+ infoUserOauthMapper.updateById(bind);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 三方登录签发 token(claim provider=本次登录渠道、providerUid=本次绑定身份),复用现有 JwtUtil + Redis 会话。 */
|
|
|
|
|
+ private AjaxResult issueOauthToken(InfoUser user, String provider, String providerUid) {
|
|
|
if (user == null) {
|
|
if (user == null) {
|
|
|
return error(MessageUtils.message("no.user.not.exist"));
|
|
return error(MessageUtils.message("no.user.not.exist"));
|
|
|
}
|
|
}
|
|
@@ -1391,6 +1443,7 @@ public class InfoUserController extends BaseController {
|
|
|
dto.setUserName(accountType == null || accountType.allowCreate()
|
|
dto.setUserName(accountType == null || accountType.allowCreate()
|
|
|
? user.getPhone() : user.getUserName());
|
|
? user.getPhone() : user.getUserName());
|
|
|
dto.setProvider(provider);
|
|
dto.setProvider(provider);
|
|
|
|
|
+ dto.setProviderUid(providerUid);
|
|
|
fillLoginUserInfo(dto);
|
|
fillLoginUserInfo(dto);
|
|
|
String token = JwtUtil.setToken(tokenKey, dto);
|
|
String token = JwtUtil.setToken(tokenKey, dto);
|
|
|
Object loginUser = accountType == OAuthAccountType.MERCHANT ? merchantLoginView(user) : user;
|
|
Object loginUser = accountType == OAuthAccountType.MERCHANT ? merchantLoginView(user) : user;
|