Django钱包交易后如何修改Product模型关联用户、实现交易追踪与余额校验
现存代码问题点
- 校验逻辑顺序倒置:当前代码先执行余额扣减、加账操作,之后才校验付款方/收款方是否为同一用户,触发异常时已经产生了账户余额被修改的脏数据
- 缺失余额不足拦截:没有对比付款方账户余额与交易金额的逻辑,会出现账户余额被扣成负数的非法交易
- 缺失核心购买逻辑:交易完成后没有更新Product表的关联用户字段,没有实现商品所有权转移的目标
- 无交易留痕机制:所有余额划转操作没有持久化存储,管理员无法回溯全量交易记录
- 无事务保护:多步数据库操作没有事务包裹,任意一步出错都会导致数据不一致
分模块改造实现
Model层新增交易追溯能力
在原有models.py中新增交易流水模型,用于记录全量交易动作,满足管理员后台追溯要求:
class TransactionRecord(models.Model): sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name="send_records") receiver = models.ForeignKey(User, on_delete=models.CASCADE, related_name="receive_records") related_product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="关联交易商品") amount = models.FloatField(verbose_name="交易金额") created_at = models.DateTimeField(auto_now_add=True, verbose_name="交易时间") status = models.CharField(max_length=20, default="success", verbose_name="交易状态") fail_reason = models.CharField(max_length=255, null=True, blank=True, verbose_name="失败原因") def __str__(self): return f"{self.sender.username} 转给 {self.receiver.username} | 金额 {self.amount}"
在admin.py中注册该模型,即可在管理员后台直接查看、检索所有交易记录,不需要额外写后台页面:
from django.contrib import admin from .models import TransactionRecord admin.site.register(TransactionRecord)
View层逻辑修正
调整原有buy_c视图的执行顺序,补全校验、所有权转移、事务保护、交易留痕逻辑:
from django.db import transaction as db_transaction def buy_c(request, ok): ctp_val = Product.objects.get(id=ok) msg = "Enter Details" if request.method == "POST": # 开启数据库事务,所有操作要么全部生效,要么全部回滚,避免脏数据 with db_transaction.atomic(): try: username = request.POST["username"] amount = float(request.POST["amount"]) sender_user = request.user receiver_user = User.objects.get(username=username) sender_balance = Balance.objects.get(user=sender_user) receiver_balance = Balance.objects.get(user=receiver_user) # 前置校验环节,不通过直接抛异常触发事务回滚 # 1. 禁止自转账 if sender_user == receiver_user: raise Exception("无法向自己发起交易") # 2. 余额不足拦截 if sender_balance.balance < amount: raise Exception("账户余额不足,无法完成支付") # 3. 禁止购买自己已持有的商品 if ctp_val.user == sender_user: raise Exception("无法购买自己持有的商品") # 校验全部通过后再执行余额变更 sender_balance.balance -= amount receiver_balance.balance += amount sender_balance.save() receiver_balance.save() # 核心业务逻辑:将商品所有权转移到付款方 ctp_val.user = sender_user ctp_val.transection_id += 1 ctp_val.save() # 写入成功交易流水 TransactionRecord.objects.create( sender=sender_user, receiver=receiver_user, related_product=ctp_val, amount=amount, status="success" ) msg = "Transaction Success" return redirect("/user") except Exception as e: # 事务自动回滚,不会产生余额错扣的脏数据 # 写入失败交易记录,方便后续排障追溯 try: TransactionRecord.objects.create( sender=request.user, receiver=User.objects.get(username=request.POST.get("username", "")), related_product=ctp_val, amount=float(request.POST.get("amount", 0)), status="failed", fail_reason=str(e) ) except: pass msg = f"交易失败:{str(e)}" context = {"coin": ctp_val, "msg": msg} return render(request, 'coinwall.html', context)
模板端适配
原有coinwall.html不需要做结构改动,原有{{msg}}变量会自动渲染所有校验失败、交易成功的提示信息,readonly的输入框也可以正常展示收款方账号、交易金额,避免用户手动篡改交易参数。
内容的提问来源于stack exchange,提问作者sr freak
相关产品推荐
相关产品推荐

