Django项目实战:数据库事务处理与支付宝支付集成
本文详细讲解在Django项目中如何利用[[wp:a0]]模块实现数据库事务,确保订单、商品等多表数据的一致性。同时深入剖析接入支付宝沙箱支付的全流程,涵盖密钥生成、支付链接构造及回调处理,为电商系统提供完整的支付解决方案。
此前一直计划整理项目中关于数据库事务及支付宝支付功能的实际应用,因个人疏忽未能及时完成。今日趁兴致尚存,特此记录相关技术细节与实现过程。
Django数据库事务处理与订单保存
电商商城的核心功能之一是支付订单模块,这必然涉及订单数据的持久化及支付接口的对接。首先分析订单保存逻辑:在数据库模型设计阶段,我们将订单拆分为“订单表”与“订单商品表”。订单表存储订单号、用户信息及运费等基础数据;订单商品表则记录该订单下的具体商品明细。保存订单时,需同时新建这两个表的数据。此外,还需更新商品表以反映库存变化,因为订单成功意味着商品售出。因此,订单保存操作涉及三张表的联动修改。为保证数据一致性,这些操作必须遵循“同时成功或同时失败”的原则,这正是数据库事务的典型应用场景,故在此引入事务机制来完成订单保存。
在Django框架中,可通过 django.db.transaction模块提供的atomic函数来定义事务。atomic提供了两种使用方式:一种是作为装饰器使用,另一种是作为上下文管理器(with语句)使用。
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# 这些代码会在一个事务中执行
...
from django.db import transaction
def viewfunc(request):
# 这部分代码不在事务中,会被Django自动提交
...
with transaction.atomic():
# 这部分代码会在事务中执行
...
Django还支持事务保存点(savepoint)。在事务执行过程中,可以创建保存点以记录数据的特定状态。若后续操作出现错误,数据库可回滚至该保存点的状态,从而部分恢复数据。
from django.db import transaction
# 创建保存点
save_id = transaction.savepoint()
# 回滚到保存点
transaction.savepoint_rollback(save_id)
# 提交从保存点到当前状态的所有数据库事务操作
transaction.savepoint_commit(save_id)
因此,我们可以在序列化器的create方法中封装事务逻辑,执行数据的修改、保存及新建操作。若过程中任何环节出错,则直接触发回滚;若一切正常,则提交事务。具体代码实现如下。
def create(self, validated_data): """ 保存订单 """ # 获取当前下单用户 user = self.context['request'].user # 组织订单编号 20170903153611+user.id # timezone.now() -> datetime order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id) address = validated_data['address'] pay_method = validated_data['pay_method'] # 生成订单 with transaction.atomic(): # 创建一个保存点 save_id = transaction.savepoint() try: # 创建订单信息 order = OrderInfo.objects.create( order_id=order_id, user=user, address=address, total_count=0, total_amount=Decimal(0), freight=Decimal(10), pay_method=pay_method, status=OrderInfo.ORDER_STATUS_ENUM['UNSEND'] if pay_method == OrderInfo.PAY_METHODS_ENUM['CASH'] else OrderInfo.ORDER_STATUS_ENUM['UNPAID'] ) # 获取购物车信息 redis_conn = get_redis_connection("cart") redis_cart = redis_conn.hgetall("cart_%s" % user.id) cart_selected = redis_conn.smembers('cart_selected_%s' % user.id) # 将bytes类型转换为int类型 cart = {} for sku_id in cart_selected: cart[int(sku_id)] = int(redis_cart[sku_id]) # # 一次查询出所有商品数据 # skus = SKU.objects.filter(id__in=cart.keys()) # 处理订单商品 sku_id_list = cart.keys() for sku_id in sku_id_list: while True: sku = SKU.objects.get(id=sku_id) sku_count = cart[sku.id] # 判断库存 origin_stock = sku.stock # 原始库存 origin_sales = sku.sales # 原始销量 if sku_count > origin_stock: transaction.savepoint_rollback(save_id) raise serializers.ValidationError('商品库存不足') # 用于演示并发下单 # import time # time.sleep(5) # 减少库存 # sku.stock -= sku_count # sku.sales += sku_count # sku.save() new_stock = origin_stock - sku_count new_sales = origin_sales + sku_count # 根据原始库存条件更新,返回更新的条目数,乐观锁 ret = SKU.objects.filter(id=sku.id, stock=origin_stock).update(stock=new_stock, sales=new_sales) if ret == 0: continue # 累计商品的SPU 销量信息 sku.goods.sales += sku_count sku.goods.save() # 累计订单基本信息的数据 order.total_count += sku_count # 累计总金额 order.total_amount += (sku.price * sku_count) # 累计总额 # 保存订单商品 OrderGoods.objects.create( order=order, sku=sku, count=sku_count, price=sku.price, ) # 更新成功 break # 更新订单的金额数量信息 order.total_amount += order.freight order.save() except serializers.ValidationError: raise except Exception as e: logger.error(e) transaction.savepoint_rollback(save_id) raise # 提交事务 transaction.savepoint_commit(save_id) # 更新redis中保存的购物车数据 pl = redis_conn.pipeline() pl.hdel('cart_%s' % user.id, *cart_selected) pl.srem('cart_selected_%s' % user.id, *cart_selected) pl.execute() return order
此外需注意,当订单提交成功后,购物车中对应的商品记录应当被清除。至此,Django事务处理的核心内容介绍完毕。
支付宝支付功能集成流程
接下来讨论支付宝支付功能的接入。目前绝大多数涉及支付的项目都会引入第三方支付渠道,其中支付宝和微信支付最为普及。本文以支付宝支付为例进行说明。在订单创建完成后,即进入支付环节。
首先梳理整体业务流程:用户点击支付按钮请求跳转至支付宝界面,完成登录后执行支付操作,支付成功后支付宝会向服务器发起回调通知。
第一步,用户点击按钮后,后端负责拼接支付URL,并将该URL返回给前端。前端接收到URL后进行页面跳转,引导用户进入支付宝界面完成登录及支付操作。
参考pythonsdk文档,首先需通过openssl命令生成密钥对(包含公钥和私钥)。私钥由开发者自行保管,公钥用于支付宝侧的校验。具体命令如下。
openssl
OpenSSL> genrsa -out app_private_key.pem 2048 # 私钥 OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem # 导出公钥 OpenSSL> exit
同时,你需要从支付宝平台获取一个公钥字符串,其格式可参考:https://github.com/fzlee/alipay/blob/master/tests/certs/ali/ali_public_key.pem
由于上述步骤使用的是RSA算法生成的密钥,因此在支付宝配置中也需对应使用RSA公钥。

密钥配置完成后,即可开始编写视图逻辑,代码如下。
class PaymentView(APIView):
"""
支付
"""
permission_classes = (IsAuthenticated,)
def get(self, request, order_id):
"""
获取支付链接
"""
# 判断订单信息是否正确
try:
order = OrderInfo.objects.get(order_id=order_id, user=request.user,
pay_method=OrderInfo.PAY_METHODS_ENUM["ALIPAY"],
status=OrderInfo.ORDER_STATUS_ENUM["UNPAID"])
except OrderInfo.DoesNotExist:
return Response({'message': '订单信息有误'}, status=status.HTTP_400_BAD_REQUEST)
# 构造支付宝支付链接地址
alipay = AliPay(
appid=settings.ALIPAY_APPID,
app_notify_url=None, # 默认回调url
app_private_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/app_private_key.pem"),
alipay_public_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/alipay_public_key.pem"), # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
sign_type="RSA2", # RSA 或者 RSA2
debug=settings.ALIPAY_DEBUG # 默认False
)
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=order_id,
total_amount=str(order.total_amount),
subject="美多商城%s" % order_id,
return_url="http://www.meiduo.site:8080/pay_success.html",
)
# 需要跳转到https://openapi.alipay.com/gateway.do? + order_string
# 拼接链接返回前端
alipay_url = settings.ALIPAY_URL + "?" + order_string
return Response({'alipay_url': alipay_url})
相关参数可预先在配置文件中定义(如ALPAY_APPID、ALPAY_URL、ALPAY_DEBUG)。需注意,仅当ALPAY设置为True时,系统才会启用沙箱环境。用户支付成功后,支付宝会向配置的回调网址发起回调,返回参数如图所示。
前端页面接收此数据并发送至后端,后端负责校验并保存支付结果。以上即为完整流程。具体实现细节可参考pythonsdk文档。
总结
以上介绍了在Django中使用事务及接入支付宝支付功能的全过程,希望能为读者提供帮助。如有任何疑问,欢迎留言,小编将及时回复!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













