路飞学成项目之表结构和业务逻辑

路飞学成表结构

课程相关表结构

model;

class CourseCategory(models.Model):
    """课程大类, e.g 前端  后端..."""
    name = models.CharField(max_length=64, unique=True)

    def __str__(self):
        return "%s" % self.name

    class Meta:
        verbose_name_plural = "01.课程大类"


class CourseSubCategory(models.Model):
    """课程子类, e.g python linux """
    category = models.ForeignKey("CourseCategory",on_delete=models.CASCADE)
    name = models.CharField(max_length=64, unique=True)

    def __str__(self):
        return "%s" % self.name

    class Meta:
        verbose_name_plural = "02.课程子类"


class DegreeCourse(models.Model):
    """学位课程"""
    name = models.CharField(max_length=128, unique=True)
    course_img = models.CharField(max_length=255, verbose_name="缩略图")
    brief = models.TextField(verbose_name="学位课程简介", )
    total_scholarship = models.PositiveIntegerField(verbose_name="总奖学金(贝里)", default=40000)
    mentor_compensation_bonus = models.PositiveIntegerField(verbose_name="本课程的导师辅导费用(贝里)", default=15000)
    period = models.PositiveIntegerField(verbose_name="建议学习周期(days)", default=150)  # 为了计算学位奖学金
    prerequisite = models.TextField(verbose_name="课程先修要求", max_length=1024)
    teachers = models.ManyToManyField("Teacher", verbose_name="课程讲师")



    # 用于GenericForeignKey反向查询, 不会生成表字段,切勿删除
    # coupon = GenericRelation("Coupon")

    # 用于GenericForeignKey反向查询,不会生成表字段,切勿删除
    degreecourse_price_policy = GenericRelation("PricePolicy")

    # 查询常见问题
    asked_question = GenericRelation("OftenAskedQuestion")

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = "03.学位课"


class Teacher(models.Model):
    """讲师、导师表"""
    name = models.CharField(max_length=32)
    role_choices = ((0, '讲师'), (1, '导师'))
    role = models.SmallIntegerField(choices=role_choices, default=0)
    title = models.CharField(max_length=64, verbose_name="职位、职称")
    signature = models.CharField(max_length=255, help_text="导师签名", blank=True, null=True)
    image = models.CharField(max_length=128)
    brief = models.TextField(max_length=1024)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = "04.导师或讲师"


class Scholarship(models.Model):
    """学位课程奖学金"""
    degree_course = models.ForeignKey("DegreeCourse",on_delete=models.CASCADE)
    time_percent = models.PositiveSmallIntegerField(verbose_name="奖励档位(时间百分比)", help_text="只填百分值,如80,代表80%")
    value = models.PositiveIntegerField(verbose_name="奖学金数额")

    def __str__(self):
        return "%s:%s" % (self.degree_course, self.value)

    class Meta:
        verbose_name_plural = "05.学位课奖学金"


class Course(models.Model):
    """专题课程 OR 学位课模块"""
    name = models.CharField(max_length=128, unique=True) # Python基础
    course_img = models.CharField(max_length=255)

    sub_category = models.ForeignKey("CourseSubCategory",on_delete=models.CASCADE) #

    course_type_choices = ((0, '付费'), (1, 'VIP专享'), (2, '学位课程'))
    course_type = models.SmallIntegerField(choices=course_type_choices)

    degree_course = models.ForeignKey("DegreeCourse", blank=True, null=True, help_text="若是学位课程,此处关联学位表",on_delete=models.CASCADE)


    brief = models.TextField(verbose_name="课程(模块)概述", max_length=2048)
    level_choices = ((0, '初级'), (1, '中级'), (2, '高级'))
    level = models.SmallIntegerField(choices=level_choices, default=1)
    pub_date = models.DateField(verbose_name="发布日期", blank=True, null=True)
    period = models.PositiveIntegerField(verbose_name="建议学习周期(days)", default=7)
    order = models.IntegerField("课程顺序", help_text="从上一个课程数字往后排")
    attachment_path = models.CharField(max_length=128, verbose_name="课件路径", blank=True, null=True)
    status_choices = ((0, '上线'), (1, '下线'), (2, '预上线'))
    status = models.SmallIntegerField(choices=status_choices, default=0)
    template_id = models.SmallIntegerField("前端模板id", default=1)

    # 如果是专题课时,获取相关优惠券
    # coupon = GenericRelation("Coupon")

    # 用于GenericForeignKey反向查询,不会生成表字段,切勿删除
    price_policy = GenericRelation("PricePolicy")

    # 查询常见问题
    asked_question = GenericRelation("OftenAskedQuestion")

    def __str__(self):
        return self.name


class CourseDetail(models.Model):
    """课程详情页内容"""
    course = models.OneToOneField("Course",on_delete=models.CASCADE)
    hours = models.IntegerField("课时")
    course_slogan = models.CharField(max_length=125, blank=True, null=True)
    video_brief_link = models.CharField(verbose_name='课程介绍', max_length=255, blank=True, null=True)
    why_study = models.TextField(verbose_name="为什么学习这门课程")
    what_to_study_brief = models.TextField(verbose_name="我将学到哪些内容")
    career_improvement = models.TextField(verbose_name="此项目如何有助于我的职业生涯")
    prerequisite = models.TextField(verbose_name="课程先修要求", max_length=1024)
    recommend_courses = models.ManyToManyField("Course", related_name="recommend_by", blank=True)
    teachers = models.ManyToManyField("Teacher", verbose_name="课程讲师")

    def __str__(self):
        return "%s" % self.course

    class Meta:
        verbose_name_plural = "07.课程或学位模块详细"


class OftenAskedQuestion(models.Model):
    """常见问题"""
    content_type = models.ForeignKey(ContentType,on_delete=models.CASCADE)  # 关联course or degree_course
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    question = models.CharField(max_length=255)
    answer = models.TextField(max_length=1024)

    def __str__(self):
        return "%s-%s" % (self.content_object, self.question)

    class Meta:
        unique_together = ('content_type', 'object_id', 'question')
        verbose_name_plural = "08. 常见问题"


class CourseOutline(models.Model):
    """课程大纲"""
    course_detail = models.ForeignKey("CourseDetail",on_delete=models.CASCADE)

    # 前端显示顺序
    order = models.PositiveSmallIntegerField(default=1)

    title = models.CharField(max_length=128)
    content = models.TextField("内容", max_length=2048)

    def __str__(self):
        return "%s" % self.title

    class Meta:
        unique_together = ('course_detail', 'title')
        verbose_name_plural = "09. 课程大纲"


class CourseChapter(models.Model):
    """课程章节"""
    course = models.ForeignKey("Course", related_name='coursechapters',on_delete=models.CASCADE)
    chapter = models.SmallIntegerField(verbose_name="第几章", default=1)
    name = models.CharField(max_length=128)
    summary = models.TextField(verbose_name="章节介绍", blank=True, null=True)
    pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True)

    class Meta:
        unique_together = ("course", 'chapter')
        verbose_name_plural = "10. 课程章节"

    def __str__(self):
        return "%s:(第%s章)%s" % (self.course, self.chapter, self.name)


class CourseSection(models.Model):
    """课时目录"""
    chapter = models.ForeignKey("CourseChapter", related_name='coursesections',on_delete=models.CASCADE)
    name = models.CharField(max_length=128)
    order = models.PositiveSmallIntegerField(verbose_name="课时排序", help_text="建议每个课时之间空1至2个值,以备后续插入课时")
    section_type_choices = ((0, '文档'), (1, '练习'), (2, '视频'))
    section_type = models.SmallIntegerField(default=2, choices=section_type_choices)
    # 59EE3275E977AADB9C33DC5901307461
    section_link = models.CharField(max_length=255, blank=True, null=True, help_text="若是video,填vid,若是文档,填link")


    video_time = models.CharField(verbose_name="视频时长", blank=True, null=True, max_length=32)  # 仅在前端展示使用
    pub_date = models.DateTimeField(verbose_name="发布时间", auto_now_add=True)
    free_trail = models.BooleanField("是否可试看", default=False)

    class Meta:
        unique_together = ('chapter', 'section_link')
        verbose_name_plural = "11. 课时"

    def __str__(self):
        return "%s-%s" % (self.chapter, self.name)


class Homework(models.Model):
    chapter = models.ForeignKey("CourseChapter",on_delete=models.CASCADE)
    title = models.CharField(max_length=128, verbose_name="作业题目")
    order = models.PositiveSmallIntegerField("作业顺序", help_text="同一课程的每个作业之前的order值间隔1-2个数")
    homework_type_choices = ((0, '作业'), (1, '模块通关考核'))
    homework_type = models.SmallIntegerField(choices=homework_type_choices, default=0)
    requirement = models.TextField(max_length=1024, verbose_name="作业需求")
    threshold = models.TextField(max_length=1024, verbose_name="踩分点")
    recommend_period = models.PositiveSmallIntegerField("推荐完成周期(天)", default=7)
    scholarship_value = models.PositiveSmallIntegerField("为该作业分配的奖学金(贝里)")
    note = models.TextField(blank=True, null=True)
    enabled = models.BooleanField(default=True, help_text="本作业如果后期不需要了,不想让学员看到,可以设置为False")

    class Meta:
        unique_together = ("chapter", "title")
        verbose_name_plural = "12. 章节作业"

    def __str__(self):
        return "%s - %s" % (self.chapter, self.title)
class PricePolicy(models.Model):
    """价格与有课程效期表"""
    content_type = models.ForeignKey(ContentType,on_delete=models.CASCADE)  # 关联course or degree_course
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    # course = models.ForeignKey("Course")
    valid_period_choices = ((1, '1天'), (3, '3天'),
                            (7, '1周'), (14, '2周'),
                            (30, '1个月'),
                            (60, '2个月'),
                            (90, '3个月'),
                            (180, '6个月'), (210, '12个月'),
                            (540, '18个月'), (720, '24个月'),
                            )
    valid_period = models.SmallIntegerField(choices=valid_period_choices)
    price = models.FloatField()

    class Meta:
        unique_together = ("content_type", 'object_id', "valid_period")
        verbose_name_plural = "15. 价格策略"

    def __str__(self):
        return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price)
View Code

 

深科技相关表结构

class ArticleSource(models.Model):
    """文章来源"""
    name = models.CharField(max_length=64, unique=True)

    class Meta:
        verbose_name_plural = "16. 文章来源"

    def __str__(self):
        return self.name

class Article(models.Model):
    """文章资讯"""
    title = models.CharField(max_length=255, unique=True, db_index=True, verbose_name="标题")
    source = models.ForeignKey("ArticleSource", verbose_name="来源",on_delete=models.CASCADE)
    article_type_choices = ((0, '资讯'), (1, '视频'))
    article_type = models.SmallIntegerField(choices=article_type_choices, default=0)
    brief = models.TextField(max_length=512, verbose_name="摘要")
    head_img = models.CharField(max_length=255)
    content = models.TextField(verbose_name="文章正文")
    pub_date = models.DateTimeField(verbose_name="上架日期")
    offline_date = models.DateTimeField(verbose_name="下架日期")
    status_choices = ((0, '在线'), (1, '下线'))
    status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="状态")
    order = models.SmallIntegerField(default=0, verbose_name="权重", help_text="文章想置顶,可以把数字调大,不要超过1000")
    vid = models.CharField(max_length=128, verbose_name="视频VID", help_text="文章类型是视频, 则需要添加视频VID", blank=True, null=True)
    comment_num = models.SmallIntegerField(default=0, verbose_name="评论数")
    agree_num = models.SmallIntegerField(default=0, verbose_name="点赞数")
    view_num = models.SmallIntegerField(default=0, verbose_name="观看数")
    collect_num = models.SmallIntegerField(default=0, verbose_name="收藏数")

    # tags = models.ManyToManyField("Tags", blank=True, verbose_name="标签")
    date = models.DateTimeField(auto_now_add=True, verbose_name="创建日期")

    position_choices = ((0, '信息流'), (1, 'banner大图'), (2, 'banner小图'))
    position = models.SmallIntegerField(choices=position_choices, default=0, verbose_name="位置")


    comment = GenericRelation("Comment")

    class Meta:
        verbose_name_plural = "17. 文章"

    def __str__(self):
        return "%s-%s" % (self.source, self.title)

class Collection(models.Model):
    """收藏"""
    content_type = models.ForeignKey(ContentType,on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    account = models.ForeignKey("UserInfo",on_delete=models.CASCADE)
    date = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ('content_type', 'object_id', 'account')
        verbose_name_plural = "18. 通用收藏表"

class Comment(models.Model):
    """通用的评论表"""
    content_type = models.ForeignKey(ContentType, blank=True, null=True, verbose_name="类型",on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField(blank=True, null=True)
    content_object = GenericForeignKey('content_type', 'object_id')

    p_node = models.ForeignKey("self", blank=True, null=True, verbose_name="父级评论",on_delete=models.CASCADE)
    content = models.TextField(max_length=1024)
    account = models.ForeignKey("UserInfo", verbose_name="会员名",on_delete=models.CASCADE)
    disagree_number = models.IntegerField(default=0, verbose_name="")
    agree_number = models.IntegerField(default=0, verbose_name="赞同数")
    date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.content

    class Meta:
        verbose_name_plural = "19. 通用评论表"
View Code

 

结算中心相关(优惠券)

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="券类型")

    """
    通用:
        money_equivalent_value=100
        off_percent=null
        minimum_consume=0
    满减:
        money_equivalent_value=100
        off_percent=null
        minimum_consume=1000
    折扣:
        money_equivalent_value=0
        off_percent=79
        minimum_consume=0
    """
    money_equivalent_value = models.IntegerField(verbose_name="等值货币")
    off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只针对折扣券,例7.9折,写79", blank=True, null=True)
    minimum_consume = models.PositiveIntegerField("最低消费", default=0, help_text="仅在满减券时填写此字段")

    content_type = models.ForeignKey(ContentType, blank=True, null=True,on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField("绑定课程", blank=True, null=True, help_text="可以把优惠券跟课程绑定")
    content_object = GenericForeignKey('content_type', 'object_id')

    quantity = models.PositiveIntegerField("数量(张)", default=1)
    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 = "31. 优惠券生成记录"

    def __str__(self):
        return "%s(%s)" % (self.get_coupon_type_display(), self.name)

    def save(self, *args, **kwargs):
        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 ")
            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)


class CouponRecord(models.Model):
    """优惠券发放、消费纪录"""
    coupon = models.ForeignKey("Coupon",on_delete=models.CASCADE)
    number = models.CharField(max_length=64, unique=True)
    account = models.ForeignKey("Account", verbose_name="拥有者",on_delete=models.CASCADE)
    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(blank=True, null=True, verbose_name="使用时间")
    # order = models.ForeignKey("Order", blank=True, null=True, verbose_name="关联订单")  # 一个订单可以有多个优惠券

    class Meta:
        verbose_name_plural = "32. 用户优惠券"

    def __str__(self):
        return '%s-%s-%s' % (self.account, self.number, self.status)
View Code

 

去支付表结构

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="支付第3方订单号", null=True, blank=True)
    order_number = models.CharField(max_length=128, verbose_name="订单号", unique=True)  # 考虑到订单合并支付的问题
    account = models.ForeignKey("UserInfo",on_delete=models.CASCADE)
    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 = "37. 订单表"

    def __str__(self):
        return "%s" % self.order_number


class OrderDetail(models.Model):
    """订单详情"""
    order = models.ForeignKey("Order",on_delete=models.CASCADE)

    content_type = models.ForeignKey(ContentType,on_delete=models.CASCADE)  # 可关联普通课程或学位
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    original_price = models.FloatField("课程原价")
    price = models.FloatField("折后价格")
    content = models.CharField(max_length=255, blank=True, null=True)  #
    valid_period_display = models.CharField("有效期显示", max_length=32)  # 在订单页显示
    valid_period = models.PositiveIntegerField("有效期(days)")  # 课程有效期
    memo = models.CharField(max_length=255, blank=True, null=True)

    def __str__(self):
        return "%s - %s - %s" % (self.order, self.content_type, self.price)

    class Meta:
        verbose_name_plural = "38. 订单详细"
        unique_together = ("order", 'content_type', 'object_id')

class TransactionRecord(models.Model):
    """贝里交易纪录"""
    account = models.ForeignKey("UserInfo",on_delete=models.CASCADE)
    amount = models.IntegerField("金额")
    # balance = models.IntegerField("账户余额")
    transaction_type_choices = ((0, '收入'), (1, '支出'), (2, '退款'), (3, "提现"))  # 2 为了处理 订单过期未支付时,锁定期贝里的回退
    transaction_type = models.SmallIntegerField(choices=transaction_type_choices)

    content_type = models.ForeignKey(ContentType, blank=True, null=True,on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField(blank=True, null=True, verbose_name="关联对象")
    content_object = GenericForeignKey('content_type', 'object_id')

    transaction_number = models.CharField(unique=True, verbose_name="流水号", max_length=128)
    date = models.DateTimeField(auto_now_add=True)
    memo = models.CharField(max_length=128, blank=True, null=True)

    class Meta:
        verbose_name_plural = "40. 贝里交易记录"

    def __str__(self):
        return "%s" % self.transaction_number
View Code

 

课程相关业务逻辑

查看所有课程

url:

path('course/',course.CourseView.as_view({'get':'list'})),

view:

class CourseView(ViewSetMixin,APIView):
    def list(self,request,*args,**kwargs):
        '''查看课程列表'''
        ret=BaseResponse()
        try:
            queryset=models.Course.objects.filter(status=0).order_by('order')
            ser=apiserializer.CourseModelSerializer(instance=queryset,many=True)
            ret.data=ser.data
        except Exception as e:
            ret.code=1001
            ret.error='获取数据错误'
        return Response(ret.dict)

 

serializer:

class CourseModelSerializer(ModelSerializer):
    '''Course表的序列化'''
    level=serializers.CharField(source='get_level_display')
    class Meta:
        model=models.Course
        fields=['id','name','course_img','level','brief']

 

response.py

class BaseResponse(object):
    '''响应的数据结构'''
    def __init__(self):
        self.code=1000
        self.data=None
        self.error=None

    @property
    def dict(self):
        '''重建__dict__方法'''
        return self.__dict__

 

查看课程详细

url:

    re_path(r'^course/(?P<pk>\d+)/$',course.CourseView.as_view({'get':'retrieve'})),

view:

    def retrieve(self,request,*args,**kwargs):
        '''查看课程详细'''
        ret=BaseResponse()
        try:
            pk=kwargs.get('pk')
            obj=models.CourseDetail.objects.filter(course_id=pk).select_related('course').first()
            ser=apiserializer.CourseDetailModelSerializer(instance=obj)
            ret.data=ser.data
        except Exception as e:
            ret.code = 1001
            ret.error = '获取数据错误'

        return Response(ret.dict)

serializer

class CourseDetailModelSerializer(ModelSerializer):
    '''获取课程详细信息'''
    recommend_courses=serializers.SerializerMethodField()
    course_img=serializers.CharField(source='course.course_img')
    PricePolicy = serializers.SerializerMethodField()
    chapter=serializers.SerializerMethodField()
    asked_question=serializers.SerializerMethodField()

    def get_recommend_courses(self,obj):
        '''获取关联推荐课程'''
        queryset=obj.recommend_courses.all()
        return [{'id':row.id,'name':row.name} for row in queryset]

    def get_PricePolicy(self,obj):
        '''获取对应价格策略'''
        queryset=obj.course.price_policy.all()
        return [{'price':row.price,'valid':row.get_valid_period_display() } for row in queryset]

    def get_chapter(self,obj):
        '''获取章节'''
        queryset=obj.course.coursechapters.all()
        return [ {'chapter':row.chapter,'name':row.name} for row in queryset]

    def get_asked_question(self,obj):
        '''获取常见问题'''
        queryset=obj.course.asked_question.all()
        return [ {'question':row.question,'answer':row.answer} for row in queryset]

    class Meta:
        model=models.CourseDetail
        fields=['hours','course_slogan','video_brief_link','why_study',
                'what_to_study_brief','career_improvement','prerequisite',
                'course_img','recommend_courses','PricePolicy','chapter',
                'asked_question',
                ]

 

购物车业务逻辑

 导入模块和dispath初始化信息

from rest_framework.response import Response
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.viewsets import ViewSetMixin,ModelViewSet
from django_redis import get_redis_connection

from ..auth.auth import AuthLogin
from utils.exception import PolicyInvalid
from utils.response import BaseResponse

class ShopCarView(ViewSetMixin,APIView):

    authentication_classes = [AuthLogin,]

    def dispatch(self, request, *args, **kwargs):
        '''构建连接池,和redis的key'''

        self.poll=get_redis_connection('defalut')
        self.shop_car_key='luffy_shop_car_%s_%s'

        ret=super().dispatch(self, request, *args, **kwargs)

        return ret

 

创建购物车信息

    def post(self,request,*args,**kwargs):
        '''添加购物车信息'''
        ret=BaseResponse()
        try:
            # 获取课程ID 和 价格策略ID
            course_id=request.data.get('course_id')
            policy_id=request.data.get('policy_id')

            # 获取课程信息
            course_obj=models.Course.objects.get(id=course_id)

            # 获取课程相关的价格策略
            price_policy_list=course_obj.price_policy.all()

            # 构造价格策略的字典
            '''
                {1:{},2:{}}
            '''
            price_policy_dict={obj.id:{
                'valid_period':obj.valid_period,
                'valid_period_display':obj.get_valid_period_display(),
                'price':obj.price
             }  for obj in price_policy_list}

            # 判断是否在价格策略里
            if policy_id not in price_policy_dict:
                raise PolicyInvalid('价格策略是非法的')

            shop_car_dict={
                'name':course_obj.name,
                'course_img':course_obj.course_img,
                'policy':json.dumps(price_policy_dict),
                'default':policy_id
            }
            # 将创建的购买课程更新或者添加到redis
            self.poll.hmset(self.shop_car_key%(request.auth.user_id,course_id),shop_car_dict)

            ret.data='添加成功'
except PolicyInvalid as e:
            ret.code=1001
            ret.error=e.msg

        except ObjectDoesNotExist as e:
            ret.code=1002
            ret.error='获取课程错误'

        except Exception as e:
            ret.code=1003
            ret.error=str(e)

        return Response(ret.dict)

 

修改购物车价格策略

    def patch(self,request,*args,**kwargs):
        '''修改课程价格策略'''
        ret = BaseResponse()
        try:
            # 获取ID信息
            course_id = request.data.get('course_id')
            policy_id = str(request.data.get('policy_id'))

            # 构建redis的key值
            car_key = self.shop_car_key % (request.auth.user_id, course_id)

            # 判断课程是否在已购的课程里
            if not self.poll.hexists(car_key):
                ret.code = 1002
                ret.error = '购物车不存在此课程'
                return Response(ret.dict)

            # 获取价格策略
            price_policy_dict = json.loads(str(self.poll.hget(car_key, 'policy'), encoding='utf-8'))

            # 判断修改的价格策略是否合法
            if policy_id not in price_policy_dict:
                ret.code = 1003
                ret.error = '非法的价格策略'
                return Response(ret.dict)

            # 修改购物车默认的价格策略
            self.poll.hset(car_key, 'default', policy_id)

            ret.data = '更新成功'

        except IndexError as e:
            ret.code = 1001
            ret.error = '获取ID失败'

        return Response(ret.dict)

 

删除购物车信息

    def delete(self,request,*args,**kwargs):
        '''删除课程'''

        ret=BaseResponse()
        try:
            # 获取用户的id和需要删除的课程id {'course_list':[1,2]}
            user_id=request.auth.user_id
            course_list=request.data.get('course_list')

            # 构建多个需要删除的购物车key
            # self.shop_car_key = 'luffy_shop_car_%s_%s'

            car_key=self.shop_car_key
            del_course_key=[car_key%(user_id,row) for row in course_list ]

            # 删除购物车key
            self.poll.hdel(*del_course_key)

            ret.data='删除成功'


        except IndexError as e:
            ret.code=1001
            ret.error='获取课程id失败'

        except Exception as e:
            ret.code=1002
            ret.error=str(e)

        return Response(ret.dict)

 

查看购物车所有信息

    def get(self,request,*args,**kwargs):
        ret=BaseResponse()
        # 匹配该用户所有的购物车的key
        key_match=self.shop_car_key%(request.auth.user_id,'*')
        shop_car_list=[]
        try:
            # 构建字典
            for key in self.poll.hscan_iter(key_match):
                shop_car_info={
                    'name':str(self.poll.hget(key,'name'),encoding='utf-8'),
                    'course_img':str(self.poll.hget(key,'course_img'),encoding='utf-8'),
                    'policy':json.loads(str(self.poll.hget(key,'policy'),encoding='utf-8')),
                    'default':str(self.poll.hget(key,'default'),encoding='utf-8')
                }
                # 将字典添加到列表中
                shop_car_list.append(shop_car_info)
            ret.data = shop_car_list

        except Exception as e:
            ret.code=1001
            ret.error='获取信息错误'
        return Response(ret.dict)

 

结算中心业务逻辑

结算中心数据结构

class PayMentView(APIView):
    authentication_classes = [AuthLogin,]
    conn=get_redis_connection('default')


SHOP_CAR_KEY='luffy_shop_car_%s_%s'
PAMENT_KEY='luffy_payment_%s_%s'
PAMENT_COUPON_KEY='payment_global_coupon_%s'
# 第一个循环构建的数据格式
all_payment_dict={
    #课程id
    1:{ 'course_id':1,
        'title':self.conn.hget(car_key, 'title').decode('utf-8'),
        'img'  :self.conn.hget(car_key, 'img').decode('utf-8'),
        'coupon':{},
        'default_coupon':0,
        }
        
    2:{ 'course_id':2,
        'title':self.conn.hget(car_key, 'title').decode('utf-8'),
        'img'  :self.conn.hget(car_key, 'img').decode('utf-8'),
        'coupon':{},
        'default_coupon':0,
        }
}



#redis的数据格式
redis={
    # payment_用户ID_课程ID
    payment_1_1:{
        'course_id':1
        'title':self.conn.hget(car_key, 'title').decode('utf-8'),
        'img'  :self.conn.hget(car_key, 'img').decode('utf-8'),
        'coupon':{
            coupon_id:{'coupon_type':1,'coupon_type_choices':'通用券',
                        'money_equivalent_value':100,}
            },
            coupon_id:{'coupon_type':2,'coupon_type_choices':'通用券',
                        'money_equivalent_value':100,}
            },
        'default_coupon':0,
        'prolicy_id':10,
        'name':'有效期1个月',
        'price':123,    
    },
    payment_1_2:{
    
    }
    payment_global_coupon_1:{
        'coupon':{
            3:{'coupon_type':1,'coupon_type_choices':'通用券',
                'money_equivalent_value':100,},
            3:{'coupon_type':1,'coupon_type_choices':'通用券',
                'money_equivalent_value':100,}
        },
        'default_coupon':0
        
    }
}

    
    

 

 

添加数据到结算中心

    def post(self,request,*args,**kwargs):

        # 添加结算中心
        # 只有在限制了结算中心的长度才用keys
        # 若果不知道key有多少的话,最好用scan_iter()
        # 清空结算中心缓存
        key_list=self.conn.keys(settings.PAMENT_KEY%(request.auth.user_id,'*'))
        key_list.append(settings.PAMENT_COUPON_KEY(request.auth.user_id))
        self.conn.delete(*key_list)

        ret= BaseResponse()
        # {course_id:{},course_id:{}}
        all_payment_dict={}
        try:
             # 1 获取用户需要结算的ID
            courseids=request.data.get('courseids')
            for course_id in courseids:
                car_key=settings.SHOP_CAR_KEY%(request.auth.user_id,course_id)

                # 2判断要结算的课程是否加入购物车
                if not self.conn.exists(car_key):
                    ret.code=1001
                    ret.error='课程没有加入购物车'

                else:
                    # 3 从购物车中获取信息,放入结算中心
                    # 3.1 获取价格策略
                    policy = json.loads(str(self.conn.hget(car_key, 'policy'), encoding='utf-8'))
                    default_policy = self.conn.hget(car_key, 'default')
                    default_policy_dict=policy[default_policy]

                    # 3.2 构建结算中心的字典
                    payment_dict={
                        'course_id':str(course_id),
                        'title':self.conn.hget(car_key, 'title').decode('utf-8'),
                        'img'  :self.conn.hget(car_key, 'img').decode('utf-8'),
                        'coupon':{},
                        'default_coupon':0,
                    }
                    # 3.3 将价格策略的字典合并到结算中心的字典中
                    payment_dict.update(default_policy_dict)
                    all_payment_dict[course_id]=payment_dict

            # 4 获取优惠券信息:
            # 获取当前时间 2018-8-2
            now = datetime.date.today()
            coupon_list=models.CouponRecord.objects.select_related('coupon').filter(
                account=request.auth.user,
                status=0,
                coupon__valid_begin_date__lte=now,
                coupon__valid_end_date__gte=now,
            )
            # 通用优惠券字典
            payment_global_coupon={
                'coupon':{},
            }
            for item in coupon_list:
                # 获取当前的优惠券ID
                coupon_id=str(item.id)

                # 获取当前优惠券的类型
                coupon_type=item.coupon.coupon_type

                # 优惠券字典
                info={
                    'coupon_type':coupon_type,
                    'coupon_type_display':item.coupon.get_coupon_type_display(),
                }
                # 如果优惠券是通用类
                if coupon_type==0:
                    info['money_equivalent_value']=item.coupon.money_equivalent_value

                # 优惠券是满减类
                elif coupon_type==1:
                    info['money_equivalent_value']=item.coupon.money_equivalent_value
                    info['minimum_consume']=item.coupon.minimum_consume

                # 优惠券是折扣券
                else:
                    info['off_percent']=item.coupon.off_percent

                # 获取当前优惠券所关联的课程id
                coupon_course_id=str(item.coupon.object_id)

                # 如果优惠券没有关联课程
                if coupon_course_id not in all_payment_dict:
                    payment_global_coupon['defalut_coupon']=0
                    payment_global_coupon['coupon'][coupon_id]=info

                # 优惠券关联了课程
                else:
                    all_payment_dict[coupon_course_id]['coupon'][coupon_id]=info

            #将结算中心的数据放入到redis中
            for cid,cinfo in all_payment_dict.items():
                cinfo['coupon']=json.dumps(cinfo['coupon'])
                self.conn.hmset(settings.PAMENT_KEY%(request.auth.user_id,cid,),cinfo)

            payment_global_coupon['coupon']=json.dumps(payment_global_coupon['coupon'])
            self.conn.hmset(settings.PAMENT_COUPON_KEY%(request.auth.user_id),payment_global_coupon)


        except IndexError as e:
            ret.code=1002
            ret.error='获取课程id错误'

        except Exception as e:
            ret.code=1003
            ret.error=str(e)

        return Response(ret.dict)
View Code

 

修改结算中心的优惠券信息

    def patch(self,request,*args,**kwargs):
        '''修改优惠券'''
        ret= BaseResponse()

        # 没传课程id则修改全站优惠券
        # 获取课程id 和优惠券id
        try:
            course=request.data.get('course_id')
            course_id=str(course) if course else course
            coupon_id=str(request.data.get('coupon_id'))


            #构建redis的结算中心key值
            redies_payment_key=settings.PAMENT_KEY%(request.auth.user_id,course_id)
            redis_global_coupon_key=settings.PAMENT_COUPON_KEY%(request.auth.user_id)

            # 如果是全站优惠券
            if not course_id:

                # 不使用全站优惠券 
                if coupon_id=='0':
                    self.conn.hset(redis_global_coupon_key,'defalut_coupon',coupon_id)
                    ret.data='修改成功'
                    return Response(ret.dict)

                # 使用全站优惠券
                global_coupon_dict=self.conn.hget(redis_global_coupon_key,'coupon')

                #如果全站优惠卷是非法的
                if coupon_id not in global_coupon_dict:
                    ret.code=1001
                    ret.data='该全站优惠卷是非法的'
                    return Response(ret.dict)

                self.conn.hget(redis_global_coupon_key,'defalut_coupon',coupon_id)
                ret.data='修改成功'
                return Response(ret.dict)

            # 修改课程优惠券

            if coupon_id =='0':
                #该课程不使用优惠券
                self.conn.hset(redies_payment_key,'default_coupon',coupon_id)
                ret.data='修改成功'
                return Response(ret.dict)

            course_coupon_dict=json.loads(str(self.conn.hget(redies_payment_key,'coupon'),encoding='utf-8'))

            # 判断优惠券是否是合法的
            if coupon_id not in course_coupon_dict:
                ret.code=1002
                ret.data='该课程优惠券是非法的'
                return  Response(ret.dict)

            self.conn.hset(redies_payment_key,'default_coupon',coupon_id)
            ret.data='修改成功'

        except Exception as e:
            ret.code=1003
            ret.data='修改失败'

        return Response(ret.dict)

查询结算中心的所有信息

    def get(self,request,*args,**kwargs):
        '''从redis中获取所有的结算中心信息'''

        ret=BaseResponse()
        try:
            # 构造redis结算匹配的key
            course_payment_key=settings.PAMENT_KEY%(request.auth.user_id,'*',)

            # 构造全栈优惠券的key值
            global_coupon_key=settings.PAMENT_COUPON_KEY%(request.auth.user_id,)

            course_list=[]
            # 获取结算中心含有优惠券的数据
            for item in self.conn.scan_iter(course_payment_key):
                course_payment_info={}
                for key,value in self.conn.hgetall(item):
                    key=key.decode('utf-8')
                    if key=='coupon':
                        course_payment_info[key]=json.loads(str(value),encoding='utf-8')
                    else:
                        course_payment_info[key]=value.decode('utf-8')

                course_list.append(course_payment_info)


            # 获取全站优惠券信息
            galbal_coupon_dict={
                'coupon':json.loads(str(self.conn.hget(global_coupon_key,'global_coupon_key'),encoding='utf-8')),
                'default_coupon':str(self.conn.hget(global_coupon_key,'default_coupon'))
            }

            ret.data={
                'course_list':course_list,
                'galbal_coupon_dict':galbal_coupon_dict,
            }
        except Exception as e:
            ret.code=1001
            ret.error='获取失败'

        return Response(ret.dict)

 去支付

添加去支付

去支付流程

    去支付:
    1.获取用户提交数据
    {
        balance:1000,
        money:900,
    }
    balance=request.data.get('balance')
    money=request.data.get('money')
    
    2.数据验证
        -大于等于0
        -个人账户是否有1000贝里
        
        if request.auth.user.balance < balance
            账户贝里余额不足
            
    优惠券ID_LIST=[]
    总价
    实际支付
    3. 去结算中心获取课程信息
        for course_dict in redis 的结算钟获取:
            # 获取课程ID 
            # 根据course_id检查是否过期
            
            # 获取价格策略
            #根据policy_id去检查数据库是否存在价格策略
            
            #获取使用优惠券ID
            # 根据优惠券ID 检查是否过期
            
            获取原价+获取优惠券类型
                -立减
                    折后价格= 获取原价-优惠券金额
                -满减 是否满足限制
                    折后价格=获取原价-优惠券金额
                -折扣:
                    折扣价格=获取原价*80/100
    4.全站优惠券
        -去数据库校验全站优惠券的合法性
        -应用优惠券:
            -立减
                折后价格= 获取原价-优惠券金额
            -满减 是否满足限制
                折后价格=获取原价-优惠券金额
            -折扣:
                折扣价格=获取原价*80/100
                
    5.贝里抵扣
    
    6.总金额校验
        实际支付  -贝里 =money:900
        
    7. 为当前课程生成订单
        -基于事务在完成
        -订单表创建一条数据
            -订单详细表创建一条数据Order OderDetail
            
        -如果有贝里支付
            -贝里金额扣除 Account
            -交易记录       TransactionRecord
            
        -优惠券的状态更新 CouponRecord
        
        注意:
            如果支付宝支付金额0,表示订单状态:已支付
            如果支付宝金额100,    表示订单状态:未支付
                -生成URL(含订单号)
                -回调函数:更新订单状态

去支付代码:

    def post(self,request,*args,**kwargs):
        try:
            # 获取用户抵用的贝里
            balance =request.data.get('balance')

            # 获取用户提交的金额
            money=request.data.get('money')

            self.check_valid(request,balance,money)
            if self.ret.code !=1000:
                return Response(self.ret.dict)

            self.create_model(request)

            self.ret.data='支付成功'


        except ObjectDoesNotExist as e:
            self.ret.code=1008
            self.ret.error='获取model不存在'

        except IndexError as e:
            self.ret.code=1009
            self.ret.error='index错误'

        except Exception as e:
            self.ret.code=1010
            self.ret.error='错误'

        return Response(self.ret.dict)
 def check_valid(self,request,balance,money):
        '''校验合法性'''
        # 如果提交的结算金额小于0
        if money < 0 :
            self.ret.code=1001
            self.ret.error='结算金额必须大于等于0'
            return

        # 如果用户拥有的贝里小于优惠的贝里
        if request.auth.user.balance < balance:
            self.ret.code=1002
            self.ret.error='客户的贝里不足'
            return

        # 构建查找该用户结算所有的信息key
        payment_key=settings.PAMENT_KEY%(request.auth.user_id,'*')

        # 需要更新的状态优惠券id
        self.coupon_list=[]
        go_pay_dict={}
        for key in self.conn.scan_iter(payment_key):

            # 获取结算课程的id、使用的优惠券、课程价格策略
            course_id=self.conn.hget(key,'course_id').decode('utf-8')
            use_coupon=self.conn.hget(key,'default_coupon').decode('utf-8')
            policy_id=self.conn.hget(key,'prolicy_id').decode('utf-8')
            coupon_dict=json.loads(str(self.conn.hget(key,'coupon')),encoding='utf-8').get(use_coupon)
            # 判断课程是否已经下载
            if not models.Course.objects.filter(id=course_id,status=0).exists():
                self.ret.code=1003
                self.ret.error='课程已经下架'
                return

            # 判断价格策略是否合法
            if not models.PricePolicy.objects.filter(id=policy_id,object_id=course_id).exists():
                self.ret.code=1005
                self.ret.error='价格策略不合法'
                return

            # 判断优惠券是否合法
            coupon_obj=models.CouponRecord.objects.filter(id=use_coupon,status=0,account=request.auth.user,
                                                             coupon__object_id=course_id).first()

            # 使用了优惠券,但是优惠券不存在
            if use_coupon!='0' and not coupon_obj:
                self.ret.code=1004
                self.ret.error='使用的优惠券不存在'
                return

            # 如果没有使用优惠券
            if use_coupon=='0':
                go_pay_dict[course_id] = {}

            # 如果使用了课程优惠券
            else:
                go_pay_dict[course_id] = coupon_dict
                self.coupon_list.append({'id':use_coupon,'coupon':coupon_obj.coupon_id})
            # 构建购数据
            go_pay_dict[course_id]['price'] = self.conn.hget(key,'price').decode('utf-8')
            go_pay_dict[course_id]['valid_period']=self.conn.hget(key,'valid_period').decode('utf-8')

        # 计算课程类优惠券
        course_money=0
        for key,values in go_pay_dict.items():
            #获取该优惠券类型
            coupon_type=values.get('coupon_type')
            price=int(values.get('price'))
            if not coupon_type:
                course_money+=price
                continue

            if coupon_type =='0':
                course_money+=price-int(values.get('money_equivalent_value'))

            elif coupon_type=='1':
                minimum_consume=int(values.get('minimum_consume'))
                if not  minimum_consume < price:
                    self.ret.code=1005
                    self.ret.error='该课程没有达到满减要求'
                course_money+=price-int(values.get('money_equivalent_value'))

            else:
                course_money=course_money+price*int(values.get('off_percent'))/100
            go_pay_dict[key]['coupon_money']=course_money


        price = self.handler_galbal(request,course_money)

        self.handler_order(request.auth.user_id,price)

        self.handler_order_detail(go_pay_dict,)
check_valid
    def handler_galbal(self,request,course_money):
        # 构造获取该用户全局的key
        global_default_key = settings.PAMENT_COUPON_KEY % (request.auth.user_id,)
        global_default_coupon = self.conn.hget(global_default_key, 'default_key')

        # 如果没有使用优惠券
        if global_default_coupon == '0':
            return course_money

        # 全站使用优惠券
        else:
            # 判断全站优惠券是否合法
            coupon_obj = models.CouponRecord.objects.filter(id=global_default_coupon, status=0,
                                                               account=request.auth.user).first()

            if not coupon_obj:
                self.ret.code = 1006
                self.ret.error = '全站优惠券不合法'
            #获取使用的全站优惠券信息
            self.coupon_list.append(({'id':global_default_coupon,'coupon':coupon_obj.coupon_id}))
            global_coupon_dict = json.loads(str(self.conn.hget(global_default_key, 'coupon')), encoding='utf-8'). \
                get(global_default_coupon)
            # 获取全站优惠券的类型
            glo_bal_coupon_type = global_coupon_dict.get('coupon_type')

            # 如果全站优惠券是通用券
            if glo_bal_coupon_type == '0':
                global_money =course_money-int(global_coupon_dict.get('money_equivalent_value'))

            # 如果全站优惠券是满减卷
            elif glo_bal_coupon_type == '1':
                minimum_consume = int(global_coupon_dict.get('minimum_consume'))
                if not minimum_consume < course_money:
                    self.ret.code = 1005
                    self.ret.error = '该课程没有达到满减要求'

                global_money = course_money - int(global_coupon_dict.get('money_equivalent_value'))

            # 如果全站优惠券是折扣券
            else:
                global_money = course_money * int(global_coupon_dict.get('off_percent')) / 100

            #如果减到为负数
            if global_money<0:
                global_money=0
            return global_money
handler_galbal
    def handler_order(self,account_id,actual_amount):
        '''构造生成订单的字典'''
        self.order_dict={
            'payment_type':1,
            'order_number':str(uuid.uuid4()),
            'account':account_id,
            'actual_amount':actual_amount,
            'status':1,
        }
handler_order
    def handler_order_detail(self,pay_dict):

        self.order_list=[]

        for key,values in pay_dict.items():
            info={
                'content_object':models.Course.objects.get(id=key),
                'original_price':values.get('price'),
                'price':values.get('coupon_money'),
                'valid_period_display':values.get('valid_period'),
                'valid_period':values.get('valid_period'),
            }
            self.order_list.append(info)
handler_order_detail
    def create_model(self,request):
        with transaction.atomic():
            order_obj=models.Order.objects.create(**self.order_dict)

            temp=[]
            for item in self.order_list:
                item['order']=order_obj
                obj=models.OrderDetail(**item)
                temp.append(obj)
            models.OrderDetail.objects.bulk_create(temp)
            now=datetime.datetime.now()
            for obj in self.coupon_list:
                obj['account_id']=request.auth.user_id
                obj['status']=0
                models.CouponRecord.objects.filter(**obj).update(status=1,used_time=now)
create_model

 

posted @ 2018-07-31 16:29  R00M  阅读(488)  评论(0编辑  收藏  举报