当前位置: 首页 > news >正文

网上商城之订单

订单

  • 结算订单
    • 1. 结算订单逻辑分析
    • 2. 结算订单接口设计和定义
    • 3. 结算订单后端逻辑实现
    • 4.结算订单页面渲染(直接拷贝)
        • 笔记
  • 提交订单
    • 创建订单数据库表
        • 笔记
    • 保存订单基本信息和订单商品信息
      • 1. 提交订单接口设计和定义
        • 笔记
      • 2. 保存订单基本信息
      • 3. 保存订单商品信息
        • 笔记
    • 使用事务保存订单数据
      • 1. Django中事务的使用
      • 2. 使用事务保存订单数据
        • 笔记
    • 使用乐观锁并发下单
      • 1. 并发下单问题演示和解决方案
      • 2. 使用乐观锁并发下单
        • 笔记
      • 3. MySQL事务隔离级别
    • 展示提交订单成功页面
  • 我的订单(这个自己做)
  • 笔记

结算订单

在这里插入图片描述
用来确认订单信息有没有问题,只会结算勾选了的商品

1. 结算订单逻辑分析

结算订单是从Redis购物车中查询出被勾选的商品信息进行结算并展示。

2. 结算订单接口设计和定义

  1. 请求方式
选项方案
请求方法GET
请求地址/orders/settlement/
  1. 请求参数:

  1. 响应结果:HTML

place_order.html

  1. 后端接口定义
class OrderSettlementView(LoginRequiredMixin, View):
    """结算订单"""

    def get(self, request):
        """提供订单结算页面"""
        return render(request, 'place_order.html')

3. 结算订单后端逻辑实现

class OrderSettlementView(LoginRequiredMixin, View):
    """结算订单"""

    def get(self, request):
        """提供订单结算页面"""
        # 获取登录用户
        user = request.user
        # 查询地址信息
        try:
            addresses = Address.objects.filter(user=request.user, is_deleted=False)
        except Address.DoesNotExist:
            # 如果地址为空,渲染模板时会判断,并跳转到地址编辑页面
            addresses = None

        # 从Redis购物车中查询出被勾选的商品信息
        redis_conn = get_redis_connection('carts')
        redis_cart = redis_conn.hgetall('carts_%s' % user.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])

        # 准备初始值
        total_count = 0
        total_amount = Decimal(0.00)
        # 查询商品信息
        skus = SKU.objects.filter(id__in=cart.keys())
        for sku in skus:
            sku.count = cart[sku.id]
            sku.amount = sku.count * sku.price
            # 计算总数量和总金额
            total_count += sku.count
            total_amount += sku.count * sku.price
        # 补充运费
        freight = Decimal('10.00')

        # 渲染界面
        context = {
            'addresses': addresses,
            'skus': skus,
            'total_count': total_count,
            'total_amount': total_amount,
            'freight': freight,
            'payment_amount':total_amount + freight
        }

        return render(request, 'place_order.html', context)

4.结算订单页面渲染(直接拷贝)

<h3 class="common_title">确认收货地址</h3>
<div class="common_list_con clearfix" id="get_site">
    <dl>
    {% if addresses %}
        <dt>寄送到:</dt>
        {% for address in addresses %}
        <dd @click="nowsite={{ address.id }}"><input type="radio" v-model="nowsite" value="{{ address.id }}">{{ address.province }} {{ address.city }} {{ address.district }} ({{ address.receiver }} 收) {{ address.mobile }}</dd>
        {% endfor %}
    {% endif %}
    </dl>
    <a href="{{ url('users:address') }}" class="edit_site">编辑收货地址</a>
</div>
<h3 class="common_title">支付方式</h3>
<div class="common_list_con clearfix">
    <div class="pay_style_con clearfix">
        <input type="radio" name="pay_method" value="1" v-model="pay_method">
        <label class="cash">货到付款</label>
        <input type="radio" name="pay_method" value="2" v-model="pay_method">
        <label class="zhifubao"></label>
    </div>
</div>
<h3 class="common_title">商品列表</h3>
<div class="common_list_con clearfix">
    <ul class="goods_list_th clearfix">
        <li class="col01">商品名称</li>
        <li class="col02">商品单位</li>
        <li class="col03">商品价格</li>
        <li class="col04">数量</li>
        <li class="col05">小计</li>
    </ul>
    {% for sku in skus %}
    <ul class="goods_list_td clearfix">
        <li class="col01">{{loop.index}}</li>
        <li class="col02"><img src="{{ sku.default_image.url }}"></li>
        <li class="col03">{{ sku.name }}</li>
        <li class="col04"></li>
        <li class="col05">{{ sku.price }}元</li>
        <li class="col06">{{ sku.count }}</li>
        <li class="col07">{{ sku.amount }}元</li>
    </ul>
    {% endfor %}
</div>
<h3 class="common_title">总金额结算</h3>
<div class="common_list_con clearfix">
    <div class="settle_con">
        <div class="total_goods_count"><em>{{ total_count }}</em>件商品,总金额<b>{{ total_amount }}元</b></div>
        <div class="transit">运费:<b>{{ freight }}元</b></div>
        <div class="total_pay">实付款:<b>{{ payment_amount }}元</b></div>
    </div>
</div>
<div class="order_submit clearfix">
    <a @click="on_order_submit" id="order_btn">提交订单</a>
</div>

笔记

  1. 创建子应用时,子应用创建于当前目录下
  2. 在这里插入图片描述
    注册
  3. 在这里插入图片描述
    因为响应的是html数据
  4. 在这里插入图片描述
    创建完子应用写url,这个需要注册

在这里插入图片描述
要检查模板

  1. 字典,列表最后一项数据都可以加逗号

  2. 在这里插入图片描述
    作为键的字段在查询的时候可以赋值一个对象

  3. 在这里插入图片描述
    查询指定用户的整个hash表

  4. 在这里插入图片描述
    查询指定用户的全部被勾选的信息

  5. 在这里插入图片描述
    在这里插入图片描述
    集合数据可以被遍历

  6. 在这里插入图片描述
    给出一组sku_id,可以获得一组sku

在这里插入图片描述
有address 就进行渲染

  1. 在这里插入图片描述
    operand 操作数
    Decimal 是一个更精确的浮点类型,会将小数拆成整数
    float 也是浮点类型,是用机器存储的
    Decimal 用于高精度小数的存储
    在这里插入图片描述

  2. 发现请求页面静态资源没有更新首先考虑缓存问题

  3. 在这里插入图片描述
    当捕获到异常是不会打印栈追踪路径的,最好手动加上print(e),以便于发现异常

在这里插入图片描述
直接获取默认地址

  1. 写代码的时候没有特别熟悉的需求,先从容易想到的步骤出发,逐步补全,最后针对具体错误进行改错
  2. 在这里插入图片描述
    对于这种报错可以加引号也可以自己导入

在这里插入图片描述
import 后好像就可以跟多个东西,导入当前域中的工具

提交订单

就是收集结算页面的数据存到数据库中
提示:

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

创建订单数据库表

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

  1. 订单数据库表分析
    注意:

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

    • 商品信息是一对多的关系。

在这里插入图片描述
在这里插入图片描述
由时间和user_id 拼起来的
不使用id作为订单基本信息
orm对于有自定义主键的使用自定义的,没有的话就自动生成主键

  1. 订单模型类迁移建表
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

    def __str__(self):
        return self.order_id


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="数量")
    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

    def __str__(self):
        return self.sku.name

笔记

在这里插入图片描述
choices用于限制字段值,值的格式是元祖套元祖
在这里插入图片描述
在这里插入图片描述
ENUM 是用来防止代码中出现整数的
在这里插入图片描述
进行建表

  1. 在地址栏里发出的请求时get方式

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

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

  1. 请求方式
选项方案
请求方法POST
请求地址/orders/commit/
  1. 请求参数:JSON
参数名类型是否必传说明
address_idint用户地址编号
pay_methodint用户支付方式
  1. 响应结果:JSON
字段说明
code状态码
errmsg错误信息
order_id订单编号
  1. 后端接口定义
class OrderCommitView(LoginRequiredJSONMixin, View):
    """订单提交"""

    def post(self, request):
        """保存订单信息和订单商品信息"""
        pass

提示:

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

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

笔记

  1. 保存失败是以弹框的方式进行提示
  2. 说了很多次 响应json不需要进行反向解析 但是不是很理解

2. 保存订单基本信息

class 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'] else
            OrderInfo.ORDER_STATUS_ENUM['UNSEND']
        )
        pass

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_count
            sku.sales += sku_count
            sku.save()

            # 修改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)

        # 添加邮费和保存订单信息
        order.total_amount += order.freight
        order.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})

笔记

  1. 在这里插入图片描述

  2. 接收参数,校验参数的代码直接进行拷贝
    在这里插入图片描述
    新的校验参数的方式,好像可以用列表生成式

  3. 在这里插入图片描述

  4. 在这里插入图片描述
    localtime 先获取时间, strftime 格式化时间 ,拼接9位用户id

  5. 赋值语句应该先把表达式计算出来吧

  6. 在这里插入图片描述
    给某一个变量赋值的时候加判断

  7. 在这里插入图片描述
    传递参数可以 以 k=v, 或者 v, 的形式写多行

在这里插入图片描述
这些代码直接进行拷贝

  1. 在这里插入图片描述
    新增数据时可以只穿部分字段,未给出字段用默认值

在这里插入图片描述

使用事务保存订单数据

重要提示:

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

1. Django中事务的使用

  1. Django中事务的使用方案
  • 在Django中可以通过django.db.transaction模块提供的atomic来定义一个事务。

  • atomic提供两种方案实现事务:

    • 装饰器用法:
from django.db import transaction

@transaction.atomic
def viewfunc(request):
  # 这个view中的mysql会在一个事务中执行
  # 但是这样不够灵活,因为代码可能不是都要在一个事务中执行
  # 最好是可以指定哪些sql需要在一个事务中执行
  ......
  • with语句用法:
from django.db import transaction

def viewfunc(request):
  # 这部分代码不在事务中,会被Django自动提交
  ......

  with transaction.atomic():
      # 这部分sql会在事务中执行
  ......
  1. 事务方案的选择:
  • **装饰器用法**:整个视图中所有MySQL数据库的操作都看做一个事务,范围太大,不够灵活。而且无法直接作用于类视图。

  • with语句用法:可以灵活的有选择性的把某些MySQL数据库的操作看做一个事务。而且不用关心视图的类型。

  • 综合考虑后我们选择 with语句实现事务

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

from django.db import transaction

# 创建保存点,保存点相当于打了一个标记,用来提交和还原的
# 标记了数据库的状态
save_id = transaction.savepoint()  
# 回滚到保存点
transaction.savepoint_rollback(save_id)
# 提交从保存点到当前状态的所有数据库事务操作
transaction.savepoint_commit(save_id)

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'] else
                    OrderInfo.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_count
                    sku.sales += sku_count
                    sku.save()

                    # 修改SPU销量
                    sku.spu.sales += sku_count
                    sku.spu.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)

                # 添加邮费和保存订单信息
                order.total_amount += order.freight
                order.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})

笔记

  1. mysql执行sql语句的时候会默认开启一个事务,执行完之后会提交这个事务
  2. create()会创造insert
  3. 将应该放在一个事务中的sql放在with语句内,没有错就提交,有错就进行回滚

使用乐观锁并发下单

重要提示:

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

1. 并发下单问题演示和解决方案

解决办法:

  • 悲观锁

    • 当查询某条记录时,即让数据库为该记录加锁,锁住记录后别人无法操作,使用类似如下语法
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,一个订单一个订单的处理。
    • 任务队列比乐观锁性能好些,但是代码实现的难度比较大
    • 秒杀一般用任务队列,下单用乐观锁就可以

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'] else
                    OrderInfo.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:
                    while True:
                        # 查询SKU信息
                        sku = SKU.objects.get(id=sku_id)

                        # 读取原始库存
                        origin_stock = sku.stock
                        origin_sales = sku.sales

                        # 判断SKU库存
                        sku_count = carts[sku.id]
                        if sku_count > origin_stock:
                            # 事务回滚
                            transaction.savepoint_rollback(save_id)
                            return http.JsonResponse({'code': RETCODE.STOCKERR, 'errmsg': '库存不足'})

                        # 模拟延迟
                        # import time
                        # time.sleep(5)

                        # SKU减少库存,增加销量
                        # sku.stock -= sku_count
                        # sku.sales += sku_count
                        # sku.save()

                        # 乐观锁更新库存和销量
                        new_stock = origin_stock - sku_count
                        new_sales = origin_sales + sku_count
                        result = SKU.objects.filter(id=sku_id, stock=origin_stock).update(stock=new_stock, sales=new_sales)
                        # 如果下单失败,但是库存足够时,继续下单,直到下单成功或者库存不足为止
                        if result == 0:
                            continue

                        # 修改SPU销量
                        sku.spu.sales += sku_count
                        sku.spu.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 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})

笔记

  1. 可以在写代码途中进行import
  2. continue 作用于最近的一层循环
  3. 写后端逻辑的时候,先分析页面,而后知道要查询和准备哪些数据
  4. python可以动态为对象添加属性,但是objective-c 不行
  5. 动态为其添加属性,可以方便的进行页面渲染

3. MySQL事务隔离级别

事务中有数据,数据库中也有数据,这指的是两个东西

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

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

    • Serializable:串行化,一个事务一个事务的执行。
    • Repeatable read:可重复读,无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响。
    • Read committed:读取已提交,其他事务提交了对数据的修改后(提交到数据库中),本事务就能读取到修改后的数据值。
    • Read uncommitted:读取未提交,其他事务只要修改了数据,即使未提交,本事务也能看到修改后的数据值。
    • MySQL数据库默认使用可重复读( Repeatable read)。
  • 使用乐观锁的时候,如果一个事务修改了库存并提交了事务,那其他的事务应该可以读取到修改后的数据值,所以不能使用可重复读的隔离级别(默认),应该修改为读取已提交(Read committed)。

  • 修改方式:
    在这里插入图片描述
    在这里插入图片描述
    网络上有很多使用命令修改隔离级别的,使用命令修改的方式只是临时生效
    修改配置就要重启

展示提交订单成功页面

支付方式:货到付款

在这里插入图片描述

支付方式:支付宝

在这里插入图片描述

  1. 请求方式
选项方案
请求方法GET
请求地址/orders/success/
  1. 请求参数:

  1. 响应结果:HTML

order_success.html

  1. 后端接口定义和实现
class OrderSuccessView(LoginRequiredMixin, View):
    """提交订单成功"""

    def get(self, request):
        order_id = request.GET.get('order_id')
        payment_amount = request.GET.get('payment_amount')
        pay_method = request.GET.get('pay_method')

        context = {
            'order_id':order_id,
            'payment_amount':payment_amount,
            'pay_method':pay_method
        }
        return render(request, 'order_success.html', context)
  1. 渲染提交订单成功页面信息(直接拷贝,调整缩进)
<div class="common_list_con clearfix">
    <div class="order_success">
        <p><b>订单提交成功,订单总价<em>¥{{ payment_amount }}</em></b></p>
        <p>您的订单已成功生成,选择您想要的支付方式,订单号:{{ order_id }}</p>
        <p><a href="{{ url('orders:info', args=(1, )) }}">您可以在【用户中心】->【我的订单】查看该订单</a></p>
    </div>
</div>
<div class="order_submit clearfix">
    {% if pay_method == '1' %}
        <a href="{{ url('contents:index') }}">继续购物</a>
    {% else %}
        <a @click="order_payment" class="payment">去支付</a>
    {% endif %}
</div>

我的订单(这个自己做)

  1. 请求方式
选项方案
请求方法GET
请求地址/orders/info/(?P<page_num>\d+)/
  1. 请求参数:路径参数
参数名类型是否必传说明
page_numint当前页码
  1. 响应结果:HTML

user_center_order.html

  1. 后端接口定义和实现
class UserOrderInfoView(LoginRequiredMixin, View):
    """我的订单"""

    def get(self, request, page_num):
        """提供我的订单页面"""
        user = request.user
        # 查询订单
        orders = user.orderinfo_set.all().order_by("-create_time")
        # 遍历所有订单
        for order in orders:
            # 绑定订单状态
            order.status_name = OrderInfo.ORDER_STATUS_CHOICES[order.status-1][1]
            # 绑定支付方式
            order.pay_method_name = OrderInfo.PAY_METHOD_CHOICES[order.pay_method-1][1]
            order.sku_list = []
            # 查询订单商品
            order_goods = order.skus.all()
            # 遍历订单商品
            for order_good in order_goods:
                sku = order_good.sku
                sku.count = order_good.count
                sku.amount = sku.price * sku.count
                order.sku_list.append(sku)

        # 分页
        page_num = int(page_num)
        try:
            paginator = Paginator(orders, constants.ORDERS_LIST_LIMIT)
            page_orders = paginator.page(page_num)
            total_page = paginator.num_pages
        except EmptyPage:
            return http.HttpResponseNotFound('订单不存在')

        context = {
            "page_orders": page_orders,
            'total_page': total_page,
            'page_num': page_num,
        }
        return render(request, "user_center_order.html", context)
  1. 渲染我的订单信息
<div class="right_content clearfix">
    <h3 class="common_title2">全部订单</h3>
    {% for order in page_orders %}
    <ul class="order_list_th w978 clearfix">
        <li class="col01">{{ order.create_time.strftime('%Y-%m-%d %H:%M:%S') }}</li>
        <li class="col02">订单号:{{ order.order_id }}</li>
    </ul>
    <table class="order_list_table w980">
        <tbody>
            <tr>
                <td width="55%">
                    {% for sku in order.sku_list %}
                    <ul class="order_goods_list clearfix">
                        <li class="col01"><img src="{{ sku.default_image.url }}"></li>
                        <li class="col02"><span>{{ sku.name }}</span><em>{{ sku.price }}元</em></li>
                        <li class="col03">{{ sku.count }}</li>
                        <li class="col04">{{ sku.amount }}元</li>
                    </ul>
                    {% endfor %}
                </td>
                <td width="15%">{{ order.total_amount }}元<br>含运费:{{ order.freight }}元</td>
                <td width="15%">{{ order.pay_method_name }}</td>
                <td width="15%">
                    <a @click="oper_btn_click('{{ order.order_id }}', {{ order.status }})" class="oper_btn">{{ order.status_name }}</a>
                </td>
            </tr>
        </tbody>
    </table>
    {% endfor %}
    <div class="pagenation">
        <div id="pagination" class="page"></div>
    </div>
</div>

笔记

  1. 在这里插入图片描述
    不使用LoginRequiredMixin这个类时
  2. 在这里插入图片描述

相关文章:

  • 数学建模----拟合的实现
  • v-bind用法详解
  • Java实现随机人名抽取
  • 泰克TDS3012C数字荧光示波器TDS3012C
  • LSTM介绍理解
  • 深度学习——day27 class1 week3 神经网络概览及表示
  • 六级高频词汇——Group06
  • 云计算(一)-理解云计算
  • JavaEE——No.1 多线程案例
  • MySQL如何优化性能
  • 【C语言】自定义类型—位段、枚举、联合体
  • opencv从入门到精通 哦吼 05
  • 计算机毕业设计 基于HTML+CSS+JavaScript 大气的甜品奶茶美食餐饮文化网页设计与实现23页面
  • Gadmin企业级开发平台V5.0.9版本发布
  • Linux内核设计与实现 第十六章 页高速缓存与页回写
  • 分享的文章《人生如棋》
  • log4j2输出到kafka
  • Node项目之评分系统(二)- 数据库设计
  • PHP那些事儿
  • Protobuf3语言指南
  • TypeScript迭代器
  • vue 个人积累(使用工具,组件)
  • XForms - 更强大的Form
  • 观察者模式实现非直接耦合
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 机器学习学习笔记一
  • 每个JavaScript开发人员应阅读的书【1】 - JavaScript: The Good Parts
  • 每天一个设计模式之命令模式
  • 系统认识JavaScript正则表达式
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 一道闭包题引发的思考
  • Java总结 - String - 这篇请使劲喷我
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • $L^p$ 调和函数恒为零
  • (Java)【深基9.例1】选举学生会
  • (Pytorch框架)神经网络输出维度调试,做出我们自己的网络来!!(详细教程~)
  • (zhuan) 一些RL的文献(及笔记)
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (原+转)Ubuntu16.04软件中心闪退及wifi消失
  • (转)jQuery 基础
  • (转)程序员疫苗:代码注入
  • (转载)在C#用WM_COPYDATA消息来实现两个进程之间传递数据
  • ***测试-HTTP方法
  • ***通过什么方式***网吧
  • .MSSQLSERVER 导入导出 命令集--堪称经典,值得借鉴!
  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能
  • .net操作Excel出错解决
  • .net实现客户区延伸至至非客户区
  • [【JSON2WEB】 13 基于REST2SQL 和 Amis 的 SQL 查询分析器
  • [383] 赎金信 js
  • [C#]C# OpenVINO部署yolov8图像分类模型
  • [C#]手把手教你打造Socket的TCP通讯连接(一)
  • [iOS]-网络请求总结
  • [ios-必看] IOS调试技巧:当程序崩溃的时候怎么办 iphone IOS
  • [PHP]严格类型