django订单部分--数据库事务

匿名 (未验证) 提交于 2019-12-03 00:43:02

订单号不再采用数据库自增主键,而是由后端生成创建。

终端命令python manage.py startapp orders 创建订单应用orders,编辑模型类models.py

from django.db import models from meiduo_mall.utils.models import BaseModel from users.models import User, Address from goods.models import SKU  # Create your models here.   class OrderInfo(BaseModel):     """     订单信息     """     PAY_METHODS_ENUM = {         "CASH": 1,         "ALIPAY": 2     }      PAY_METHOD_CHOICES = (         (1, "货到付款"),         (2, "支付宝"),     )      ORDER_STATUS_ENUM = {         "UNPAID": 1,         "UNSEND": 2,         "UNRECEIVED": 3,         "UNCOMMENT": 4,         "FINISHED": 5     }      ORDER_STATUS_CHOICES = (         (1, "待支付"),         (2, "待发货"),         (3, "待收货"),         (4, "待评价"),         (5, "已完成"),         (6, "已取消"),     )      order_id = models.CharField(max_length=64, primary_key=True, verbose_name="订单号")     user = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name="下单用户")     address = models.ForeignKey(Address, on_delete=models.PROTECT, verbose_name="收获地址")     total_count = models.IntegerField(default=1, verbose_name="商品总数")     total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="商品总金额")     freight = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="运费")     pay_method = models.SmallIntegerField(choices=PAY_METHOD_CHOICES, default=1, verbose_name="支付方式")     status = models.SmallIntegerField(choices=ORDER_STATUS_CHOICES, default=1, verbose_name="订单状态")      class Meta:         db_table = "tb_order_info"         verbose_name = 订单基本信息         verbose_name_plural = verbose_name   class OrderGoods(BaseModel):     """     订单商品     """     SCORE_CHOICES = (         (0, 0分),         (1, 20分),         (2, 40分),         (3, 60分),         (4, 80分),         (5, 100分),     )     order = models.ForeignKey(OrderInfo, related_name=skus, on_delete=models.CASCADE, verbose_name="订单")     sku = models.ForeignKey(SKU, on_delete=models.PROTECT, verbose_name="订单商品")     count = models.IntegerField(default=1, verbose_name="数量")     # max_digits :最大的位数。decimal_places :小数点后面保留多少位     price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="单价")     comment = models.TextField(default="", verbose_name="评价信息")     score = models.SmallIntegerField(choices=SCORE_CHOICES, default=5, verbose_name=满意度评分)     is_anonymous = models.BooleanField(default=False, verbose_name=是否匿名评价)     is_commented = models.BooleanField(default=False, verbose_name=是否评价了)      class Meta:         db_table = "tb_order_goods"         verbose_name = 订单商品         verbose_name_plural = verbose_name

settings.py配置文件(红色斜体):

INSTALLED_APPS = [     django.contrib.admin,     django.contrib.auth,     django.contrib.contenttypes,     django.contrib.sessions,     django.contrib.messages,     django.contrib.staticfiles,      rest_framework,     corsheaders,     ckeditor,  # 富文本编辑器     ckeditor_uploader,  # 富文本编辑器上传图片模块     django_crontab,  # 定时任务     haystack,#对接Elasticsearch      users.apps.UsersConfig,#注册用户模块应用     verifications.apps.VerificationsConfig,#验证模块     oauth.apps.OauthConfig,#第三方登录     areas.apps.AreasConfig,#省市区数据     contents.apps.ContentsConfig,#主页广告内容     goods.apps.GoodsConfig,#商品信息     ‘orders.apps.OrdersConfig‘, # 订单  ]

生成迁移文件并执行迁移:

python manage.py makemigrations

python manage.py migrate

订单结算页面所需的数据从购物车中勾选而来。

在orders/serialziers.py中创建序列化器

class CartSKUSerializer(serializers.ModelSerializer):     """     购物车商品数据序列化器     """     count = serializers.IntegerField(label=数量)      class Meta:         model = SKU         fields = (id, name, default_image_url, price, count)   class OrderSettlementSerializer(serializers.Serializer):     """     订单结算数据序列化器     """     freight = serializers.DecimalField(label=运费, max_digits=10, decimal_places=2)     skus = CartSKUSerializer(many=True)

在orders/views.py中编写视图:

from rest_framework.views import APIView from rest_framework.permissions import IsAuthenticated from django_redis import get_redis_connection from rest_framework.response import Response from decimal import Decimal  from goods.models import SKU from . import serializers   class OrderSettlementView(APIView):     """     订单结算     """     permission_classes = [IsAuthenticated]      def get(self, request):         """         获取         """         user = request.user          # 从购物车中获取用户勾选要结算的商品信息         redis_conn = get_redis_connection(carts)         # 存放的是redis中所有的购物车数据(包含了勾选的和未勾选的)         redis_cart = redis_conn.hgetall(cart_%s % user.id)         # 读取出购物车中被勾选的商品的sku_id         cart_selected = redis_conn.smembers(selected_%s % user.id)          cart = {} # 保存被勾选的商品的信息         for sku_id in cart_selected:             cart[int(sku_id)] = int(redis_cart[sku_id])          # 查询商品信息         skus = SKU.objects.filter(id__in=cart.keys())         for sku in skus:             sku.count = cart[sku.id]          # 运费         # float 1.23  ==>  123 * 10 ^ -2  ==> 1.2299999999...         # Decimal 1.23   1(整数部分)  23(小数部分)   ==> 1.23         freight = Decimal(10.00)          serializer = serializers.OrderSettlementSerializer({freight: freight, skus: skus})         return Response(serializer.data)

配置主路由(红色加粗斜体):

urlpatterns = [     url(r^admin/, admin.site.urls),     #验证码     url(r^,include(verifications.urls)),     #用户     url(r^,include(users.urls)),     #QQ登录     url(r^oauth/,include(oauth.urls)),     # 省市区     url(r^, include(areas.urls)),     # CKEditor     url(r^ckeditor/, include(ckeditor_uploader.urls)),     # 商品     url(r^, include(goods.urls)),     # 购物车     url(r^, include(carts.urls)),     # 订单     url(r‘^‘, include(‘orders.urls‘)),      ]

orders/urls.py中添加路由:

from django.conf.urls import url from . import views   urlpatterns = [     # 确认订单     url(r^orders/settlement/$, views.OrderSettlementView.as_view()), ]

前端:修改place_order.html,增加Vue变量,新建place_order.js

在orders/views.py中创建视图:

class CommitOrderView(CreateAPIView):     """提交订单"""      # 登录用户才能访问     permission_classes = [IsAuthenticated]     # 指定序列化器     serializer_class = serializers.CommitOrderSerializer

在orders/serializers.py中创建序列化器:

class CommitOrderSerializer(serializers.ModelSerializer):     """提交订单"""      class Meta:         model = OrderInfo         # order_id :输出;address 和 pay_method : 输入         fields = (order_id, address, pay_method)         read_only_fields = (order_id,)         # 指定address 和 pay_method 为输出         extra_kwargs = {             address: {                 write_only: True,             },             pay_method: {                 write_only: True,             }         }      def create(self, validated_data):         """保存订单"""         pass         
def create(self, validated_data):     # 获取当前下单用户      # 生成订单编号      # 保存订单基本信息数据 OrderInfo      # 从redis中获取购物车结算商品数据      # 遍历结算商品:          # 判断商品库存是否充足          # 减少商品库存,增加商品销量          # 保存订单商品数据      # 在redis购物车中删除已计算商品数据

在保存订单数据中,涉及到多张表(OrderInfo、OrderGoods、SKU)的数据修改,对这些数据的修改应该是一个整体事务,即要么一起成功,要么一起失败。

Django中对于数据库的事务,默认每执行一句数据库操作,便会自动提交。我们需要在保存订单中自己控制数据库事务的执行流程。

在Django中可以通过django.db.transaction模块提供的atomic来定义一个事务,atomic提供两种用法

装饰器用法:可以装饰在视图函数上

from django.db import transaction  @transaction.atomic def viewfunc(request):     # 这些代码会在一个事务中执行     ...

with语句用法

from django.db import transaction  def viewfunc(request):     # 这部分代码不在事务中,会被Django自动提交     ...      with transaction.atomic():         # 这部分代码会在事务中执行         ...

在Django中,还提供了保存点的支持,可以在事务中创建保存点来记录数据的特定状态,数据库出现错误时,可以恢复到数据保存点的状态

from django.db import transaction  # 创建保存点 save_id = transaction.savepoint()    # 回滚到保存点 transaction.savepoint_rollback(save_id)  # 提交从保存点到当前状态的所有数据库事务操作 transaction.savepoint_commit(save_id)

   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())                  # 处理订单商品                 for sku in skus:                     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)                      # 减少库存                     new_stock = origin_stock - sku_count                     new_sales = origin_sales + sku_count                      sku.stock = new_stock                     sku.sales = new_sales                     sku.save()                      # 累计商品的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,                     )                  # 更新订单的金额数量信息                 order.total_amount += order.freight                 order.save()              except 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

在多个用户同时发起对同一个商品的下单请求时,先查询商品库存,再修改商品库存,会出现资源竞争问题,导致库存的最终结果出现异常。

悲观锁

  • 当查询某条记录时,即让数据库为该记录加锁,锁住记录后别人无法操作,使用类似如下语法

select stock from tb_sku where id=1 for update;  SKU.objects.select_for_update().get(id=1)

悲观锁类似于我们在多线程资源竞争时添加的互斥锁,容易出现死锁现象,采用不多。

乐观锁

乐观锁并不是真实存在的锁,而是在更新的时候判断此时的库存是否是之前查询出的库存,如果相同,表示没人修改,可以更新库存,否则表示别人抢过资源,不再执行库存更新。类似如下操作

update tb_sku set stock=2 where id=1 and stock=7;  SKU.objects.filter(id=1, stock=7).update(stock=2)

任务队列

例如秒杀功能,将下单的逻辑放到任务队列中(如celery),将并行转为串行,所有人排队下单。比如开启只有一个进程的Celery,一个订单一个订单的处理。

def create(self, validated_data):         """创建订单记录:保存OrderInfo和OrderGoods信息"""          # 获取当前保存订单时需要的信息         # 获取当前的登录用户         user = self.context[request].user         # 生成订单编号         # order_id = ‘时间‘+‘user_id‘         # timezone.now() == datetime类型的对象         # 20180706085001+000000001         order_id = timezone.now().strftime(%Y%m%d%H%M%S) + (%09d % user.id)         # 获取地址和支付方式         address = validated_data.get(address)         pay_method = validated_data.get(pay_method)          # 明显的开启事务         with transaction.atomic():             # 在安全的地方,创建保存点,将来操作数据库失败回滚到此             save_id = transaction.savepoint()              try:                 # 保存订单基本信息 OrderInfo                 order = OrderInfo.objects.create(                     order_id=order_id,                     user = user,                     address = address,                     total_count = 0,                     total_amount = 0,                     freight = Decimal(10.00),                     pay_method = pay_method,                     # 如果用户传入的是"支付宝支付",那么下了订单后,订单的状态要是"待支付"                     # 如果用户传入的是"货到付款",那么下了订单后,订单的状态要是"代发货"                     status = OrderInfo.ORDER_STATUS_ENUM[UNPAID] if pay_method==OrderInfo.PAY_METHODS_ENUM[ALIPAY] else OrderInfo.ORDER_STATUS_ENUM[UNSEND]                 )                  # 从redis读取购物车中被勾选的商品信息                 redis_conn = get_redis_connection(carts)                 # 读取出所有的购物车数据                 # redis_cart = {b‘1‘:b‘10‘, b‘2‘:b‘20‘, b‘3‘:b‘30‘}                 redis_cart = redis_conn.hgetall(cart_%s % user.id)                 # cart_selected = [b‘1‘, b‘2‘]                 cart_selected = redis_conn.smembers(selected_%s % user.id)                 # 定义将来要支付的商品信息的字典                 # carts = {1:10, 2:20}                 carts = {}                 for sku_id in cart_selected:                     carts[int(sku_id)] = int(redis_cart[sku_id])                  # 读取出所有要支付的商品的sku_id                 # sku_ids = [1,2]                 sku_ids = carts.keys()                 # 遍历购物车中被勾选的商品信息                 for sku_id in sku_ids:                      # 死循环的下单:当库存满足,你在下单时,库存没有同时的被别人的更改,下单成功                     # 如果下单库存被更改看,但是你的sku_count依然在被更改后的库存范围内,继续下单                     # 直到库存真的不满足条件时才下单失败                     while True:                          # 获取sku对象                         sku = SKU.objects.get(id=sku_id)                          # 获取原始的库存和销量                         origin_stock = sku.stock                         origin_sales = sku.sales                          sku_count = carts[sku_id]                         # 判断库存?                         if sku_count > origin_stock:                             # 回滚                             transaction.savepoint_rollback(save_id)                             raise serializers.ValidationError(库存不足)                          # 模拟网络延迟                         import time                         time.sleep(5)                          # 减少库存,增加销量 SKU?                         # sku.stock -= sku_count                             # origin_stock = sku.stock                             # new_stock = origin_stock - sku_count                          # sku.sales += sku_count                         # sku.save()                          # 读取要更新的库存和销量                         new_stock = origin_stock - sku_count                         new_sales = origin_sales + sku_count                          # 使用乐观锁更新库存:在调用update()去更新库存之前,使用filter()拿着原始的库存去查询记录是否存在                         # 如果记录不存在的,在调用update()时返回0                         result = SKU.objects.filter(id=sku_id, stock=origin_stock).update(stock=new_stock, sales=new_sales)                         if 0 == result:                             # 死循环的下单:当库存满足,你在下单时,库存没有同时的被别人的更改,下单成功                             # 如果下单库存被更改看,但是你的sku_count依然在被更改后的库存范围内,继续下单                             # 直到库存真的不满足条件时才下单失败                             continue                          # 修改SPU销量                         sku.goods.sales += sku_count                         sku.goods.save()                          # 保存订单商品信息 OrderGoods                         OrderGoods.objects.create(                             order=order,                             sku = sku,                             count = sku_count,                             price = sku.price,                         )                          # 累加计算总数量和总价                         order.total_count += sku_count                         order.total_amount += (sku_count * sku.price)                          # 下单成功要跳出死循环                         break                  # 最后加入邮费和保存订单信息                 order.total_amount += order.freight                 order.save()             except serializers.ValidationError:                 # 这里不会滚的原因,是因为前面已经有了回滚的动作                 raise             except Exception:                 transaction.savepoint_rollback(save_id)                 raise # 自动的将捕获的异常抛出,不需要给异常起别名              # 没有问题,需要明显的提交             transaction.savepoint_commit(save_id)              # 清除购物车中已结算的商品             pl = redis_conn.pipeline()             pl.hdel(cart_%s % user.id, *sku_ids)             pl.srem(selected_%s % user.id, *sku_ids)             pl.execute()              # 响应结果             return order

事务隔离级别指的是在处理同一个数据的多个事务中,一个事务修改数据后,其他事务何时能看到修改后的结果。

MySQL数据库事务隔离级别主要有四种:

MySQL数据库默认使用可重复读( Repeatable read),而使用乐观锁的时候,如果一个事务修改了库存并提交了事务,那其他的事务应该可以读取到修改后的数据值,所以不能使用可重复读的隔离级别,应该修改为读取已提交Read committed。

修改方法:

在orders/urls.py文件中添加路由(红色斜体):

urlpatterns = [     # 确认订单     url(r^orders/settlement/$, views.OrderSettlementView.as_view()),     # 提交订单     url(r‘^orders/$‘, views.CommitOrderView.as_view()), ]

原文:https://www.cnblogs.com/cl-python/p/9343966.html

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!