提交
This commit is contained in:
+56
@@ -0,0 +1,56 @@
|
||||
package com.baiyue.lastleg.service;
|
||||
|
||||
import com.baiyue.lastleg.entity.LastlegEventOutbox;
|
||||
import com.baiyue.lastleg.mapper.LastlegEventOutboxMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ContainerUnloadOutboxRelay {
|
||||
private static final int MAX_ATTEMPTS = 12;
|
||||
@Resource private LastlegEventOutboxMapper outbox;
|
||||
@Resource private EventBridgePublisher publisher;
|
||||
private final String worker = ManagementFactory.getRuntimeMXBean().getName() + ':' + UUID.randomUUID();
|
||||
|
||||
@Scheduled(fixedDelayString = "${baiyue.container-unload-relay.fixed-delay:3000}")
|
||||
public void relay() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime staleBefore = now.minusMinutes(2);
|
||||
for (Long id : outbox.relayCandidates(50, MAX_ATTEMPTS, now, staleBefore)) {
|
||||
relayOne(id, now, staleBefore);
|
||||
}
|
||||
}
|
||||
|
||||
void relayOne(long id, LocalDateTime now, LocalDateTime staleBefore) {
|
||||
if (outbox.claim(id, worker, now, staleBefore) != 1) return;
|
||||
LastlegEventOutbox row = outbox.claimed(id, worker);
|
||||
if (row == null) return;
|
||||
try {
|
||||
publisher.publish(row.getPayloadJson());
|
||||
outbox.markPublished(id, worker, LocalDateTime.now());
|
||||
} catch (Exception exception) {
|
||||
int attempt = row.getRetryCount() == null ? 1 : row.getRetryCount() + 1;
|
||||
boolean exhausted = attempt >= MAX_ATTEMPTS;
|
||||
LocalDateTime retryAt = exhausted ? null : LocalDateTime.now().plusSeconds(backoffSeconds(attempt));
|
||||
outbox.markFailed(id, worker, exhausted ? "FAILED" : "PENDING", retryAt,
|
||||
abbreviate(exception.getMessage()));
|
||||
log.warn("海外卸柜事件投递失败 id={}, attempt={}, reason={}", id, attempt, exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static long backoffSeconds(int attempt) {
|
||||
return Math.min(900L, 5L << Math.min(Math.max(attempt - 1, 0), 8));
|
||||
}
|
||||
|
||||
private static String abbreviate(String value) {
|
||||
String safe = value == null || value.isBlank() ? "未知投递错误" : value;
|
||||
return safe.length() <= 1000 ? safe : safe.substring(0, 1000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.baiyue.lastleg.service;
|
||||
|
||||
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;
|
||||
import java.time.Duration;
|
||||
|
||||
@Component
|
||||
public class EventBridgePublisher {
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@Value("${baiyue.clients.event-bridge.url:http://127.0.0.1:9312}") private String baseUrl;
|
||||
@Value("${baiyue.security.internal-service-token:}") private String serviceToken;
|
||||
private HttpClient httpClient;
|
||||
|
||||
@PostConstruct
|
||||
void initialize() {
|
||||
httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(3)).build();
|
||||
}
|
||||
|
||||
public void publish(String envelopeJson) throws Exception {
|
||||
HttpRequest request = HttpRequest.newBuilder(URI.create(baseUrl + "/internal/event-bridge/events"))
|
||||
.timeout(Duration.ofSeconds(10)).header("Content-Type", "application/json")
|
||||
.header("X-Internal-Service-Token", serviceToken)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(envelopeJson)).build();
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
JsonNode body = objectMapper.readTree(response.body());
|
||||
if (response.statusCode() / 100 != 2 || body == null || !"200".equals(body.path("code").asText())) {
|
||||
throw new IllegalStateException("事件桥接收失败: HTTP " + response.statusCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -120,8 +120,7 @@ public class LoadingCompletedConsumer {
|
||||
order.setVolumeWeightKg(source.items().stream().map(FirstLegLoadingPlanCompletedV1.Item::dimensionalWeightKg)
|
||||
.filter(Objects::nonNull).reduce(BigDecimal.ZERO,BigDecimal::add));
|
||||
order.setCurrentStatus("PENDING_ASSIGNMENT"); order.setSourceEventId(event.eventId());
|
||||
order.setSourceLoadingPlanId(payload.loadingPlanId()); order.setSourceContainerBookingId(payload.containerBookingId());
|
||||
order.setSourceLoadingPlanVersion(event.aggregateVersion());
|
||||
order.setSourceLoadingPlanId(payload.loadingPlanId()); order.setSourceLoadingPlanVersion(event.aggregateVersion());
|
||||
order.setSourceHandoverVersion(event.handoverVersion()); order.setSourcePayloadHash(orderHash);
|
||||
order.setCreatedAt(LocalDateTime.now()); order.setUpdatedAt(LocalDateTime.now());
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user