|
|
@@ -13,7 +13,10 @@ import com.ruoyi.common.exception.ServiceException;
|
|
|
import com.ruoyi.app.user.dto.OAuthBindDto;
|
|
|
import com.ruoyi.app.user.dto.OAuthLoginDto;
|
|
|
import com.ruoyi.app.user.service.DeviceTrustService;
|
|
|
+import com.ruoyi.app.utils.oauth.OAuthIdentity;
|
|
|
import com.ruoyi.app.utils.oauth.OAuthVerifyService;
|
|
|
+import com.ruoyi.common.constant.CacheConstants;
|
|
|
+import com.ruoyi.common.core.domain.model.LoginUserDto;
|
|
|
import com.ruoyi.app.utils.oauth.LineOAuthStateService;
|
|
|
import com.ruoyi.system.domain.InfoUser;
|
|
|
import com.ruoyi.system.domain.InfoUserOauth;
|
|
|
@@ -46,10 +49,13 @@ import org.springframework.test.util.ReflectionTestUtils;
|
|
|
import org.springframework.web.context.request.RequestContextHolder;
|
|
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
|
|
|
|
+import com.ruoyi.system.service.IVipUserService;
|
|
|
+
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Collection;
|
|
|
import java.util.Collections;
|
|
|
import java.util.Locale;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
|
@@ -95,6 +101,7 @@ class InfoUserControllerTest {
|
|
|
messageSource.addMessage("merchant.subaccount.platform.managed", Locale.getDefault(),
|
|
|
"分管账号只能通过专用入口管理");
|
|
|
messageSource.addMessage("no.oauth.phone.blank", Locale.getDefault(), "手机号不能为空");
|
|
|
+ messageSource.addMessage("no.oauth.needphone", Locale.getDefault(), "请绑定手机号");
|
|
|
messageSource.addMessage("no.user.login.success", Locale.getDefault(), "登录成功");
|
|
|
messageSource.addMessage("no.user.stop", Locale.getDefault(), "账号已停用");
|
|
|
messageSource.addMessage("no.user.not.exist", Locale.getDefault(), "账号不存在");
|
|
|
@@ -594,6 +601,7 @@ class InfoUserControllerTest {
|
|
|
String token = (String) result.get("token");
|
|
|
assertTrue(JWT.decode(token).getId().startsWith("qtw_tokens:user:"));
|
|
|
assertEquals(provider, JWT.decode(token).getClaim("provider").asString());
|
|
|
+ assertEquals("provider-uid", JWT.decode(token).getClaim("providerUid").asString());
|
|
|
}
|
|
|
|
|
|
@ParameterizedTest
|
|
|
@@ -605,7 +613,8 @@ class InfoUserControllerTest {
|
|
|
OAuthLoginDto request = new OAuthLoginDto();
|
|
|
request.setProvider(provider);
|
|
|
request.setCredential("credential");
|
|
|
- when(oauthVerifyService.verify(provider, "credential")).thenReturn("provider-uid");
|
|
|
+ when(oauthVerifyService.verifyIdentity(provider, "credential"))
|
|
|
+ .thenReturn(new OAuthIdentity("provider-uid", null));
|
|
|
InfoUserOauth binding = new InfoUserOauth();
|
|
|
binding.setUserId(72L);
|
|
|
when(infoUserOauthMapper.selectOne(any())).thenReturn(binding);
|
|
|
@@ -627,6 +636,184 @@ class InfoUserControllerTest {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // ==================== 017 第三方名称展示增量(2026-09-24)====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void boundLineLoginRefreshesDisplayNameFromProfile() {
|
|
|
+ OAuthLoginDto request = new OAuthLoginDto();
|
|
|
+ request.setProvider("line_user");
|
|
|
+ request.setCredential("credential");
|
|
|
+ when(oauthVerifyService.verifyIdentity("line_user", "credential"))
|
|
|
+ .thenReturn(new OAuthIdentity("provider-uid", "LINE-顯示名"));
|
|
|
+ InfoUserOauth binding = new InfoUserOauth();
|
|
|
+ binding.setId(9L);
|
|
|
+ binding.setUserId(72L);
|
|
|
+ binding.setProvider("line_user");
|
|
|
+ when(infoUserOauthMapper.selectOne(any())).thenReturn(binding);
|
|
|
+ InfoUser user = activeUser(72L, "0");
|
|
|
+ user.setPhone("0912345678");
|
|
|
+ when(infoUserService.getOne(any(Wrapper.class))).thenReturn(user);
|
|
|
+ when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
|
|
|
+
|
|
|
+ AjaxResult result = controller.oauthLogin(request);
|
|
|
+
|
|
|
+ assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
|
|
|
+ ArgumentCaptor<InfoUserOauth> refreshed = ArgumentCaptor.forClass(InfoUserOauth.class);
|
|
|
+ verify(infoUserOauthMapper).updateById(refreshed.capture());
|
|
|
+ assertEquals(9L, refreshed.getValue().getId());
|
|
|
+ assertEquals("LINE-顯示名", refreshed.getValue().getProviderDisplayName());
|
|
|
+ assertEquals("provider-uid", JWT.decode((String) result.get("token")).getClaim("providerUid").asString());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void boundLineLoginKeepsOldDisplayNameWhenProfileNameMissing() {
|
|
|
+ OAuthLoginDto request = new OAuthLoginDto();
|
|
|
+ request.setProvider("line_user");
|
|
|
+ request.setCredential("credential");
|
|
|
+ when(oauthVerifyService.verifyIdentity("line_user", "credential"))
|
|
|
+ .thenReturn(new OAuthIdentity("provider-uid", null));
|
|
|
+ InfoUserOauth binding = new InfoUserOauth();
|
|
|
+ binding.setUserId(72L);
|
|
|
+ when(infoUserOauthMapper.selectOne(any())).thenReturn(binding);
|
|
|
+ InfoUser user = activeUser(72L, "0");
|
|
|
+ user.setPhone("0912345678");
|
|
|
+ when(infoUserService.getOne(any(Wrapper.class))).thenReturn(user);
|
|
|
+ when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
|
|
|
+
|
|
|
+ AjaxResult result = controller.oauthLogin(request);
|
|
|
+
|
|
|
+ assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
|
|
|
+ verify(infoUserOauthMapper, never()).updateById(any(InfoUserOauth.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void appleFirstAuthorizationSavesFullNameIntoNewBinding() {
|
|
|
+ OAuthLoginDto login = new OAuthLoginDto();
|
|
|
+ login.setProvider("apple");
|
|
|
+ login.setCredential("credential");
|
|
|
+ login.setFullName("王小明");
|
|
|
+ when(oauthVerifyService.verifyIdentity("apple", "credential"))
|
|
|
+ .thenReturn(new OAuthIdentity("provider-uid", null));
|
|
|
+ when(infoUserOauthMapper.selectOne(any())).thenReturn(null);
|
|
|
+
|
|
|
+ AjaxResult loginResult = controller.oauthLogin(login);
|
|
|
+
|
|
|
+ assertEquals(HttpStatus.SUCCESS, loginResult.get(AjaxResult.CODE_TAG));
|
|
|
+ ArgumentCaptor<String> cacheKey = ArgumentCaptor.forClass(String.class);
|
|
|
+ ArgumentCaptor<Object> cacheValue = ArgumentCaptor.forClass(Object.class);
|
|
|
+ verify(redisCache).setCacheObject(cacheKey.capture(), cacheValue.capture(),
|
|
|
+ org.mockito.ArgumentMatchers.eq(5), org.mockito.ArgumentMatchers.eq(TimeUnit.MINUTES));
|
|
|
+ assertEquals("apple@provider-uid@王小明", cacheValue.getValue());
|
|
|
+
|
|
|
+ when(redisCache.getCacheObject(cacheKey.getValue())).thenReturn((String) cacheValue.getValue());
|
|
|
+ when(redisCache.deleteObject(cacheKey.getValue())).thenReturn(true);
|
|
|
+ OAuthBindDto bind = new OAuthBindDto();
|
|
|
+ bind.setTempKey(cacheKey.getValue().substring("oauth:bind:".length()));
|
|
|
+ bind.setPhone("0912345678");
|
|
|
+ bind.setCode("8888");
|
|
|
+ InfoUser user = activeUser(72L, "0");
|
|
|
+ user.setPhone("0912345678");
|
|
|
+ when(infoUserService.list(any(Wrapper.class)))
|
|
|
+ .thenReturn(java.util.List.of(), java.util.List.of(user));
|
|
|
+ when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
|
|
|
+
|
|
|
+ AjaxResult bindResult = controller.oauthBindPhone(bind);
|
|
|
+
|
|
|
+ assertEquals(HttpStatus.SUCCESS, bindResult.get(AjaxResult.CODE_TAG));
|
|
|
+ ArgumentCaptor<InfoUserOauth> inserted = ArgumentCaptor.forClass(InfoUserOauth.class);
|
|
|
+ verify(infoUserOauthMapper).insert(inserted.capture());
|
|
|
+ assertEquals("apple", inserted.getValue().getProvider());
|
|
|
+ assertEquals("王小明", inserted.getValue().getProviderDisplayName());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void legacyTempKeyWithoutThirdPartyNameBindsWithNullDisplayName() {
|
|
|
+ OAuthBindDto request = oauthRequest("apple");
|
|
|
+ InfoUser user = activeUser(72L, "0");
|
|
|
+ user.setPhone(request.getPhone());
|
|
|
+ when(infoUserService.list(any(Wrapper.class))).thenReturn(java.util.List.of(user));
|
|
|
+ when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
|
|
|
+
|
|
|
+ AjaxResult result = controller.oauthBindPhone(request);
|
|
|
+
|
|
|
+ assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
|
|
|
+ ArgumentCaptor<InfoUserOauth> inserted = ArgumentCaptor.forClass(InfoUserOauth.class);
|
|
|
+ verify(infoUserOauthMapper).insert(inserted.capture());
|
|
|
+ assertNull(inserted.getValue().getProviderDisplayName());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void getuserinfoReturnsSessionChannelAndThirdPartyName() {
|
|
|
+ LoginUserDto session = new LoginUserDto();
|
|
|
+ session.setUserId(72L);
|
|
|
+ session.setUserName("0912345678");
|
|
|
+ session.setProvider("apple");
|
|
|
+ session.setProviderUid("uid-A");
|
|
|
+ String token = JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, session);
|
|
|
+ ReflectionTestUtils.setField(controller, "vipUserService", mock(IVipUserService.class));
|
|
|
+ InfoUser user = activeUser(72L, "0");
|
|
|
+ when(infoUserService.getOne(any(Wrapper.class))).thenReturn(user);
|
|
|
+ when(userWalletService.selectByUserId(72L)).thenReturn(null);
|
|
|
+ InfoUserOauth binding = new InfoUserOauth();
|
|
|
+ binding.setProviderDisplayName("AppleName");
|
|
|
+ when(infoUserOauthMapper.selectOne(any())).thenReturn(binding);
|
|
|
+
|
|
|
+ AjaxResult result = controller.getuserinfo(token);
|
|
|
+
|
|
|
+ InfoUser data = (InfoUser) result.get(AjaxResult.DATA_TAG);
|
|
|
+ assertEquals("apple", data.getOauthProvider());
|
|
|
+ assertEquals("AppleName", data.getProviderDisplayName());
|
|
|
+ // 精确按会话身份(userId + providerUid)反查,不任意取同渠道其它绑定,也不串其它账号
|
|
|
+ ArgumentCaptor<LambdaQueryWrapper<InfoUserOauth>> query =
|
|
|
+ ArgumentCaptor.forClass(LambdaQueryWrapper.class);
|
|
|
+ verify(infoUserOauthMapper).selectOne(query.capture());
|
|
|
+ query.getValue().getSqlSegment();
|
|
|
+ Collection<Object> params = query.getValue().getParamNameValuePairs().values();
|
|
|
+ assertTrue(params.contains(72L));
|
|
|
+ assertTrue(params.contains("uid-A"));
|
|
|
+ assertTrue(params.contains("apple"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void getuserinfoLeavesChannelEmptyForPhoneSession() {
|
|
|
+ LoginUserDto session = new LoginUserDto();
|
|
|
+ session.setUserId(72L);
|
|
|
+ session.setUserName("0912345678");
|
|
|
+ String token = JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, session);
|
|
|
+ ReflectionTestUtils.setField(controller, "vipUserService", mock(IVipUserService.class));
|
|
|
+ InfoUser user = activeUser(72L, "0");
|
|
|
+ when(infoUserService.getOne(any(Wrapper.class))).thenReturn(user);
|
|
|
+ when(userWalletService.selectByUserId(72L)).thenReturn(null);
|
|
|
+
|
|
|
+ AjaxResult result = controller.getuserinfo(token);
|
|
|
+
|
|
|
+ InfoUser data = (InfoUser) result.get(AjaxResult.DATA_TAG);
|
|
|
+ assertNull(data.getOauthProvider());
|
|
|
+ assertNull(data.getProviderDisplayName());
|
|
|
+ verify(infoUserOauthMapper, never()).selectOne(any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void getuserinfoWithoutSessionIdentityDoesNotGuessBinding() {
|
|
|
+ LoginUserDto session = new LoginUserDto();
|
|
|
+ session.setUserId(72L);
|
|
|
+ session.setUserName("0912345678");
|
|
|
+ session.setProvider("apple");
|
|
|
+ String token = JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, session);
|
|
|
+ ReflectionTestUtils.setField(controller, "vipUserService", mock(IVipUserService.class));
|
|
|
+ InfoUser user = activeUser(72L, "0");
|
|
|
+ when(infoUserService.getOne(any(Wrapper.class))).thenReturn(user);
|
|
|
+ when(userWalletService.selectByUserId(72L)).thenReturn(null);
|
|
|
+
|
|
|
+ AjaxResult result = controller.getuserinfo(token);
|
|
|
+
|
|
|
+ InfoUser data = (InfoUser) result.get(AjaxResult.DATA_TAG);
|
|
|
+ // 旧会话无 providerUid:只展示渠道,不按 user_id/provider 任意挑一条绑定
|
|
|
+ assertEquals("apple", data.getOauthProvider());
|
|
|
+ assertNull(data.getProviderDisplayName());
|
|
|
+ verify(infoUserOauthMapper, never()).selectOne(any());
|
|
|
+ }
|
|
|
+
|
|
|
@ParameterizedTest
|
|
|
@CsvSource({"apple", "google"})
|
|
|
void userFirstBindingStillCreatesAUserAndWallet(String provider) {
|