侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

django多种支付、并发订单处理实例代码

框架(架构)  /  管理员 发布于 7年前   205

django实现多种支付方式

'''#思路    我们希望,通过插拔的方式来实现多方式登录,比如新增一种支付方式,那么只要在项目中新增一个py文件,导入里面的pay方法就可以了,这样在支付业务中支付语句是不发生变化的。  所以就可以使用python的鸭子类型及面向对象的反射方法来实现功能'''##新建一个Pay文件夹,里面放支付方式.py文件#Alipay.pyclass Alipay:  def pay(self):    pass#Visapay.pyclass Visapay:  def pay(self):    pass#Wxpay.py(完全按照接口文档来得)import time#记得导入商户号和key哦!from app01.wx import settingsclass Wxpay:  def pay(self,order_data):    self.order_id = order_data["order_id"]    self.open_id = order_data['open_id']    self.ip = order_data['ip']    data_body = self.get_body_data()    import requests    url = "https://api.mch.weixin.qq.com/pay/unifiedorder"    response = requests.post(url, data_body.encode("utf-8"), headers={'content-type': "application/xml"})    res_dict = self.xml_to_dic(response.content)    timeStamp = str(int(time.time()))    paySign = self.get_pay_sign(res_dict, timeStamp)    data_dic = {      'timeStamp': timeStamp,      'nonceStr': res_dict['nonce_str'],      'package': f"prepay_id={res_dict['prepay_id']}",      'signType': 'MD5',      "paySign": paySign,    }    return data_dic  def get_pay_sign(self, res_dict, timeStamp):    print("res_dict", res_dict)    data_dic = {      'appId': res_dict['appid'],      'timeStamp': timeStamp,      'nonceStr': res_dict['nonce_str'],      'package': f"prepay_id={res_dict['prepay_id']}",      "signType": "MD5"    }    sign_str = "&".join([f"{k}={data_dic[k]}" for k in sorted(data_dic)])    sign_str = f"{sign_str}&key={settings.pay_apikey}"    import hashlib    md5 = hashlib.md5()    md5.update(sign_str.encode("utf-8"))    sign = md5.hexdigest()    return sign.upper()  def xml_to_dic(self, xml_data):    import xml.etree.ElementTree as ET    '''    xml to dict    :param xml_data:    :return:    '''    xml_dict = {}    root = ET.fromstring(xml_data)    for child in root:      xml_dict[child.tag] = child.text    return xml_dict  def get_random(self):    import random    data = "123456789zxcvbnmasdfghjklqwertyuiopZXCVBNMASDFGHJKLQWERTYUIOP"    nonce_str = "".join(random.sample(data, 30))    return nonce_str  def get_sign(self):    data_dic = {      "nonce_str": self.nonce_str,      "out_trade_no": self.out_trade_no,      "spbill_create_ip": self.spbill_create_ip,      "notify_url": self.notify_url,      "openid": self.open_id,      "body": self.body,      "trade_type": "JSAPI",      "appid": self.appid,      "total_fee": "1",      "mch_id": self.mch_id    }    sign_str = "&".join([f"{k}={data_dic[k]}" for k in sorted(data_dic)])    sign_str = f"{sign_str}&key={settings.pay_apikey}"    import hashlib    md5 = hashlib.md5()    md5.update(sign_str.encode("utf-8"))    sign = md5.hexdigest()    return sign.upper()  def get_body_data(self):    self.appid = settings.AppId    # openid=self.open_id    self.mch_id = str(settings.pay_mchid)    self.nonce_str = self.get_random()    self.out_trade_no = self.order_id    self.spbill_create_ip = self.ip    self.notify_url = "https://www.test.com"    self.body = "老男孩学费"    self.sign = self.get_sign()    body_data = f"""      <xml>        <appid>{self.appid}</appid>        <mch_id>{self.mch_id}</mch_id>        <nonce_str>{self.nonce_str}</nonce_str>        <sign>{self.sign}</sign>        <body>{self.body}</body>        <out_trade_no>{self.out_trade_no}</out_trade_no>        <total_fee>1</total_fee>        <spbill_create_ip>{ self.spbill_create_ip}</spbill_create_ip>        <notify_url>{self.notify_url}</notify_url>        <openid>{self.open_id}</openid>        <trade_type>JSAPI</trade_type>       </xml>"""    return body_data      ##调用支付方法的语句(一般支付都是发生在订单创建好之后)import importlibfrom rest_framework.response import Responsepay_method = "Wxpay" #这里是举例子,所以把pay_method写死了,正常情况下,应该是外面传来的支付方式,然后用pay_method接收try:  #用字符串导入支付方式的py文件,例如这里的app01.Pay.{pay_method}  pay_field = importlib.import_module(f"app01.Pay.{pay_method}")    #用反射拿到该文件下面的类  pay_method_class = getattr(pay_field, pay_method)except:  return Response({"code": 205, "msg": "错误的支付方式"})order_data['ip'] = host_iporder_data['open_id'] = open_id#完成支付,并把支付数据返回pay_data = pay_method_class().pay(order_data)'''这里直接用反射拿到的支付类,然后使用它的pay方法,完成支付'''return Response({"code": 200, "msg": "ok", "data": pay_data})

django实现订单创建及支付

'''几个注意点:  1.a,b两个用户同时买一个库存为1的商品,这样为了保证数据安全(即a买了,库存没更新,b又买了,这样就存在安全问题),需要在数据库操作时加锁,有两个选择:悲观锁和乐观锁。  悲观锁:冲突比较多的时候,使用悲观锁。悲观锁获取数据时对数据行了锁定,其他事务要想获取锁,必须等原事务结束。  乐观锁:冲突比较少的时候,使用乐观锁。查询时不锁数据,提交更改时进行判断.使用乐观锁前,要先 设置mysql事务的隔离级别transaction-isolation = READ-COMMITTED  2.a用户的订单有两种商品,这样提交订单后,假如第一种成功,第二种失败,很显然在订单一个函数里面写一个逻辑是行不通的,应该要么同时成功,要么同时失败。于是自然而然就用到了事务。'''#urls.pyfrom django.urls import pathfrom app01.view import orderurlpattern = [  path('order/create', order.Create.as_view()),]#common文件夹下的func.py文件import time,randomdef get_order_id():  str_all="1242356796734534"  return time.strftime("%Y%m%d%H%M%S")+"".join(random.sample(str_all,5))#view文件夹下的order.py文件from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom django.db import transactionfrom django import formsfrom django.core.cache import cachefrom common import funcclass OrderForm():  phone = forms.CharField(    error_message = {      'required': "手机号不能为空"    },       # 调用Form组件中的验证器来校验手机号    # validators=[RegexValidator(r'1[1-9][0-9]{9}', '手机号格式不正确')],   )      token = forms.CharField( error_messages={      "required": "token不能为空"    })  province=forms.CharField( error_messages={      "required": "省份不能为空"    })  city = forms.CharField(error_messages={    "required": "城市不能为空"  })  county = forms.CharField(error_messages={    "required": "县/区不能为空"  })  address = forms.CharField(error_messages={    "required": "详细地址不能为空"  })  name = forms.CharField(error_messages={    "required": "姓名不能为空"  })        class Create(APIView):  @transaction.atomic  def post(self, requset):    param = request.data    #form表单检验订单数据是否符合规范    for_obj = OrderForm(param)    #校验成功,并且买东西了    if for_obj.is_valid() and param.get('buy_list'):       buy_list = param.get("buy_list")        #固定方法拿到付款用户的id        if request.META.get("HTTP_X_FORWARDED_FOR"):          host_ip = request.META["HTTP_X_FROWARDED_FOR"]        else:          host_ip = request.META["REMOTE_ADDR"]        #校验token,保证登入状态        cache_data = cache.get(param["token"])        if not cache_data:          return Response({"code": 202, "msg": "错误的token"})        openid = cache_data.split("&")[0]        #通过openid查找用户        user_data = models.Wxuser.objects.filter(openid=openid).first()    #组织部分总订单数据  order_data = {"consignee_mobile": param['phone'],   'consignee_name': param['name'],   'wxuser_id': user_data.id,   "memo": param['remark'],   "consignee_area": f"{param['province']},{param['city']},{param['county']}",   "consignee_address": param['address'],   }    order_data['order_id']=func.get_order_id()        order_data["order_total"]=0#获取用户购买商品的id        all_product_id=list(buy_list.keys())        #通过id来获取商品的信息        product_data=models.Product.objects.filter(product_id__in=all_product_id).all()#开启事务 sid = transaction.savepoint()        for product in product_data:          product.product_id=str(product.product_id)          # num=buy_list[product.id]          #获取商品的库存          for i in range(3):product_stock = product.stock.quantitynew_product_stock = product_stock-buy_list[product.product_id]if new_product_stock<0:  transaction.savepoint_rollback(sid)  return Response({"code": 204, "msg": f"{product.name}库存不足"})#乐观锁res=models.Stock.objects.filter(quantity=product_stock,stock_id=product.stock.stock_id).update(quantity=new_product_stock)if not res :  #如果两次都不成功,就让用户重新下单  if i==2:    transaction.savepoint_rollback(sid)    return Response({"code": 205, "msg": "下单失败从新下单"})  else:    continueelse:  break          #组织子订单数据          order_item_data = {'order_id': order_data['order_id'], 'product_id': product.product_id,"name": product.name, "image": product.image, "price": product.price,        "nums": buy_list[product.product_id], "brief": product.brief}          #创建数据          models.Order_items.objects.create(**order_item_data)          #获取订单总金额          order_data["order_total"] += buy_list[product.product_id]*product.price        #创建总订单        models.Order.objects.create(**order_data)        transaction.savepoint_commit(sid)        #提交延时任务,判断订单再指定时间内,有没有支付,如果没有支付,回滚库存,取消订单        func.check_order(order_data['order_id'])        #如果pay_methon是外面传的        pay_methon= "Wxpay"        try:          #导入app01.Pay.{pay_methon}          pay_filed=importlib.import_module(f"app01.Pay.{pay_methon}")          #用反射获取,这个文件中的类          pay_methon_class=getattr(pay_filed,pay_methon)        except:          return Response({"code": 205, "msg": "错误的支付方式"})        order_data['ip'] = host_ip        order_data['open_id'] = openid        #获取小程序所需的支付数据        pay_data= pay_methon_class().pay(order_data)        # pay_methon ="Alipay"        # if pay_methon=="Wxpay":        #   Wxpay().pay        # elif:...        # pay_methon        return Response({"code": 200, "msg": "ok","data":pay_data})    else:      return Response({"code": 203, "msg": "缺少参数"})        class Notify(APIView):  def post(self,request,paymethon):    pay_filed = importlib.import_module(f"app01.Pay.{paymethon}")    pay_methon_class = getattr(pay_filed, paymethon)    data=pay_methon_class().notify(request.data)    if data['stauts']=="suc":      models.Order.objects.filter(order_id=data['order_id']).update(pay_status="")

celery实现库存回滚

#proj_celery文件夹下的celery.py文件import celeryimport time# broker='redis://127.0.0.1:6379/2' 不加密码backend = 'redis://127.0.0.1:6379/1'broker = 'redis://127.0.0.1:6379/2'cel = celery.Celery('test', backend=backend, broker=broker)import os, sysimport djangoBASE_DIR = os.path.dirname(os.path.dirname(__file__)) # 定位到你的django根目录# sys.path.append(os.path.join(BASE_DIR, "app01"))sys.path.append(os.path.abspath(BASE_DIR))os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")django.setup()from django.db import [email protected]@transaction.atomicdef del_order(order_id):  from app01 import models  # 查看订单数据  order_data = models.Order.objects.filter(order_id=order_id, pay_status=False).first()  # 如果有数据表示没有支付,要进行库存回滚,和取消订单  if order_data:    # 获取该订单下的所有子订单    order_items = models.Order_items.objects.filter(order_id=order_id).all()    # 将子订单中的数据转变成 {商品id:购买数量,。。。}的格式    product_all_dic = {item.product_id: item.nums for item in order_items}    # 获取所有商品的id,成为list格式    product_all_id = list(product_all_dic.keys())    # 获取所有的商品    all_product = models.Product.objects.filter(product_id__in=product_all_id).all()    sid = transaction.savepoint()    # 把对应的商品进行库存回滚    for product in all_product:      for i in range(3):        stock = product.stock.quantity        new_stock = stock + product_all_dic[product.product_id]        #乐观锁        res = models.Stock.objects.filter(stock_id=product.stock.stock_id, quantity=stock).update(          quantity=new_stock)        if not res:          if i == 2:transaction.savepoint_rollback(sid)# 如果这个执行失败了,那我们要从新提交任务,不然库存无法回滚from app01.common import funcfunc.check_order(order_id, 1)return          else:continue        else:          break    # 修改订单状态    res1 = models.Order.objects.filter(order_id=order_id, pay_status=False).update(status="dead")    if res1:      transaction.savepoint_commit(sid)    else:      transaction.savepoint_rollback(sid)

悲观锁和乐观锁

悲观锁和乐观锁与并发订单处理

以上就是本次介绍的全部相关知识点,感谢大家的学习和对AIDI的支持。


  • 上一条:
    django框架中ajax的使用及避开CSRF 验证的方式详解
    下一条:
    django框架单表操作之增删改实例分析
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Filament v3.1版本发布(0个评论)
    • docker + gitea搭建一个git服务器流程步骤(0个评论)
    • websocket的三种架构方式使用优缺点浅析(0个评论)
    • ubuntu20.4系统中宿主机安装nginx服务,docker容器中安装php8.2实现运行laravel10框架网站(0个评论)
    • phpstudy_pro(小皮面板)中安装最新php8.2.9版本流程步骤(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2018-05
    • 2020-02
    • 2020-03
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-11
    • 2021-03
    • 2021-09
    • 2021-10
    • 2021-11
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-08
    • 2023-08
    • 2023-10
    • 2023-12
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客