zoukankan      html  css  js  c++  java
  • 商城——结算中心模块

    一、结算中心表结构

      编写 LuffyCity/shopping/models.py 文件,设计结算中心表结构。

    1、优惠券

      该类定义的是优惠券生成规则。总共设计有三种优惠券类型:通用券、满减券、折扣券。

    from django.db import models
    from django.contrib.contenttypes.models import ContentType
    from django.contrib.contenttypes.fields import GenericForeignKey
    from Course.models import Account
    
    # Create your models here.
    __all__ = ["Coupon", "CouponRecord", "Order", "OrderDetail", "TransactionRecord"]
    
    
    class Coupon(models.Model):
        """优惠券生成规则"""
        name = models.CharField(max_length=64, verbose_name="活动名称")
        brief = models.TextField(blank=True, null=True, verbose_name="优惠券介绍")
        # 优惠券类型
        coupon_type_choices = ((0, '通用券'),(1, '满减劵'),(2, '折扣券'))
        coupon_type = models.SmallIntegerField(choices=coupon_type_choices, default=0, verbose_name="券类型")
    
        # null=True:数据库创建时该字段可不填,用NULL填充
        # blank=True:创建数据库记录时该字段可传空白
        money_equivalent_value = models.IntegerField(verbose_name="等值货币", null=True, blank=True, default=0)
        off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只针对折扣券,例7.9折,写79",
                                                       null=True, blank=True, default=100)
        minimum_consume = models.PositiveIntegerField("最低消费", help_text="仅在满减券时填写此字段",
                                                      null=True, blank=True, default=0)
    
        content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=None)
        object_id = models.PositiveIntegerField("绑定课程", blank=True, null=True, help_text="可以把优惠券跟课程绑定")
        # 不绑定代表全局优惠券
        content_object = GenericForeignKey("content_type", "object_id")
    
        open_date = models.DateField("优惠券领取开始时间")
        close_date = models.DateField("优惠券领取结束时间")
        valid_begin_date = models.DateField(verbose_name="有效期开始时间", blank=True, null=True)
        valid_end_date = models.DateField(verbose_name="有效结束时间", blank=True, null=True)
        coupon_valid_days = models.PositiveIntegerField(verbose_name="优惠券有效期(天)", blank=True, null=True,
                                                        help_text="自券被领时开始算起")
        date = models.DateTimeField(auto_now_add=True)
    
        class Meta:
            verbose_name_plural = "13. 优惠券生成规则记录"
            db_table = verbose_name_plural
            verbose_name = verbose_name_plural
    
        def __str__(self):
            return "%s(%s)" % (self.get_coupon_type_display(), self.name)
    
        def save(self, *args, **kwargs):
            # save前做如下判断
            # 如果没有优惠券有效期,也没有优惠券起止时间
            if not self.coupon_valid_days or (self.valid_begin_date and self.valid_end_date):
                # 如果有优惠券起止时间
                if self.valid_begin_date and self.valid_end_date:
                    # 如果结束时间早于开始时间,抛出异常
                    if self.valid_end_date <= self.valid_begin_date:
                        raise ValueError("valid_end_date 有效期结束日期必须晚于 valid_begin_date ")
                # 如果优惠券有效期为0,抛出异常
                if self.coupon_valid_days == 0:
                    raise ValueError("coupon_valid_days 有效期不能为0")
            # 如果优惠券结束时间早于优惠券开始时间,抛出异常
            if self.close_date < self.open_date:
                raise ValueError("close_date 优惠券领取结束时间必须晚于 open_date优惠券领取开始时间")
    
            super(Coupon, self).save(*args, **kwargs)

    2、优惠券记录

      一个优惠券往往对应多个优惠券记录。

    class CouponRecord(models.Model):
        """优惠券发放、消费记录"""
        coupon = models.ForeignKey("Coupon", on_delete=None)
        number = models.CharField(max_length=64, unique=True, verbose_name="用户优惠券记录的流水号")
        account = models.ForeignKey(to=Account, verbose_name="拥有者", on_delete=None)
        status_choices = ((0, '未使用'), (1, '已使用'), (2, '已过期'))
        status = models.SmallIntegerField(choices=status_choices, default=0)
        get_time = models.DateTimeField(verbose_name="领取时间", help_text="用户领取时间")
        used_time = models.DateTimeField(verbose_name="使用时间", blank=True, null=True)
        # 一个订单可以有多张优惠券,因此是一对多关系
        order = models.ForeignKey("Order", verbose_name="关联订单", blank=True, null=True, on_delete=None)
    
        class Meta:
            verbose_name_plural = "14. 用户优惠券领取使用记录表"
            db_table = verbose_name_plural
            verbose_name = verbose_name_plural
    
        def __str__(self):
            return '%s-%s-%s' % (self.account, self.number, self.status)

    3、订单

      只要创建订单,订单即存在,与是否完成支付无关。

    class Order(models.Model):
        """订单"""
        payment_type_choices = (
            (0, '微信'), (1, '支付宝'), (2, '优惠码'), (3, '贝里')
        )
        payment_type = models.SmallIntegerField(choices=payment_type_choices)
        payment_number = models.CharField(max_length=128, verbose_name="支付第三方订单号", null=True, blank=True)
        # 考虑到订单合并支付的问题
        order_number = models.CharField(max_length=128, verbose_name="订单号", unique=True)
        account = models.ForeignKey(to=Account, on_delete=None)
        actual_amount = models.FloatField(verbose_name="实付金额")
        # 注意只要创建了订单,订单即存在,与完成付款无关
        status_choices = (
            (0, '交易成功'), (1, '待支付'), (2, '退费申请中'), (3, '已退费'), (4, '主动取消'), (5, '超时取消')
        )
        status = models.SmallIntegerField(choices=status_choices, verbose_name="状态")
        date = models.DateTimeField(auto_now_add=True, verbose_name="订单生成时间")
        pay_time = models.DateTimeField(blank=True, null=True, verbose_name="付款时间")
        cancel_time = models.DateTimeField(blank=True, null=True, verbose_name="订单取消时间")
    
        class Meta:
            verbose_name_plural = "15. 订单表"
            db_table = verbose_name_plural
            verbose_name = verbose_name_plural
    
        def __str__(self):
            return "%s" % self.order_number

    4、订单详情

      一个订单可能包含购买的多个商品。需要通过订单详情来展示订单中包含的所有商品。

    class OrderDetail(models.Model):
        """订单详情"""
        order = models.ForeignKey("Order", on_delete=None)     # 关联订单,一个订单可能有多个订单详情
    
        content_type = models.ForeignKey(ContentType, on_delete=None)   # 关联普通课程或学位
        object_id = models.PositiveIntegerField()
        content_object = GenericForeignKey('content_type', 'object_id')
    
        original_price = models.FloatField("课程原价")
        price = models.FloatField("折后价格")
        valid_period_display = models.CharField("有效期显示", max_length=32)    # 订单页显示
        valid_period = models.PositiveIntegerField("有效期(days)")       # 课程有效期
        memo = models.CharField(verbose_name="备忘录", max_length=255, blank=True, null=True)
    
        class Meta:
            verbose_name_plural = "16. 订单详细"
            db_table = verbose_name_plural
            verbose_name = verbose_name_plural
    
        def __str__(self):
            return "%s - %s -%s" % (self.order, self.content_type, self.price)

    5、贝里(积分)交易记录

    class TransactionRecord(models.Model):
        """贝里交易记录"""
        account = models.ForeignKey(to=Account, on_delete=None)
        amount = models.IntegerField("金额")
        balance = models.IntegerField("账户余额")
        # 交易类型
        transaction_type_choices = (
            (0, '收入'), (1, '支出'), (2, '退款'), (3, '提现')
        )
        transaction_type = models.SmallIntegerField(choices=transaction_type_choices)
        transaction_number = models.CharField(verbose_name="流水号", unique=True, max_length=128)
        date = models.DateTimeField(auto_now_add=True)      # 创建交易记录时间
        memo = models.CharField(verbose_name="备忘录", max_length=128, blank=True, null=True)
    
        class Meta:
            verbose_name_plural = "17. 贝里交易记录"
            db_table = verbose_name_plural
            verbose_name = verbose_name_plural
    
        def __str__(self):
            return "%s" % self.transaction_number

    二、加入结算中心接口

    1、添加结算路由

      在 LuffyCity/shopping/urls.py 添加结算中心路由:

    from django.urls import path
    from .views import ShoppingCarView
    from .settlement_view import SettlementView
    
    urlpatterns = [
        path('shopping_car', ShoppingCarView.as_view()),   # 购物车
        path('settlement', SettlementView.as_view())       # 结算中心
    ]

      由于view.py已经写了购物车视图,这里添加settlement_view.py作为结算中心视图。

    2、redis数据结构

      post提交购物车的商品,生成订单,前端传来的数据:couse_list。

      设计写入reids的数据如下所示:

    redis = {
        settlement_userid_courseid: {
            id, 课程id,
            title,
            course_img,
            valid_period_display(有效期),
            price,
            course_coupon_dict: {
                coupon_id: {优惠券信息},
                coupon_id2: {优惠券信息},
                coupon_id3: {优惠券信息},
            }
            # 默认不给选优惠券,这个字段只有更新的时候添加
            default_coupon_id:1
        }
        global_coupon_userid: {
            coupon_id: {优惠券信息}
            coupon_id2: {优惠券信息},
            coupon_id3: {优惠券信息},
            # 这个字段只有更新的时候才添加
            default_global_coupon_id: 1
        }
    }

    3、添加结算中心接口视图

      在结算中心视图settlement_view.py中,添加结算视图类:SettlementView,编写post方法以添加结算中心:

    import redis
    import json
    from django.utils.timezone import now
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from utils.base_response import BaseResponse
    from utils.redis_pool import POOL     # 连接池
    from utils.my_auth import LoginAuth   # 登录认证
    from .views import SHOPPINGCAR_KEY
    from .models import CouponRecord
    
    
    CONN = redis.Redis(connection_pool=POOL)
    SETTLEMENT_KEY = "SETTLEMENT_%s_%s"
    GLOBAL_COUPON_KEY = "GLOBAL_COUPON_%s"
    
    
    class SettlementView(APIView):
        authentication_classes = [LoginAuth,]
    
        def post(self, request):
            res = BaseResponse()
            # 1.获取前端的数据以及user_id
            course_list = request.data.get("course_list", "")
            user_id = request.user.pk
            # 2.校验数据的合法性
            for course_id in course_list:
                # 2.1 判断course_id 是否在购物车中
                shopping_car_key = SHOPPINGCAR_KEY % (user_id, course_id)
                if not CONN.exists(shopping_car_key):
                    res.code = 1050
                    res.error = "课程ID不合法"
                    return Response(res.dict)
                # 3.构建数据结构
                # 3.1 获取用户所有合法优惠券
                user_all_coupons = CouponRecord.objects.filter(
                    account_id = user_id,
                    status = 0,
                    coupon__valid_begin_date__lte = now(),   # 开始时间小于现在时间
                    coupon__valid_end_date__gte = now(),     # 结束时间大于现在时间
    
                ).all()             # 拿到所有对象
                # 3.2 构建两个优惠券的dict
                course_coupon_dict = {}
                global_coupon_dict = {}
                for coupon_record in user_all_coupons:
                    coupon = coupon_record.coupon
                    if coupon.objects_id == course_id:   # 说明是这个课程的所有优惠券
                        course_coupon_dict[coupon.id] = {
                            "id": coupon.id,
                            "name": coupon.name,
                            "coupon_type": coupon.get_coupon_type_display(),   # 拿到中文类型
                            "object_id": coupon.object_id,
                            "money_equivalent_value": coupon.money_equivalent_value,   # 等值货币
                            "off_percent": coupon.off_percent,
                            "minimum_consume": coupon.minimum_consume
                        }
                    elif coupon.object_id == "":        # 为空说明是全局优惠券
                        global_coupon_dict[coupon.id] = {
                            "id": coupon.id,
                            "name": coupon.name,
                            "coupon_type": coupon.get_coupon_type_display(),  # 拿到中文类型
                            "money_equivalent_value": coupon.money_equivalent_value,  # 等值货币
                            "off_percent": coupon.off_percent,
                            "minimum_consume": coupon.minimum_consume
                        }
                # 3.3 构建将写入redis的数据结构
                course_info = CONN.hgetall(shopping_car_key)
                price_policy_dict = json.loads(course_info["price_policy_dict"])
                default_policy_id = course_info["default_price_policy_id"]    # 默认价格策略
                valid_period = price_policy_dict[default_policy_id]["valid_period_display"]    # 有效期
                price = price_policy_dict[default_policy_id]["price"]           # 价格
    
                settlement_info = {
                    "id": course_id,
                    "title": course_info["title"],
                    "course_img": course_info["course_img"],
                    "valid_period": valid_period,
                    "price": price,
                    "course_coupon_dict": json.dumps(course_coupon_dict, ensure_ascii=False)
                }
                # 4.写入redis
                settlement_key = SETTLEMENT_KEY % (user_id, course_id)
                global_coupon_key = GLOBAL_COUPON_KEY % user_id
                CONN.hmset(settlement_key, settlement_info)
                if global_coupon_dict:
                    CONN.hmset(global_coupon_key, global_coupon_dict)
                # 5.删除购物车中的数据
                CONN.delete(shopping_car_key)
            res.data = "加入结算中心成功"
            return Response(res.dict)

    4、添加结算测试

    (1)加入购物车

      首先登录获取token:

      

       两次添加购物车:{"course_id":1, "price_policy_id": 2}、{"course_id":2, "price_policy_id": 3}

      

       查看当前用户所有购物车信息:

      

    (2)加入结算中心

      提交结算信息:{"course_list": [1, 2]}

      

       如上所示,添加结算中心成功,此时再查看购物车,可以发现购物车清空:

      

    三、查看结算中心接口

    1、查看结算中心接口视图

    import redis
    import json
    from django.utils.timezone import now
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from utils.base_response import BaseResponse
    from utils.redis_pool import POOL     # 连接池
    from utils.my_auth import LoginAuth   # 登录认证
    from .views import SHOPPINGCAR_KEY
    from .models import CouponRecord
    
    
    CONN = redis.Redis(connection_pool=POOL)
    SETTLEMENT_KEY = "SETTLEMENT_%s_%s"
    GLOBAL_COUPON_KEY = "GLOBAL_COUPON_%s"
    
    
    class SettlementView(APIView):
        authentication_classes = [LoginAuth,]
    
        def post(self, request):...
    
        def get(self, request):
            res = BaseResponse()
            # 1.获取user_id
            user_id = request.user.pk
            # 2.拼接所有key
            settlement_key = SETTLEMENT_KEY % (user_id, "*")
            global_coupon_key = GLOBAL_COUPON_KEY % user_id
            all_keys = CONN.scan_iter(settlement_key)     # 增量式迭代获取,redis里匹配的的name
            # 3.去redis取数据
            ret = []
            for key in all_keys:
                ret.append(CONN.hgetall(key))    # 取key对应所有数据
            global_coupon_info = CONN.hgetall(global_coupon_key)
            res.data = {
                "settlement_info": ret,
                "global_coupon_dict": global_coupon_info
            }
            return Response(res.dict)

    2、查看结算中心测试

      用户登录后,使用获取的token来查看当前用户结算中心信息:

      

    四、更新结算中心接口

    1、更新结算中心接口视图

    class SettlementView(APIView):
        authentication_classes = [LoginAuth,]
    
        def post(self, request)...
    
        def get(self, request):...
    
        def put(self, request):
            # 更新课程优惠券会传递course_id、course_coupon_id、global_coupon_id
            res = BaseResponse()    # 获取前端传递数据
            # 1.获取前端传递过来的数据
            course_id = request.data.get("course_id", "")
            course_coupon_id = request.data.get("course_coupon_id", "")
            global_coupon_id = request.data.get("global_coupon_id", "")
            user_id = request.user.pk
    
            # 2.校验数据合法性
            key = SETTLEMENT_KEY % (user_id, course_id)
            # 2.1 校验course_id是否合法
            if course_id:
                if not CONN.exists(key):
                    # 不存在这个key
                    res.code = 1060
                    res.error = "课程id不合法"
                    return Response(res.data)
            # 2.2 校验course_coupon_id 是否合法
            if course_coupon_id:
                course_coupon_dict = json.loads(CONN.hget(key, "course_coupon_dict"))
                if str(course_coupon_id) not in course_coupon_dict:
                    res.code = 1061
                    res.error = "课程优惠券id不合法"
                    return Response(res.dict)
            # 2.3 校验global_coupon_id 是否合法
            if global_coupon_id:
                global_coupon_key = GLOBAL_COUPON_KEY % user_id
                if not CONN.exists(global_coupon_key):
                    res.code = 1062
                    res.error = "全局优惠券id不合法"
                    return  Response(res.dict)
                CONN.hset(global_coupon_key, "default_global_coupon_id", global_coupon_id)
            # 3.修改redis中数据
            CONN.hset(key, "default_coupon_id", course_coupon_id)
            res.data = "更新成功"
            return Response(res.dict)

    2、更新结算中心接口测试

      使用POSTMAN提交PUT请求:{"course_id":1, "course_coupon_id": 2}

  • 相关阅读:
    confluent-kafka python Producer Consumer实现
    kafka producer.poll producer.flush consumer.poll的区别
    kafka Java创建生产者报错:Invalid partition given with record: 1 is not in the range [0...1)
    Kafka通讯的Java实例
    虚机克隆搭建kafka服务器集群
    kafka报错解决:Broker may not be avaliable
    Kafka+Zookeeper+confluent-kafka搭建
    Kafka学习笔记
    【SpringCloud】 第十篇: 高可用的服务注册中心
    【SpringCloud】 第九篇: 服务链路追踪(Spring Cloud Sleuth)
  • 原文地址:https://www.cnblogs.com/xiugeng/p/12102064.html
Copyright © 2011-2022 走看看