Django中利用post_save信号实现资金回退时的资金到账判断异常问题
嘿,我看了你这段代码,一眼就发现问题出在资金到账的判断逻辑上——你现在写的check_if_funds_received = Account.objects.filter(account_balance=amount).exists() 完全不是在判断“接收方有没有收到这笔钱”,而是在找“系统里有没有任何一个账户的余额刚好等于这笔交易的金额”,这逻辑完全跑偏了!
举个例子:如果接收方原来账户有50块,你转100块过去,接收方余额变成150,这时候你的判断会因为找不到余额等于100的账户(除非刚好有其他账户余额是100)而返回False,直接把交易标记为Reversed,还把钱退给发送方,这就导致明明转账成功了却被反转的问题;反过来,如果有个账户本来余额就是100,哪怕你根本没给这个账户转钱,你的判断也会返回True,错误标记交易为Success,这完全乱套了。
我给你梳理下正确的解决方案,分两种情况:
情况一:修正现有信号的判断逻辑
如果你想保留当前“先处理余额再创建交易”的流程,那正确的判断应该是针对当前交易的接收方账户,验证它的余额是否正确接收了这笔钱,而不是乱查所有账户。
另外,先纠正你代码里的两个小错误:
receiver_account = account.objects.get(...)这里的account要大写成Account(模型类名是大写的),不然会报错;- 视图里创建Transaction时,
account_number=receiver_account应该改成account_number=receiver_account.account_number(因为Transaction的account_number是BigIntegerField,不能直接存Account对象)。
修正后的信号函数代码:
from django.db.models.signals import post_save from django.dispatch import receiver from .models import Transaction, Account @receiver(post_save, sender=Transaction) def transaction_status(sender, instance, created, **kwargs): if created: amount = instance.amount sender_account = instance.sender.account try: # 拿到当前交易对应的接收方账户 receiver_account = Account.objects.get(account_number=instance.account_number) # 这里其实可以直接标记为Success,因为视图里已经完成了余额转账操作 # 如果非要做验证,可以在Transaction模型里加一个字段记录接收方转账前的余额 # 比如添加 original_receiver_balance = models.DecimalField(max_digits=12, decimal_places=6) # 然后在视图创建Transaction时,把这个字段设为receiver_account.account_balance(加钱之前的余额) # 然后这里验证:receiver_account.account_balance == instance.original_receiver_balance + instance.amount instance.status = 'Success' instance.save() except Account.DoesNotExist: # 如果接收方账户不存在,才执行反转操作 instance.status = 'Reversed' instance.save() # 返还资金给发送方 sender_account.account_balance += amount sender_account.save()
情况二:更严谨的流程设计(推荐)
其实你当前“先处理余额再创建交易”的流程有风险:如果处理完余额后,创建Transaction时出现异常,就会导致余额已经变动但没有交易记录的情况。
更合理的流程是:先创建一个状态为Pending的交易,再处理余额操作,成功就把交易改成Success,失败就改成Reversed并回滚余额。这样可以避免状态不一致的问题,而且也不需要依赖post_save信号来判断资金到账。
修改后的视图代码:
from decimal import Decimal from django.contrib import messages from .models import Account, Transaction def create_transfer(request): if request.method == 'POST': amount = Decimal(request.POST.get('amount')) pin = request.POST.get('pin') sender_account = Account.objects.get(user=request.user) receiver_acc_num = request.POST.get('account_number') try: receiver_account = Account.objects.get(account_number=receiver_acc_num) except Account.DoesNotExist: messages.error(request, '接收方账户不存在') return redirect('Transaction') # 检查发送方余额是否足够 if sender_account.account_balance >= amount: # 1. 先创建Pending状态的交易记录 transaction = Transaction.objects.create( sender=sender_account.user, receiver=receiver_account.user, amount=amount, pin=pin, first_name=receiver_account.first_name, last_name=receiver_account.last_name, account_number=receiver_acc_num, status='Pending' # 初始状态设为待处理 ) try: # 2. 处理余额转账 sender_account.account_balance -= amount sender_account.save() receiver_account.account_balance += amount receiver_account.save() # 3. 转账成功,更新交易状态 transaction.status = 'Success' transaction.save() messages.success(request, '转账成功!') except Exception as e: # 4. 转账失败,回滚余额并更新交易状态 transaction.status = 'Reversed' transaction.save() sender_account.account_balance += amount sender_account.save() messages.error(request, f'转账失败:{str(e)}') else: messages.error(request, '账户余额不足') return redirect('Transaction') return render(request, 'create_transfer.html')
如果这时候你还需要用信号做一些后续操作(比如给用户发通知),可以写一个监听交易状态变化的信号:
@receiver(post_save, sender=Transaction) def transaction_notification(sender, instance, created, **kwargs): if not created: # 只在交易状态更新时触发 if instance.status == 'Success': # 给发送方和接收方发送转账成功的通知 pass elif instance.status == 'Reversed': # 给发送方发送转账失败的通知 pass
最后再提醒你几个细节:
- 处理Decimal类型的余额时,要注意精度问题,尽量用Django的DecimalField来存储,避免浮点运算误差;
- 数据库操作尽量用原子性操作(比如用
transaction.atomic()包裹余额修改的代码),避免并发情况下的余额错误; - 不要在信号里做太复杂的操作,信号是同步执行的,会影响请求的响应速度。
备注:内容来源于stack exchange,提问作者Bubble

