revert: 回滚 settleToBalance 的直接 Prisma 实现,准备用 Unit of Work 模式重新实现

This commit is contained in:
hailin 2026-01-06 07:07:27 -08:00
parent 4c6e64a604
commit 7dc25b75d2
1 changed files with 30 additions and 77 deletions

View File

@ -909,89 +909,42 @@ export class WalletApplicationService {
}> { }> {
this.logger.log(`Settling ${params.usdtAmount} USDT to balance for ${params.accountSequence}`); this.logger.log(`Settling ${params.usdtAmount} USDT to balance for ${params.accountSequence}`);
// 生成结算ID
const settlementId = `STL_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
let balanceAfter = 0;
let userId: bigint = BigInt(0);
try { try {
// 使用事务确保账户余额更新和流水记录的原子性 // 1. 查找钱包
await this.prisma.$transaction(async (tx) => { const wallet = await this.walletRepo.findByAccountSequence(params.accountSequence);
// 1. 查找钱包 if (!wallet) {
const walletRecord = await tx.walletAccount.findUnique({ throw new WalletNotFoundError(`accountSequence: ${params.accountSequence}`);
where: { accountSequence: params.accountSequence }, }
});
if (!walletRecord) {
throw new WalletNotFoundError(`accountSequence: ${params.accountSequence}`);
}
userId = walletRecord.userId; const usdtAmount = Money.USDT(params.usdtAmount);
const userId = wallet.userId.value;
// 2. 验证钱包状态是否 ACTIVE // 2. 生成结算ID
if (walletRecord.status !== 'ACTIVE') { const settlementId = `STL_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
throw new Error(`Wallet is not active: ${walletRecord.status}`);
}
const Decimal = (await import('decimal.js')).default; // 3. 执行钱包结算
const usdtAmountDecimal = new Decimal(params.usdtAmount); wallet.settleToBalance(usdtAmount, settlementId);
const currentSettleable = new Decimal(walletRecord.settleableUsdt.toString()); await this.walletRepo.save(wallet);
const currentAvailable = new Decimal(walletRecord.usdtAvailable.toString());
const currentSettledTotal = new Decimal(walletRecord.settledTotalUsdt.toString());
// 3. 验证可结算余额足够 // 4. 记录账本流水(含详细来源信息)
if (currentSettleable.lessThan(usdtAmountDecimal)) { const ledgerEntry = LedgerEntry.create({
throw new Error(`Insufficient settleable balance: ${currentSettleable} < ${usdtAmountDecimal}`); accountSequence: wallet.accountSequence,
} userId: UserId.create(userId),
entryType: LedgerEntryType.REWARD_SETTLED,
// 4. 计算新余额 amount: usdtAmount,
const newSettleable = currentSettleable.minus(usdtAmountDecimal); balanceAfter: wallet.balances.usdt.available,
const newAvailable = currentAvailable.plus(usdtAmountDecimal); refOrderId: settlementId,
const newSettledTotal = currentSettledTotal.plus(usdtAmountDecimal); memo: params.memo || `结算 ${params.usdtAmount} 绿积分到钱包余额`,
balanceAfter = newAvailable.toNumber(); payloadJson: {
settlementType: 'SETTLE_TO_BALANCE',
// 5. 更新钱包账户(在事务内,使用乐观锁) rewardEntryIds: params.rewardEntryIds,
const currentVersion = walletRecord.version; rewardCount: params.rewardEntryIds.length,
const updateResult = await tx.walletAccount.updateMany({ breakdown: params.breakdown,
where: { },
accountSequence: params.accountSequence,
version: currentVersion, // 乐观锁:只有版本匹配才更新
},
data: {
settleableUsdt: newSettleable.toFixed(8),
usdtAvailable: newAvailable.toFixed(8),
settledTotalUsdt: newSettledTotal.toFixed(8),
version: currentVersion + 1,
updatedAt: new Date(),
},
});
if (updateResult.count === 0) {
throw new Error(`Optimistic lock conflict for wallet ${params.accountSequence}`);
}
// 6. 创建流水记录(在事务内)
await tx.ledgerEntry.create({
data: {
accountSequence: params.accountSequence,
userId: walletRecord.userId,
entryType: LedgerEntryType.REWARD_SETTLED,
amount: usdtAmountDecimal.toFixed(8),
assetType: 'USDT',
balanceAfter: newAvailable.toFixed(8),
refOrderId: settlementId,
memo: params.memo || `结算 ${params.usdtAmount} 绿积分到钱包余额`,
payloadJson: {
settlementType: 'SETTLE_TO_BALANCE',
rewardEntryIds: params.rewardEntryIds,
rewardCount: params.rewardEntryIds.length,
breakdown: params.breakdown,
},
createdAt: new Date(),
},
});
}); });
await this.ledgerRepo.save(ledgerEntry);
// 7. 使缓存失效(在事务外,事务成功后执行) // 5. 使缓存失效
await this.walletCacheService.invalidateWallet(userId); await this.walletCacheService.invalidateWallet(userId);
this.logger.log(`Successfully settled ${params.usdtAmount} USDT to balance for ${params.accountSequence}`); this.logger.log(`Successfully settled ${params.usdtAmount} USDT to balance for ${params.accountSequence}`);
@ -1000,7 +953,7 @@ export class WalletApplicationService {
success: true, success: true,
settlementId, settlementId,
settledAmount: params.usdtAmount, settledAmount: params.usdtAmount,
balanceAfter, balanceAfter: wallet.balances.usdt.available.value,
}; };
} catch (error) { } catch (error) {
this.logger.error(`Failed to settle to balance for ${params.accountSequence}: ${error.message}`); this.logger.error(`Failed to settle to balance for ${params.accountSequence}: ${error.message}`);