路飞学成项目之表结构和业务逻辑
路飞学成表结构
课程相关表结构
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)
深科技相关表结构
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. 通用评论表"
结算中心相关(优惠券)
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)
去支付表结构
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
课程相关业务逻辑
查看所有课程
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)
修改结算中心的优惠券信息
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,)
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
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, }
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)
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)