feat(mining-admin): implement transactional idempotent consumer for 100% exactly-once semantics

- Use Prisma $transaction with Serializable isolation level
- Insert idempotency record FIRST, then execute business logic
- Unique constraint violation (P2002) indicates duplicate event
- All operations atomic - either fully commit or fully rollback
- Modified all handlers to accept transaction client parameter
- Removed old non-atomic isEventProcessed/recordProcessedEvent methods

This ensures 100% data consistency for CDC synchronization, which is
critical for financial data where any error is catastrophic.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-12 19:11:30 -08:00
parent 577f626972
commit 70135938c4
3 changed files with 881 additions and 1157 deletions

View File

@ -48,6 +48,8 @@ export interface ServiceEvent {
export type CdcHandler = (event: CdcEvent) => Promise<void>;
export type ServiceEventHandler = (event: ServiceEvent) => Promise<void>;
/** 支持事务的 handler 类型tx 参数为 Prisma 事务客户端 */
export type TransactionalServiceEventHandler = (event: ServiceEvent, tx: any) => Promise<void>;
@Injectable()
export class CdcConsumerService implements OnModuleInit, OnModuleDestroy {

View File

@ -1,22 +1,27 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../persistence/prisma/prisma.service';
import {
CdcConsumerService,
CdcEvent,
ServiceEvent,
ServiceEventHandler,
TransactionalServiceEventHandler,
} from './cdc-consumer.service';
import { WalletSyncHandlers } from './wallet-sync.handlers';
/** Prisma 事务客户端类型 */
type TransactionClient = Prisma.TransactionClient;
/**
* CDC
* 2.0 mining-admin-service
*
* CDC
* 1. 使 (sourceTopic, eventId)
* 2.
* 3.
* 100%
* 1. 使
* 2.
* 3.
*/
@Injectable()
export class CdcSyncService implements OnModuleInit {
@ -35,21 +40,58 @@ export class CdcSyncService implements OnModuleInit {
}
/**
* handler
* CDC exactly-once
* - 100% exactly-once
*
*
* 1. 使 SELECT FOR UPDATE
* 2.
* 3.
*
*
*/
private withIdempotency(handler: ServiceEventHandler): ServiceEventHandler {
private withIdempotency(handler: TransactionalServiceEventHandler): ServiceEventHandler {
return async (event: ServiceEvent) => {
// 1. 检查是否已处理
if (await this.isEventProcessed(event)) {
this.logger.debug(`Skipping duplicate event: ${event.sourceTopic}:${event.id} (${event.eventType})`);
const idempotencyKey = `${event.sourceTopic}:${event.id}`;
try {
await this.prisma.$transaction(async (tx) => {
// 1. 尝试插入幂等记录(使用唯一约束防止重复)
// 如果记录已存在,会抛出唯一约束冲突异常
try {
await tx.processedEvent.create({
data: {
eventId: event.id,
eventType: event.eventType,
sourceService: event.sourceTopic,
},
});
} catch (error: any) {
// 唯一约束冲突 = 事件已处理,直接返回(不执行业务逻辑)
if (error.code === 'P2002') {
this.logger.debug(`Skipping duplicate event: ${idempotencyKey} (${event.eventType})`);
return;
}
throw error;
}
// 2. 执行实际处理逻辑
await handler(event);
// 2. 执行业务逻辑(传入事务客户端)
await handler(event, tx);
// 3. 记录已处理(在 handler 内部完成,这里不再重复)
this.logger.debug(`Processed event in transaction: ${idempotencyKey} (${event.eventType})`);
}, {
// 设置事务隔离级别为 Serializable防止并发问题
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
timeout: 30000, // 30秒超时
});
} catch (error: any) {
// 唯一约束冲突在事务外也可能发生(并发场景)
if (error.code === 'P2002') {
this.logger.debug(`Skipping duplicate event (concurrent): ${idempotencyKey}`);
return;
}
this.logger.error(`Failed to process event: ${idempotencyKey}`, error);
throw error;
}
};
}
@ -318,11 +360,9 @@ export class CdcSyncService implements OnModuleInit {
// 用户事件处理
// ===========================================================================
private async handleUserCreated(event: ServiceEvent): Promise<void> {
private async handleUserCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedUser.upsert({
await tx.syncedUser.upsert({
where: { originalUserId: payload.id },
create: {
originalUserId: payload.id,
@ -341,26 +381,18 @@ export class CdcSyncService implements OnModuleInit {
realName: payload.realName,
},
});
await this.recordProcessedEvent(event);
this.logger.debug(`Synced user: ${payload.accountSequence}`);
} catch (error) {
this.logger.error(`Failed to sync user: ${payload.id}`, error);
}
}
/**
* auth-service user.registered
* payload: { accountSequence, phone, source, registeredAt }
*/
private async handleUserRegistered(event: ServiceEvent): Promise<void> {
private async handleUserRegistered(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedUser.upsert({
await tx.syncedUser.upsert({
where: { accountSequence: payload.accountSequence },
create: {
originalUserId: payload.accountSequence, // 使用 accountSequence 作为 originalUserId
originalUserId: payload.accountSequence,
accountSequence: payload.accountSequence,
phone: payload.phone,
status: 'ACTIVE',
@ -374,23 +406,15 @@ export class CdcSyncService implements OnModuleInit {
isLegacyUser: payload.source === 'V1',
},
});
await this.recordProcessedEvent(event);
this.logger.log(`Synced user from auth-service: ${payload.accountSequence}`);
} catch (error) {
this.logger.error(`Failed to sync user from auth-service: ${payload.accountSequence}`, error);
}
}
/**
* auth-service user.legacy.migrated
* payload: { accountSequence, phone, nickname, migratedAt }
*/
private async handleLegacyUserMigrated(event: ServiceEvent): Promise<void> {
private async handleLegacyUserMigrated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedUser.upsert({
await tx.syncedUser.upsert({
where: { accountSequence: payload.accountSequence },
create: {
originalUserId: payload.accountSequence,
@ -409,19 +433,11 @@ export class CdcSyncService implements OnModuleInit {
isLegacyUser: true,
},
});
await this.recordProcessedEvent(event);
this.logger.log(`Synced legacy migrated user: ${payload.accountSequence}`);
} catch (error) {
this.logger.error(`Failed to sync legacy migrated user: ${payload.accountSequence}`, error);
}
}
private async handleUserUpdated(event: ServiceEvent): Promise<void> {
private async handleUserUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedUser.updateMany({
await tx.syncedUser.updateMany({
where: { originalUserId: payload.id },
data: {
phone: payload.phone,
@ -430,47 +446,26 @@ export class CdcSyncService implements OnModuleInit {
realName: payload.realName,
},
});
await this.recordProcessedEvent(event);
this.logger.debug(`Updated user: ${payload.id}`);
} catch (error) {
this.logger.error(`Failed to update user: ${payload.id}`, error);
}
}
private async handleKycStatusChanged(event: ServiceEvent): Promise<void> {
private async handleKycStatusChanged(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedUser.updateMany({
await tx.syncedUser.updateMany({
where: { accountSequence: payload.accountSequence },
data: {
kycStatus: payload.kycStatus,
realName: payload.realName,
},
});
await this.recordProcessedEvent(event);
this.logger.debug(`Updated KYC status: ${payload.accountSequence}`);
} catch (error) {
this.logger.error(
`Failed to update KYC status: ${payload.accountSequence}`,
error,
);
}
}
// ===========================================================================
// 算力账户事件处理
// ===========================================================================
private async handleContributionAccountUpdated(
event: ServiceEvent,
): Promise<void> {
private async handleContributionAccountUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedContributionAccount.upsert({
await tx.syncedContributionAccount.upsert({
where: { accountSequence: payload.accountSequence },
create: {
accountSequence: payload.accountSequence,
@ -496,32 +491,15 @@ export class CdcSyncService implements OnModuleInit {
unlockedBonusTiers: payload.unlockedBonusTiers,
},
});
await this.recordProcessedEvent(event);
this.logger.debug(
`Synced contribution account: ${payload.accountSequence}`,
);
} catch (error) {
this.logger.error(
`Failed to sync contribution account: ${payload.accountSequence}`,
error,
);
}
}
/**
* ContributionCalculated
* contribution-service
*/
private async handleContributionCalculated(
event: ServiceEvent,
): Promise<void> {
private async handleContributionCalculated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
// ContributionCalculated 事件只包含部分信息,需要获取完整数据
// 这里只更新已存在的记录,或创建基本记录等待后续同步
await this.prisma.syncedContributionAccount.upsert({
await tx.syncedContributionAccount.upsert({
where: { accountSequence: payload.accountSequence },
create: {
accountSequence: payload.accountSequence,
@ -530,39 +508,23 @@ export class CdcSyncService implements OnModuleInit {
teamBonusContribution: 0,
totalContribution: 0,
effectiveContribution: 0,
hasAdopted: true, // 有算力计算说明已认种
hasAdopted: true,
directReferralCount: 0,
unlockedLevelDepth: 0,
unlockedBonusTiers: 0,
},
update: {
// 增量更新个人算力
personalContribution: {
increment: parseFloat(payload.personalContribution) || 0,
},
hasAdopted: true,
},
});
await this.recordProcessedEvent(event);
this.logger.debug(
`Processed contribution calculation: ${payload.accountSequence}`,
);
} catch (error) {
this.logger.error(
`Failed to process contribution calculation: ${payload.accountSequence}`,
error,
);
}
}
private async handleSystemContributionUpdated(
event: ServiceEvent,
): Promise<void> {
private async handleSystemContributionUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedSystemContribution.upsert({
await tx.syncedSystemContribution.upsert({
where: { accountType: payload.accountType },
create: {
accountType: payload.accountType,
@ -576,27 +538,14 @@ export class CdcSyncService implements OnModuleInit {
contributionNeverExpires: payload.contributionNeverExpires,
},
});
await this.recordProcessedEvent(event);
this.logger.debug(
`Synced system contribution: ${payload.accountType}`,
);
} catch (error) {
this.logger.error(
`Failed to sync system contribution: ${payload.accountType}`,
error,
);
}
}
/**
* ReferralSynced -
*/
private async handleReferralSynced(event: ServiceEvent): Promise<void> {
private async handleReferralSynced(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedReferral.upsert({
await tx.syncedReferral.upsert({
where: { accountSequence: payload.accountSequence },
create: {
accountSequence: payload.accountSequence,
@ -614,22 +563,14 @@ export class CdcSyncService implements OnModuleInit {
depth: payload.depth || 0,
},
});
await this.recordProcessedEvent(event);
this.logger.debug(`Synced referral: ${payload.accountSequence}`);
} catch (error) {
this.logger.error(`Failed to sync referral: ${payload.accountSequence}`, error);
}
}
/**
* AdoptionSynced -
*/
private async handleAdoptionSynced(event: ServiceEvent): Promise<void> {
private async handleAdoptionSynced(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedAdoption.upsert({
await tx.syncedAdoption.upsert({
where: { originalAdoptionId: BigInt(payload.originalAdoptionId) },
create: {
originalAdoptionId: BigInt(payload.originalAdoptionId),
@ -647,22 +588,14 @@ export class CdcSyncService implements OnModuleInit {
contributionPerTree: payload.contributionPerTree,
},
});
await this.recordProcessedEvent(event);
this.logger.debug(`Synced adoption: ${payload.originalAdoptionId}`);
} catch (error) {
this.logger.error(`Failed to sync adoption: ${payload.originalAdoptionId}`, error);
}
}
/**
* ContributionRecordSynced -
*/
private async handleContributionRecordSynced(event: ServiceEvent): Promise<void> {
private async handleContributionRecordSynced(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedContributionRecord.upsert({
await tx.syncedContributionRecord.upsert({
where: { originalRecordId: BigInt(payload.originalRecordId) },
create: {
originalRecordId: BigInt(payload.originalRecordId),
@ -697,26 +630,18 @@ export class CdcSyncService implements OnModuleInit {
isExpired: payload.isExpired || false,
},
});
await this.recordProcessedEvent(event);
this.logger.debug(`Synced contribution record: ${payload.originalRecordId}`);
} catch (error) {
this.logger.error(`Failed to sync contribution record: ${payload.originalRecordId}`, error);
}
}
/**
* NetworkProgressUpdated -
*/
private async handleNetworkProgressUpdated(event: ServiceEvent): Promise<void> {
private async handleNetworkProgressUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
// 全网进度只保留一条记录
const existing = await this.prisma.syncedNetworkProgress.findFirst();
const existing = await tx.syncedNetworkProgress.findFirst();
if (existing) {
await this.prisma.syncedNetworkProgress.update({
await tx.syncedNetworkProgress.update({
where: { id: existing.id },
data: {
totalTreeCount: payload.totalTreeCount,
@ -729,7 +654,7 @@ export class CdcSyncService implements OnModuleInit {
},
});
} else {
await this.prisma.syncedNetworkProgress.create({
await tx.syncedNetworkProgress.create({
data: {
totalTreeCount: payload.totalTreeCount,
totalAdoptionOrders: payload.totalAdoptionOrders,
@ -741,25 +666,15 @@ export class CdcSyncService implements OnModuleInit {
},
});
}
await this.recordProcessedEvent(event);
this.logger.debug(
`Synced network progress: trees=${payload.totalTreeCount}, unit=${payload.currentUnit}, multiplier=${payload.currentMultiplier}`
);
} catch (error) {
this.logger.error('Failed to sync network progress', error);
}
}
// ===========================================================================
// 挖矿账户事件处理
// ===========================================================================
private async handleMiningAccountUpdated(event: ServiceEvent): Promise<void> {
private async handleMiningAccountUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedMiningAccount.upsert({
await tx.syncedMiningAccount.upsert({
where: { accountSequence: payload.accountSequence },
create: {
accountSequence: payload.accountSequence,
@ -775,24 +690,13 @@ export class CdcSyncService implements OnModuleInit {
totalContribution: payload.totalContribution,
},
});
await this.recordProcessedEvent(event);
this.logger.debug(`Synced mining account: ${payload.accountSequence}`);
} catch (error) {
this.logger.error(
`Failed to sync mining account: ${payload.accountSequence}`,
error,
);
}
}
private async handleMiningConfigUpdated(event: ServiceEvent): Promise<void> {
private async handleMiningConfigUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
// 只保留一条挖矿配置记录
await this.prisma.syncedMiningConfig.deleteMany({});
await this.prisma.syncedMiningConfig.create({
await tx.syncedMiningConfig.deleteMany({});
await tx.syncedMiningConfig.create({
data: {
totalShares: payload.totalShares,
distributionPool: payload.distributionPool,
@ -801,26 +705,14 @@ export class CdcSyncService implements OnModuleInit {
currentEra: payload.currentEra || 1,
minuteDistribution: payload.minuteDistribution,
isActive: payload.isActive || false,
activatedAt: payload.activatedAt
? new Date(payload.activatedAt)
: null,
activatedAt: payload.activatedAt ? new Date(payload.activatedAt) : null,
},
});
await this.recordProcessedEvent(event);
this.logger.debug('Synced mining config');
} catch (error) {
this.logger.error('Failed to sync mining config', error);
}
}
private async handleDailyMiningStatCreated(
event: ServiceEvent,
): Promise<void> {
private async handleDailyMiningStatCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedDailyMiningStat.upsert({
await tx.syncedDailyMiningStat.upsert({
where: { statDate: new Date(payload.date) },
create: {
statDate: new Date(payload.date),
@ -838,15 +730,6 @@ export class CdcSyncService implements OnModuleInit {
avgContributionRate: payload.avgContributionRate,
},
});
await this.recordProcessedEvent(event);
this.logger.debug(`Synced daily mining stat: ${payload.date}`);
} catch (error) {
this.logger.error(
`Failed to sync daily mining stat: ${payload.date}`,
error,
);
}
}
// ===========================================================================
@ -855,11 +738,11 @@ export class CdcSyncService implements OnModuleInit {
private async handleTradingAccountUpdated(
event: ServiceEvent,
tx: TransactionClient,
): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedTradingAccount.upsert({
await tx.syncedTradingAccount.upsert({
where: { accountSequence: payload.accountSequence },
create: {
accountSequence: payload.accountSequence,
@ -880,21 +763,13 @@ export class CdcSyncService implements OnModuleInit {
},
});
await this.recordProcessedEvent(event);
this.logger.debug(`Synced trading account: ${payload.accountSequence}`);
} catch (error) {
this.logger.error(
`Failed to sync trading account: ${payload.accountSequence}`,
error,
);
}
}
private async handleDayKLineCreated(event: ServiceEvent): Promise<void> {
private async handleDayKLineCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedDayKLine.upsert({
await tx.syncedDayKLine.upsert({
where: { klineDate: new Date(payload.date) },
create: {
klineDate: new Date(payload.date),
@ -917,80 +792,25 @@ export class CdcSyncService implements OnModuleInit {
},
});
await this.recordProcessedEvent(event);
this.logger.debug(`Synced day K-line: ${payload.date}`);
} catch (error) {
this.logger.error(`Failed to sync day K-line: ${payload.date}`, error);
}
}
private async handleCirculationPoolUpdated(
event: ServiceEvent,
tx: TransactionClient,
): Promise<void> {
const { payload } = event;
try {
// 只保留一条流通池记录
await this.prisma.syncedCirculationPool.deleteMany({});
await this.prisma.syncedCirculationPool.create({
await tx.syncedCirculationPool.deleteMany({});
await tx.syncedCirculationPool.create({
data: {
totalShares: payload.totalShares || 0,
totalCash: payload.totalCash || 0,
},
});
await this.recordProcessedEvent(event);
this.logger.debug('Synced circulation pool');
} catch (error) {
this.logger.error('Failed to sync circulation pool', error);
}
}
// ===========================================================================
// 辅助方法
// ===========================================================================
/**
*
* 使 sourceTopic + eventId
* CDC
*/
private async isEventProcessed(event: ServiceEvent): Promise<boolean> {
try {
const existing = await this.prisma.processedEvent.findUnique({
where: {
sourceService_eventId: {
sourceService: event.sourceTopic,
eventId: event.id,
},
},
});
return !!existing;
} catch (error) {
this.logger.warn(`Failed to check processed event: ${event.sourceTopic}:${event.id}`);
return false;
}
}
private async recordProcessedEvent(event: ServiceEvent): Promise<void> {
try {
await this.prisma.processedEvent.upsert({
where: {
sourceService_eventId: {
sourceService: event.sourceTopic,
eventId: event.id,
},
},
create: {
eventId: event.id,
eventType: event.eventType,
sourceService: event.sourceTopic,
},
update: {},
});
} catch (error) {
// 忽略幂等性记录失败
this.logger.warn(`Failed to record processed event: ${event.sourceTopic}:${event.id}`);
}
}
}

View File

@ -1,25 +1,26 @@
import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../persistence/prisma/prisma.service';
import { Prisma } from '@prisma/client';
import { ServiceEvent } from './cdc-consumer.service';
/** Prisma 事务客户端类型 */
type TransactionClient = Prisma.TransactionClient;
/**
* mining-wallet-service CDC
* handler
*/
@Injectable()
export class WalletSyncHandlers {
private readonly logger = new Logger(WalletSyncHandlers.name);
constructor(private readonly prisma: PrismaService) {}
// ===========================================================================
// 区域数据处理
// ===========================================================================
async handleProvinceCreated(event: ServiceEvent): Promise<void> {
async handleProvinceCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedProvince.upsert({
await tx.syncedProvince.upsert({
where: { originalId: payload.id },
create: {
originalId: payload.id,
@ -35,16 +36,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Synced province: ${payload.code}`);
} catch (error) {
this.logger.error(`Failed to sync province: ${payload.code}`, error);
}
}
async handleProvinceUpdated(event: ServiceEvent): Promise<void> {
async handleProvinceUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedProvince.updateMany({
await tx.syncedProvince.updateMany({
where: { originalId: payload.id },
data: {
code: payload.code,
@ -54,16 +51,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Updated province: ${payload.code}`);
} catch (error) {
this.logger.error(`Failed to update province: ${payload.code}`, error);
}
}
async handleCityCreated(event: ServiceEvent): Promise<void> {
async handleCityCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedCity.upsert({
await tx.syncedCity.upsert({
where: { originalId: payload.id },
create: {
originalId: payload.id,
@ -81,16 +74,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Synced city: ${payload.code}`);
} catch (error) {
this.logger.error(`Failed to sync city: ${payload.code}`, error);
}
}
async handleCityUpdated(event: ServiceEvent): Promise<void> {
async handleCityUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedCity.updateMany({
await tx.syncedCity.updateMany({
where: { originalId: payload.id },
data: {
provinceId: payload.provinceId,
@ -101,16 +90,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Updated city: ${payload.code}`);
} catch (error) {
this.logger.error(`Failed to update city: ${payload.code}`, error);
}
}
async handleUserRegionMappingCreated(event: ServiceEvent): Promise<void> {
async handleUserRegionMappingCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedUserRegionMapping.upsert({
await tx.syncedUserRegionMapping.upsert({
where: { accountSequence: payload.accountSequence },
create: {
accountSequence: payload.accountSequence,
@ -126,16 +111,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Synced user region mapping: ${payload.accountSequence}`);
} catch (error) {
this.logger.error(`Failed to sync user region mapping: ${payload.accountSequence}`, error);
}
}
async handleUserRegionMappingUpdated(event: ServiceEvent): Promise<void> {
async handleUserRegionMappingUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedUserRegionMapping.updateMany({
await tx.syncedUserRegionMapping.updateMany({
where: { accountSequence: payload.accountSequence },
data: {
cityId: payload.cityId,
@ -145,20 +126,16 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Updated user region mapping: ${payload.accountSequence}`);
} catch (error) {
this.logger.error(`Failed to update user region mapping: ${payload.accountSequence}`, error);
}
}
// ===========================================================================
// 系统账户处理
// ===========================================================================
async handleWalletSystemAccountCreated(event: ServiceEvent): Promise<void> {
async handleWalletSystemAccountCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedWalletSystemAccount.upsert({
await tx.syncedWalletSystemAccount.upsert({
where: { originalId: payload.id },
create: {
originalId: payload.id,
@ -196,16 +173,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Synced wallet system account: ${payload.code}`);
} catch (error) {
this.logger.error(`Failed to sync wallet system account: ${payload.code}`, error);
}
}
async handleWalletSystemAccountUpdated(event: ServiceEvent): Promise<void> {
async handleWalletSystemAccountUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedWalletSystemAccount.updateMany({
await tx.syncedWalletSystemAccount.updateMany({
where: { originalId: payload.id },
data: {
name: payload.name,
@ -222,20 +195,16 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Updated wallet system account: ${payload.code}`);
} catch (error) {
this.logger.error(`Failed to update wallet system account: ${payload.code}`, error);
}
}
// ===========================================================================
// 池账户处理
// ===========================================================================
async handleWalletPoolAccountCreated(event: ServiceEvent): Promise<void> {
async handleWalletPoolAccountCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedWalletPoolAccount.upsert({
await tx.syncedWalletPoolAccount.upsert({
where: { originalId: payload.id },
create: {
originalId: payload.id,
@ -260,16 +229,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Synced wallet pool account: ${payload.poolType}`);
} catch (error) {
this.logger.error(`Failed to sync wallet pool account: ${payload.poolType}`, error);
}
}
async handleWalletPoolAccountUpdated(event: ServiceEvent): Promise<void> {
async handleWalletPoolAccountUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedWalletPoolAccount.updateMany({
await tx.syncedWalletPoolAccount.updateMany({
where: { originalId: payload.id },
data: {
name: payload.name,
@ -283,20 +248,16 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Updated wallet pool account: ${payload.poolType}`);
} catch (error) {
this.logger.error(`Failed to update wallet pool account: ${payload.poolType}`, error);
}
}
// ===========================================================================
// 用户钱包处理
// ===========================================================================
async handleUserWalletCreated(event: ServiceEvent): Promise<void> {
async handleUserWalletCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedUserWallet.upsert({
await tx.syncedUserWallet.upsert({
where: { originalId: payload.id },
create: {
originalId: payload.id,
@ -318,16 +279,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Synced user wallet: ${payload.accountSequence}/${payload.walletType}`);
} catch (error) {
this.logger.error(`Failed to sync user wallet: ${payload.accountSequence}/${payload.walletType}`, error);
}
}
async handleUserWalletUpdated(event: ServiceEvent): Promise<void> {
async handleUserWalletUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedUserWallet.updateMany({
await tx.syncedUserWallet.updateMany({
where: { originalId: payload.id },
data: {
balance: payload.balance,
@ -339,20 +296,16 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Updated user wallet: ${payload.accountSequence}/${payload.walletType}`);
} catch (error) {
this.logger.error(`Failed to update user wallet: ${payload.accountSequence}/${payload.walletType}`, error);
}
}
// ===========================================================================
// 提现请求处理
// ===========================================================================
async handleWithdrawRequestCreated(event: ServiceEvent): Promise<void> {
async handleWithdrawRequestCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedWithdrawRequest.upsert({
await tx.syncedWithdrawRequest.upsert({
where: { originalId: payload.id },
create: {
originalId: payload.id,
@ -386,16 +339,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Synced withdraw request: ${payload.requestNo}`);
} catch (error) {
this.logger.error(`Failed to sync withdraw request: ${payload.requestNo}`, error);
}
}
async handleWithdrawRequestUpdated(event: ServiceEvent): Promise<void> {
async handleWithdrawRequestUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedWithdrawRequest.updateMany({
await tx.syncedWithdrawRequest.updateMany({
where: { originalId: payload.id },
data: {
status: payload.status,
@ -410,20 +359,16 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Updated withdraw request: ${payload.requestNo}`);
} catch (error) {
this.logger.error(`Failed to update withdraw request: ${payload.requestNo}`, error);
}
}
// ===========================================================================
// 充值记录处理
// ===========================================================================
async handleDepositRecordCreated(event: ServiceEvent): Promise<void> {
async handleDepositRecordCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedDepositRecord.upsert({
await tx.syncedDepositRecord.upsert({
where: { originalId: payload.id },
create: {
originalId: payload.id,
@ -448,16 +393,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Synced deposit record: ${payload.txHash}`);
} catch (error) {
this.logger.error(`Failed to sync deposit record: ${payload.txHash}`, error);
}
}
async handleDepositRecordUpdated(event: ServiceEvent): Promise<void> {
async handleDepositRecordUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedDepositRecord.updateMany({
await tx.syncedDepositRecord.updateMany({
where: { originalId: payload.id },
data: {
confirmations: payload.confirmations,
@ -468,20 +409,16 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Updated deposit record: ${payload.txHash}`);
} catch (error) {
this.logger.error(`Failed to update deposit record: ${payload.txHash}`, error);
}
}
// ===========================================================================
// DEX Swap 处理
// ===========================================================================
async handleDexSwapRecordCreated(event: ServiceEvent): Promise<void> {
async handleDexSwapRecordCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedDexSwapRecord.upsert({
await tx.syncedDexSwapRecord.upsert({
where: { originalId: payload.id },
create: {
originalId: payload.id,
@ -513,16 +450,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Synced dex swap record: ${payload.swapNo}`);
} catch (error) {
this.logger.error(`Failed to sync dex swap record: ${payload.swapNo}`, error);
}
}
async handleDexSwapRecordUpdated(event: ServiceEvent): Promise<void> {
async handleDexSwapRecordUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedDexSwapRecord.updateMany({
await tx.syncedDexSwapRecord.updateMany({
where: { originalId: payload.id },
data: {
toAmount: payload.toAmount,
@ -536,20 +469,16 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Updated dex swap record: ${payload.swapNo}`);
} catch (error) {
this.logger.error(`Failed to update dex swap record: ${payload.swapNo}`, error);
}
}
// ===========================================================================
// 地址绑定处理
// ===========================================================================
async handleBlockchainAddressBindingCreated(event: ServiceEvent): Promise<void> {
async handleBlockchainAddressBindingCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedBlockchainAddressBinding.upsert({
await tx.syncedBlockchainAddressBinding.upsert({
where: { originalId: payload.id },
create: {
originalId: payload.id,
@ -569,16 +498,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Synced blockchain address binding: ${payload.accountSequence}`);
} catch (error) {
this.logger.error(`Failed to sync blockchain address binding: ${payload.accountSequence}`, error);
}
}
async handleBlockchainAddressBindingUpdated(event: ServiceEvent): Promise<void> {
async handleBlockchainAddressBindingUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedBlockchainAddressBinding.updateMany({
await tx.syncedBlockchainAddressBinding.updateMany({
where: { originalId: payload.id },
data: {
kavaAddress: payload.kavaAddress,
@ -589,20 +514,16 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Updated blockchain address binding: ${payload.accountSequence}`);
} catch (error) {
this.logger.error(`Failed to update blockchain address binding: ${payload.accountSequence}`, error);
}
}
// ===========================================================================
// 黑洞合约处理
// ===========================================================================
async handleBlackHoleContractCreated(event: ServiceEvent): Promise<void> {
async handleBlackHoleContractCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedBlackHoleContract.upsert({
await tx.syncedBlackHoleContract.upsert({
where: { originalId: payload.id },
create: {
originalId: payload.id,
@ -623,16 +544,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Synced black hole contract: ${payload.contractAddress}`);
} catch (error) {
this.logger.error(`Failed to sync black hole contract: ${payload.contractAddress}`, error);
}
}
async handleBlackHoleContractUpdated(event: ServiceEvent): Promise<void> {
async handleBlackHoleContractUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedBlackHoleContract.updateMany({
await tx.syncedBlackHoleContract.updateMany({
where: { originalId: payload.id },
data: {
name: payload.name,
@ -644,20 +561,16 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Updated black hole contract: ${payload.contractAddress}`);
} catch (error) {
this.logger.error(`Failed to update black hole contract: ${payload.contractAddress}`, error);
}
}
// ===========================================================================
// 销毁记录处理
// ===========================================================================
async handleBurnToBlackHoleRecordCreated(event: ServiceEvent): Promise<void> {
async handleBurnToBlackHoleRecordCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedBurnToBlackHoleRecord.upsert({
await tx.syncedBurnToBlackHoleRecord.upsert({
where: { originalId: payload.id },
create: {
originalId: payload.id,
@ -679,20 +592,16 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Synced burn to black hole record: ${payload.id}`);
} catch (error) {
this.logger.error(`Failed to sync burn to black hole record: ${payload.id}`, error);
}
}
// ===========================================================================
// 费率配置处理
// ===========================================================================
async handleFeeConfigCreated(event: ServiceEvent): Promise<void> {
async handleFeeConfigCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedFeeConfig.upsert({
await tx.syncedFeeConfig.upsert({
where: { originalId: payload.id },
create: {
originalId: payload.id,
@ -719,16 +628,12 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Synced fee config: ${payload.feeType}`);
} catch (error) {
this.logger.error(`Failed to sync fee config: ${payload.feeType}`, error);
}
}
async handleFeeConfigUpdated(event: ServiceEvent): Promise<void> {
async handleFeeConfigUpdated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
const { payload } = event;
try {
await this.prisma.syncedFeeConfig.updateMany({
await tx.syncedFeeConfig.updateMany({
where: { originalId: payload.id },
data: {
feeRate: payload.feeRate,
@ -743,8 +648,5 @@ export class WalletSyncHandlers {
});
this.logger.debug(`Updated fee config: ${payload.feeType}`);
} catch (error) {
this.logger.error(`Failed to update fee config: ${payload.feeType}`, error);
}
}
}