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


Posted in Python onDecember 13, 2019

django实现多种支付方式

'''
#思路
  
  我们希望,通过插拔的方式来实现多方式登录,比如新增一种支付方式,那么只要在项目中新增一个py文件,导入里面的pay方法就可以了,这样在支付业务中支付语句是不发生变化的。
  所以就可以使用python的鸭子类型及面向对象的反射方法来实现功能

'''

##新建一个Pay文件夹,里面放支付方式.py文件
#Alipay.py
class Alipay:
  def pay(self):
    pass
#Visapay.py
class Visapay:
  def pay(self):
    pass
#Wxpay.py(完全按照接口文档来得)
import time
#记得导入商户号和key哦!
from app01.wx import settings
class 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 importlib
from rest_framework.response import Response
pay_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_ip
order_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.py
from django.urls import path
from app01.view import order
urlpattern = [
  path('order/create', order.Create.as_view()),
]


#common文件夹下的func.py文件
import time,random
def 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 APIView
from rest_framework.response import Response
from django.db import transaction
from django import forms
from django.core.cache import cache
from common import func

class 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.quantity
            new_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:
                continue
            else:
              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 celery
import 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, sys
import django

BASE_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 transaction


@cel.task
@transaction.atomic
def 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 func
            func.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)

悲观锁和乐观锁

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

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

Python 相关文章推荐
python有证书的加密解密实现方法
Nov 19 Python
Python进程间通信用法实例
Jun 04 Python
Python 中 Meta Classes详解
Feb 13 Python
CentOS 6.5下安装Python 3.5.2(与Python2并存)
Jun 05 Python
python交互式图形编程实例(三)
Nov 17 Python
python微信跳一跳系列之色块轮廓定位棋盘
Feb 26 Python
解决win64 Python下安装PIL出错问题(图解)
Sep 03 Python
Python使用APScheduler实现定时任务过程解析
Sep 11 Python
python画图常规设置方式
Mar 05 Python
Python求解排列中的逆序数个数实例
May 03 Python
Python使用paramiko连接远程服务器执行Shell命令的实现
Mar 04 Python
Python面向对象之成员相关知识总结
Jun 24 Python
Python+OpenCV+图片旋转并用原底色填充新四角的例子
Dec 12 #Python
Python+OpenCV 实现图片无损旋转90°且无黑边
Dec 12 #Python
使用python去除图片白色像素的实例
Dec 12 #Python
用Python去除图像的黑色或白色背景实例
Dec 12 #Python
python 实现将小图片放到另一个较大的白色或黑色背景图片中
Dec 12 #Python
flask的orm框架SQLAlchemy查询实现解析
Dec 12 #Python
python实现批量处理将图片粘贴到另一张图片上并保存
Dec 12 #Python
You might like
在php MYSQL中插入当前时间
2008/04/06 PHP
深入mysql_fetch_row()与mysql_fetch_array()的区别详解
2013/06/05 PHP
解析php防止form重复提交的方法
2013/07/01 PHP
部署PHP项目应该注意的几点事项分享
2013/12/20 PHP
PHP把数字转成人民币大写的函数分享
2014/06/30 PHP
qq登录,新浪微博登录接口申请过程中遇到的问题
2014/07/22 PHP
PHP会话控制实例分析
2016/12/24 PHP
php DES加密算法实例分析
2019/09/18 PHP
JavaScript面向对象编程
2008/03/02 Javascript
用jquery ajax获取网站Alexa排名的代码
2009/12/12 Javascript
jquery+json实现的搜索加分页效果
2010/03/31 Javascript
JavaScript通过元素的ID和name设置样式
2014/07/08 Javascript
用js通过url传参把数据从一个页面传到另一个页面
2014/09/01 Javascript
使用RequireJS库加载JavaScript模块的实例教程
2016/06/06 Javascript
分分钟玩转Vue.js组件
2016/10/25 Javascript
详解nodejs微信公众号开发——1.接入微信公众号
2017/04/10 NodeJs
移动端触摸滑动插件swiper使用方法详解
2017/08/11 Javascript
JS实现匀速与减速缓慢运动的动画效果封装示例
2018/08/27 Javascript
vue组件中watch props根据v-if动态判断并挂载DOM的问题
2019/05/12 Javascript
js实现数据导出为EXCEL(支持大量数据导出)
2020/03/31 Javascript
React.js组件实现拖拽排序组件功能过程解析
2020/04/27 Javascript
JS求解两数之和算法详解
2020/04/28 Javascript
[01:14]英雄,所敬略同——2018完美盛典宣传视频4K
2018/12/05 DOTA
Django中URLconf和include()的协同工作方法
2015/07/20 Python
Python实现将Excel转换成xml的方法示例
2018/08/25 Python
python实现微信机器人: 登录微信、消息接收、自动回复功能
2019/04/29 Python
Python Django框架url反向解析实现动态生成对应的url链接示例
2019/10/18 Python
Pandas+Matplotlib 箱式图异常值分析示例
2019/12/09 Python
使用css3绘制出各种几何图形
2016/08/17 HTML / CSS
办公室主任主任岗位责任制
2014/02/11 职场文书
元旦活动感言
2014/03/08 职场文书
实习协议书范本
2014/04/22 职场文书
社区活动总结报告
2014/05/05 职场文书
保护黄河倡议书
2014/05/16 职场文书
高中生逃课检讨书
2014/10/10 职场文书
2016年七夕爱情寄语
2015/12/04 职场文书