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 { 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 { 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 { 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 { 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; } }