Merge test fixes into dev dropship flow

This commit is contained in:
2026-09-10 18:18:41 +08:00
131 changed files with 3062 additions and 651 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 atomically provisioning four default box-size dimension records. */
/** Firstleg internal contract for atomically provisioning box-size and weight-coefficient defaults. */
@FeignClient(name = "firstleg-service", contextId = "customerDimensionRuleClient",
url = "${baiyue.clients.firstleg.url:}",
path = "/internal/firstleg/v1/customer-dimension-configs")
@@ -24,7 +24,8 @@ public interface CustomerDimensionRuleClient {
@RequestParam long tenantId,
@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) { }
record InitializationRequest(long tenantId, long operatorId, String initializationKey,
String customerName) { }
record InitializationResponse(Long configId, Long chargeRuleId,
boolean createdByThisInitialization) { }
}
@@ -3,10 +3,10 @@ package com.baiyue.iam.model;
public record CustomerDimensionInitializationTask(
long tenantId,
long operatorId,
String customerName,
String initializationKey,
String action,
String status,
int attemptCount,
int taskVersion) {
}
@@ -73,11 +73,14 @@ public class CustomerDimensionInitializationRepository {
+ "WHERE tenant_id=? AND status='PENDING' AND next_retry_at<=?",
workerId, now.plusMinutes(2), now, tenantId, now);
if (claimed != 1) return null;
return jdbc.queryForObject("SELECT tenant_id,operator_id,initialization_key,action,status,"
+ "attempt_count,task_version FROM iam_customer_dimension_initialization_task "
+ "WHERE tenant_id=? AND status='SENDING' AND lease_owner=?",
return jdbc.queryForObject("SELECT task.tenant_id,task.operator_id,tenant.tenant_name,"
+ "task.initialization_key,task.action,task.status,task.attempt_count,task.task_version "
+ "FROM iam_customer_dimension_initialization_task task "
+ "JOIN iam_tenant tenant ON tenant.id=task.tenant_id "
+ "WHERE task.tenant_id=? AND task.status='SENDING' AND task.lease_owner=?",
(rs, row) -> new CustomerDimensionInitializationTask(
rs.getLong("tenant_id"), rs.getLong("operator_id"), rs.getString("initialization_key"),
rs.getLong("tenant_id"), rs.getLong("operator_id"), rs.getString("tenant_name"),
rs.getString("initialization_key"),
rs.getString("action"), rs.getString("status"), rs.getInt("attempt_count"),
rs.getInt("task_version")), tenantId, workerId);
}
@@ -43,10 +43,11 @@ public class CustomerDimensionInitializationRelay {
}
ApiResponse<CustomerDimensionRuleClient.InitializationResponse> response = client.initialize(
internalToken, new CustomerDimensionRuleClient.InitializationRequest(
task.tenantId(), task.operatorId(), task.initializationKey()));
task.tenantId(), task.operatorId(), task.initializationKey(), task.customerName()));
requireSuccess(response, true);
if (response.data() == null || response.data().configId() == null) {
throw new DeliveryFailure("S0503", "Firstleg 未返回默认尺寸配置ID", false);
if (response.data() == null || response.data().configId() == null
|| response.data().chargeRuleId() == null) {
throw new DeliveryFailure("S0503", "Firstleg 未完整返回默认尺寸和权重系数规则ID", false);
}
repository.markReady(task, workerId, response.data().configId());
} catch (Exception failure) {
@@ -0,0 +1,20 @@
-- 订单申报与运营预估权限模块来源。
-- 只操作 IAM 权限资源和角色授权,不创建账号、客户或业务数据。
USE baiyue_iam;
SET NAMES utf8mb4;
INSERT INTO iam_permission
(id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name,
route_path, http_method, sort_no, status, deleted)
VALUES
(5102027, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:declaration:edit',
'修改订单申报与运营预估', NULL, NULL, 130, 'ACTIVE', 0)
ON DUPLICATE KEY UPDATE
permission_name = VALUES(permission_name), parent_id = VALUES(parent_id),
permission_type = VALUES(permission_type), app_code = VALUES(app_code),
sort_no = VALUES(sort_no), status = 'ACTIVE', deleted = 0;
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT role_id, 5102027, 0
FROM (SELECT 4000001 AS role_id UNION ALL SELECT 4000003 AS role_id) roles
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
@@ -130,18 +130,19 @@ VALUES
(5102019, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:create', '平台代客创建订单', NULL, NULL, 90, 'ACTIVE', 0),
(5102024, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:edit', '平台编辑订单草稿', NULL, NULL, 100, 'ACTIVE', 0),
(5102025, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:submit', '平台代客提交订单', NULL, NULL, 110, 'ACTIVE', 0),
(5102026, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:export', '导出订单列表', NULL, NULL, 120, 'ACTIVE', 0)
(5102026, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:export', '导出订单列表', NULL, NULL, 120, 'ACTIVE', 0),
(5102027, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:declaration:edit', '修改订单申报与运营预估', NULL, NULL, 130, 'ACTIVE', 0)
ON DUPLICATE KEY UPDATE
permission_name = VALUES(permission_name), parent_id = VALUES(parent_id), status = 'ACTIVE', deleted = 0;
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, id, 0 FROM iam_permission
WHERE id IN (5102019, 5102024, 5102025, 5102026)
WHERE id IN (5102019, 5102024, 5102025, 5102026, 5102027)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000003, id, 0 FROM iam_permission
WHERE id IN (5102019, 5102024, 5102025, 5102026)
WHERE id IN (5102019, 5102024, 5102025, 5102026, 5102027)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT INTO iam_membership_role (membership_id, role_id, tenant_id) VALUES
@@ -9,7 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class CustomerDimensionRuleClientContractTest {
@Test
void usesOneAtomicCallForTheFourDefaultBoxDimensionRecords() throws Exception {
void usesOneAtomicCallForBoxDimensionsAndWeightCoefficientRule() throws Exception {
FeignClient feign = CustomerDimensionRuleClient.class.getAnnotation(FeignClient.class);
PostMapping initialize = CustomerDimensionRuleClient.class
.getMethod("initialize", String.class,
@@ -25,6 +25,6 @@ class CustomerDimensionRuleClientContractTest {
assertThat(compensate.value()).containsExactly("/defaults");
assertThat(CustomerDimensionRuleClient.InitializationResponse.class.getRecordComponents())
.extracting(java.lang.reflect.RecordComponent::getName)
.containsExactly("configId", "createdByThisInitialization");
.containsExactly("configId", "chargeRuleId", "createdByThisInitialization");
}
}
@@ -0,0 +1,44 @@
package com.baiyue.iam.repository;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class PermissionInitializationSqlContractTest {
@Test
void keepsSharedPermissionEntryFocusedAndAlignedWithFullBaseline() throws Exception {
String module = new ClassPathResource("db/order-declaration-planning-permission.sql")
.getContentAsString(StandardCharsets.UTF_8);
String permissionOnly = Files.readString(resolve("deploy/sql/权限点初始化-MySQL8.sql"),
StandardCharsets.UTF_8);
String unified = Files.readString(resolve("deploy/sql/头程数据库统一增量-MySQL8.sql"),
StandardCharsets.UTF_8);
for (String source : new String[] {module, permissionOnly, unified}) {
assertThat(source)
.contains("5102027")
.contains("platform:order:declaration:edit")
.contains("4000001")
.contains("4000003");
}
assertThat(permissionOnly)
.contains("START TRANSACTION")
.contains("permission_initialization_status")
.contains("SIGNAL SQLSTATE '45000'")
.contains("apply_permission_points")
.doesNotContain("apply_domestic_permission_points", "CREATE TABLE", "ALTER TABLE",
"INSERT INTO iam_tenant", "INSERT INTO iam_user", "INSERT INTO iam_membership",
"INSERT INTO auth_account");
}
private static Path resolve(String relative) {
Path direct = Path.of(relative);
return Files.exists(direct) ? direct : Path.of("..").resolve(relative);
}
}
@@ -43,15 +43,33 @@ class CustomerDimensionInitializationRelayTest {
CustomerDimensionInitializationTask task = task("INITIALIZE", 0);
when(repository.claim(anyLong(), anyString(), any(LocalDateTime.class))).thenReturn(task);
when(client.initialize(anyString(), any())).thenReturn(ApiResponse.success(
new CustomerDimensionRuleClient.InitializationResponse(91L, true)));
new CustomerDimensionRuleClient.InitializationResponse(91L, 191L, true)));
relay.deliver(10001L);
ArgumentCaptor<CustomerDimensionRuleClient.InitializationRequest> request =
ArgumentCaptor.forClass(CustomerDimensionRuleClient.InitializationRequest.class);
verify(client).initialize(anyString(), request.capture());
assertTrue("柏悦物流".equals(request.getValue().customerName()));
verify(repository).markReady(task, workerId(), 91L);
verify(repository, never()).recordFailure(any(), anyString(), anyInt(), any(), anyString(),
anyString(), any(Boolean.class));
}
@Test
void missingWeightCoefficientRuleKeepsInitializationFromBecomingReady() {
CustomerDimensionInitializationTask task = task("INITIALIZE", 0);
when(repository.claim(anyLong(), anyString(), any(LocalDateTime.class))).thenReturn(task);
when(client.initialize(anyString(), any())).thenReturn(ApiResponse.success(
new CustomerDimensionRuleClient.InitializationResponse(91L, null, true)));
relay.deliver(10001L);
verify(repository, never()).markReady(any(), anyString(), any());
verify(repository).recordFailure(any(), anyString(), anyInt(), any(), anyString(),
anyString(), any(Boolean.class));
}
@Test
void transientFailureStaysPendingWithDiagnosticAndBackoff() {
CustomerDimensionInitializationTask task = task("INITIALIZE", 0);
@@ -95,7 +113,7 @@ class CustomerDimensionInitializationRelayTest {
}
private CustomerDimensionInitializationTask task(String action, int attempts) {
return new CustomerDimensionInitializationTask(10001L, 9001L, "iam-customer-create:10001",
return new CustomerDimensionInitializationTask(10001L, 9001L, "柏悦物流", "iam-customer-create:10001",
action, "SENDING", attempts, 3);
}
@@ -38,6 +38,15 @@ SELECT 'platform_admin_not_fully_granted'
WHERE (SELECT COUNT(*) FROM baiyue_iam.iam_role_permission WHERE role_id = 4000001)
<> (SELECT COUNT(*) FROM baiyue_iam.iam_permission WHERE tenant_id = 0 AND app_code = 'PLATFORM' AND permission_type IN ('MENU', 'BUTTON'))
UNION ALL
SELECT 'missing_order_declaration_edit_permission'
WHERE NOT EXISTS (
SELECT 1 FROM baiyue_iam.iam_permission
WHERE id = 5102027 AND tenant_id = 0 AND app_code = 'PLATFORM'
AND permission_code = 'platform:order:declaration:edit' AND status = 'ACTIVE' AND deleted = 0
)
OR (SELECT COUNT(*) FROM baiyue_iam.iam_role_permission
WHERE tenant_id = 0 AND permission_id = 5102027 AND role_id IN (4000001, 4000003)) <> 2
UNION ALL
SELECT 'missing_global_merchant_owner_template'
WHERE NOT EXISTS (
SELECT 1 FROM baiyue_iam.iam_role