package com.baiyue.eventbridge.service; import com.baiyue.eventbridge.config.EventDispatcherProperties; import com.baiyue.eventbridge.domain.BridgeDeliveryAttempt; import com.baiyue.eventbridge.domain.BridgeInbox; import com.baiyue.eventbridge.mapper.BridgeDeliveryAttemptMapper; import com.baiyue.eventbridge.mapper.BridgeInboxMapper; 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 OverseasEventDispatcher { @Resource private BridgeInboxMapper inboxMapper; @Resource private BridgeDeliveryAttemptMapper attemptMapper; @Resource private LastlegEventClient eventClient; @Resource private FirstlegEventClient firstlegEventClient; @Resource private EventDispatcherProperties properties; private final String leaseOwner = ManagementFactory.getRuntimeMXBean().getName() + ":" + UUID.randomUUID(); @Scheduled(fixedDelayString = "${baiyue.event-dispatcher.fixed-delay:3000}") public void dispatch() { for (String eventId : inboxMapper.dispatchCandidates(properties.batchSize())) { dispatchOne(eventId); } } void dispatchOne(String eventId) { if (inboxMapper.claim(eventId, leaseOwner, properties.leaseDuration().toSeconds()) != 1) { return; } BridgeInbox event = inboxMapper.selectById(eventId); int retryAttempt = event.getRetryCount() == null ? 1 : event.getRetryCount() + 1; int deliveryAttemptNo = inboxMapper.nextAttemptNo(eventId); BridgeDeliveryAttempt attempt = startAttempt(event, deliveryAttemptNo); try { String responseCode = deliver(event); if (inboxMapper.markProcessed(eventId, leaseOwner) != 1) { throw new IllegalStateException("事件租约已失效,无法确认投递结果"); } finishAttempt(attempt, "DELIVERED", responseCode, null); } catch (Exception exception) { String error = abbreviate(exception.getMessage(), 1000); inboxMapper.markFailed(eventId, leaseOwner, properties.maxAttempts(), backoffSeconds(retryAttempt), error); finishAttempt(attempt, retryAttempt >= properties.maxAttempts() ? "DEAD" : "RETRY", null, error); log.warn("跨区事件投递失败 eventId={}, retryAttempt={}, deliveryAttempt={}, reason={}", eventId, retryAttempt, deliveryAttemptNo, error); } } private String deliver(BridgeInbox event) throws Exception { return "CN".equals(event.getTargetRegion()) ? firstlegEventClient.deliver(event) : eventClient.deliver(event); } private BridgeDeliveryAttempt startAttempt(BridgeInbox event, int attemptNo) { BridgeDeliveryAttempt attempt = new BridgeDeliveryAttempt(); attempt.setEventId(event.getEventId()); attempt.setSourceRegion(event.getSourceRegion()); attempt.setTargetRegion(event.getTargetRegion()); attempt.setAttemptNo(attemptNo); attempt.setStatus("PROCESSING"); attempt.setStartedAt(LocalDateTime.now()); attemptMapper.insert(attempt); return attempt; } private void finishAttempt(BridgeDeliveryAttempt attempt, String status, String responseCode, String error) { attempt.setStatus(status); attempt.setResponseCode(responseCode); attempt.setErrorMessage(error); attempt.setFinishedAt(LocalDateTime.now()); attemptMapper.updateById(attempt); } private long backoffSeconds(int attemptNo) { long initial = properties.initialBackoff().toSeconds(); int shift = Math.min(Math.max(attemptNo - 1, 0), 20); long delay = initial > Long.MAX_VALUE >> shift ? Long.MAX_VALUE : initial << shift; return Math.min(delay, properties.maxBackoff().toSeconds()); } private static String abbreviate(String value, int maxLength) { String safe = value == null || value.isBlank() ? "未知投递错误" : value; return safe.length() <= maxLength ? safe : safe.substring(0, maxLength); } }