rwadurian/backend/services/contribution-service/src/application/event-handlers/referral-synced.handler.ts

161 lines
5.6 KiB
TypeScript

import { Injectable, Logger } from '@nestjs/common';
import { CDCEvent } from '../../infrastructure/kafka/cdc-consumer.service';
import { SyncedDataRepository } from '../../infrastructure/persistence/repositories/synced-data.repository';
import { UnitOfWork } from '../../infrastructure/persistence/unit-of-work/unit-of-work';
/**
* 引荐关系 CDC 事件处理器
* 处理从1.0 referral-service同步过来的referral_relationships数据
*
* 1.0 表结构 (referral_relationships):
* - user_id: BigInt (用户ID)
* - account_sequence: String (账户序列号)
* - referrer_id: BigInt (推荐人用户ID, 注意:不是 account_sequence)
* - ancestor_path: BigInt[] (祖先路径数组,存储 user_id)
* - depth: Int (层级深度)
*
* 2.0 存储策略:
* - 保存 original_user_id (1.0 的 user_id)
* - 保存 referrer_user_id (1.0 的 referrer_id)
* - 尝试查找 referrer 的 account_sequence 并保存
* - ancestor_path 转换为逗号分隔的字符串
*/
@Injectable()
export class ReferralSyncedHandler {
private readonly logger = new Logger(ReferralSyncedHandler.name);
constructor(
private readonly syncedDataRepository: SyncedDataRepository,
private readonly unitOfWork: UnitOfWork,
) {}
async handle(event: CDCEvent): Promise<void> {
const { op, before, after } = event.payload;
try {
switch (op) {
case 'c': // create
case 'r': // read (snapshot)
await this.handleCreate(after, event.sequenceNum);
break;
case 'u': // update
await this.handleUpdate(after, event.sequenceNum);
break;
case 'd': // delete
await this.handleDelete(before);
break;
default:
this.logger.warn(`Unknown CDC operation: ${op}`);
}
} catch (error) {
this.logger.error(`Failed to handle referral CDC event`, error);
throw error;
}
}
private async handleCreate(data: any, sequenceNum: bigint): Promise<void> {
if (!data) return;
// 1.0 字段映射
const accountSequence = data.account_sequence || data.accountSequence;
const originalUserId = data.user_id || data.userId;
const referrerUserId = data.referrer_id || data.referrerId;
const ancestorPathArray = data.ancestor_path || data.ancestorPath;
const depth = data.depth || 0;
// 将 BigInt[] 转换为逗号分隔的字符串
const ancestorPath = this.convertAncestorPath(ancestorPathArray);
// 尝试查找推荐人的 account_sequence
let referrerAccountSequence: string | null = null;
if (referrerUserId) {
const referrer = await this.syncedDataRepository.findSyncedReferralByOriginalUserId(BigInt(referrerUserId));
if (referrer) {
referrerAccountSequence = referrer.accountSequence;
} else {
this.logger.debug(
`Referrer user_id ${referrerUserId} not found yet for ${accountSequence}, will resolve later`,
);
}
}
await this.unitOfWork.executeInTransaction(async () => {
await this.syncedDataRepository.upsertSyncedReferral({
accountSequence,
referrerAccountSequence,
referrerUserId: referrerUserId ? BigInt(referrerUserId) : null,
originalUserId: originalUserId ? BigInt(originalUserId) : null,
ancestorPath,
depth,
sourceSequenceNum: sequenceNum,
});
});
this.logger.log(
`Referral synced: ${accountSequence} (user_id: ${originalUserId}) -> referrer_id: ${referrerUserId || 'none'}`,
);
}
private async handleUpdate(data: any, sequenceNum: bigint): Promise<void> {
if (!data) return;
const accountSequence = data.account_sequence || data.accountSequence;
const originalUserId = data.user_id || data.userId;
const referrerUserId = data.referrer_id || data.referrerId;
const ancestorPathArray = data.ancestor_path || data.ancestorPath;
const depth = data.depth || 0;
const ancestorPath = this.convertAncestorPath(ancestorPathArray);
// 尝试查找推荐人的 account_sequence
let referrerAccountSequence: string | null = null;
if (referrerUserId) {
const referrer = await this.syncedDataRepository.findSyncedReferralByOriginalUserId(BigInt(referrerUserId));
if (referrer) {
referrerAccountSequence = referrer.accountSequence;
}
}
await this.syncedDataRepository.upsertSyncedReferral({
accountSequence,
referrerAccountSequence,
referrerUserId: referrerUserId ? BigInt(referrerUserId) : null,
originalUserId: originalUserId ? BigInt(originalUserId) : null,
ancestorPath,
depth,
sourceSequenceNum: sequenceNum,
});
this.logger.debug(`Referral updated: ${accountSequence}`);
}
private async handleDelete(data: any): Promise<void> {
if (!data) return;
// 引荐关系删除需要特殊处理
this.logger.warn(`Referral delete event received: ${data.account_sequence || data.accountSequence}`);
}
/**
* 将 BigInt[] 数组转换为逗号分隔的字符串
* @param ancestorPath BigInt 数组或 null
* @returns 逗号分隔的字符串或 null
*/
private convertAncestorPath(ancestorPath: any): string | null {
if (!ancestorPath) return null;
// 处理可能的数组格式
if (Array.isArray(ancestorPath)) {
return ancestorPath.map((id) => String(id)).join(',');
}
// 如果已经是字符串 (可能是 PostgreSQL 数组的字符串表示)
if (typeof ancestorPath === 'string') {
// PostgreSQL 数组格式: {1,2,3} 或 [1,2,3]
const cleaned = ancestorPath.replace(/[{}\[\]]/g, '');
return cleaned || null;
}
return null;
}
}