diff --git a/backend/services/wallet-service/src/application/services/wallet-application.service.ts b/backend/services/wallet-service/src/application/services/wallet-application.service.ts index 5ceeb220..9a6d4b1d 100644 --- a/backend/services/wallet-service/src/application/services/wallet-application.service.ts +++ b/backend/services/wallet-service/src/application/services/wallet-application.service.ts @@ -7,6 +7,7 @@ import { IWithdrawalOrderRepository, WITHDRAWAL_ORDER_REPOSITORY, IPendingRewardRepository, PENDING_REWARD_REPOSITORY, } from '@/domain/repositories'; +import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service'; import { LedgerEntry, DepositOrder, SettlementOrder, WithdrawalOrder, PendingReward, PendingRewardStatus } from '@/domain/aggregates'; import { UserId, Money, Hashpower, LedgerEntryType, AssetType, ChainType, SettleCurrency, @@ -87,6 +88,7 @@ export class WalletApplicationService { private readonly pendingRewardRepo: IPendingRewardRepository, private readonly walletCacheService: WalletCacheService, private readonly eventPublisher: EventPublisherService, + private readonly prisma: PrismaService, ) {} // =============== Commands =============== @@ -1316,6 +1318,8 @@ export class WalletApplicationService { * 结算用户所有待领取奖励 * 当用户认种后调用,将 PENDING 状态的奖励转为 SETTLED * 同时将金额和算力转入钱包的可结算余额 + * + * 使用数据库事务确保 pending_rewards 状态更新和 wallet_accounts 余额更新的原子性 */ async settleUserPendingRewards(accountSequence: string): Promise<{ settledCount: number; @@ -1331,44 +1335,77 @@ export class WalletApplicationService { ); if (pendingRewards.length === 0) { + this.logger.debug(`[settleUserPendingRewards] No pending rewards for ${accountSequence}`); return { settledCount: 0, totalUsdt: 0, totalHashpower: 0 }; } let totalUsdt = 0; let totalHashpower = 0; - // 标记为已结算 + // 计算总金额 for (const reward of pendingRewards) { - reward.markAsSettled(); totalUsdt += reward.usdtAmount.value; totalHashpower += reward.hashpowerAmount.value; } - // 批量更新状态 - await this.pendingRewardRepo.updateAll(pendingRewards); + this.logger.log(`[settleUserPendingRewards] Found ${pendingRewards.length} pending rewards, total: ${totalUsdt} USDT, ${totalHashpower} hashpower`); - // 更新钱包可结算余额 + // 使用事务确保原子性:pending_rewards 状态更新 + wallet_accounts 余额更新 + await this.prisma.$transaction(async (tx) => { + // 1. 更新 pending_rewards 状态为 SETTLED + const now = new Date(); + await tx.pendingReward.updateMany({ + where: { + id: { in: pendingRewards.map(r => r.id) }, + status: PendingRewardStatus.PENDING, // 双重检查,防止并发问题 + }, + data: { + status: PendingRewardStatus.SETTLED, + settledAt: now, + }, + }); + + // 2. 更新 wallet_accounts 可结算余额 + const walletRecord = await tx.walletAccount.findUnique({ + where: { accountSequence }, + }); + + if (walletRecord) { + await tx.walletAccount.update({ + where: { accountSequence }, + data: { + settleableUsdt: { increment: totalUsdt }, + settleableHashpower: { increment: totalHashpower }, + hashpower: { increment: totalHashpower }, + // 同时减少 pending 余额 + pendingUsdt: { decrement: totalUsdt }, + pendingHashpower: { decrement: totalHashpower }, + }, + }); + + // 3. 记录流水 + if (totalUsdt > 0) { + await tx.ledgerEntry.create({ + data: { + accountSequence, + userId: walletRecord.userId, + entryType: LedgerEntryType.REWARD_TO_SETTLEABLE, + amount: totalUsdt, + assetType: 'USDT', + memo: `${pendingRewards.length} pending rewards settled`, + }, + }); + } + + this.logger.log(`[settleUserPendingRewards] Transaction committed: ${pendingRewards.length} rewards settled for ${accountSequence}`); + } else { + this.logger.warn(`[settleUserPendingRewards] Wallet not found for ${accountSequence}, skipping wallet update`); + } + }); + + // 清除钱包缓存 const wallet = await this.walletRepo.findByAccountSequence(accountSequence); if (wallet) { - // 将待领取转为可结算 - wallet.addSettleableReward( - Money.USDT(totalUsdt), - Hashpower.create(totalHashpower), - ); - await this.walletRepo.save(wallet); - - // 记录流水 - if (totalUsdt > 0) { - const ledgerEntry = LedgerEntry.create({ - accountSequence, - userId: wallet.userId, - entryType: LedgerEntryType.REWARD_TO_SETTLEABLE, - amount: Money.USDT(totalUsdt), - memo: `${pendingRewards.length} pending rewards settled`, - }); - await this.ledgerRepo.save(ledgerEntry); - } - await this.walletCacheService.invalidateWallet(wallet.userId.value); }