美多商城之订单(提交订单1)

二、提交订单

提示:

  • 确认了要结算的商品信息后,就可以去提交订单了。

2.1 创建订单数据库表

生成的订单数据要做持久化处理,而且需要在《我的订单》页面展示出来。

2.1.1. 订单数据库表分析

注意:

  • 订单号不再采用数据库自增主键,而是由后端生成。
  • 一个订单中可以有多个商品信息,订单基本信息和订单商品信息是一对多的关系。

2.1.2. 订单模型类迁移建表

from meiduo_mall.utils.models import BaseModel
from users.models import User, Address
from goods.models import SKUclass 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_namedef __str__(self):return self.order_idclass 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="数量")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_namedef __str__(self):return self.sku.name

模型迁移:

 解决办法:

2.2 保存订单基本信息和订单商品信息

2.2.1. 提交订单接口设计和定义

1.请求方式

选项方案
请求方法POST
请求地址/orders/commit/
#  订单提交url(r'^orders/commit/$', views.OrderCommitView.as_view()),

2.请求参数:JSON

参数名类型是否必传说明
address_idint用户地址编号
pay_methodint用户支付方式

3.响应结果:JSON

字段说明
code状态码
errmsg错误信息
order_id订单编号

4.后端接口定义

from django.contrib.auth.mixins import LoginRequiredMixin# Create your views here.
class OrderCommitView(LoginRequiredMixin, View):"""订单提交"""def post(self, request):"""保存订单信息和订单商品信息"""pass

提示:

  • 订单数据分为订单基本信息和订单商品信息,二者为一对多的关系。

  • 保存到订单的数据是从Redis购物车中的已勾选的商品信息。

2.2.2. 保存订单基本信息

from django.contrib.auth.mixins import LoginRequiredMixin
import json
from django import http
from .models import OrderInfo, OrderGoods
from django.utils import timezone
from meiduo_mall.utils.response_code import RETCODEclass OrderCommitView(LoginRequiredJSONMixin, View):"""提交订单"""def post(self, request):"""保存订单信息和订单商品信息"""# 获取当前要保存的订单数据json_dict = json.loads(request.body.decode())address_id = json_dict.get('address_id')pay_method = json_dict.get('pay_method')# 校验参数if not all([address_id, pay_method]):return http.HttpResponseForbidden('缺少必传参数')# 判断address_id是否合法try:address = Address.objects.get(id=address_id)except Exception:return http.HttpResponseForbidden('参数address_id错误')# 判断pay_method是否合法if pay_method not in [OrderInfo.PAY_METHODS_ENUM['CASH'], OrderInfo.PAY_METHODS_ENUM['ALIPAY']]:return http.HttpResponseForbidden('参数pay_method错误')# 获取登录用户user = request.user# 生成订单编号:年月日时分秒+用户编号order_id = timezone.localtime().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id)# 保存订单基本信息 OrderInfo(一)order = OrderInfo.objects.create(order_id=order_id,user=user,address=address,total_count=0,  # 这儿的数字未知,先写默认,后来再给它赋值total_amount=Decimal('0'),freight=Decimal('10.00'),pay_method=pay_method,status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] elseOrderInfo.ORDER_STATUS_ENUM['UNSEND'])pass

2.2.3. 保存订单商品信息

class OrderCommitView(LoginRequiredJSONMixin, View):"""提交订单"""def post(self, request):"""保存订单信息和订单商品信息"""# 获取当前保存订单时需要的信息......# 保存订单基本信息 OrderInfo(一)......# 从redis读取购物车中被勾选的商品信息redis_conn = get_redis_connection('carts')redis_cart = redis_conn.hgetall('carts_%s' % user.id)selected = redis_conn.smembers('selected_%s' % user.id)carts = {}for sku_id in selected:carts[int(sku_id)] = int(redis_cart[sku_id])sku_ids = carts.keys()# 遍历购物车中被勾选的商品信息for sku_id in sku_ids:# 查询SKU信息sku = SKU.objects.get(id=sku_id)# 判断SKU库存sku_count = carts[sku.id]if sku_count > sku.stock:return http.JsonResponse({'code': RETCODE.STOCKERR, 'errmsg': '库存不足'})# SKU减少库存,增加销量sku.stock -= sku_countsku.sales += sku_countsku.save()# 修改SPU销量sku.goods.sales += sku_countsku.goods.save()# 保存订单商品信息 OrderGoods(多)OrderGoods.objects.create(order=order,sku=sku,count=sku_count,price=sku.price,)# 保存商品订单中总价和总数量order.total_count += sku_countorder.total_amount += (sku_count * sku.price)# 添加邮费和保存订单信息order.total_amount += order.freightorder.save()# 清除购物车中已结算的商品pl = redis_conn.pipeline()pl.hdel('carts_%s' % user.id, *selected)pl.srem('selected_%s' % user.id, *selected)pl.execute()# 响应提交订单结果return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '下单成功', 'order_id': order.order_id})

2.3 使用事务保存订单数据

重要提示:

  • 在保存订单数据时,涉及到多张表(OrderInfo、OrderGoods、SKU、SPU)的数据修改,对这些数据的修改应该是一个整体事务,即要么一起成功,要么一起失败。
  • Django中对于数据库的事务,默认每执行一句数据库操作,便会自动提交。所以我们需要在保存订单中自己控制数据库事务的执行流程。

2.3.1. Django中事务的使用

1.Django中事务的使用方案

  • 在Django中可以通过django.db.transaction模块提供的atomic来定义一个事务。
  • atomic提供两种方案实现事务:

    • 装饰器用法:

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

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

2.事务方案的选择:

  • 装饰器用法:整个视图中所有MySQL数据库的操作都看做一个事务,范围太大,不够灵活。而且无法直接作用于类视图。
  • with语句用法:可以灵活的有选择性的把某些MySQL数据库的操作看做一个事务。而且不用关心视图的类型。
  • 综合考虑后我们选择 with语句实现事务

3.事务中的保存点:

  • 在Django中,还提供了保存点的支持,可以在事务中创建保存点来记录数据的特定状态,数据库出现错误时,可以回滚到数据保存点的状态。
from django.db import transaction# 创建保存点
save_id = transaction.savepoint()  
# 回滚到保存点
transaction.savepoint_rollback(save_id)
# 提交从保存点到当前状态的所有数据库事务操作
transaction.savepoint_commit(save_id)

2.3.2. 使用事务保存订单数据

class OrderCommitView(LoginRequiredJSONMixin, View):"""订单提交"""def post(self, request):"""保存订单信息和订单商品信息"""# 获取当前保存订单时需要的信息......# 显式的开启一个事务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=Decimal('0'),freight=Decimal('10.00'),pay_method=pay_method,status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] elseOrderInfo.ORDER_STATUS_ENUM['UNSEND'])# 从redis读取购物车中被勾选的商品信息redis_conn = get_redis_connection('carts')redis_cart = redis_conn.hgetall('carts_%s' % user.id)selected = redis_conn.smembers('selected_%s' % user.id)carts = {}for sku_id in selected:carts[int(sku_id)] = int(redis_cart[sku_id])sku_ids = carts.keys()# 遍历购物车中被勾选的商品信息for sku_id in sku_ids:# 查询SKU信息sku = SKU.objects.get(id=sku_id)# 判断SKU库存sku_count = carts[sku.id]if sku_count > sku.stock:# 出错就回滚transaction.savepoint_rollback(save_id)return http.JsonResponse({'code': RETCODE.STOCKERR, 'errmsg': '库存不足'})# SKU减少库存,增加销量sku.stock -= sku_countsku.sales += sku_countsku.save()# 修改SPU销量sku.spu.sales += sku_countsku.spu.save()# 保存订单商品信息 OrderGoods(多)OrderGoods.objects.create(order=order,sku=sku,count=sku_count,price=sku.price,)# 保存商品订单中总价和总数量order.total_count += sku_countorder.total_amount += (sku_count * sku.price)# 添加邮费和保存订单信息order.total_amount += order.freightorder.save()except Exception as e:logger.error(e)transaction.savepoint_rollback(save_id)return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '下单失败'})# 提交订单成功,显式的提交一次事务transaction.savepoint_commit(save_id)# 清除购物车中已结算的商品pl = redis_conn.pipeline()pl.hdel('carts_%s' % user.id, *selected)pl.srem('selected_%s' % user.id, *selected)pl.execute()# 响应提交订单结果return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '下单成功', 'order_id': order.order_id})


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部