This commit is contained in:
2026-08-07 20:07:30 +08:00
parent c698290106
commit c4b0972dc9
31 changed files with 1089 additions and 204 deletions
@@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
/** Firstleg internal contract for provisioning one customer dimension rule. */
/** Firstleg internal contract for atomically provisioning four default box-size dimension records. */
@FeignClient(name = "firstleg-service", contextId = "customerDimensionRuleClient",
url = "${baiyue.clients.firstleg.url:}",
path = "/internal/firstleg/v1/customer-dimension-configs")
@@ -25,5 +25,6 @@ public interface CustomerDimensionRuleClient {
@RequestParam String initializationKey);
record InitializationRequest(long tenantId, long operatorId, String initializationKey) { }
/** configId is the first record ID retained only for contract compatibility. */
record InitializationResponse(Long configId, boolean createdByThisInitialization) { }
}
@@ -12,7 +12,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/** Provisions the customer dimension rule and compensates it when local customer creation rolls back. */
/** Provisions four default box-size dimensions and compensates them when customer creation rolls back. */
@Service
public class CustomerDimensionRuleInitializationService {
private static final Logger log = LoggerFactory.getLogger(CustomerDimensionRuleInitializationService.class);
@@ -40,13 +40,13 @@ public class CustomerDimensionRuleInitializationService {
return result;
}
lastFailure = unavailable(response);
log.warn("客户尺寸规则初始化失败,tenantId={}, attempt={}, code={}, requestId={}, traceId={}",
log.warn("客户4条默认箱规尺寸初始化失败,tenantId={}, attempt={}, code={}, requestId={}, traceId={}",
tenantId, attempt, response == null ? null : response.code(),
response == null ? null : response.requestId(),
response == null ? null : response.traceId());
} catch (RuntimeException failure) {
lastFailure = failure;
log.warn("客户尺寸规则初始化调用异常,tenantId={}, attempt={}", tenantId, attempt, failure);
log.warn("客户4条默认箱规尺寸初始化调用异常,tenantId={}, attempt={}", tenantId, attempt, failure);
}
}
compensateAfterUncertainInitialization(tenantId, initializationKey);
@@ -67,7 +67,7 @@ public class CustomerDimensionRuleInitializationService {
try {
compensateRequired(tenantId, result.initializationKey());
} catch (RuntimeException compensationFailure) {
log.error("客户创建事务回滚后尺寸规则补偿失败,tenantId={}, initializationKey={}",
log.error("客户创建事务回滚后4条默认箱规尺寸补偿失败,tenantId={}, initializationKey={}",
tenantId, result.initializationKey(), compensationFailure);
}
}
@@ -78,7 +78,7 @@ public class CustomerDimensionRuleInitializationService {
try {
compensateRequired(tenantId, initializationKey);
} catch (RuntimeException compensationFailure) {
log.error("客户尺寸规则初始化结果不确定且补偿失败,tenantId={}, initializationKey={}",
log.error("客户4条默认箱规尺寸初始化结果不确定且补偿失败,tenantId={}, initializationKey={}",
tenantId, initializationKey, compensationFailure);
}
}
@@ -95,20 +95,20 @@ public class CustomerDimensionRuleInitializationService {
}
}
throw new BizException(ResultCode.SERVICE_UNAVAILABLE,
"客户尺寸规则补偿失败,请根据客户ID修复数据一致性: " + tenantId);
"客户4条默认箱规尺寸补偿失败,请根据客户ID修复数据一致性: " + tenantId);
}
private static BizException unavailable(ApiResponse<?> response) {
String detail = response == null || response.message() == null || response.message().isBlank()
? "Firstleg 服务未返回有效响应" : response.message();
return new BizException(ResultCode.SERVICE_UNAVAILABLE, "默认尺寸规则初始化失败: " + detail);
return new BizException(ResultCode.SERVICE_UNAVAILABLE, "4条默认箱规尺寸初始化失败: " + detail);
}
private static BizException unavailable(RuntimeException cause) {
String detail = cause == null || cause.getMessage() == null || cause.getMessage().isBlank()
? "Firstleg 服务暂时不可用" : cause.getMessage();
return new BizException(ResultCode.SERVICE_UNAVAILABLE,
"默认尺寸规则初始化失败,已重试 3 次: " + detail);
"4条默认箱规尺寸初始化失败,已重试 3 次: " + detail);
}
private static String initializationKey(long tenantId) {
@@ -9,7 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class CustomerDimensionRuleClientContractTest {
@Test
void usesTheFirstlegInternalDimensionConfigContract() throws Exception {
void usesOneAtomicCallForTheFourDefaultBoxDimensionRecords() throws Exception {
FeignClient feign = CustomerDimensionRuleClient.class.getAnnotation(FeignClient.class);
PostMapping initialize = CustomerDimensionRuleClient.class
.getMethod("initialize", String.class,
@@ -23,5 +23,8 @@ class CustomerDimensionRuleClientContractTest {
assertThat(feign.path()).isEqualTo("/internal/firstleg/v1/customer-dimension-configs");
assertThat(initialize.value()).containsExactly("/defaults");
assertThat(compensate.value()).containsExactly("/defaults");
assertThat(CustomerDimensionRuleClient.InitializationResponse.class.getRecordComponents())
.extracting(java.lang.reflect.RecordComponent::getName)
.containsExactly("configId", "createdByThisInitialization");
}
}
@@ -42,7 +42,7 @@ class CustomerDimensionRuleInitializationServiceTest {
}
@Test
void retriesWithOneStableKeyAndReturnsTheIdempotentResult() {
void retriesOneAtomicFourBoxDefaultsCallWithAStableKey() {
when(client.initialize(anyString(), any()))
.thenThrow(new RuntimeException("timeout"))
.thenReturn(ApiResponse.failure(ResultCode.SERVICE_UNAVAILABLE,
@@ -66,7 +66,7 @@ class CustomerDimensionRuleInitializationServiceTest {
}
@Test
void reportsServiceUnavailableAfterThreeFailedAttemptsAndTriesCompensation() {
void reportsServiceUnavailableWhenFourBoxDefaultsFailThreeTimesAndTriesCompensation() {
when(client.initialize(anyString(), any())).thenThrow(new RuntimeException("timeout"));
when(client.compensate(anyString(), anyLong(), anyString())).thenReturn(ApiResponse.success(null));
@@ -78,7 +78,7 @@ class CustomerDimensionRuleInitializationServiceTest {
}
@Test
void localRollbackCompensatesOnlyAConfigurationCreatedByThisRegistration() {
void localRollbackCompensatesFourBoxDefaultsCreatedByThisRegistration() {
when(client.initialize(anyString(), any()))
.thenReturn(ApiResponse.success(new CustomerDimensionRuleClient.InitializationResponse(91L, true)));
when(client.compensate(anyString(), anyLong(), anyString())).thenReturn(ApiResponse.success(null));
@@ -95,7 +95,7 @@ class CustomerDimensionRuleInitializationServiceTest {
}
@Test
void localRollbackDoesNotDeleteAConfigurationThatAlreadyExisted() {
void localRollbackKeepsFourBoxDefaultsThatAlreadyExisted() {
when(client.initialize(anyString(), any()))
.thenReturn(ApiResponse.success(new CustomerDimensionRuleClient.InitializationResponse(91L, false)));
TransactionSynchronizationManager.initSynchronization();
@@ -110,7 +110,7 @@ class CustomerDimensionRuleInitializationServiceTest {
}
@Test
void responseTimeoutThenIdempotentSuccessStillCompensatesOnLocalRollback() {
void responseTimeoutThenIdempotentFourBoxSuccessStillCompensatesOnLocalRollback() {
when(client.initialize(anyString(), any()))
.thenThrow(new RuntimeException("response timeout"))
.thenReturn(ApiResponse.success(new CustomerDimensionRuleClient.InitializationResponse(91L, true)));
@@ -115,7 +115,7 @@ class MerchantOnboardingServiceTest {
}
@Test
void clientRegistrationFailsWhenDefaultDimensionRuleCannotBeInitialized() {
void clientRegistrationFailsWhenFourDefaultBoxDimensionsCannotBeInitialized() {
IamRole ownerRole = new IamRole();
ownerRole.setId(4000100L);
ownerRole.setTenantId(0L);
@@ -183,7 +183,7 @@ class TenantServiceTest {
}
@Test
void platformRegistrationFailsWhenDefaultDimensionRuleCannotBeInitialized() {
void platformRegistrationFailsWhenFourDefaultBoxDimensionsCannotBeInitialized() {
doThrow(new BizException(ResultCode.SERVICE_UNAVAILABLE, "初始化失败"))
.when(dimensionRuleInitialization).initialize(any(Long.class), any(Long.class));