关键词: 竞拍系统、保证金、并发控制、乐观锁、数据库事务、余额冻结、微服务
代码片段(Java - 账户冻结与扣款核心逻辑):
java
@Service
@Transactional
public class AccountService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private BalanceFreezeLogMapper freezeLogMapper;
public boolean freezeBalance(Long userId, BigDecimal amount, String auctionId) {
// 1. 使用乐观锁更新账户冻结余额
Account account = accountMapper.selectForUpdate(userId);
if (account.getAvailableBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException("可用余额不足");
}
int rows = accountMapper.freezeBalance(userId, amount, account.getVersion());
if (rows == 0) {
// 版本冲突,更新失败,通常由高并发引起,需重试或提示用户
throw new ConcurrentUpdateException("账户信息已变更,请重试");
}
// 2. 记录冻结流水,用于后续解冻或结算时核对
BalanceFreezeLog log = new BalanceFreezeLog();
log.setUserId(userId);
log.setAmount(amount);
log.setAuctionId(auctionId);
log.setStatus(FreezeStatus.FROZEN);
freezeLogMapper.insert(log);
return true;
}
public void settleAuction(Long userId, String auctionId, boolean success, BigDecimal finalPrice) {
// 1. 查找本次竞拍对应的冻结记录
BalanceFreezeLog freezeLog = freezeLogMapper.selectByUserAndAuction(userId, auctionId);
if (freezeLog == null) return;
if (success) {
// 竞拍成功:实际扣款
BigDecimal actualDeduction = freezeLog.getAmount().min(finalPrice);
accountMapper.deductBalance(userId, actualDeduction);
// 解冻剩余部分(如果有)
BigDecimal unfreezeAmount = freezeLog.getAmount().subtract(actualDeduction);
if (unfreezeAmount.compareTo(BigDecimal.ZERO) > 0) {
accountMapper.unfreezeBalance(userId, unfreezeAmount);
}
freezeLog.setStatus(FreezeStatus.SETTLED);
} else {
// 竞拍失败:全额解冻
accountMapper.unfreezeBalance(userId, freezeLog.getAmount());
freezeLog.setStatus(FreezeStatus.UNFROZEN);
}
freezeLogMapper.updateById(freezeLog);
}
}
核心亮点与踩坑复盘:
防并发超扣:使用version字段的乐观锁,避免在并发出价时出现余额超扣的安全漏洞。
事务一致性:@Transactional注解确保冻结余额、扣款、记录流水的原子性。
流水可追溯:详细的BalanceFreezeLog记录便于对账、排查问题和应对资金审计。