django多种支付、并发订单处理实例代码
django实现多种支付方式
'''
#思路
我们希望,通过插拔的方式来实现多方式登录,比如新增一种支付方式,那么只要在项目中新增一个py文件,导入里面的pay方法就可以了,这样在支付业务中支付语句是不发生变化的。
所以就可以使用python的鸭子类型及面向对象的反射方法来实现功能
'''
##新建一个Pay文件夹,里面放支付方式.py文件
#Alipay.py
classAlipay:
defpay(self):
pass
#Visapay.py
classVisapay:
defpay(self):
pass
#Wxpay.py(完全按照接口文档来得)
importtime
#记得导入商户号和key哦!
fromapp01.wximportsettings
classWxpay:
defpay(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()
importrequests
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,
}
returndata_dic
defget_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]}"forkinsorted(data_dic)])
sign_str=f"{sign_str}&key={settings.pay_apikey}"
importhashlib
md5=hashlib.md5()
md5.update(sign_str.encode("utf-8"))
sign=md5.hexdigest()
returnsign.upper()
defxml_to_dic(self,xml_data):
importxml.etree.ElementTreeasET
'''
xmltodict
:paramxml_data:
:return:
'''
xml_dict={}
root=ET.fromstring(xml_data)
forchildinroot:
xml_dict[child.tag]=child.text
returnxml_dict
defget_random(self):
importrandom
data="123456789zxcvbnmasdfghjklqwertyuiopZXCVBNMASDFGHJKLQWERTYUIOP"
nonce_str="".join(random.sample(data,30))
returnnonce_str
defget_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]}"forkinsorted(data_dic)])
sign_str=f"{sign_str}&key={settings.pay_apikey}"
importhashlib
md5=hashlib.md5()
md5.update(sign_str.encode("utf-8"))
sign=md5.hexdigest()
returnsign.upper()
defget_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"""
{self.appid}
{self.mch_id}
{self.nonce_str}
{self.sign}
{self.body}
{self.out_trade_no}
1
{self.spbill_create_ip}
{self.notify_url}
{self.open_id}
JSAPI
"""
returnbody_data
##调用支付方法的语句(一般支付都是发生在订单创建好之后)
importimportlib
fromrest_framework.responseimportResponse
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:
returnResponse({"code":205,"msg":"错误的支付方式"})
order_data['ip']=host_ip
order_data['open_id']=open_id
#完成支付,并把支付数据返回
pay_data=pay_method_class().pay(order_data)
'''
这里直接用反射拿到的支付类,然后使用它的pay方法,完成支付
'''
returnResponse({"code":200,"msg":"ok","data":pay_data})
django实现订单创建及支付
'''
几个注意点:
1.a,b两个用户同时买一个库存为1的商品,这样为了保证数据安全(即a买了,库存没更新,b又买了,这样就存在安全问题),需要在数据库操作时加锁,有两个选择:悲观锁和乐观锁。
悲观锁:冲突比较多的时候,使用悲观锁。悲观锁获取数据时对数据行了锁定,其他事务要想获取锁,必须等原事务结束。
乐观锁:冲突比较少的时候,使用乐观锁。查询时不锁数据,提交更改时进行判断.使用乐观锁前,要先设置mysql事务的隔离级别transaction-isolation=READ-COMMITTED
2.a用户的订单有两种商品,这样提交订单后,假如第一种成功,第二种失败,很显然在订单一个函数里面写一个逻辑是行不通的,应该要么同时成功,要么同时失败。于是自然而然就用到了事务。
'''
#urls.py
fromdjango.urlsimportpath
fromapp01.viewimportorder
urlpattern=[
path('order/create',order.Create.as_view()),
]
#common文件夹下的func.py文件
importtime,random
defget_order_id():
str_all="1242356796734534"
returntime.strftime("%Y%m%d%H%M%S")+"".join(random.sample(str_all,5))
#view文件夹下的order.py文件
fromrest_framework.viewsimportAPIView
fromrest_framework.responseimportResponse
fromdjango.dbimporttransaction
fromdjangoimportforms
fromdjango.core.cacheimportcache
fromcommonimportfunc
classOrderForm():
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":"姓名不能为空"
})
classCreate(APIView):
@transaction.atomic
defpost(self,requset):
param=request.data
#form表单检验订单数据是否符合规范
for_obj=OrderForm(param)
#校验成功,并且买东西了
iffor_obj.is_valid()andparam.get('buy_list'):
buy_list=param.get("buy_list")
#固定方法拿到付款用户的id
ifrequest.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"])
ifnotcache_data:
returnResponse({"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()
forproductinproduct_data:
product.product_id=str(product.product_id)
#num=buy_list[product.id]
#获取商品的库存
foriinrange(3):
product_stock=product.stock.quantity
new_product_stock=product_stock-buy_list[product.product_id]
ifnew_product_stock<0:
transaction.savepoint_rollback(sid)
returnResponse({"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)
ifnotres:
#如果两次都不成功,就让用户重新下单
ifi==2:
transaction.savepoint_rollback(sid)
returnResponse({"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:
returnResponse({"code":205,"msg":"错误的支付方式"})
order_data['ip']=host_ip
order_data['open_id']=openid
#获取小程序所需的支付数据
pay_data=pay_methon_class().pay(order_data)
#pay_methon="Alipay"
#ifpay_methon=="Wxpay":
#Wxpay().pay
#elif:...
#pay_methon
returnResponse({"code":200,"msg":"ok","data":pay_data})
else:
returnResponse({"code":203,"msg":"缺少参数"})
classNotify(APIView):
defpost(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)
ifdata['stauts']=="suc":
models.Order.objects.filter(order_id=data['order_id']).update(pay_status="")
celery实现库存回滚
#proj_celery文件夹下的celery.py文件
importcelery
importtime
#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)
importos,sys
importdjango
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()
fromdjango.dbimporttransaction
@cel.task
@transaction.atomic
defdel_order(order_id):
fromapp01importmodels
#查看订单数据
order_data=models.Order.objects.filter(order_id=order_id,pay_status=False).first()
#如果有数据表示没有支付,要进行库存回滚,和取消订单
iforder_data:
#获取该订单下的所有子订单
order_items=models.Order_items.objects.filter(order_id=order_id).all()
#将子订单中的数据转变成{商品id:购买数量,。。。}的格式
product_all_dic={item.product_id:item.numsforiteminorder_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()
#把对应的商品进行库存回滚
forproductinall_product:
foriinrange(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)
ifnotres:
ifi==2:
transaction.savepoint_rollback(sid)
#如果这个执行失败了,那我们要从新提交任务,不然库存无法回滚
fromapp01.commonimportfunc
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")
ifres1:
transaction.savepoint_commit(sid)
else:
transaction.savepoint_rollback(sid)
悲观锁和乐观锁
悲观锁和乐观锁与并发订单处理
以上就是本次介绍的全部相关知识点,感谢大家的学习和对毛票票的支持。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。