This commit is contained in:
2026-08-04 09:05:28 +08:00
parent fdb1e8af1c
commit 3afcff320a
116 changed files with 6749 additions and 749 deletions
@@ -1,6 +1,8 @@
package com.baiyue.auth.client;
import com.baiyue.common.core.api.ApiResponse;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -14,6 +16,11 @@ public interface IamOnboardingClient {
@RequestHeader("X-Internal-Service-Token") String internalToken,
@RequestBody OnboardingRequest request);
@DeleteMapping("/merchant-registrations/{authUserId}/compensation")
ApiResponse<Void> compensate(
@RequestHeader("X-Internal-Service-Token") String internalToken,
@PathVariable("authUserId") Long authUserId);
record OnboardingRequest(Long authUserId, String displayName, String companyName,
String legalPerson, String legalPhone, String contactEmail,
String countryCode, String address) { }
@@ -47,7 +47,7 @@ public class AuthController {
@SecurityRequirements
@PostMapping("/register")
@Operation(summary = "Merchant registration / 客户注册",
description = "Create a login account and an enterprise profile awaiting one business-license upload.")
description = "Create a login account and an enterprise profile awaiting four required qualification files.")
public ApiResponse<MerchantRegisterResponse> register(@Valid @RequestBody MerchantRegisterRequest request) {
return ApiResponse.success(registrationService.register(request));
}
@@ -11,13 +11,21 @@ import com.baiyue.common.audit.BusinessAudit;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import feign.FeignException;
import jakarta.annotation.Resource;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.annotation.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.atomic.AtomicBoolean;
@Service
public class MerchantRegistrationService {
private static final Logger LOGGER = LoggerFactory.getLogger(MerchantRegistrationService.class);
@Resource private AuthAccountRepository accountRepository;
@Resource private PasswordEncoder passwordEncoder;
@Resource private IamOnboardingClient onboardingClient;
@@ -31,6 +39,9 @@ public class MerchantRegistrationService {
throw new BizException(ResultCode.CONFLICT, "Username already exists");
}
long accountId = IdWorker.getId();
String passwordHash = passwordEncoder.encode(request.password());
AtomicBoolean compensationCompleted = new AtomicBoolean(false);
registerRollbackCompensation(accountId, compensationCompleted);
IamOnboardingClient.OnboardingResponse onboarding;
try {
var response = onboardingClient.register(internalToken,
@@ -51,17 +62,26 @@ public class MerchantRegistrationService {
}
onboarding = response.data();
} catch (FeignException exception) {
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "IAM registration service is unavailable");
BizException failure = new BizException(ResultCode.SERVICE_UNAVAILABLE,
"IAM registration service is unavailable");
failure.addSuppressed(exception);
compensateOnboarding(accountId, failure, compensationCompleted);
throw failure;
} catch (BizException exception) {
compensateOnboarding(accountId, exception, compensationCompleted);
throw exception;
} catch (RuntimeException exception) {
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "IAM registration service is unavailable");
BizException failure = new BizException(ResultCode.SERVICE_UNAVAILABLE,
"IAM registration service is unavailable");
failure.addSuppressed(exception);
compensateOnboarding(accountId, failure, compensationCompleted);
throw failure;
}
AuthAccount account = new AuthAccount();
account.setId(accountId);
account.setUsername(request.username());
account.setPasswordHash(passwordEncoder.encode(request.password()));
account.setPasswordHash(passwordHash);
account.setDisplayName(request.displayName());
account.setAccountStatus("ACTIVE");
account.setFailedLoginCount(0);
@@ -71,8 +91,61 @@ public class MerchantRegistrationService {
account.setDefaultMembershipId(onboarding.membershipId());
account.setDefaultAppCode("MERCHANT");
account.setDeleted(0);
accountRepository.insert(account);
try {
accountRepository.insert(account);
} catch (DuplicateKeyException exception) {
BizException failure = new BizException(ResultCode.CONFLICT, "Username already exists");
failure.addSuppressed(exception);
compensateOnboarding(accountId, failure, compensationCompleted);
throw failure;
} catch (RuntimeException exception) {
compensateOnboarding(accountId, exception, compensationCompleted);
throw exception;
}
return new MerchantRegisterResponse(accountId, onboarding.tenantId(), onboarding.membershipId(),
onboarding.tenantStatus(), "LOGIN_AND_UPLOAD_BUSINESS_LICENSE");
onboarding.tenantStatus(), "LOGIN_AND_COMPLETE_QUALIFICATIONS");
}
private void registerRollbackCompensation(long accountId, AtomicBoolean compensationCompleted) {
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
return;
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCompletion(int status) {
if (status == STATUS_COMMITTED || compensationCompleted.get()) {
return;
}
IllegalStateException rollbackFailure = new IllegalStateException(
"Auth transaction rolled back after IAM onboarding");
if (!compensateOnboarding(accountId, rollbackFailure, compensationCompleted)) {
LOGGER.error("IAM registration compensation failed after Auth rollback, authUserId={}",
accountId, rollbackFailure);
}
}
});
}
private boolean compensateOnboarding(long accountId, RuntimeException registrationFailure,
AtomicBoolean compensationCompleted) {
if (compensationCompleted.get()) {
return true;
}
try {
var response = onboardingClient.compensate(internalToken, accountId);
if (response != null && response.successful()) {
compensationCompleted.set(true);
return true;
}
{
String code = response == null ? "empty-response" : response.code();
String message = response == null ? "IAM compensation returned no response" : response.message();
registrationFailure.addSuppressed(new IllegalStateException(
"IAM registration compensation failed: code=" + code + ", message=" + message));
}
} catch (RuntimeException compensationFailure) {
registrationFailure.addSuppressed(compensationFailure);
}
return false;
}
}