134 lines
5.0 KiB
TypeScript
134 lines
5.0 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 { ContributionAccountRepository } from '../../infrastructure/persistence/repositories/contribution-account.repository';
|
||
import { ContributionAccountAggregate } from '../../domain/aggregates/contribution-account.aggregate';
|
||
import { UnitOfWork } from '../../infrastructure/persistence/unit-of-work/unit-of-work';
|
||
|
||
/**
|
||
* 用户 CDC 事件处理器
|
||
* 处理从身份服务同步过来的用户数据
|
||
*/
|
||
@Injectable()
|
||
export class UserSyncedHandler {
|
||
private readonly logger = new Logger(UserSyncedHandler.name);
|
||
|
||
constructor(
|
||
private readonly syncedDataRepository: SyncedDataRepository,
|
||
private readonly contributionAccountRepository: ContributionAccountRepository,
|
||
private readonly unitOfWork: UnitOfWork,
|
||
) {}
|
||
|
||
async handle(event: CDCEvent): Promise<void> {
|
||
const { op, before, after } = event.payload;
|
||
|
||
this.logger.log(`[CDC] User event received: op=${op}, seq=${event.sequenceNum}`);
|
||
this.logger.debug(`[CDC] User event payload: ${JSON.stringify(after || before)}`);
|
||
|
||
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(`[CDC] Unknown CDC operation: ${op}`);
|
||
}
|
||
} catch (error) {
|
||
this.logger.error(`[CDC] Failed to handle user CDC event, op=${op}, seq=${event.sequenceNum}`, error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
private async handleCreate(data: any, sequenceNum: bigint): Promise<void> {
|
||
if (!data) {
|
||
this.logger.warn(`[CDC] User create: empty data received`);
|
||
return;
|
||
}
|
||
|
||
// 兼容不同的字段命名(CDC 使用 snake_case)
|
||
const userId = data.user_id ?? data.id;
|
||
const accountSequence = data.account_sequence ?? data.accountSequence;
|
||
const phone = data.phone_number ?? data.phone ?? null;
|
||
const status = data.status ?? 'ACTIVE';
|
||
|
||
this.logger.log(`[CDC] User create: userId=${userId}, accountSequence=${accountSequence}, phone=${phone}, status=${status}`);
|
||
|
||
if (!userId || !accountSequence) {
|
||
this.logger.warn(`[CDC] Invalid user data: missing user_id or account_sequence`, { data });
|
||
return;
|
||
}
|
||
|
||
await this.unitOfWork.executeInTransaction(async () => {
|
||
// 保存同步的用户数据
|
||
this.logger.log(`[CDC] Upserting synced user: ${accountSequence}`);
|
||
await this.syncedDataRepository.upsertSyncedUser({
|
||
originalUserId: BigInt(userId),
|
||
accountSequence,
|
||
phone,
|
||
status,
|
||
sourceSequenceNum: sequenceNum,
|
||
});
|
||
|
||
// 为用户创建算力账户(如果不存在)
|
||
const existingAccount = await this.contributionAccountRepository.findByAccountSequence(accountSequence);
|
||
|
||
if (!existingAccount) {
|
||
const newAccount = ContributionAccountAggregate.create(accountSequence);
|
||
await this.contributionAccountRepository.save(newAccount);
|
||
this.logger.log(`[CDC] Created contribution account for user: ${accountSequence}`);
|
||
} else {
|
||
this.logger.debug(`[CDC] Contribution account already exists for user: ${accountSequence}`);
|
||
}
|
||
});
|
||
|
||
this.logger.log(`[CDC] User synced successfully: ${accountSequence}`);
|
||
}
|
||
|
||
private async handleUpdate(data: any, sequenceNum: bigint): Promise<void> {
|
||
if (!data) {
|
||
this.logger.warn(`[CDC] User update: empty data received`);
|
||
return;
|
||
}
|
||
|
||
// 兼容不同的字段命名(CDC 使用 snake_case)
|
||
const userId = data.user_id ?? data.id;
|
||
const accountSequence = data.account_sequence ?? data.accountSequence;
|
||
const phone = data.phone_number ?? data.phone ?? null;
|
||
const status = data.status ?? 'ACTIVE';
|
||
|
||
this.logger.log(`[CDC] User update: userId=${userId}, accountSequence=${accountSequence}, status=${status}`);
|
||
|
||
if (!userId || !accountSequence) {
|
||
this.logger.warn(`[CDC] Invalid user update data: missing user_id or account_sequence`, { data });
|
||
return;
|
||
}
|
||
|
||
await this.syncedDataRepository.upsertSyncedUser({
|
||
originalUserId: BigInt(userId),
|
||
accountSequence,
|
||
phone,
|
||
status,
|
||
sourceSequenceNum: sequenceNum,
|
||
});
|
||
|
||
this.logger.log(`[CDC] User updated successfully: ${accountSequence}`);
|
||
}
|
||
|
||
private async handleDelete(data: any): Promise<void> {
|
||
if (!data) {
|
||
this.logger.warn(`[CDC] User delete: empty data received`);
|
||
return;
|
||
}
|
||
const accountSequence = data.account_sequence ?? data.accountSequence;
|
||
// 用户删除一般不处理,保留历史数据
|
||
this.logger.log(`[CDC] User delete event received: ${accountSequence} (not processed, keeping history)`);
|
||
}
|
||
}
|