From 03d04431f2aec8e32b5227b83bb3923c5552c549 Mon Sep 17 00:00:00 2001
From: ouyangxq <775153912@qq.com>
Date: Wed, 2 Sep 2026 16:17:41 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../event/BusinessFeeSourceChangedV1.java | 52 +++++
.../com/baiyue/common/event/EventTypes.java | 1 +
config/nacos/cn/event-bridge-service.yml | 1 +
.../service/FinanceEventClient.java | 7 +
.../service/HttpFinanceEventClient.java | 56 +++++
.../service/OverseasEventDispatcher.java | 4 +-
.../src/main/resources/application.yml | 1 +
finance/pom.xml | 1 +
.../InternalBusinessFeeSourceController.java | 38 ++++
.../BusinessFeeSourceEventService.java | 199 ++++++++++++++++++
.../service/FinanceFeeSourceEventService.java | 24 +--
finance/src/main/resources/db/finance.sql | 15 +-
.../PlatformContainerFeeDetailController.java | 44 ++++
.../firstleg/dto/ContainerFeeDetailDtos.java | 25 +++
.../entity/ContainerFeeDetailEntity.java | 4 +
.../ContainerFeeSourceSnapshotEntity.java | 4 +
.../ContainerFeeDetailRepository.java | 17 ++
.../ContainerFeeSourceSnapshotRepository.java | 4 +
.../service/ContainerFeeDetailService.java | 151 ++++++++++++-
.../FinanceFeeSourceSnapshotService.java | 4 +-
.../db/finance-fee-source-migration.sql | 24 +++
.../PlatformOverseasFeeSourceController.java | 87 ++++++++
.../financefee/dto/OverseasFeeSourceDtos.java | 58 +++++
.../financefee/entity/OverseasFeeCommand.java | 21 ++
.../financefee/entity/OverseasFeeSource.java | 59 ++++++
.../mapper/OverseasFeeCommandMapper.java | 14 ++
.../mapper/OverseasFeeSourceMapper.java | 13 ++
.../OverseasFeeOutboxRepository.java | 56 +++++
.../service/OverseasFeeBusinessGuard.java | 46 ++++
.../service/OverseasFeeOutboxRelay.java | 46 ++++
.../service/OverseasFeeOutboxService.java | 79 +++++++
.../service/OverseasFeeSourceService.java | 195 +++++++++++++++++
.../src/main/resources/db/lastleg.sql | 39 ++++
33 files changed, 1367 insertions(+), 22 deletions(-)
create mode 100644 common-event-contract/src/main/java/com/baiyue/common/event/BusinessFeeSourceChangedV1.java
create mode 100644 event-bridge-service/src/main/java/com/baiyue/eventbridge/service/FinanceEventClient.java
create mode 100644 event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpFinanceEventClient.java
create mode 100644 finance/src/main/java/com/baiyue/finance/controller/InternalBusinessFeeSourceController.java
create mode 100644 finance/src/main/java/com/baiyue/finance/service/BusinessFeeSourceEventService.java
create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/financefee/controller/PlatformOverseasFeeSourceController.java
create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/financefee/dto/OverseasFeeSourceDtos.java
create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/financefee/entity/OverseasFeeCommand.java
create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/financefee/entity/OverseasFeeSource.java
create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/financefee/mapper/OverseasFeeCommandMapper.java
create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/financefee/mapper/OverseasFeeSourceMapper.java
create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/financefee/repository/OverseasFeeOutboxRepository.java
create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/financefee/service/OverseasFeeBusinessGuard.java
create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/financefee/service/OverseasFeeOutboxRelay.java
create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/financefee/service/OverseasFeeOutboxService.java
create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/financefee/service/OverseasFeeSourceService.java
diff --git a/common-event-contract/src/main/java/com/baiyue/common/event/BusinessFeeSourceChangedV1.java b/common-event-contract/src/main/java/com/baiyue/common/event/BusinessFeeSourceChangedV1.java
new file mode 100644
index 000000000..8ae7a67e5
--- /dev/null
+++ b/common-event-contract/src/main/java/com/baiyue/common/event/BusinessFeeSourceChangedV1.java
@@ -0,0 +1,52 @@
+package com.baiyue.common.event;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 跨区域业务费用来源的完整不可变快照。
+ *
+ *
海外业务与国内 Finance 不做同步回查,因此事件必须携带可独立校验、入池的完整快照。
+ * 标识统一使用字符串,避免跨语言大整数精度损失。
+ */
+public record BusinessFeeSourceChangedV1(
+ String action,
+ String sourceSystem,
+ String sourceType,
+ String sourceFeeId,
+ int sourceVersion,
+ String customerId,
+ String customerName,
+ String sourceNo,
+ String businessType,
+ String businessId,
+ String businessNo,
+ String orderId,
+ String inboundId,
+ String inboundCode,
+ String containerId,
+ String containerBookingId,
+ String containerNo,
+ String warehouseId,
+ String feeItemCode,
+ String feeItemName,
+ String direction,
+ String supplierId,
+ String supplierName,
+ String supplierCategory,
+ String settlementMode,
+ Instant paidAt,
+ String paymentMethod,
+ String paymentReferenceNo,
+ List paymentVoucherFileIds,
+ LocalDate businessDate,
+ String currency,
+ BigDecimal totalAmount,
+ String reviewStatus,
+ String publicationStatus,
+ String sourceStatus,
+ String sourceHash
+) {
+}
diff --git a/common-event-contract/src/main/java/com/baiyue/common/event/EventTypes.java b/common-event-contract/src/main/java/com/baiyue/common/event/EventTypes.java
index 029056ada..0357657b8 100644
--- a/common-event-contract/src/main/java/com/baiyue/common/event/EventTypes.java
+++ b/common-event-contract/src/main/java/com/baiyue/common/event/EventTypes.java
@@ -13,6 +13,7 @@ public final class EventTypes {
public static final String FIRST_LEG_CONTAINER_SHIPPING_NODE_RECORDED =
"FirstLegContainerShippingNodeRecorded";
public static final String OVERSEAS_CONTAINER_UNLOADED = "OverseasContainerUnloaded";
+ public static final String BUSINESS_FEE_SOURCE_CHANGED = "BusinessFeeSourceChanged";
public static final String IAM_USER_STATUS_CHANGED = "IamUserStatusChanged";
public static final String IAM_TENANT_STATUS_CHANGED = "IamTenantStatusChanged";
public static final String IAM_PERMISSION_VERSION_CHANGED = "IamPermissionVersionChanged";
diff --git a/config/nacos/cn/event-bridge-service.yml b/config/nacos/cn/event-bridge-service.yml
index 5b81ac297..d14d59c6d 100644
--- a/config/nacos/cn/event-bridge-service.yml
+++ b/config/nacos/cn/event-bridge-service.yml
@@ -11,3 +11,4 @@ baiyue:
event-dispatcher:
consumer-url: ${EVENT_CONSUMER_URL:http://firstleg:9202/internal/firstleg/v1/container-events}
firstleg-consumer-url: ${FIRSTLEG_CONTAINER_EVENT_CONSUMER_URL:http://firstleg:9202/internal/firstleg/v1/container-events}
+ finance-consumer-url: ${FINANCE_FEE_EVENT_CONSUMER_URL:http://finance:9208/internal/finance/v1/business-fee-source-events}
diff --git a/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/FinanceEventClient.java b/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/FinanceEventClient.java
new file mode 100644
index 000000000..a8d4e7107
--- /dev/null
+++ b/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/FinanceEventClient.java
@@ -0,0 +1,7 @@
+package com.baiyue.eventbridge.service;
+
+import com.baiyue.eventbridge.domain.BridgeInbox;
+
+public interface FinanceEventClient {
+ String deliver(BridgeInbox event) throws Exception;
+}
diff --git a/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpFinanceEventClient.java b/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpFinanceEventClient.java
new file mode 100644
index 000000000..fcdd42872
--- /dev/null
+++ b/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpFinanceEventClient.java
@@ -0,0 +1,56 @@
+package com.baiyue.eventbridge.service;
+
+import com.baiyue.common.event.EventEnvelope;
+import com.baiyue.common.event.RegionCode;
+import com.baiyue.eventbridge.config.EventDispatcherProperties;
+import com.baiyue.eventbridge.domain.BridgeInbox;
+import com.baiyue.eventbridge.trace.EventBridgeHttpTracePropagation;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.Resource;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+
+@Component
+public class HttpFinanceEventClient implements FinanceEventClient {
+ @Resource private ObjectMapper objectMapper;
+ @Resource private EventDispatcherProperties properties;
+ @Resource private EventBridgeHttpTracePropagation tracePropagation;
+ @Value("${baiyue.event-dispatcher.finance-consumer-url:http://127.0.0.1:9208/internal/finance/v1/business-fee-source-events}")
+ private URI consumerUrl;
+ @Value("${baiyue.security.internal-service-token:}") private String serviceToken;
+ private HttpClient httpClient;
+
+ @PostConstruct
+ void initializeHttpClient() {
+ httpClient = HttpClient.newBuilder().connectTimeout(properties.connectTimeout()).build();
+ }
+
+ @Override
+ public String deliver(BridgeInbox row) throws Exception {
+ EventEnvelope envelope = new EventEnvelope(row.getEventId(), row.getEventType(), row.getEventVersion(),
+ row.getOccurredAt(), RegionCode.valueOf(row.getSourceRegion()), RegionCode.valueOf(row.getTargetRegion()),
+ row.getCustomerId(), row.getAggregateType(), row.getAggregateId(), row.getAggregateVersion(),
+ row.getHandoverVersion(), row.getTraceId(), row.getCorrelationId(), row.getCausationId(),
+ objectMapper.readTree(row.getPayloadJson()), row.getPayloadHash());
+ HttpRequest request = tracePropagation.apply(HttpRequest.newBuilder(consumerUrl)
+ .timeout(properties.requestTimeout()).header("Content-Type", "application/json")
+ .header("X-Internal-Service-Token", serviceToken))
+ .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(envelope))).build();
+ HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ JsonNode body;
+ try { body = objectMapper.readTree(response.body()); }
+ catch (Exception ignored) { body = null; }
+ if (response.statusCode() / 100 != 2 || body == null || !"200".equals(body.path("code").asText())) {
+ throw new IllegalStateException("Finance消费失败: HTTP " + response.statusCode()
+ + ", code=" + (body == null ? "missing" : body.path("code").asText("missing")));
+ }
+ return String.valueOf(response.statusCode());
+ }
+}
diff --git a/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/OverseasEventDispatcher.java b/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/OverseasEventDispatcher.java
index b20be547f..e8b23334e 100644
--- a/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/OverseasEventDispatcher.java
+++ b/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/OverseasEventDispatcher.java
@@ -27,6 +27,7 @@ public class OverseasEventDispatcher {
@Resource private BridgeDeliveryAttemptMapper attemptMapper;
@Resource private LastlegEventClient eventClient;
@Resource private FirstlegEventClient firstlegEventClient;
+ @Resource private FinanceEventClient financeEventClient;
@Resource private SecurityProjectionEventClient securityProjectionEventClient;
@Resource private EventDispatcherProperties properties;
@Resource private EventBridgeMetrics metrics;
@@ -89,7 +90,8 @@ public class OverseasEventDispatcher {
throw new IllegalArgumentException("事件类型不能为空,无法选择下游消费者");
}
return switch (event.getTargetRegion()) {
- case "CN" -> firstlegEventClient.deliver(event);
+ case "CN" -> EventTypes.BUSINESS_FEE_SOURCE_CHANGED.equals(eventType)
+ ? financeEventClient.deliver(event) : firstlegEventClient.deliver(event);
case "OVERSEAS" -> SECURITY_EVENT_TYPES.contains(eventType)
? securityProjectionEventClient.deliver(event) : eventClient.deliver(event);
default -> throw new IllegalArgumentException("不支持的事件目标区域: " + event.getTargetRegion());
diff --git a/event-bridge-service/src/main/resources/application.yml b/event-bridge-service/src/main/resources/application.yml
index b20743a41..82980c9c3 100644
--- a/event-bridge-service/src/main/resources/application.yml
+++ b/event-bridge-service/src/main/resources/application.yml
@@ -33,6 +33,7 @@ baiyue:
event-dispatcher:
consumer-url: ${EVENT_CONSUMER_URL:${LASTLEG_CONSUMER_URL:http://127.0.0.1:9303/internal/lastleg/events}}
firstleg-consumer-url: ${FIRSTLEG_CONTAINER_EVENT_CONSUMER_URL:http://127.0.0.1:9202/internal/firstleg/v1/container-events}
+ finance-consumer-url: ${FINANCE_FEE_EVENT_CONSUMER_URL:http://127.0.0.1:9208/internal/finance/v1/business-fee-source-events}
security-consumer-url: ${LASTLEG_SECURITY_EVENT_CONSUMER_URL:http://127.0.0.1:9303/internal/lastleg/security/v1/events}
fixed-delay: ${EVENT_DISPATCH_FIXED_DELAY:3000}
batch-size: ${EVENT_DISPATCH_BATCH_SIZE:50}
diff --git a/finance/pom.xml b/finance/pom.xml
index 08d3df433..8da2c4701 100644
--- a/finance/pom.xml
+++ b/finance/pom.xml
@@ -11,6 +11,7 @@
finance-service
+ com.baiyue.sharedcommon-event-contract${project.version}
com.baiyue.sharedbaiyue-common-web${project.version}
com.baiyue.sharedbaiyue-common-openapi${project.version}
com.baiyue.sharedbaiyue-common-security${project.version}
diff --git a/finance/src/main/java/com/baiyue/finance/controller/InternalBusinessFeeSourceController.java b/finance/src/main/java/com/baiyue/finance/controller/InternalBusinessFeeSourceController.java
new file mode 100644
index 000000000..88f9dc71e
--- /dev/null
+++ b/finance/src/main/java/com/baiyue/finance/controller/InternalBusinessFeeSourceController.java
@@ -0,0 +1,38 @@
+package com.baiyue.finance.controller;
+
+import com.baiyue.common.core.api.ApiResponse;
+import com.baiyue.common.event.EventEnvelope;
+import com.baiyue.finance.service.BusinessFeeSourceEventService;
+import com.baiyue.finance.service.FinanceEventException;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.Map;
+
+/** Receives full immutable fee snapshots from cross-region business producers. */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/internal/finance/v1/business-fee-source-events")
+public class InternalBusinessFeeSourceController {
+ private final BusinessFeeSourceEventService service;
+ @Value("${baiyue.security.internal-service-token}") private String expectedToken;
+
+ @PostMapping
+ public ApiResponse