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