路飞学城结算中心的优惠券表结构
# ########################### 优惠券 ################################ 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) 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") number = models.CharField(max_length=64, unique=True) account = models.ForeignKey("Account", verbose_name="拥有者") 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)
结算中心的实现思路及数据结构
结算中心 1.购物车(可以选择价格策略) { luffy_shopping_car_6_11:{ 'title':'21天入门到放弃', 'src':'xxx.png', 'policy':{ 1:{id:'xx'.....}, 2:{id:'xx'.....}, 3:{id:'xx'.....}, 4:{id:'xx'.....}, }, 'default_policy':3 }, luffy_shopping_car_6_13:{ ... } } 2.结算(可以选择优惠券) a. POST请求,去结算 请求体: { courseids:[1,2] } 业务处理: 1. 检测课程ID是否已经加入到购物车 2. 获取指定价格策略信息 3. 获取优惠券信息 4. 构造结构放入redis b. GET请求,获取结算中心数据 业务处理: 1. 获取结算中心里的课程信息(绑定课程优惠券) 2. 获取全局优惠券 c. PATCH请求,选择优惠券 请求体: { courseid:0 couponid:12 } 业务处理: 1. 校验结算中心是否存在该课程 2. 校验优惠券是否可用 注意: 1. 优惠券状态 2. 优惠券使用时间
#结算中心 { payment_1_2:{ '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':{ 'coupon_id':coupon_id, 'coupon_type':coupon_type, 'coupon_type_display':item.coupon.get_coupon_type_display(), 'money_equivalent_value':item.coupon.money_equivalent_value, }, 'default_coupon':0, }, payment_1_3:{ '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':{ 'coupon_id':coupon_id, 'coupon_type':coupon_type, 'coupon_type_display':item.coupon.get_coupon_type_display(), 'money_equivalent_value':item.coupon.money_equivalent_value, }, 'default_coupon':0, }, } payment_global_coupon_1={ 'coupon':{ coupon_id:{ 'coupon_id':coupon_id, 'coupon_type':coupon_type, 'coupon_type_display':item.coupon.get_coupon_type_display(), 'money_equivalent_value':item.coupon.money_equivalent_value, }, }, 'defalut_coupon':0 }
结算数据及目标: payment_dict = { '2': { course_id:2, 'title': 'CRM客户关系管理系统实战开发-专题', 'img': 'CRM.jpg', 'policy_id': '4', 'coupon': {}, 'default_coupon': 0, 'period': 210, 'period_display': '12个月', 'price': 122.0}, '1': { course_id:2, 'title': '爬虫开发-专题', 'img': '爬虫开发-专题.jpg', 'policy_id': '2', 'coupon': { 4: {'coupon_type': 0, 'coupon_display': '立减券', 'money_equivalent_value': 40}, 6: {'coupon_type': 1, 'coupon_display': '满减券', 'money_equivalent_value': 60, 'minimum_consume': 100} }, 'default_coupon': 0, 'period': 60, 'period_display': '2个月', 'price': 599.0} } global_coupon_dict = { 'coupon': { 2: {'coupon_type': 1, 'coupon_display': '满减券', 'money_equivalent_value': 200, 'minimum_consume': 500} }, 'default_coupon': 0 } ========================================= redis ============================================== redis = { payment_1_2:{ course_id:2, 'title': 'CRM客户关系管理系统实战开发-专题', 'img': 'CRM.jpg', 'policy_id': '4', 'coupon': {}, 'default_coupon': 0, 'period': 210, 'period_display': '12个月', 'price': 122.0}, }, payment_1_1:{ course_id:1, 'title': '爬虫开发-专题', 'img': '爬虫开发-专题.jpg', 'policy_id': '2', 'coupon': { 4: {'coupon_type': 0, 'coupon_display': '立减券', 'money_equivalent_value': 40}, 6: {'coupon_type': 1, 'coupon_display': '满减券', 'money_equivalent_value': 60, 'minimum_consume': 100} }, 'default_coupon': 0, 'period': 60, 'period_display': '2个月', 'price': 599.0} }, payment_global_coupon_1:{ 'coupon': { 2: {'coupon_type': 1, 'coupon_display': '满减券', 'money_equivalent_value': 200, 'minimum_consume': 500} }, 'default_coupon': 0 } }
import redis conn = redis.Redis(host='127.0.0.1',port=6379) conn.hset('k4','username','alex') conn.hset('k4','age','19') conn.hset('k3','k3key','k3value') for i in conn.scan_iter('k*'): data = conn.hgetall(i) for k,v in data.items(): print(k,v)
settings.py
SHOPPING_CAR_KEY = "luffy_shopping_car_%s_%s" PAYMENT_KEY = "luffy_payment_%s_%s" PAYMENT_COUPON_KEY = "luffy_payment_coupon_%s"
url(r'^payment/$', payment.PaymentViewSet.as_view()),
from rest_framework.views import APIView from rest_framework.response import Response from utils.auth import LuffyAuth from django.conf import settings from django_redis import get_redis_connection import json from utils.response import BaseResponse from api import models import datetime class PaymentViewSet(APIView): authentication_classes = [LuffyAuth,] conn = get_redis_connection("default") def post(self,request,*args,**kwargs): ret = BaseResponse() try: # 清空当前用户结算中心的数据 # luffy_payment_1_* # luffy_payment_coupon_1 key_list = self.conn.keys( settings.PAYMENT_KEY %(request.auth.user_id,"*",) ) key_list.append(settings.PAYMENT_COUPON_KEY %(request.auth.user_id,)) self.conn.delete(*key_list) payment_dict = {} global_coupon_dict = { "coupon":{}, "default_coupon":0 } # 1. 获取用户要结算课程ID course_id_list = request.data.get('courseids') for course_id in course_id_list: car_key = settings.SHOPPING_CAR_KEY %(request.auth.user_id,course_id,) # 1.1 检测用户要结算的课程是否已经加入购物车 if not self.conn.exists(car_key): ret.code = 1001 ret.error = "课程需要先加入购物车才能结算" # 1.2 从购物车中获取信息,放入到结算中心。 # 获取标题和图片 policy = json.loads(self.conn.hget(car_key, 'policy').decode('utf-8')) default_policy = self.conn.hget(car_key, 'default_policy').decode('utf-8') policy_info = policy[default_policy] payment_course_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'), "policy_id":default_policy, "coupon":{}, "default_coupon":0 } payment_course_dict.update(policy_info) payment_dict[str(course_id)] = payment_course_dict # 2. 获取优惠券 ctime = datetime.date.today() coupon_list = models.CouponRecord.objects.filter( account=request.auth.user, status=0, coupon__valid_begin_date__lte=ctime, coupon__valid_end_date__gte=ctime, ) for item in coupon_list: # 只处理绑定课程的优惠券 if not item.coupon.object_id: # 优惠券ID coupon_id = item.id # 优惠券类型:满减、折扣、立减 coupon_type = item.coupon.coupon_type info = {} info['coupon_type'] = coupon_type info['coupon_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 global_coupon_dict['coupon'][coupon_id] = info continue # 优惠券绑定课程的ID coupon_course_id = str(item.coupon.object_id) # 优惠券ID coupon_id = item.id # 优惠券类型:满减、折扣、立减 coupon_type = item.coupon.coupon_type info = {} info['coupon_type'] = coupon_type info['coupon_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 if coupon_course_id not in payment_dict: # 获取到优惠券,但是没有购买此课程 continue # 将优惠券设置到指定的课程字典中 payment_dict[coupon_course_id]['coupon'][coupon_id] = info # 可以获取绑定的优惠券 # 3. 将绑定优惠券课程+全站优惠券 写入到redis中(结算中心)。 # 3.1 绑定优惠券课程放入redis for cid,cinfo in payment_dict.items(): pay_key = settings.PAYMENT_KEY %(request.auth.user_id,cid,) cinfo['coupon'] = json.dumps(cinfo['coupon']) self.conn.hmset(pay_key,cinfo) # 3.2 将全站优惠券写入redis gcoupon_key = settings.PAYMENT_COUPON_KEY %(request.auth.user_id,) global_coupon_dict['coupon'] = json.dumps(global_coupon_dict['coupon']) self.conn.hmset(gcoupon_key,global_coupon_dict) except Exception as e: pass return Response(ret.dict) def patch(self,request,*args,**kwargs): ret = BaseResponse() try: # 1. 用户提交要修改的优惠券 course = request.data.get('courseid') course_id = str(course) if course else course coupon_id = str(request.data.get('couponid')) # payment_global_coupon_1 redis_global_coupon_key = settings.PAYMENT_COUPON_KEY %(request.auth.user_id,) # 修改全站优惠券 if not course_id: if coupon_id == "0": # 不使用优惠券,请求数据:{"couponid":0} self.conn.hset(redis_global_coupon_key,'default_coupon',coupon_id) ret.data = "修改成功" return Response(ret.dict) # 使用优惠券,请求数据:{"couponid":2} coupon_dict = json.loads(self.conn.hget(redis_global_coupon_key,'coupon').decode('utf-8')) # 判断用户选择得优惠券是否合法 if coupon_id not in coupon_dict: ret.code = 1001 ret.error = "全站优惠券不存在" return Response(ret.dict) # 选择的优惠券合法 self.conn.hset(redis_global_coupon_key, 'default_coupon', coupon_id) ret.data = "修改成功" return Response(ret.dict) # 修改课程优惠券 # luffy_payment_1_1 redis_payment_key = settings.PAYMENT_KEY % (request.auth.user_id, course_id,) # 不使用优惠券 if coupon_id == "0": self.conn.hset(redis_payment_key,'default_coupon',coupon_id) ret.data = "修改成功" return Response(ret.dict) # 使用优惠券 coupon_dict = json.loads(self.conn.hget(redis_payment_key,'coupon').decode('utf-8')) if coupon_id not in coupon_dict: ret.code = 1010 ret.error = "课程优惠券不存在" return Response(ret.dict) self.conn.hset(redis_payment_key,'default_coupon',coupon_id) except Exception as e: ret.code = 1111 ret.error = "修改失败" return Response(ret.dict) def get(self,request,*args,**kwargs): ret = BaseResponse() try: # luffy_payment_1_* redis_payment_key = settings.PAYMENT_KEY %(request.auth.user_id,"*",) # luffy_payment_coupon_1 redis_global_coupon_key = settings.PAYMENT_COUPON_KEY %(request.auth.user_id,) # 1. 获取绑定课程信息 course_list = [] for key in self.conn.scan_iter(redis_payment_key): # 取到所有的与用户有关的key。。。。 info = {} data = self.conn.hgetall(key) for k,v in data.items(): kk = k.decode('utf-8') if kk == "coupon": info[kk] = json.loads(v.decode('utf-8')) else: info[kk] = v.decode('utf-8') course_list.append(info) # 2.全站优惠券 global_coupon_dict = { 'coupon':json.loads(self.conn.hget(redis_global_coupon_key,'coupon').decode('utf-8')), 'default_coupon':self.conn.hget(redis_global_coupon_key,'default_coupon').decode('utf-8') } ret.data = { "course_list":course_list, "global_coupon_dict":global_coupon_dict } except Exception as e: ret.code = 1001 ret.error = "获取失败" return Response(ret.dict)