rwadurian/backend/services/contribution-service/src/application/event-handlers/transfer-ownership.handler.ts

218 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 转让所有权事件处理器(纯新增)
* 消费 planting.ownership.removed + planting.ownership.added 事件
* 调用 TransferAdjustmentService 执行 88% 算力调整
*
* 消费模式kafkajs 原生消费者(与 CDCConsumerService 同架构)
* 幂等性ProcessedEvent 表sourceService + eventId
*
* 回滚方式:删除此文件并从 application.module.ts 中移除引用
*/
import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Kafka, Consumer, EachMessagePayload } from 'kafkajs';
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
import {
TransferAdjustmentService,
OwnershipRemovedEvent,
OwnershipAddedEvent,
} from '../services/transfer-adjustment.service';
@Injectable()
export class TransferOwnershipHandler implements OnModuleInit, OnModuleDestroy {
private readonly logger = new Logger(TransferOwnershipHandler.name);
private kafka: Kafka;
private removedConsumer: Consumer;
private addedConsumer: Consumer;
constructor(
private readonly configService: ConfigService,
private readonly prisma: PrismaService,
private readonly transferAdjustmentService: TransferAdjustmentService,
) {
const brokers = this.configService
.get<string>('KAFKA_BROKERS', 'localhost:9092')
.split(',');
this.kafka = new Kafka({
clientId: 'contribution-service-transfer',
brokers,
});
this.removedConsumer = this.kafka.consumer({
groupId: 'contribution-service-transfer-removed',
});
this.addedConsumer = this.kafka.consumer({
groupId: 'contribution-service-transfer-added',
});
}
async onModuleInit(): Promise<void> {
try {
// 订阅卖方减持事件
await this.removedConsumer.connect();
await this.removedConsumer.subscribe({
topic: 'planting.ownership.removed',
fromBeginning: false,
});
await this.removedConsumer.run({
eachMessage: async (payload) => {
await this.handleOwnershipRemoved(payload);
},
});
// 订阅买方增持事件
await this.addedConsumer.connect();
await this.addedConsumer.subscribe({
topic: 'planting.ownership.added',
fromBeginning: false,
});
await this.addedConsumer.run({
eachMessage: async (payload) => {
await this.handleOwnershipAdded(payload);
},
});
this.logger.log(
'Subscribed to planting.ownership.removed + planting.ownership.added',
);
} catch (error) {
this.logger.error('Failed to initialize transfer ownership consumers', error);
}
}
async onModuleDestroy(): Promise<void> {
try {
await this.removedConsumer.disconnect();
await this.addedConsumer.disconnect();
} catch (error) {
this.logger.error('Failed to disconnect transfer consumers', error);
}
}
/**
* 处理卖方减持事件
*/
private async handleOwnershipRemoved(payload: EachMessagePayload): Promise<void> {
const { message } = payload;
if (!message.value) return;
let event: OwnershipRemovedEvent;
try {
const raw = JSON.parse(message.value.toString());
// 支持两种消息格式:直接 payload 或嵌套在 value 字段中
event = raw.value ? raw.value : raw;
} catch (error) {
this.logger.error('[TRANSFER-REMOVED] Failed to parse message', error);
return;
}
const processedEventId = `removed:${event.transferOrderNo}`;
this.logger.log(
`[TRANSFER-REMOVED] Processing: ${event.transferOrderNo}, ` +
`seller=${event.sellerAccountSequence}, trees=${event.treeCount}`,
);
try {
// 幂等性检查
const existing = await this.prisma.processedEvent.findFirst({
where: {
sourceService: 'planting-service',
eventId: processedEventId,
},
});
if (existing) {
this.logger.log(`[TRANSFER-REMOVED] Already processed: ${processedEventId}`);
return;
}
// 执行卖方算力扣减
await this.transferAdjustmentService.processOwnershipRemoved(event);
// 记录已处理
await this.prisma.processedEvent.create({
data: {
sourceService: 'planting-service',
eventId: processedEventId,
eventType: 'PlantingOwnershipRemoved',
},
});
this.logger.log(
`[TRANSFER-REMOVED] ✓ Processed: ${event.transferOrderNo}, seller=${event.sellerAccountSequence}`,
);
} catch (error) {
this.logger.error(
`[TRANSFER-REMOVED] ✗ Failed for ${event.transferOrderNo}:`,
error,
);
throw error; // 让 kafkajs 重试
}
}
/**
* 处理买方增持事件
*/
private async handleOwnershipAdded(payload: EachMessagePayload): Promise<void> {
const { message } = payload;
if (!message.value) return;
let event: OwnershipAddedEvent;
try {
const raw = JSON.parse(message.value.toString());
event = raw.value ? raw.value : raw;
} catch (error) {
this.logger.error('[TRANSFER-ADDED] Failed to parse message', error);
return;
}
const processedEventId = `added:${event.transferOrderNo}`;
this.logger.log(
`[TRANSFER-ADDED] Processing: ${event.transferOrderNo}, ` +
`buyer=${event.buyerAccountSequence}, trees=${event.treeCount}`,
);
try {
// 幂等性检查
const existing = await this.prisma.processedEvent.findFirst({
where: {
sourceService: 'planting-service',
eventId: processedEventId,
},
});
if (existing) {
this.logger.log(`[TRANSFER-ADDED] Already processed: ${processedEventId}`);
return;
}
// 执行买方算力新增
await this.transferAdjustmentService.processOwnershipAdded(event);
// 记录已处理
await this.prisma.processedEvent.create({
data: {
sourceService: 'planting-service',
eventId: processedEventId,
eventType: 'PlantingOwnershipAdded',
},
});
this.logger.log(
`[TRANSFER-ADDED] ✓ Processed: ${event.transferOrderNo}, buyer=${event.buyerAccountSequence}`,
);
} catch (error) {
this.logger.error(
`[TRANSFER-ADDED] ✗ Failed for ${event.transferOrderNo}:`,
error,
);
throw error; // 让 kafkajs 重试
}
}
}