一、添加到购物车:
1.后端接口设计:
请求方式: POST /cart
请求参数: json或表单
参数 | 类型 | 是否必须 | 说明 |
---|---|---|---|
sku_id | int | 是 | 商品sku_id |
count | int | 是 | 数量 |
selected | bool | 否 | 是否勾选, 默认为True |
返回值: JSON
参数 | 类型 | 是否必须 | 说明 |
---|---|---|---|
sku_id | int | 是 | 商品sku_id |
count | int | 是 | 数量 |
selected | bool | 否 | 是否勾选, 默认为True |
访问此接口,无论用户是否登录, 前端请求都需携带请求头Authorization,由后端判断是否登录
2.后端实现:
因为前端可能携带cookie,为了保证跨域请求中,允许后端使用cookie,确保在配置文件有如下设置:
# meiduo_mall/settings/dev.py
CORS_ALLOW_CREDENTIALS = True
创建carts子应用:
# meiduo_mall/apps/
python ../../manager.py startapp carts
编写视图:
注意:因为前端请求时携带了Authorization请求头(主要是JWT), 如果用户为登录, 请求头的JWT无意义(没有值), 为了防止REST framework框架在验证此无意义的请求头时抛出401验证异常, 因此在视图中需要做两个处理:
- 重写perform_authentication()方法, 此方法是REST framework检车用户身份的方法;
- 在获取request.user属性时捕获异常,REST framework
在返回时会检查Authorization请求头,无效的Authorization请求头会导致抛出异常
在carts/views.py中创建视图:
# meiduo_mall/apps/carts/views.py
import base64
import pickle
from django.shortcuts import render
# Create your views here.
from django_redis import get_redis_connection
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from carts import constants
from goods.models import SKU
class CartVIew(APIView):
"""购物车"""
def perform_authentication(self, request):
"""
重写父类的用户验证方法,不在进入视图前就检查JWT
:param request:
:return:
"""
pass
def post(self, request):
"""
添加购物车
:param request:
:return:
"""
params = request.data
skuid = params.get('sku_id')
count = params.get('count')
selected = params.get('selected', True)
# 检查数据
try:
sku = SKU.objects.get(id = skuid)
except Exception as e:
return Response({'data':"商品查找失败..."})
try:
count = int(count)
except Exception as e:
return Response({'data': "传参不正确..."})
# 验证用户
try:
user = request.user
except Exception as e:
user = None
if user is not None and user.is_authenticated:
# 用户已经登录,在redis中保存
redis_conn = get_redis_connection('cart')
pl = redis_conn.pipeline()
# 记录购物车商品数量
pl.hincrby('cart_{}'.format(user.id), skuid, count)
# 记录购物车的勾选项
# 勾选
if selected:
pl.sadd('cart_selected_{}'.format(user.id),skuid)
pl.execute()
return Response({'sku_id':skuid,
'count':count,
'selected':selected}, status=status.HTTP_201_CREATED)
else:
# 用户未登录, 在cookie中保存
# {
# 1001:{'count':10,'selected':true},
# ...
# }
# 使用pickle序列化购物车数据, pickle操作的是bytes类型
cart = request.COOKIES.get('cart')
if cart is not None:
cart = pickle.loads(base64.b64decode(cart))
else:
cart = {}
sku = cart.get(skuid)
if sku:
count += int(sku.get('count'))
cart[skuid] = {
'count': count,
'selected': selected
}
cookie_cart = base64.b64encode(pickle.dumps(cart))
response = Response({'sku_id':skuid,
'count':count,
'selected':selected}, status=status.HTTP_201_CREATED)
response.set_cookie('cart',cookie_cart, max_age=constants.CART_COOKIE_EXPIRES)
return response
在carts中新建constants.py 常量文件
#meiduo_mall/apps/carts/constants.py
# 购物车cookie的有效期
CART_COOKIE_EXPIRES = 365 * 24 * 60 * 60
二、查询购物车:
1.后端接口设计:
请求方式: GET /cart/
请求参数: 无
返回值: json
参数 | 类型 | 是否必须 | 说明 |
---|---|---|---|
id | int | 是 | 商品sku_id |
name | str | 是 | 商品名称 |
count | int | 是 | 商品数量 |
sku.default_image_url | str | 是 | 商品默认图片路径 |
price | decimal | 是 | 商品单价 |
selected | bool | 是 | 商品是否被勾选 |
[
{
"id": 9,
"count": 3,
"name": "华为 HUAWEI P10 Plus 6GB+64GB 钻雕金 移动联通电信4G手机 双卡双待",
"default_image_url": "http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRcUeAHp9pAARfIK95am88523545",
"price": "3388.00",
"selected": true
},
{
"id": 12,
"count": 1,
"name": "华为 HUAWEI P10 Plus 6GB+64GB 钻雕蓝 移动联通电信4G手机 双卡双待",
"default_image_url": "http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRdICAO_CRAAcPaeOqMpA2024091",
"price": "3388.00",
"selected": true
}
]
2.后端实现:
在carts/views.py的视图类中,增加get方法:
# meiduo_mall/apps/carts/views.py
class CartVIew(APIView):
"""购物车"""
...
def get(self, request):
"""
查询购物车数据
:param request:
:return:
"""
try:
user = request.user
except Exception as e:
user = None
if user is not None and user.is_authenticated:
# 用户已登录,从redis中获取数据
redis_conn = get_redis_connection('cart')
redis_cart = redis_conn.hgetall('cart_{}'.format(user.id))
redis_selected = redis_conn.smembers('cart_selected_{}'.format(user.id))
cart = {}
for sku_id, count in redis_cart.items():
cart[int(sku_id)] = {
'count':int(count),
'selected':sku_id in redis_selected
}
else:
# 用户未登录, 从cookie中读取
cart = request.COOKIES.get('cart')
if cart:
cart = pickle.load(base64.b64decode(cart))
else:
cart = {}
# 遍历处理购物车数据
skus = SKU.objects.filter(id__in=cart.keys())
skus_list = []
for sku in skus:
sku_dict = {}
sku_dict['id'] = sku.id
sku_dict['count'] = cart[sku.id]['count']
sku_dict['name']=sku.name
sku_dict['default_image_url'] = sku.default_image_url
sku_dict['price'] = sku.price
sku_dict['selected'] = cart[sku.id]['selected']
skus_list.append(sku_dict)
return Response(skus_list)
三、修改购物车数据:
请求方式: PUT /cart/
请求参数: json或表单类型
参数 | 类型 | 是否必须 | 说明 |
---|---|---|---|
sku_id | int | 是 | 商品sku id |
count | int | 是 | 修改后的数量 |
selected | bool | 是 | 修改后的选中状态 |
返回数据: JSON
参数 | 类型 | 是否必须 | 说明 |
---|---|---|---|
sku_id | int | 是 | 商品sku id |
count | int | 是 | 修改后的数量 |
selected | bool | 是 | 修改后的选中状态 |
2.后端实现:
在carts/views.py中修改视图,添加put方法
# meiduo_mall/apps/carts/views.py
class CartVIew(APIView):
"""购物车"""
...
def put(self, request):
"""
修改购物车数据
:param request:
:return:
"""
params = request.data
skuid = params.get('sku_id')
count = params.get('count')
selected = params.get('selected', True)
# 检查数据
try:
sku = SKU.objects.get(id=skuid)
except Exception as e:
return Response({'data': "商品查找失败..."})
try:
count = int(count)
except Exception as e:
return Response({'data': "传参不正确..."})
# 验证用户
try:
user = request.user
except Exception as e:
user = None
if user is not None and user.is_authenticated:
# 用户已经登录,在redis中保存
redis_conn = get_redis_connection('cart')
pl = redis_conn.pipeline()
# 记录购物车商品数量
pl.hincrby('cart_{}'.format(user.id), skuid, count)
# 记录购物车的勾选项
# 勾选
if selected:
pl.sadd('cart_selected_{}'.format(user.id),skuid)
pl.execute()
return Response({'sku_id':skuid,
'count':count,
'selected':selected}, status=status.HTTP_201_CREATED)
else:
# 用户未登录, 在cookie中保存
# {
# 1001:{'count':10,'selected':true},
# ...
# }
# 使用pickle序列化购物车数据, pickle操作的是bytes类型
cart = request.COOKIES.get('cart')
if cart is not None:
cart = pickle.loads(base64.b64decode(cart))
else:
cart = {}
sku = cart.get(skuid)
if sku:
count += int(sku.get('count'))
cart[skuid] = {
'count': count,
'selected': selected
}
cookie_cart = base64.b64encode(pickle.dumps(cart))
response = Response({'sku_id':skuid,
'count':count,
'selected':selected}, status=status.HTTP_201_CREATED)
response.set_cookie('cart',cookie_cart, max_age=constants.CART_COOKIE_EXPIRES)
return response
四、删除购物车数据:
1.后端接口设计:
请求方式: DELETE /cart/
请求参数:
参数 | 类型 | 是否必须 | 说明 |
---|---|---|---|
sku_id | int | 是 | 商品sku_id |
返回数据: 无 状态码204
2.后端代码:
在carts/views.py的视图类中添加delete方法
# meiduo_mall/apps/carts/views.py
class CartVIew(APIView):
"""购物车"""
...
def delete(self, request):
"""
删除购物车数据
:param request:
:return:
"""
params = request.data
skuid = params.get('sku_id')
# 检查数据
try:
sku = SKU.objects.get(id=skuid)
except Exception as e:
return Response({'data': "商品查找失败..."})
# 验证用户
try:
user = request.user
except Exception as e:
user = None
if user is not None and user.is_authenticated:
redis_conn = get_redis_connection('cart')
pl = redis_conn.pipeline()
pl.hdel('cart_{}'.format(user.id), skuid)
pl.srem('cart_selected_{}'.format(user.id), skuid)
pl.execute()
return Response(status=status.HTTP_204_NO_CONTENT)
else:
response = Response(status=status.HTTP_204_NO_CONTENT)
# 使用pickle序列化购物车数据,pickle操作的是bytes类型
cart = request.COOKIES.get('cart')
if cart is not None:
cart = pickle.loads(base64.b64decode(cart.encode()))
if skuid in cart:
del cart[skuid]
cookie_cart = base64.b64encode(pickle.dumps(cart)).decode()
# 设置购物车的cookie
# 需要设置有效期,否则是临时cookie
response.set_cookie('cart', cookie_cart, max_age=constants.CART_COOKIE_EXPIRES)
return response
五、商品选中/取消选中:
1.后台接口设计:
请求方式:
请求参数: JSON或表单
参数 | 类型 | 是否必须 | 说明 |
---|---|---|---|
selected | bool | 是 | 是否全选,true表示全选,false表示取消全选 |
返回数据: JSON
返回值 | 类型 | 是否必须 | 说明 |
---|---|---|---|
message | str | 是 | ok |
2.后端实现:
在carts/views.py的视图类中添加put方法:
# meiduo_mall/apps/carts/views.py
class CartVIew(APIView):
"""购物车"""
...
def put(self, request):
"""
修改全部选中状态
:param request:
:return:
"""
params = request.data
skuid = params.get('sku_id')
count = params.get('count')
selected = params.get('selected', True)
# 检查数据
try:
sku = SKU.objects.get(id=skuid)
except Exception as e:
return Response({'data': "商品查找失败..."})
try:
count = int(count)
except Exception as e:
return Response({'data': "传参不正确..."})
# 验证用户
try:
user = request.user
except Exception as e:
user = None
if user is not None and user.is_authenticated:
# 用户已经登录,在redis中保存
redis_conn = get_redis_connection('cart')
pl = redis_conn.pipeline()
# 记录购物车商品数量
pl.hincrby('cart_{}'.format(user.id), skuid, count)
# 记录购物车的勾选项
# 勾选
if selected == True:
pl.sadd('cart_selected_{}'.format(user.id), skuid)
elif selected == False:
pl.srem('cart_selected_{}'.format(user.id), skuid)
pl.execute()
return Response({'message': 'OK'}, status=status.HTTP_204_NO_CONTENT)
else:
# 用户未登录, 在cookie中保存
# {
# 1001:{'count':10,'selected':true},
# ...
# }
# 使用pickle序列化购物车数据, pickle操作的是bytes类型
cart = request.COOKIES.get('cart')
if cart is not None:
cart = pickle.loads(base64.b64decode(cart))
else:
cart = {}
sku = cart.get(skuid)
if sku:
count += int(sku.get('count'))
cart[skuid] = {
'count': count,
'selected': selected
}
cookie_cart = base64.b64encode(pickle.dumps(cart))
response = Response({'message': 'OK'}, status=status.HTTP_204_NO_CONTENT)
response.set_cookie('cart', cookie_cart, max_age=constants.CART_COOKIE_EXPIRES)
return response
六、购物车全选:
请求方式: PUT /cart/selection/
请求参数: json或表单
参数 | 类型 | 是否必须 | 说明 |
---|---|---|---|
select | bool | 是 | 是否全选,true表示全选,false表示取消全选 |
返回数据: JSON
参数 | 类型 | 是否必须 | 说明 |
---|---|---|---|
message | str | 是 | ok |
2.后端实现:
在carts/views.py中重新创建一个视图:
# meiduo_mall/apps/carts/views.py
...
class CartSelectAllView(APIView):
"""
购物车全选
"""
def perform_authentication(self, request):
"""
重写父类的用户验证方法,不在进入视图前就检查JWT
:param request:
:return:
"""
pass
def put(self, request):
"""
购物车全选/取消全选
:param request:
:return:
"""
params = request.data
selected = params.get('selected')
# 检查参数
if not isinstance(selected, bool):
return Response({'message': '参数类型错误'},status=status.HTTP_400_BAD_REQUEST)
try:
user = request.user
except Exception as e:
user = None
if user is not None and user.is_authenticated:
# 用户已登录,在redis中保存
redis_conn = get_redis_connection('cart')
cart = redis_conn.hgetall('cart_%s' % user.id)
sku_id_list = cart.keys()
if selected:
# 全选
redis_conn.sadd('cart_selected_%s' % user.id, *sku_id_list)
else:
# 取消全选
redis_conn.srem('cart_selected_%s' % user.id, *sku_id_list)
return Response({'message': 'OK'})
else:
# cookie
cart = request.COOKIES.get('cart')
response = Response({'message': 'OK'})
if cart is not None:
cart = pickle.loads(base64.b64decode(cart.encode()))
for sku_id in cart:
cart[sku_id]['selected'] = selected
cookie_cart = base64.b64encode(pickle.dumps(cart)).decode()
# 设置购物车的cookie
# 需要设置有效期,否则是临时cookie
response.set_cookie('cart', cookie_cart, max_age=constants.CART_COOKIE_EXPIRES)
return response
七、登录合并购物车:
用户登录时,将cookie中的购物车数据合并到redis中,并清除cookie中的购物车数据。
普通登录和第三方登录都要合并,所以要将合并逻辑放到公共函数中实现。
在carts/utils.py中创建merge_cart_cookie_to_redis方法:
# meiduo_mall/apps/carts/utils.py