import { Injectable, Logger, OnModuleInit, Inject } from '@nestjs/common'; import { ClientKafka } 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; } /** * 事件确认消费者 * * B方案核心组件:监听消费方的确认事件 * 当消费方(reward-service, referral-service, authorization-service) * 成功处理事件后,会发送确认消息到 planting.events.ack topic * * 工作流程: * 1. planting-service 发送事件到 Kafka,标记为 SENT * 2. 消费方处理事件成功后,发送确认到 planting.events.ack * 3. 本消费者收到确认,将事件标记为 CONFIRMED * 4. 超时未收到确认的事件会被重发 */ @Injectable() export class EventAckConsumer implements OnModuleInit { private readonly logger = new Logger(EventAckConsumer.name); constructor( @Inject('KAFKA_SERVICE') private readonly kafkaClient: ClientKafka, private readonly outboxRepository: OutboxRepository, ) {} async onModuleInit() { // 订阅确认 topic this.kafkaClient.subscribeToResponseOf('planting.events.ack'); this.logger.log('[ACK-CONSUMER] Subscribed to planting.events.ack topic'); // 连接后开始消费 try { await this.kafkaClient.connect(); this.startConsuming(); } catch (error) { this.logger.error('[ACK-CONSUMER] Failed to connect to Kafka:', error); } } private startConsuming() { // 使用 ClientKafka 的 emit 来订阅消息 // 注意:NestJS 的 ClientKafka 主要用于生产者,消费者通常使用 @MessagePattern // 这里我们使用轮询方式或者通过 Controller 来处理 this.logger.log('[ACK-CONSUMER] Event acknowledgment consumer started'); } /** * 处理确认消息 * 此方法由 Kafka Controller 调用 */ async handleAckMessage(message: EventAckMessage): Promise { this.logger.debug(`[ACK-CONSUMER] Received ack: ${JSON.stringify(message)}`); try { if (message.success) { // 标记事件为已确认 const confirmed = await this.outboxRepository.markAsConfirmed(message.eventId); if (confirmed) { this.logger.log( `[ACK-CONSUMER] ✓ Event ${message.eventId} confirmed by ${message.consumerService}`, ); } } else { // 消费方处理失败,记录错误但不改变状态 // 超时后会自动重发 this.logger.warn( `[ACK-CONSUMER] ✗ Event ${message.eventId} failed in ${message.consumerService}: ${message.error}`, ); } } catch (error) { this.logger.error( `[ACK-CONSUMER] Error processing ack for event ${message.eventId}:`, error, ); } } }