路飞学城项目-支付相关-购物车接口
############### 支付相关的需求 ################
""" 支付相关的需求: 购物车需求: 1,在课程详情页面,点击加入购物车可以把课程加入到购物车 2,点击导航条的购物车按钮可以进入购物车页面 3,在购物车页面点击删除可以把购物车删除, 4,点击购物车的有效期,可以更改价格策略,然后去购物车的数据进行修改, 5,点击购物车底部左侧的全选,可以全选,然后全选按钮旁边有删除,可以批量删除, 6,购物车底部右侧的有全部的金额合计,右侧有一个去结算按钮,点击可以到结算页面 实现购物车的新增,删除,修改,查询, 结算需求: 1,结算页面的价格策略是不能修改的,但是可以选择优惠券,使用贝里, 2,优惠券分为两种一种是绑定课程的和课程在一栏,没有绑定课程的是通用优惠券,在底部可以选择, 3,结算页面右下角显示使用优惠券之后的价格,显示使用贝里之后的价格, 4,最下面显示支付方式,应付金额,和立即支付按钮,点击按钮可以进入支付流程, 实现结算信息的新增,查看,修改,没有删除,你回去购物车之后就把结算信息情况了,可以再次新增, 支付需求: 1,点击立即支付进入支付流程,需要生成订单,然后调支付宝接口, 2,支付成功之后调回调接口,修改订单的状态,但是没有这么简单, 2.1修改订单的状态 2.2有优惠券修改优惠券的状态 2.2使用了贝里需要修改贝里的金额, 2.3你买的如果是普通课程,你就可以看了,如果是学位课,你要开通模块,配备老师, 技术使用redis数据库, 原因有两个 1,购物车和结算是临时状态, 2,需要频繁的修改购物车信息, """
############### 购物车接口实现思路 ################
""" 购物车的设计思路: 一,需要保持什么字段 1,用户id 2,课程名称,图片, 3,价格策略:你需要这个课程的价格策略都取出来, 3.1 id 3.2 有效期, 3.3 价格 4,默认选中的价格策略, 二,数据结构 注意,redis只允许有一层字典结构,所以多余一层的,需要进行序列化,变成字符串放进去,然后反序列化拿出来, 第一种数据结构: shapping_car : { user_id : { course_id : { title:XX course_img:XX price_policy_dict: { 1:{'name':有效期1个月,price:799} # 价格策略会有多个 } default_price_policy_dict: 1 } } } 第二种数据结构 shapping_car_userid_courseid : { id:XX title:XX course_img:XX price_policy_dict: { 1:{name:有效期1个月,price:99 } } default_price_policy_dict:XX } 三,开始实现加入购物车: 前端: 传过来course_id , price_policy_id ,不需要传递user_id因为已经登录了, 后端: 1,获取前端传过来的值,如何取到user_id ? 直接request里面就会有, 2,对存过来的数据进行校验,一定要校验,把这个校验深深刻进脑子 1,验证course_id 验证是否合法 2,price_policy_id 验证是否合法 3,构建我们想要的数据结构 4,写入redis 5,返回数据, 四,开始实现查看购物车 前端:进入购物车页面,发送get请求, 后端逻辑: 1,获取到userid, 2,根据userid拼接购物车的key, 3,去redis里面把这个人所有的购物车信息都拿出来, 4,然后返回给前端, 注意,redis的操作是重中之重 五,更新购物车逻辑 前端发过来字段:{ course_id:1, default_policy_id:2 } 后端逻辑: 1,获取前端传过来的课程id,策略id 2,校验数据, 3,更新数据, 4,返回数据, 六,删除购物车信息 前端传过来字典:{ course_id:[1,2,3] # 支持删除单个,和多个, } 后端逻辑: 1,获取前端传过来的课程id 2,拼接购物车的key, 3,验证key是否存在, 4,如果存在,就删除, 5,返回成功, """
############### 购物车接口实现思路 ################
1:
from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet, ViewSetMixin from rest_framework.response import Response from django_redis import get_redis_connection from utils.response import BaseResponse from utils.auth import LuffyAuth from api import models from django.core.exceptions import ObjectDoesNotExist from utils.exception import PricePolicyInvalid from django.conf import settings import json class ShoppingCarViewSet(APIView): authentication_classes = [LuffyAuth,] conn = get_redis_connection("default") # 在这个地方穿件连接池,下面在函数中连接redis的时候,需要self.conn def post(self, request, *args, **kwargs): """ 将课程添加到购物车 :param request: :param args: :param kwargs: :return: """ ret = BaseResponse() try: # 1. 获取用户提交的课程ID和价格策略ID course_id = int(request.data.get('courseid')) policy_id = int(request.data.get('policyid')) # 2. 通过课程id获取专题课信息,如果找不多会报异常,需要处理ObjectDoesNotExist course = models.Course.objects.get(id=course_id) # 3. 获取该课程相关的所有价格策略 price_policy_list = course.price_policy.all() # all返回queryset对象,可以遍历 price_policy_dict = {} for item in price_policy_list: price_policy_dict[item.id] = { "period":item.valid_period, "period_display":item.get_valid_period_display(), "price":item.price, } # 4. 判断用户提交的价格策略是否合法 if policy_id not in price_policy_dict: # 价格策略不合法 raise PricePolicyInvalid('价格策略不合法') # 5. 将购物信息添加到redis中 # self.conn # car_key = "luffy_shopping_car_%s_%s" car_key = settings.SHOPPING_CAR_KEY %(request.auth.user_id,course_id,) car_dict = { 'title':course.name, 'img':course.course_img, 'default_policy':policy_id, 'policy':json.dumps(price_policy_dict) } # conn = get_redis_connection("default") self.conn.hmset(car_key,car_dict) ret.data = '添加成功' except PricePolicyInvalid as e: ret.code = 2001 ret.error = e.msg except ObjectDoesNotExist as e: ret.code = 2001 ret.error = '课程不存在' except Exception as e: ret.code = 1001 ret.error = '获取购物车失败' return Response(ret.dict) def delete(self, request, *args, **kwargs): """ 购物车中删除课程 :param request: :param args: :param kwargs: :return: """ ret = BaseResponse() try: course_id_list = request.data.get('courseids') key_list = [ settings.SHOPPING_CAR_KEY %(request.auth.user_id,course_id,) for course_id in course_id_list] self.conn.delete(*key_list) except Exception as e: ret.code = 1002 ret.error = "删除失败" return Response(ret.dict) def patch(self, request, *args, **kwargs): """ 修改课程的价格策略 :param request: :param args: :param kwargs: :return: """ ret = BaseResponse() try: # 1. 获取价格策略ID和课程ID course_id = int(request.data.get('courseid')) policy_id = str(request.data.get('policyid')) # 因为你load回来之后,是一个字符串, # 2. 拼接课程的key key = settings.SHOPPING_CAR_KEY %(request.auth.user_id,course_id,) if not self.conn.exists(key): ret.code = 1002 ret.error = "购物车中不存在此课程" return Response(ret.dict) # 3. redis中获取所有的价格策略 policy_dict = json.loads(str(self.conn.hget(key,'policy'),encoding='utf-8')) if policy_id not in policy_dict: ret.code = 1003 ret.error = "价格策略不合法" return Response(ret.dict) # 4. 在购物车中修改该课程的默认价格策略 self.conn.hset(key,'default_policy',policy_id) ret.data = "修改成功" except Exception as e: ret.code = 1004 ret.error = "修改失败" return Response(ret.dict) def get(self,request, *args, **kwargs): """ 查看购物车中所有的商品 :param request: :param args: :param kwargs: :return: """ ret = BaseResponse() try: key_match = settings.SHOPPING_CAR_KEY %(request.auth.user_id,"*") course_list = [] for key in self.conn.scan_iter(key_match,count=10): info = { "title":self.conn.hget(key,'title').decode('utf-8'), "img":self.conn.hget(key,'img').decode('utf-8'), "policy":json.loads(self.conn.hget(key,'policy').decode('utf-8')), "default_policy":self.conn.hget(key,'default_policy').decode('utf-8') } course_list.append(info) ret.data = course_list except Exception as e: ret.code = 1002 ret.error = "获取失败" return Response(ret.dict)
2:
class ShoppingCar(APIView):
conn = get_redis_connection('default')
authentication_classes = [TokenAuth]
def post(self,request):
res = BaseException()
# 1,获取前端传过来的数据
course_id = int(request.data.get('course_id',''))
price_policy_id = int(request.data.get('price_policy_id',''))
user_id = request.user.id
print(course_id,price_policy_id,user_id)
# 2,获取课程信息
course_obj = Course.objects.filter(id=course_id).first()
if not course_obj:
res.code=1021
res.error= '课程不存在'
return Response(res.dict)
# 3,获取课程所有的价格策略
price_policy_list = course_obj.price_policy.all()
price_dict = {}
for i in price_policy_list:
price_dict[i.id]= {
"text":i.get_valid_period_display(),
"price":i.price
}
print(price_dict)
print(price_dict.keys())
print(price_policy_id)
# 判断策略是否存在
if price_policy_id not in price_dict:
res.code=1022
res.error='策略不存在'
return Response(res.dict)
# 存入redis
redis_car_key = settings.SHOPPING_CART_KEY.format(user_id,course_id)
self.conn.hmset(redis_car_key,{
"title": course_obj.name,
"course_img": course_obj.course_img,
"price": json.dumps(price_dict),
"default_price_id": price_policy_id
})
res.data='加入成功'
return Response(res.dict)
def get(self,request):
res = BaseException()
try:
# 1,获取userid
user_id = request.user.id
# 2,拼接购物车的key,
shopping_car_key = settings.SHOPPING_CART_KEY.format(user_id,"*")
print(shopping_car_key)
# 3,根据key,获取所有的购物车列表
all_key = self.conn.scan_iter(shopping_car_key)
print('all_key',all_key)
# 4,构造返回数据
shopping_car_list = []
for key in all_key:
course_id = str(key, encoding='utf-8').rsplit("_", maxsplit=1)[1] # str 类型
# print(course_id, type(course_id))
course_info = {
course_id: {
"title": self.conn.hget(key, "title").decode('utf-8'),
"img": self.conn.hget(key, "course_img").decode('utf-8'),
"default_policy": self.conn.hget(key, "default_price_id").decode('utf-8'),
"policy": json.loads(self.conn.hget(key, "price").decode('utf-8')), # 转换成 字符串
}
}
shopping_car_list.append(course_info)
res.data=shopping_car_list
except Exception as e:
print(e)
res.error=10031
res.error='获取失败'
return Response(res.dict)
def put(self,request):
res = BaseException()
try:
# 1,获取前端传过来的数据,课程id,策略id。
course_id = request.data.get('course_id')
price_id = request.data.get('default_price_id')
user_id = request.user.id
# 2,判断课程是否存在
# 逻辑,拼接shoppingkey,然后判断是否存在
shopping_car_key = settings.SHOPPING_CART_KEY.format(user_id,course_id)
if not self.conn.exists(shopping_car_key):
res.code=1033
res.error="课程不存在"
return Response(res.data)
# 3,判断策略是否存在
# 先获取到这个课程下面的价格策略
# course_info = self.conn.hgetall(shopping_car_key)
price_dict= json.loads(str(self.conn.hget(shopping_car_key,'price'),encoding='utf-8'))
print(price_dict)
if price_id not in price_dict:
res.code=1034
res.error='策略不存在'
return Response(res.dict)
self.conn.hset(shopping_car_key,'default_price_id',price_id)
res.data='修改成功'
except Exception as e:
print(e)
res.code=1034
res.error='更新失败'
return Response(res.dict)
def delete(self,request):
res = BaseException()
try:
# 1,获取前端的 courseid
course_id = request.data.get('course_id')
user_id = request.user.id
# 2,判断courseid是否存在
shopping_car_key = settings.SHOPPING_CART_KEY.format(user_id,course_id)
if not self.conn.exists(shopping_car_key):
res.code=1035
res.error='课程不存在'
# 3,删除记录
self.conn.delete(shopping_car_key)
res.data='删除成功'
except Exception as e:
res.code=1036
res.error='删除失败'
return Response(res.dict)
############### 购物车认证类 ################
from rest_framework.authentication import BaseAuthentication from api import models from rest_framework.exceptions import AuthenticationFailed class LuffyAuth(BaseAuthentication): def authenticate(self, request): """ 用户请求进行认证 :param request: :return: """ # http://wwwww...c0ovmadfasd/?token=adfasdfasdf token = request.query_params.get('token') obj = models.UserAuthToken.objects.filter(token=token).first() if not obj: raise AuthenticationFailed({'code':1001,'error':'认证失败'}) return (obj.user.username,obj)
############### 购物车异常类 ################
class PricePolicyInvalid(Exception): def __init__(self,msg): self.msg = msg
############### 购物车redis连接和key配置 ################
# django-redis 配置 CACHES = { "default": { # 一个redis连接 "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://192.168.100.128:6379", # redis的IP和端口 "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 1000}, # 连接池最大连接数 "PASSWORD": "ji10201749" # 密码 }, }, } # 购物车的 redis 中的key SHOPPING_CART_KEY = "shopping_car_{0}_{1}" PAYMENT_COURSE_KEY = "payment_{0}_{1}" # redis 中 结算中心关于课程信息+优惠券信息 PAYMENT_GLOBAL_COUPON_KEY = "payment_global_coupon_{}" # 通用优惠券信息
############### 基础的返回类 ################
class BaseResponse(object): def __init__(self): self.data = None self.code = 1000 self.error=None @property def dict(self): return self.__dict__
############### 结束线 ################
技术改变命运