import { Controller, Logger } from '@nestjs/common'; import { MessagePattern, Payload, Ctx, KafkaContext } from '@nestjs/microservices'; import { OutboxRepository } from '../persistence/repositories/outbox.repository'; /** * 事件确认消息结构 */ interface EventAckMessage { /** 原始事件的 aggregateId(如 orderNo) */ eventId: string; /** 原始事件类型 */ eventType: string; /** 消费服务名称 */ consumerService: string; /** 处理结果 */ success: boolean; /** 错误信息(如果失败) */ error?: string; /** 确认时间 */ confirmedAt: string; } /** * 事件确认 Kafka 控制器 * * B方案核心组件:监听消费方的确认事件 * 使用 @MessagePattern 装饰器来处理 Kafka 消息 */ @Controller() export class EventAckController { private readonly logger = new Logger(EventAckController.name); constructor(private readonly outboxRepository: OutboxRepository) {} /** * 处理事件确认消息 * * 消费方(reward-service, referral-service, authorization-service) * 成功处理事件后,会发送确认消息到此 topic */ @MessagePattern('planting.events.ack') async handleEventAck( @Payload() message: EventAckMessage, @Ctx() context: KafkaContext, ): Promise { const partition = context.getPartition(); const offset = context.getMessage().offset; this.logger.debug( `[ACK] Received ack from ${message.consumerService} for event ${message.eventId} ` + `[partition=${partition}, offset=${offset}]`, ); try { if (message.success) { // 标记事件为已确认(使用 eventId + eventType 精确匹配) const confirmed = await this.outboxRepository.markAsConfirmed( message.eventId, message.eventType, ); if (confirmed) { this.logger.log( `[ACK] ✓ Event ${message.eventId} (${message.eventType}) confirmed by ${message.consumerService}`, ); } else { this.logger.warn( `[ACK] Event ${message.eventId} (${message.eventType}) not found or already confirmed`, ); } } else { // 消费方处理失败 this.logger.warn( `[ACK] ✗ Event ${message.eventId} failed in ${message.consumerService}: ${message.error}`, ); // 不改变状态,等待超时重发 } } catch (error) { this.logger.error( `[ACK] Error processing ack for event ${message.eventId}:`, error, ); } } }