• 08 订单模块


    确认订单页面

    当用户在购物车页面选中需要购买的商品或在商品的详情页面的时候点击直接购买的时候,会转到提交订单的页面。

    购物车的页面

    商品的详情页面:

    通过上面用户的两种提交的请求,最终渲染出来的确认订单的页面如下:

    要想渲染出来上面的页面,前端需要向后端传送的参数有:

      1 如果用户在商品的详情页面点击直接购买需要向后端传送的参数包括: 商品的sku_id和商品的数量count

      2 如果用户在购物车页面提交订单,那么需要向后端传送的数据只需要商品的sku_id就行。

    后端的业务逻辑处理

      1 用post请求接受前端传送的参数

      2 用户必须是登陆的状态

      3 参数的校验,如果商品sku_id为空,则直接返回到购物车页面

      4 查询地址信息和商品信息 ,地址为空把值设为None,商品信息为空返回到购物车页面

      5 业务逻辑处理

        通过count是是否为None判断用户从商品的详情页面提交的数据还是从购物车页面提交过来的数据

          1 如果count的值为空则用户是从购物车中提交过来的数据,每件商品的数量count可以通过遍历传送过来的商品的sku_ids从购物车中获取,把商品添加到购物车中

          2 如果count的值不为空,数据就是从商品的详情页面提交而来,商品的数量就直接使用传送过来的count

      6 通过遍历传送过来sku_id的列表把商品的数量,每个商品对应的总价,商品的总数量和包含运费的总金额当成商品对象的属性添加进去

      7 把封装好数据返回给前端

    后端的视图函数额代码如下:

    from django.shortcuts import render,redirect
    from utils.views import LoginRequiredMixin
    from django.views.generic import View
    from django.core.urlresolvers import reverse
    from users.models import Address
    from goods.models import GoodsSKU
    from django_redis import get_redis_connection
    
    class PlaceOrderView(LoginRequiredMixin, View):
        def post(self,request):
            # 接受前端传送过来额参数:sku_ids和count
            sku_ids = request.POST.getlist('sku_ids')
            count = request.POST.get('count')
    
            # 参数的校验
            if not sku_ids:
                return redirect(reverse('cart:info'))
            # 获取用户的地址信息
            user = request.user
            try:
                address= Address.objects.filter(user=user).latest('create_time')
            except Address.DoesNotExist:
                address=None
            # 业务逻辑处理
            redis_con = get_redis_connection('default')
    
            skus = []  # 用于传递给页面
            total_skus_amount = 0  # 商品总金额
            total_count = 0  # 商品总数量
            total_amount = 0  # 包含运费的总金额
            # 商品信息
            if count is None:
                cart=redis_con.hgetall('cart_%s' %user.id)
                # 数据用户从购物车的页面提交来的
                for sku_id in sku_ids:
                    try:
                        sku = GoodsSKU.objects.get(id=sku_id)
                    except GoodsSKU.DoesNotExist:
                        return redirect(reverse('cart:info'))
                    sku_id=str(sku_id)
                    sku_count = cart.get(sku_id.encode())
                    sku_count = int(sku_count)
                    # 计算商品的金额
                    amount = sku.price * sku_count
                    sku.amount = amount
                    sku.count = sku_count
                    skus.append(sku)
    
                    # 累计总金额和数量
                    total_skus_amount += amount
                    total_count += sku_count
            else:
                # 用户是从商品的详情页面跳转过来的
                for sku_id in sku_ids:   # 虽然只有一个商品 但是也是使用列表获取而来 [id]
                    try:
                        sku = GoodsSKU.objects.get(id=sku_id)
                    except GoodsSKU.DoesNotExist:
                        # 跳转到购物车页面
                        return redirect(reverse("cart:info"))
    
                    try:
                        count = int(count)
                    except Exception:
                        return redirect(reverse("goods:detail", args=(sku_id,)))
    
                    # 判断库存
                    if count >sku.stock:
                        return redirect(reverse("goods:detail", args=(sku_id,)))
    
                    amount = sku.price * count
                    sku.amount = amount
                    sku.count = count
                    skus.append(sku)
    
                    total_skus_amount += amount
                    total_count += count
    
                    # 将这个商品放到购物车中,方便用户下单时出现问题,还能从购物车中找到信息
                    redis_con.hset('cart_%s'%user.id,sku_id,count)
    
            trans_cost = 10  # 运费
            total_amount = total_skus_amount + trans_cost
    
            context = {
                "skus": skus,
                "address": address,
                "total_count": total_count,
                "total_skus_amount": total_skus_amount,
                "total_amount": total_amount,
                "trans_cost": trans_cost,
                 # 下面生成订单信息的时候会用到
                "sku_ids": ",".join(sku_ids), 
            }
    
            return render(request, "place_order.html", context)            
    View Code

    配置请求的路径,在根路径urls中配置

    import orders.urls
    
    url(r'^orders/', include(orders.urls, namespace="orders")),

    在订单的urls

    from django.conf.urls import url
    from . import views
    
    urlpatterns = [
        url(r"^place$", views.PlaceOrderView.as_view(), name="place"),
    ]

     当用户在购物车中提交:

    生成的订单页面如下:

    提交订单的页面 (生成订单信息表和订单商品表)

    前端的页面如下所示

    前端任务

      根据后端的视图函数要生成的订单信息表和订单商品表,

      前端需要向后端传送的数据:  user 地址id、支付方式、商品id  "1,2,3,4,5"  与 数量(从购物车中获取)

    后端在生成两张表的时候,牵扯到如下的知识点:

      1 事物

      2 高并发

      3 时间函数的使用

    1 事务的使用:

    数据库事务:http://python.usyiyi.cn/translate/django_182/topics/db/transactions.html

    from django.db import transaction
    
    save_id = transaction.savepoint()  # 创建保存点
    
    transaction.savepoint_rollback(save_id)  # 回退(回滚)到保存点
    
    transaction.savepoint_commit(save_id)  # 提交保存点
    

    2 并发访问控制

     当多个用户同时去抢同一个商品的时候,就有可能会出现库存不足,把一些错误的数据保存到数据库中

     

    解决的方法: 采用悲观锁,采用乐观锁,采用队列,排队下单

    悲观锁

    SQL语句: select …where . for update

    特点:在查询的时候立即上锁 

     

    乐观锁

    查询时不锁数据,提交更改时进行判断

    只有满足条件时才会修改数据,就是所有的信息都和最初自己查询的一致时,才会修改

     update goods_sku set stock=new_stock, sales=new_sales where id=sku_id and stock=sku.stock

     这里采用的是乐观锁

    3 时间函数的使用

    from django.utils import timezone
    order_id=timezone.now() # 获取当前的时间
    order_id.strftime("%Y%m%d%H%M%S") # 把时间转换成字符串
    
    
    strftime 将时间类型转换为字符串
    strptime 将字符串转换为时间
    
    
    
    python提供的时间模块 datetime   time

    这里用户必须是登陆的,如果未登录,返回json的数据用户未登陆,这里我自定义的装饰器,用来检验登录状态,如果用户未登录,返回json数据

    在utils/views中,添加以下代码

    from django.http import JsonResponse
    from functools import wraps
    
    # 自定义的装饰器,用来检验登录状态,如果用户未登录,返回json数据
    def login_required_json(view_func):
        @wraps(view_func)
        def wrapper(request, *args, **kwargs):
            if not request.user.is_authenticated():
                # 如果用户未登录, 返回json错误信息
                return JsonResponse({"code": 1, "message": "用户未登录"})
            else:
                # 如果用户已登录,则执行视图函数
                return view_func(request, *args, **kwargs)
        return wrapper
    
    
    class LoginRequiredJsonMixin(object):
        """要求用户登录的功能补充逻辑, 使用自定义的login_required_json装饰器"""
        @classmethod
        def as_view(cls, **initkwargs):
            view = super(LoginRequiredJsonMixin, cls).as_view(**initkwargs)   # 实际上就是调用的django提供的类视图基类的as_view
            return login_required_json(view)
    

     自定义一个装饰器让视图函数支持事物的操作

    在utils/views中,添加以下代码

    from django.db import transaction
    
    
    class TransactionAtomicMixin(object):
        """支持事务的操作"""
        @classmethod
        def as_view(cls, **initkwargs):
            view = super(TransactionAtomicMixin, cls).as_view(**initkwargs)
            return transaction.atomic(view)
    

     

    视图函数使用的时候通过导入继承上面的两个类就可以了

    后端任务

     根据前端传的参数生成订单信息表和商品的订单表

    主要的业务逻辑的处理

      1 接受前端传来的订单数据  ( user 地址(address_id)、支付方式(pay_method)、商品sku_ids----》  "1,2,3,4,5"  与 数量(从购物车中获取))

      2 进行参数校验

        1 判断地址是否存在,不存在返回  return JsonResponse({"code": 2, "message": "地址不存在"})

           2  判断支付方式,如果不支持的支付,return JsonResponse({"code": 3, "message": "不支持的支付方式"})

      3 把sku_ids的字符串(‘1,2,3,4,5’)转换成商品的ID列表[1,2,3,4,5]

      4 获取购物车数据

      5 自定义订单编号

      6  创建事务用到的保存点·

      7 生成订单信息表  

      8 遍历商品sku_ids,判断商品信息合理与否的同时保存到订单的商品表

         9 采用乐观锁的方式,更新商品的库存,销量

      10   在订单商品表中保存商品的信息  

        11 更新订单信息表数据,处理总金额总数量·  

     12 提交数据的事务操作

     13 将处理后的购物车数据cart保存到redis中

     14 返回给前端处理的结果, 返回json数据

    from django.shortcuts import render, redirect
    from django.views.generic import View
    from utils.views import LoginRequiredMixin, LoginRequiredJsonMixin, TransactionAtomicMixin
    from django.core.urlresolvers import reverse
    from goods.models import GoodsSKU
    from django_redis import get_redis_connection
    from users.models import Address
    from django.http import JsonResponse, HttpResponse
    from orders.models import OrderInfo, OrderGoods
    from django_redis import get_redis_connection
    from django.utils import timezone
    from django.db import transaction
    
    
    
    class CommitOrderView(LoginRequiredJsonMixin, TransactionAtomicMixin, View):
        """提交订单"""
        def post(self, request):
            """接受订单数据, 保存订单"""
            # 获取要保存的订单的数据
            # user 地址id、支付方式、商品id与  数量(从购物车中获取)
            user = request.user
            address_id = request.POST.get("address_id")
            pay_method = request.POST.get("pay_method")  # 支付方式  "1"
            sku_ids = request.POST.get("sku_ids")  # "1,2,3,4,5"
    
            # 进行校验
            # 判断地址是否存在
            try:
                address = Address.objects.get(id=address_id, user=user)
            except Address.DoesNotExist:
                return JsonResponse({"code": 2, "message": "地址不存在"})
    
            # 判断支付方式
            pay_method = int(pay_method)
            if pay_method not in OrderInfo.PAY_METHODS.keys():
                return JsonResponse({"code": 3, "message": "不支持的支付方式"})
    
            # 判断商品存在与否
            sku_ids = sku_ids.split(",")
    
            # 获取购物车数据
            redis_conn = get_redis_connection("default")
            cart = redis_conn.hgetall("cart_%s" % user.id)
    
            # 创建一个订单的基本信息数据 OrderInfo  订单商品表的数据会用到这个
    
            # 自定义的订单编号格式 "20171026111111用户id"
            order_id = timezone.now().strftime("%Y%m%d%H%M%S") + str(user.id)
    
            # 创建事务用到的保存点
            save_id = transaction.savepoint()
    
            try:
                order = OrderInfo.objects.create(
                    order_id=order_id,
                    user=user,
                    address=address,
                    total_amount=0,
                    trans_cost=10,  # 运费暂时写死
                    pay_method=pay_method
                )
    
                # 遍历商品,判断商品信息合理与否的同时保存到订单的商品表
                total_count = 0
                total_amount = 0
                for sku_id in sku_ids:
                    # 对一商品尝试下单三次
                    for i in range(3):
                        try:
                            sku = GoodsSKU.objects.get(id=sku_id)
                        except GoodsSKU.DoesNotExist:
                            # 回退到保存点
                            transaction.savepoint_rollback(save_id)
                            return JsonResponse({"code": 4, "message": "商品信息有误"})
    
                        # 从购物车中获取用户订购的商品的数量
                        count = cart.get(sku_id.encode())
                        count = int(count)
    
                        # 判断商品的库存够不够
                        if count > sku.stock:
                            transaction.savepoint_rollback(save_id)
                            return JsonResponse({"code": 5, "message": "库存不足"})
    
                        new_stock = sku.stock - count
                        new_sales = sku.sales + count
    
                        # 采用乐观锁的方式,更新商品的库存,销量
                        # update会返回更新成功的数据数目
    
                        result = GoodsSKU.objects.filter(id=sku_id, stock=sku.stock).update(stock=new_stock, sales=new_sales)
                        # update goods_sku set stock=new_stock, sales=new_sales where id=sku_id and stock=sku.stock
                        if result == 0 and i < 2:
                            # 表示库存更新失败,下单失败
                            continue
                        elif result == 0 and i == 2:
                            # 表示尝试了三次都失败
                            transaction.savepoint_rollback(save_id)
                            return JsonResponse({"code": 6, "message": "下单失败"})
    
                        # 在订单商品表中保存商品的信息
                        OrderGoods.objects.create(
                            order=order,
                            sku=sku,
                            count=count,
                            price=sku.price,
                        )
    
                        # 计算订单的总金额
                        total_amount += (sku.price * count)
                        # 计算订单商品的总数量
                        total_count += count
    
                        # 对这个商品下单成功
                        break
    
                # 更新订单信息表数据,处理总金额总数量
                order.total_amount = total_amount + 10
                order.total_count = total_count
                order.save()
            except Exception as e:
                print(e)
                # 出现了任何异常信息,都要回滚事务
                transaction.savepoint_rollback(save_id)
                return JsonResponse({"code": 7, "message": "下单失败"})
    
            # 提交数据的事务操作
            transaction.savepoint_commit(save_id)
    
            # 将处理后的购物车数据cart保存到redis中
            # sku_ids =[1,2,3,4,5]
            #
            # redis_conn.hdel("cart_%s" % user.id, 1,2,3,4,5)
    
            redis_conn.hdel("cart_%s" % user.id, *sku_ids)
    
            # 返回给前端处理的结果, 返回json数据
            return JsonResponse({"code": 0, "message": "下单成功"})
    View Code

    在订单应用的urls中增加url的请求路径:

    url(r"^commit$", views.CommitOrderView.as_view(), name="commit"),
    

     

    当用户提交订单的信息成功后,在数据库中查看订单信息表

    在数据库中查看订单商品表

     
     
     
  • 相关阅读:
    如何评测软件工程知识技能水平?
    创新产品的需求分析:未来的图书会是什么样子?
    案例分析:设计模式与代码的结构特性
    业务领域建模Domain Modeling
    转载:10 Easy Steps to a Complete Understanding of SQL
    二十六个月Android学习工作总结
    android sdk无法更新问题解决
    android中利用view画出一条竖线
    android-Java SoftReference,WeakReference,Direct Reference简介
    Intellij IDEA开发第一个android应用教程
  • 原文地址:https://www.cnblogs.com/aaronthon/p/9347830.html
Copyright © 2020-2023  润新知