86 lines
2.5 KiB
TypeScript
86 lines
2.5 KiB
TypeScript
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<void> {
|
||
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,
|
||
);
|
||
}
|
||
}
|
||
}
|