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

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)

悲观锁和乐观锁

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

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

(0)

相关推荐

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

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

  • Django使用httpresponse返回用户头像实例代码

    本文研究的主要是Django使用httpresponse返回用户头像,下面是相关实例代码. 当请求一个页面时,Django 把请求的 metadata 数据包装成一个 HttpRequest 对象,然后 Django 加载合适的 view 方法,把这个 HttpRequest 对象作为第一个参数传给 view 方法.任何 view 方法都应该返回一个 HttpResponse 对象. 代码示例: def test(request): # 获取当前文件的路径.父路径以及下一层的路径 curr_di

  • Django中间件工作流程及写法实例代码

    熟悉web开发的同学对hook钩子肯定不陌生,通过钩子可以方便的实现一些触发和回调,并且做一些过滤和拦截. django中的中间件(middleware)就是类似钩子的一种存在.下面我们来介绍一下,并且给出一些实例. 1.Middleware的工作流程 我盗了一个图,看网上很多人用这个图,来源已经追不明白了.简单声明一下,这个图不是我的.看着图我们分析一下: 1)django的请求相应流程:HttpRequest -> RequestMiddleware -> view function -&

  • django使用xlwt导出excel文件实例代码

    本文研究的主要是记录一下下导出的方法,并没有做什么REST处理和异常处理. 维护统一的style样式,可以使导出的数据更加美观. def export_excel(request): # 设置HttpResponse的类型 response = HttpResponse(content_type='application/vnd.ms-excel') response['Content-Disposition'] = 'attachment;filename=user.xls' # new一个文

  • vue+django实现一对一聊天功能的实例代码

    vue+django实现一对一聊天和消息推送的功能.主要是通过websocket,由于Django不支持websocket,所以我使用了django-channels.考虑到存储量的问题,我并没有把聊天信息存入数据库,服务端的作用相当于一个中转站.我只讲述实现功能的结构性代码,具体的实现还请大家看源代码. 前端聊天界面 前端消息提醒界面 后端代码 效果展示 实现过程 后端 首先,我们需要先定义websocket的两条连接路径.ws/chat/xxx/(xxx指代聊天组)这条路径是当聊天双方都进入

  • Django接收照片储存文件的实例代码

    后端: from rest_framework.views import APIView from car import settings from django.shortcuts import render, redirect, HttpResponse from dal import models from django.http import JsonResponse import os BASE_DIR = os.path.dirname(os.path.dirname(os.path

  • 微信支付开发订单查询实例

    因为某一方技术的原因,可能导致商户在预期时间内都收不到最终支付通知,此时商户可以通过该API来查询订单的详细支付状态. 订单查询API的URL为: https://api.weixin.qq.com/pay/orderquery?access_token=xxxxxx URL中的参数只包含目前微信公众平台凭证access_token,而订单查询的真正数据是放在PostData中的,格式如下: {     "appid" : "wwwwb4f85f3a797777",

  • Django完整增删改查系统实例代码

    目录 一.效果图如下 二.使用步骤 1.创建并配置一个django项目 1.1新建一个项目ch3 1.2创建应用employee 1.3指定ch3项目的应用程序emyployee 1.4与数据库相连 1.5 定义与使用模型 1.6生成迁移文件 二.实现数据库增删改查 2.1视图文件 2.2前端页面 2.2.1主页面(list_dep_old.xml) 2.2.2增加数据页面(add_dep_old.html) 2.2.3修改页面(edit_dep_old.html) 2.3在主项目中添加路由 总

  • pycharm+django创建一个搜索网页实例代码

    本文主要研究的是pycharm+django创建一个搜索网页的实例代码,具体步骤和代码示例如下. 创建工程 比如,我创建的工程目录结构如下: 命令行 进入windows命令行,进入根目录: python manage.py startapp django_web 接着你会在pycharm中发现,多了一个django_web文件夹.如下将截图: 创建一个test.html <!DOCTYPE html> <html> <head> <title>开始搜索<

  • Django模板继承 extend标签实例代码详解

    在 views.py 上修改 ... def ordered(req): return render(req, "ordered.html") def shopping_car(req): return render(req, "shopping_car.html") 在 urls.py 上修改 ... path('ordered/', views.ordered), path('shopping_car/', views.shopping_car), ... 在

随机推荐