From ba5b6141a30501788f1cca1576bfc718502787da Mon Sep 17 00:00:00 2001 From: hailin Date: Tue, 9 Dec 2025 22:16:23 -0800 Subject: [PATCH] fix(planting-service): improve outbox event confirmation accuracy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - markAsConfirmed now uses aggregateId + eventType for precise matching - Prevents accidentally confirming multiple events for the same order - EventAckController passes eventType to markAsConfirmed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../kafka/event-ack.controller.ts | 9 +++++--- .../repositories/outbox.repository.ts | 23 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/backend/services/planting-service/src/infrastructure/kafka/event-ack.controller.ts b/backend/services/planting-service/src/infrastructure/kafka/event-ack.controller.ts index 991bba09..d2c12763 100644 --- a/backend/services/planting-service/src/infrastructure/kafka/event-ack.controller.ts +++ b/backend/services/planting-service/src/infrastructure/kafka/event-ack.controller.ts @@ -53,8 +53,11 @@ export class EventAckController { try { if (message.success) { - // 标记事件为已确认 - const confirmed = await this.outboxRepository.markAsConfirmed(message.eventId); + // 标记事件为已确认(使用 eventId + eventType 精确匹配) + const confirmed = await this.outboxRepository.markAsConfirmed( + message.eventId, + message.eventType, + ); if (confirmed) { this.logger.log( @@ -62,7 +65,7 @@ export class EventAckController { ); } else { this.logger.warn( - `[ACK] Event ${message.eventId} not found or already confirmed`, + `[ACK] Event ${message.eventId} (${message.eventType}) not found or already confirmed`, ); } } else { diff --git a/backend/services/planting-service/src/infrastructure/persistence/repositories/outbox.repository.ts b/backend/services/planting-service/src/infrastructure/persistence/repositories/outbox.repository.ts index 5d17dee0..08e015c4 100644 --- a/backend/services/planting-service/src/infrastructure/persistence/repositories/outbox.repository.ts +++ b/backend/services/planting-service/src/infrastructure/persistence/repositories/outbox.repository.ts @@ -145,25 +145,32 @@ export class OutboxRepository { /** * 标记事件为已确认(消费方已成功处理) * B方案:收到消费方确认后调用 + * 使用 aggregateId + eventType 组合精确匹配,避免误确认同一订单的其他事件 */ - async markAsConfirmed(eventId: string): Promise { - // 通过 aggregateId + eventType 查找事件 + async markAsConfirmed(eventId: string, eventType?: string): Promise { + const whereClause: Prisma.OutboxEventWhereInput = { + aggregateId: eventId, + status: OutboxStatus.SENT, + }; + + // 如果提供了 eventType,则精确匹配 + if (eventType) { + whereClause.eventType = eventType; + } + const result = await this.prisma.outboxEvent.updateMany({ - where: { - aggregateId: eventId, - status: OutboxStatus.SENT, - }, + where: whereClause, data: { status: OutboxStatus.CONFIRMED, }, }); if (result.count > 0) { - this.logger.log(`[OUTBOX] ✓ Event ${eventId} confirmed by consumer`); + this.logger.log(`[OUTBOX] ✓ Event ${eventId} (${eventType || 'all types'}) confirmed by consumer`); return true; } - this.logger.warn(`[OUTBOX] Event ${eventId} not found or not in SENT status`); + this.logger.warn(`[OUTBOX] Event ${eventId} (${eventType || 'any'}) not found or not in SENT status`); return false; }