feat(backend): 补齐 C 端商业化与账号缺口(金币充值/会员/搜索/语言筛选/Google登录/短信/商店可配置接入)

This commit is contained in:
Zhou Weidong committed 2026-10-20 04:20:42 +08:00
1 parent 212834d59d
commit 63b086998e
25 files changed
+859 -29

No files matched your search

@@ -69,7 +69,8 @@ class AppOrdersVerifyContractTest {
new ProductService(productMapper), storeReceiptMapper, verifier,
mock(WalletService.class), mock(EntitlementService.class));
mockMvc = ControllerContractSupport.mockMvcOf(new AppCommerceController(
mock(WalletService.class), mock(UnlockService.class), orderService));
mock(WalletService.class), mock(UnlockService.class), orderService,
mock(ProductService.class)));
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
new LoginUser(USER, "app-user", "APP_USER", List.of()), null, List.of()));
}
@@ -136,7 +137,8 @@ class AppOrdersVerifyContractTest {
new ProductService(productMapper), storeReceiptMapper, new DisabledStoreVerifier(),
mock(WalletService.class), mock(EntitlementService.class));
MockMvc disabledMvc = ControllerContractSupport.mockMvcOf(new AppCommerceController(
mock(WalletService.class), mock(UnlockService.class), disabled));
mock(WalletService.class), mock(UnlockService.class), disabled,
mock(ProductService.class)));
String body = disabledMvc.perform(post("/app/orders/verify")
.contentType(MediaType.APPLICATION_JSON).content(BODY))
@@ -19,6 +19,8 @@ import com.fruitshorts.user.enums.AppUserStatus;
import com.fruitshorts.user.service.AppAuthService;
import com.fruitshorts.user.service.AppOtpService;
import com.fruitshorts.user.service.CredentialHasher;
import com.fruitshorts.user.service.GoogleIdTokenVerifier;
import com.fruitshorts.user.service.GoogleUser;
import com.fruitshorts.user.service.OtpSender;
import com.fruitshorts.user.vo.LoginReqVO;
import com.fruitshorts.user.vo.RegisterReqVO;
@@ -60,6 +62,7 @@ class AppAuthServiceTest {
private JwtTokenProvider tokenProvider;
private PasswordEncoder passwordEncoder;
private ConfigService configService;
private GoogleIdTokenVerifier googleVerifier;
private AppAuthService authService;
@BeforeEach
@@ -74,10 +77,12 @@ class AppAuthServiceTest {
tokenProvider = mock(JwtTokenProvider.class);
passwordEncoder = mock(PasswordEncoder.class);
configService = mock(ConfigService.class);
googleVerifier = mock(GoogleIdTokenVerifier.class);
given(configService.get(anyString(), anyString()))
.willAnswer(invocation -> invocation.getArgument(1));
authService = new AppAuthService(userMapper, credentialMapper, sessionMapper, consentMapper,
deviceMapper, otpService, hasher, tokenProvider, passwordEncoder, configService);
deviceMapper, otpService, hasher, tokenProvider, passwordEncoder, configService,
googleVerifier);
}
@Test
@@ -282,6 +287,43 @@ class AppAuthServiceTest {
.isEqualTo(ErrorCode.ACCOUNT_DISABLED.getCode()));
}
@Test
void should_auto_register_and_issue_tokens_when_google_login_first_time() {
given(googleVerifier.verify(anyString()))
.willReturn(new GoogleUser("sub-1", "Alice@Example.com", "Alice", "http://pic/a.png"));
given(credentialMapper.selectOne(any())).willReturn(null);
given(hasher.newUid()).willReturn("UAB12CD");
given(hasher.newRefreshToken()).willReturn("refresh-1");
given(hasher.sha256(anyString())).willReturn("hash-1");
given(tokenProvider.createAppToken(any(), anyString(), any())).willReturn("access-1");
given(tokenProvider.accessTtlSeconds()).willReturn(7200L);
given(tokenProvider.appRefreshTtl()).willReturn(Duration.ofDays(30));
AtomicReference<AppUser> inserted = new AtomicReference<>();
given(userMapper.insert(any(AppUser.class))).willAnswer(invocation -> {
AppUser saved = invocation.getArgument(0);
saved.setId(1001L);
inserted.set(saved);
return 1;
});
given(userMapper.selectById(any())).willAnswer(invocation -> inserted.get());
TokenRespVO resp = authService.googleLogin("google-id-token");
assertThat(resp.getAccessToken()).isEqualTo("access-1");
assertThat(resp.isNewUser()).isTrue();
verify(sessionMapper).insert(any(AppUserSession.class));
}
@Test
void should_reject_google_login_when_email_is_missing() {
given(googleVerifier.verify(anyString()))
.willReturn(new GoogleUser("sub-1", "", "Alice", ""));
assertThatThrownBy(() -> authService.googleLogin("google-id-token"))
.isInstanceOf(ServiceException.class)
.hasMessage("Google 账号未返回邮箱,无法登录");
}
private void stubHappyRegister() {
given(userMapper.selectCount(any())).willReturn(0L);
given(credentialMapper.selectOne(any())).willReturn(null);
@@ -328,7 +370,8 @@ class AppAuthServiceTest {
given(otpHasher.hashOtp(any())).willReturn("hash-of-something-else");
AppOtpService realOtpService = new AppOtpService(otpMapper, mock(OtpSender.class), otpHasher);
return new AppAuthService(userMapper, credentialMapper, sessionMapper, consentMapper,
deviceMapper, realOtpService, hasher, tokenProvider, passwordEncoder, configService);
deviceMapper, realOtpService, hasher, tokenProvider, passwordEncoder, configService,
googleVerifier);
}
private void setRegisterLimits(int perIp, int perDevice) {