This commit is contained in:
2026-09-29 17:55:54 +08:00
parent 41d8387cd5
commit 54ad26c607
111 changed files with 1837 additions and 962 deletions
@@ -0,0 +1,31 @@
package com.baiyue.common.event;
import java.util.Objects;
/** A consumer returns this only after its durable processing transaction commits. */
public record DeliveryAck(String eventId, String payloadHash, Outcome outcome) {
public enum Outcome { APPLIED, DUPLICATE, STALE }
public DeliveryAck {
if (!EventIdentifiers.isSupported(eventId)) throw new IllegalArgumentException("Invalid ACK eventId");
if (payloadHash == null || !payloadHash.matches("sha256:[0-9a-fA-F]{64}")) {
throw new IllegalArgumentException("Invalid ACK payloadHash");
}
Objects.requireNonNull(outcome, "ACK outcome is required");
}
public static DeliveryAck of(EventEnvelope event, Outcome outcome) {
return new DeliveryAck(event.eventId(), event.payloadHash(), outcome);
}
/** HTTP and ApiResponse status must also be checked by the transport. */
public void requireMatches(EventEnvelope sent) {
if (!eventId.equals(sent.eventId()) || !payloadHash.equalsIgnoreCase(sent.payloadHash())) {
throw new IllegalArgumentException("Consumer ACK does not match the sent event identity and payload");
}
EventDeliveryRoute route = EventDeliveryRoute.forEventType(sent.eventType());
if (outcome == Outcome.STALE && !route.acceptsStale()) {
throw new IllegalArgumentException("STALE is not an allowed result for " + sent.eventType());
}
}
}
@@ -0,0 +1,23 @@
package com.baiyue.common.event;
/** Explicit business-event destinations. There is no regional or default destination. */
public record EventDeliveryRoute(String serviceName, String path, boolean acceptsStale) {
public static EventDeliveryRoute forEventType(String eventType) {
if (eventType == null) throw new IllegalArgumentException("Event type is required");
return switch (eventType) {
case EventTypes.FIRST_LEG_LOADING_PLAN_COMPLETED, EventTypes.WALLET_COMMAND_COMPLETED ->
new EventDeliveryRoute("lastleg-service", "/internal/lastleg/events", false);
case EventTypes.FIRST_LEG_CONTAINER_SHIPPING_NODE_RECORDED ->
new EventDeliveryRoute("lastleg-service", "/internal/lastleg/events", true);
case EventTypes.OVERSEAS_CONTAINER_UNLOADED, EventTypes.OVERSEAS_DELIVERY_COMPLETED ->
new EventDeliveryRoute("firstleg-service", "/internal/firstleg/v1/container-events", false);
case EventTypes.OVERSEAS_INBOUND_ORDER_DETAINED_CHANGED ->
new EventDeliveryRoute("firstleg-service", "/internal/firstleg/v1/container-events", true);
case EventTypes.WALLET_COMMAND_REQUESTED ->
new EventDeliveryRoute("finance-service", "/internal/finance/v1/wallet-command-events", false);
case EventTypes.BUSINESS_FEE_SOURCE_CHANGED ->
new EventDeliveryRoute("finance-service", "/internal/finance/v1/business-fee-source-events", false);
default -> throw new IllegalArgumentException("Unsupported direct-delivery event type: " + eventType);
};
}
}
@@ -0,0 +1,50 @@
package com.baiyue.common.event;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.UUID;
import static org.assertj.core.api.Assertions.*;
class DeliveryAckTest {
private EventEnvelope event(String type) {
return new EventEnvelope(UUID.randomUUID().toString(), type, 1, Instant.now(),
RegionCode.CN, RegionCode.OVERSEAS, "0", "TEST", "1", 1, null,
null, null, null, new ObjectMapper().createObjectNode(), "sha256:" + "a".repeat(64));
}
@Test
void roundTripsAndAcceptsOnlyTheSentIdentityAndHash() throws Exception {
var event = event(EventTypes.WALLET_COMMAND_REQUESTED);
var ack = DeliveryAck.of(event, DeliveryAck.Outcome.APPLIED);
var mapper = new ObjectMapper();
assertThat(mapper.readValue(mapper.writeValueAsString(ack), DeliveryAck.class)).isEqualTo(ack);
assertThatCode(() -> ack.requireMatches(event)).doesNotThrowAnyException();
assertThatThrownBy(() -> ack.requireMatches(event(EventTypes.WALLET_COMMAND_REQUESTED))).isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> new DeliveryAck(event.eventId(), "sha256:" + "b".repeat(64), DeliveryAck.Outcome.APPLIED)
.requireMatches(event)).isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> mapper.readValue("{\"eventId\":\"" + event.eventId() + "\",\"payloadHash\":\"" + event.payloadHash() + "\",\"outcome\":\"RECEIVED\"}", DeliveryAck.class))
.isInstanceOf(Exception.class);
}
@Test
void staleCannotAcknowledgeWalletCommandsResultsOrIndependentFees() {
for (String type : new String[]{EventTypes.WALLET_COMMAND_REQUESTED, EventTypes.WALLET_COMMAND_COMPLETED,
EventTypes.BUSINESS_FEE_SOURCE_CHANGED, EventTypes.OVERSEAS_DELIVERY_COMPLETED}) {
var event = event(type);
assertThatThrownBy(() -> DeliveryAck.of(event, DeliveryAck.Outcome.STALE).requireMatches(event))
.isInstanceOf(IllegalArgumentException.class);
assertThatCode(() -> DeliveryAck.of(event, DeliveryAck.Outcome.DUPLICATE).requireMatches(event)).doesNotThrowAnyException();
}
}
@Test
void mixedLastlegOutboxRoutesWalletToFinanceAndUnloadingToFirstleg() {
assertThat(EventDeliveryRoute.forEventType(EventTypes.WALLET_COMMAND_REQUESTED))
.isEqualTo(new EventDeliveryRoute("finance-service", "/internal/finance/v1/wallet-command-events", false));
assertThat(EventDeliveryRoute.forEventType(EventTypes.OVERSEAS_CONTAINER_UNLOADED).serviceName()).isEqualTo("firstleg-service");
assertThat(EventDeliveryRoute.forEventType(EventTypes.WALLET_COMMAND_COMPLETED).serviceName()).isEqualTo("lastleg-service");
assertThatThrownBy(() -> EventDeliveryRoute.forEventType("UnknownEvent")).isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> EventDeliveryRoute.forEventType(EventTypes.IAM_USER_STATUS_CHANGED)).isInstanceOf(IllegalArgumentException.class);
}
}