商城——结算中心模块

一、结算中心表结构

  编写 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}

 

posted @ 2020-01-28 21:32  休耕  阅读(564)  评论(0编辑  收藏  举报