购物车
购物车存储方案
- 用户登录与未登录状态下,都可以保存购物车数据。
- 用户对购物车数据的操作包括:增、删、改、查、全选等等
- 每个用户的购物车数据都要做唯一性的标识。
1. 登录用户购物车存储方案(比市面上的难度要大)
- 存储数据说明
- 如何描述一条完整的购物车记录?
- 用户itcast,选择了两个 iPhone8 添加到了购物车中,状态为勾选
- 一条完整的购物车记录包括:用户、商品、数量、勾选状态。
- 存储数据:user_id、sku_id、count、selected
- 存储位置说明
- 购物车数据量小,结构简单,更新频繁,所以我们选择内存型数据库Redis进行存储。
- mysql性能比较差,擅长存复杂数据结构
- 存储位置:Redis数据库 4号库
"carts": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/4",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
- 存储类型说明
- 提示:我们很难将用户、商品、数量、勾选状态存放到一条Redis记录中。所以我们要把购物车数据合理的分开存储。
如果第一时间找不到方案,可以采用遍历的方法
- 用户、商品、数量:hash
- carts_user_id: {sku_id1: count, sku_id3: count, sku_id5: count, …}
- 勾选状态:set(购物车对顺序没有要求,集合有自动去重的功能)
- 只将已勾选商品的sku_id存储到set中,比如,1号和3号商品是被勾选的。
- selected_user_id: [sku_id1, sku_id3, …]
- 存储逻辑说明
- 当要添加到购物车的商品已存在时,对商品数量进行累加计算。
- 当要添加到购物车的商品不存在时,向hash中新增field和value即可。
笔记
mset k1 v1 k2 v2 …
2. 未登录用户购物车存储方案
- 存储数据说明
存储数据:user_id、sku_id、count、selected
2. 存储位置说明
- 由于用户未登录,服务端无法拿到用户的ID,所以服务端在生成购物车记录时很难唯一标识该记录。
- 我们可以将未登录用户的购物车数据缓存到用户浏览器的cookie中,每个用户自己浏览器的cookie中存储属于自己的购物车数据。
- 存储位置:用户浏览器的cookie
- 存储类型说明
- 提示:浏览器的cookie中存储的数据类型是字符串。
- 思考:如何在字符串中描述一条购物车记录?
- 结论:JSON字符串可以描述复杂结构的字符串数据,可以保证一条购物车记录不用分开存储。
{
"sku_id1":{
"count":"1",
"selected":"True"
},
"sku_id3":{
"count":"3",
"selected":"True"
},
"sku_id5":{
"count":"3",
"selected":"False"
}
}
4. 存储逻辑说明
-
当要添加到购物车的商品已存在时,对商品数量进行累加计算。
-
当要添加到购物车的商品不存在时,向JSON中新增field和value即可。
提示: -
浏览器cookie中存储的是字符串明文数据。
- 我们需要对购物车这类隐私数据进行密文存储。
-
解决方案:pickle模块 和 base64模块
- pickle模块介绍
- pickle模块是Python的标准模块,提供了对Python数据的序列化操作,可以将数据转换为bytes类型,且序列化速度快。(性能比json,isdangerous性能哈)
- pickle模块使用:
- pickle.dumps()将Python数据序列化为bytes类型数据。
- pickle.loads()将bytes类型数据反序列化为python数据。
>>> import pickle
>>> dict = {'1': {'count': 10, 'selected': True}, '2': {'count': 20, 'selected': False}}
>>> ret = pickle.dumps(dict)
>>> ret
b'\x80\x03}q\x00(X\x01\x00\x00\x001q\x01}q\x02(X\x05\x00\x00\x00countq\x03K\nX\x08\x00\x00\x00selectedq\x04\x88uX\x01\x00\x00\x002q\x05}q\x06(h\x03K\x14h\x04\x89uu.'
>>> pickle.loads(ret)
{'1': {'count': 10, 'selected': True}, '2': {'count': 20, 'selected': False}}
- base64模块介绍
- 提示:pickle模块序列化转换后的数据是bytes类型,浏览器cookie无法存储。
- base64模块是Python的标准模块,可以对bytes类型数据进行编码,并得到bytes类型的密文数据。
- base64模块使用:
- base64.b64encode()将bytes类型数据进行base64编码,返回编码后的bytes类型数据。
- base64.b64deocde()将base64编码后的bytes类型数据进行解码,返回解码 后的bytes类型数据。
>>> import base64
>>> ret
b'\x80\x03}q\x00(X\x01\x00\x00\x001q\x01}q\x02(X\x05\x00\x00\x00countq\x03K\nX\x08\x00\x00\x00selectedq\x04\x88uX\x01\x00\x00\x002q\x05}q\x06(h\x03K\x14h\x04\x89uu.'
>>> b = base64.b64encode(ret)
>>> b
b'gAN9cQAoWAEAAAAxcQF9cQIoWAUAAABjb3VudHEDSwpYCAAAAHNlbGVjdGVkcQSIdVgBAAAAMnEFfXEGKGgDSxRoBIl1dS4='
>>> base64.b64decode(b)
b'\x80\x03}q\x00(X\x01\x00\x00\x001q\x01}q\x02(X\x05\x00\x00\x00countq\x03K\nX\x08\x00\x00\x00selectedq\x04\x88uX\x01\x00\x00\x002q\x05}q\x06(h\x03K\x14h\x04\x89uu.'
笔记
- xml存储同样的数据比json占得空间大得多
- cookie 尽量不存明文数据
- 签名好像是外人读不懂,自己可以读懂,加密好像是都读不懂
- 这里使用这两个模块,是因为isdangerous用过了,还有就是这两个模块的性能更好
- 标准库模块直接import就好
- 操作购物串的增删改查,是通过字典来做的,这样比直接操作字符串绝对简单
- 二进制数据不是最后签名的结果,还需要转换为字符串
- bytes64 能对任何类型的数据进行编码和解码,能编成你不认识的,也能解成你认识的
- 二进制数据还需要一步解码
- 还原:首先通过encode 转换为byte类型 ,在通过b64decode转换为 上一种二进制数据,再通过pickle.loads 转换为原始字典
- pickle 和 base64 多用几次
购物车管理
创建子应用
添加购物车
提示:在商品详情页添加购物车使用局部刷新的效果。
1. 添加购物车接口设计和定义
- 请求方式(ajax)
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /carts/ |
- 请求参数:JSON(前端向后端发送ajax请求)
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
sku_id | int | 是 | 商品SKU编号 |
count | int | 是 | 商品数量 |
selected | bool | 否 | 是否勾选 |
- 响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
- 后端接口定义
class CartsView(View):
"""购物车管理"""
def post(self, request):
"""添加购物车"""
# 接收和校验参数
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,操作redis购物车
pass
else:
# 用户未登录,操作cookie购物车
pass
2. 添加购物车后端逻辑实现
- 接收和校验参数
class CartsView(View):
"""购物车管理"""
def post(self, request):
"""添加购物车"""
# 接收参数
json_dict = json.loads(request.body.decode())
sku_id = json_dict.get('sku_id')
count = json_dict.get('count')
selected = json_dict.get('selected', True)
# 判断参数是否齐全
if not all([sku_id, count]):
return http.HttpResponseForbidden('缺少必传参数')
# 判断sku_id是否存在
try:
models.SKU.objects.get(id=sku_id)
except models.SKU.DoesNotExist:
return http.HttpResponseForbidden('商品不存在')
# 判断count是否为数字
try:
count = int(count)
except Exception:
return http.HttpResponseForbidden('参数count有误')
# 判断selected是否为bool值
if selected:
if not isinstance(selected, bool):
return http.HttpResponseForbidden('参数selected有误')
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,操作redis购物车
pass
else:
# 用户未登录,操作cookie购物车
pass
- 添加购物车到Redis
class CartsView(View):
"""购物车管理"""
def post(self, request):
"""添加购物车"""
# 接收和校验参数
......
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,操作redis购物车
redis_conn = get_redis_connection('carts')
pl = redis_conn.pipeline()
# 新增购物车数据,第一个数据为key
# 有数据做count累加,没有就新增,count为指定值
pl.hincrby('carts_%s' % user.id, sku_id, count)
# 新增选中的状态,select默认为true
if selected:
pl.sadd('selected_%s' % user.id, sku_id)
# 执行管道
pl.execute()
# 响应结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '添加购物车成功'})
else:
# 用户未登录,操作cookie购物车
pass
- 添加购物车到cookie
class CartsView(View):
"""购物车管理"""
def post(self, request):
"""添加购物车"""
# 接收和校验参数
......
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,操作redis购物车
......
else:
# 用户未登录,操作cookie购物车,得到的是一个字符串
cart_str = request.COOKIES.get('carts')
# 如果用户操作过cookie购物车
if cart_str:
# 将cart_str转成bytes,再将bytes转成base64的bytes,最后将bytes转字典
cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
else: # 用户从没有操作过cookie购物车
cart_dict = {}
# 判断要加入购物车的商品是否已经在购物车中,如有相同商品,累加求和,反之,直接赋值
if sku_id in cart_dict:
# 累加求和
origin_count = cart_dict[sku_id]['count']
count += origin_count
cart_dict[sku_id] = {
'count': count,
'selected': selected
}
# 将字典转成bytes,再将bytes转成base64的bytes,最后将bytes转字符串
cookie_cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()
# 创建响应对象
response = http.JsonResponse({'code': RETCODE.OK, 'errmsg': '添加购物车成功'})
# 响应结果并将购物车数据写入到cookie
# 先构造响应,再返回数据
response.set_cookie('carts', cookie_cart_str)
return response
笔记
- View 位于Django.views
- render 位于 Django.shortcuts
- 这里不使用LoginRequiredMixin ,因为无法对未登录用户操作,当登录用户和未登录用户都可进入视图,并且对两种用户的响应不同,需要手动判断用户是否登录
解析之后得到个字典
判断是否传了这些参数
应该用id = sku_id
使用哪个类用哪个类下的异常
看第一个对象是不是第二个类或其子类的实例对象
如果没有取到值的话,取第二个参数的值
用户登录 为 true,未登录为false,在该写pass的地方不写,可能会报错
实现当数据存在做增量,不存在做新建
-
绑定加入购物车事件 -
可以一下删除两条数据 -
退出登录之后这两个数据都为空值 -
导入多个模块 -
cookie只能存字符串
展示购物车
展示购物车接口设计和定义
- 请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /carts/ |
在相同视图中完成这个逻辑
- 请求参数:
无
- 响应结果:HTML
cart.html
- 后端接口定义
class CartsView(View):
"""购物车管理"""
def get(self, request):
"""展示购物车"""
user = request.user
if user.is_authenticated:
# 用户已登录,查询redis购物车
pass
else:
# 用户未登录,查询cookies购物车
pass
展示购物车后端逻辑实现
- 查询Redis购物车
class CartsView(View):
"""购物车管理"""
def get(self, request):
"""展示购物车"""
user = request.user
if user.is_authenticated:
# 用户已登录,查询redis购物车
redis_conn = get_redis_connection('carts')
# 获取redis中的购物车数据
#
redis_cart = redis_conn.hgetall('carts_%s' % user.id)
# 获取redis中的选中状态
cart_selected = redis_conn.smembers('selected_%s' % user.id)
# 将redis中的数据构造成跟cookie中的格式一致,方便统一查询
cart_dict = {}
for sku_id, count in redis_cart.items():
cart_dict[int(sku_id)] = {
'count': int(count),
'selected': sku_id in cart_selected
}
else:
# 用户未登录,查询cookies购物车
pass
- 查询cookie购物车
class CartsView(View):
"""购物车管理"""
def get(self, request):
"""展示购物车"""
user = request.user
if user.is_authenticated:
# 用户已登录,查询redis购物车
......
else:
# 用户未登录,查询cookies购物车
cart_str = request.COOKIES.get('carts')
if cart_str:
# 将cart_str转成bytes,再将bytes转成base64的bytes,最后将bytes转字典
cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
else:
cart_dict = {}
- 查询购物车SKU信息
class CartsView(View):
"""购物车管理"""
def get(self, request):
"""展示购物车"""
user = request.user
if user.is_authenticated:
# 用户已登录,查询redis购物车
......
else:
# 用户未登录,查询cookies购物车
......
# 构造购物车渲染数据
sku_ids = cart_dict.keys()
skus = models.SKU.objects.filter(id__in=sku_ids)
cart_skus = []
for sku in skus:
cart_skus.append({
'id':sku.id,
'name':sku.name,
'count': cart_dict.get(sku.id).get('count'),
'selected': str(cart_dict.get(sku.id).get('selected')), # 将True,转'True',方便json解析
'default_image_url':sku.default_image.url,
'price':str(sku.price), # 从Decimal('10.2')中取出'10.2',方便json解析
'amount':str(sku.price * cart_dict.get(sku.id).get('count')),
})
context = {
'cart_skus':cart_skus,
}
# 渲染购物车页面
return render(request, 'cart.html', context)
- 渲染购物车信息
<div class="total_count">全部商品<em>[[ total_count ]]</em>件</div>
<ul class="cart_list_th clearfix">
<li class="col01">商品名称</li>
<li class="col03">商品价格</li>
<li class="col04">数量</li>
<li class="col05">小计</li>
<li class="col06">操作</li>
</ul>
<ul class="cart_list_td clearfix" v-for="(cart_sku,index) in carts" v-cloak>
<li class="col01"><input type="checkbox" name="" v-model="cart_sku.selected" @change="update_selected(index)"></li>
<li class="col02"><img :src="cart_sku.default_image_url"></li>
<li class="col03">[[ cart_sku.name ]]</li>
<li class="col05">[[ cart_sku.price ]]元</li>
<li class="col06">
<div class="num_add">
<a @click="on_minus(index)" class="minus fl">-</a>
<input v-model="cart_sku.count" @blur="on_input(index)" type="text" class="num_show fl">
<a @click="on_add(index)" class="add fl">+</a>
</div>
</li>
<li class="col07">[[ cart_sku.amount ]]元</li>
<li class="col08"><a @click="on_delete(index)">删除</a></li>
</ul>
<ul class="settlements" v-cloak>
<li class="col01"><input type="checkbox" name="" @change="on_selected_all" v-model="selected_all"></li>
<li class="col02">全选</li>
<li class="col03">合计(不含运费):<span>¥</span><em>[[ total_selected_amount ]]</em><br>共计<b>[[ total_selected_count ]]</b>件商品</li>
<li class="col04"><a href="place_order.html">去结算</a></li>
</ul>
笔记
- 要修改cart.html 的购物车入口代码
- 模板先把数据给中间人,中间人再交给vue渲染
- 两种方式进行查询数据的结构应该构造成一样的,这样就方便渲染了
二进制字符串好像也可以做键
取出的是一个键和值的元祖
判断二进制对象是否在字典中
- 用户未登陆的代码直接做代码复用
查询所有键
vue不识别python的true
-
列表有特殊字符,防止转义 -
拷贝使用vue渲染界面的代码
修改购物车
提示:在购物车页面修改购物车使用局部刷新的效果。
1. 修改购物车接口设计和定义
- 请求方式
选项 | 方案 |
---|---|
请求方法 | PUT |
请求地址 | /carts/ |
- 请求参数:JSON(主要因为ajax实现)
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
sku_id | int | 是 | 商品SKU编号 |
count | int | 是 | 商品数量 |
selected | bool | 否 | 是否勾选 |
- 响应结果:JSON
字段 | 说明 |
---|---|
sku_id | 商品SKU编号 |
count | 商品数量 |
selected | 是否勾选 |
- 后端接口定义
class CartsView(View):
"""购物车管理"""
def put(self, request):
"""修改购物车"""
# 接收和校验参数
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,修改redis购物车
pass
else:
# 用户未登录,修改cookie购物车
pass
2. 修改购物车后端逻辑实现
- 接收和校验参数
class CartsView(View):
"""购物车管理"""
def put(self, request):
"""修改购物车"""
# 接收参数
json_dict = json.loads(request.body.decode())
sku_id = json_dict.get('sku_id')
count = json_dict.get('count')
selected = json_dict.get('selected', True)
# 判断参数是否齐全
if not all([sku_id, count]):
return http.HttpResponseForbidden('缺少必传参数')
# 判断sku_id是否存在
try:
sku = models.SKU.objects.get(id=sku_id)
except models.SKU.DoesNotExist:
return http.HttpResponseForbidden('商品sku_id不存在')
# 判断count是否为数字
try:
count = int(count)
except Exception:
return http.HttpResponseForbidden('参数count有误')
# 判断selected是否为bool值
if selected:
if not isinstance(selected, bool):
return http.HttpResponseForbidden('参数selected有误')
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,修改redis购物车
pass
else:
# 用户未登录,修改cookie购物车
pass
- 修改Redis购物车
class CartsView(View):
"""购物车管理"""
def put(self, request):
"""修改购物车"""
# 接收和校验参数
......
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,修改redis购物车
redis_conn = get_redis_connection('carts')
pl = redis_conn.pipeline()
# 因为接口设计为幂等的,直接覆盖
pl.hset('carts_%s' % user.id, sku_id, count)
# 是否选中
if selected:
pl.sadd('selected_%s' % user.id, sku_id)
else:
pl.srem('selected_%s' % user.id, sku_id)
pl.execute()
# 创建响应对象
cart_sku = {
'id':sku_id,
'count':count,
'selected':selected,
'name': sku.name,
'default_image_url': sku.default_image.url,
'price': sku.price,
'amount': sku.price * count,
}
return http.JsonResponse({'code':RETCODE.OK, 'errmsg':'修改购物车成功', 'cart_sku':cart_sku})
else:
# 用户未登录,修改cookie购物车
pass
- 修改cookie购物车
class CartsView(View):
"""购物车管理"""
def put(self, request):
"""修改购物车"""
# 接收和校验参数
......
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,修改redis购物车
......
else:
# 用户未登录,修改cookie购物车
cart_str = request.COOKIES.get('carts')
if cart_str:
# 将cart_str转成bytes,再将bytes转成base64的bytes,最后将bytes转字典
cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
else:
cart_dict = {}
# 因为接口设计为幂等的,直接覆盖
cart_dict[sku_id] = {
'count': count,
'selected': selected
}
# 将字典转成bytes,再将bytes转成base64的bytes,最后将bytes转字符串
cookie_cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()
# 创建响应对象
cart_sku = {
'id': sku_id,
'count': count,
'selected': selected,
'name': sku.name,
'default_image_url': sku.default_image.url,
'price': sku.price,
'amount': sku.price * count,
}
response = http.JsonResponse({'code':RETCODE.OK, 'errmsg':'修改购物车成功', 'cart_sku':cart_sku})
# 响应结果并将购物车数据写入到cookie
response.set_cookie('carts', cookie_cart_str, max_age=constants.CARTS_COOKIE_EXPIRES)
return response
笔记
-
修改数据的时候直接进行覆盖写入
-
修改数据和新增数据的代码很相似,将新增数据,接收参数,校验参数的代码进行拷贝
-
将对应条的hash 数据进行覆盖写入 -
通过 if selected 可以知道是选中还是没选中 -
改完代码重新进行测试
-
点了加号要等响应完成之后数量才显示2, -
测试的时候要测刷新之后数据有没有变化
-
用户未登陆先拷贝新增数据用户未登陆的代码
删除购物车
提示:在购物车页面删除购物车使用局部刷新的效果。
1. 删除购物车接口设计和定义
- 请求方式
选项 | 方案 |
---|---|
请求方法 | DELETE |
请求地址 | /carts/ |
- 请求参数:JSON
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
sku_id | int | 是 | 商品SKU编号 |
3.响应结果:JSON |
字段 说明
code 状态码
errmsg 错误信息
4.后端接口定义
class CartsView(View):
"""购物车管理"""
def delete(self, request):
"""删除购物车"""
# 接收和校验参数
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,删除redis购物车
pass
else:
# 用户未登录,删除cookie购物车
pass
2. 删除购物车后端逻辑实现
- 接收和校验参数
class CartsView(View):
"""购物车管理"""
def delete(self, request):
"""删除购物车"""
# 接收参数
json_dict = json.loads(request.body.decode())
sku_id = json_dict.get('sku_id')
# 判断sku_id是否存在
try:
models.SKU.objects.get(id=sku_id)
except models.SKU.DoesNotExist:
return http.HttpResponseForbidden('商品不存在')
# 判断用户是否登录
user = request.user
if user is not None and user.is_authenticated:
# 用户未登录,删除redis购物车
pass
else:
# 用户未登录,删除cookie购物车
pass
- 删除Redis购物车
class CartsView(View):
"""购物车管理"""
def delete(self, request):
"""删除购物车"""
# 接收和校验参数
......
# 判断用户是否登录
user = request.user
if user is not None and user.is_authenticated:
# 用户未登录,删除redis购物车
redis_conn = get_redis_connection('carts')
pl = redis_conn.pipeline()
# 删除键,就等价于删除了整条记录
pl.hdel('carts_%s' % user.id, sku_id)
pl.srem('selected_%s' % user.id, sku_id)
pl.execute()
# 删除结束后,没有响应的数据,只需要响应状态码即可
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '删除购物车成功'})
else:
# 用户未登录,删除cookie购物车
pass
- 删除cookie购物车
class CartsView(View):
"""购物车管理"""
def delete(self, request):
"""删除购物车"""
# 接收和校验参数
......
# 判断用户是否登录
user = request.user
if user is not None and user.is_authenticated:
# 用户未登录,删除redis购物车
......
else:
# 用户未登录,删除cookie购物车
cart_str = request.COOKIES.get('carts')
if cart_str:
# 将cart_str转成bytes,再将bytes转成base64的bytes,最后将bytes转字典
cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
else:
cart_dict = {}
# 创建响应对象
response = http.JsonResponse({'code': RETCODE.OK, 'errmsg': '删除购物车成功'})
if sku_id in cart_dict:
del cart_dict[sku_id]
# 将字典转成bytes,再将bytes转成base64的bytes,最后将bytes转字符串
cookie_cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()
# 响应结果并将购物车数据写入到cookie
response.set_cookie('carts', cookie_cart_str, max_age=constants.CARTS_COOKIE_EXPIRES)
return response
笔记
- 删除只能一次删除一条数据
- 接收参数,校验参数,判断用户是否已经登陆
- 对于redis 数据,只要删除key,对应的值会消失
在redis操作的时候,出错都会被忽略 - 对于字典,删除key,就能删除一条记录
对于这类易报错的指令,要进行逻辑判断或者是异常捕获
这里显示对指定请求的相应结果
全选购物车
提示:在购物车页面修改购物车使用局部刷新的效果。
1. 全选购物车接口设计和定义
- 请求方式
选项 | 方案 |
---|---|
请求方法 | PUT |
请求地址 | /carts/selection/ |
- 请求参数:JSON
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
selected | bool | 是 | 是否全选 |
3.响应结果:JSON |
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
- 后端接口定义
class CartsSelectAllView(View):
"""全选购物车"""
def put(self, request):
# 接收和校验参数
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,操作redis购物车
pass
else:
# 用户未登录,操作cookie购物车
pass
2. 全选购物车后端逻辑实现
- 接收和校验参数(这段代码直接拷贝)
class CartsSelectAllView(View):
"""全选购物车"""
def put(self, request):
# 接收参数
json_dict = json.loads(request.body.decode())
selected = json_dict.get('selected', True)
# 校验参数
if selected:
if not isinstance(selected, bool):
return http.HttpResponseForbidden('参数selected有误')
# 判断用户是否登录
user = request.user
if user is not None and user.is_authenticated:
# 用户已登录,操作redis购物车
pass
else:
# 用户已登录,操作cookie购物车
pass
- 全选Redis购物车
class CartsSelectAllView(View):
"""全选购物车"""
def put(self, request):
# 接收和校验参数
......
# 判断用户是否登录
user = request.user
if user is not None and user.is_authenticated:
# 用户已登录,操作redis购物车
redis_conn = get_redis_connection('carts')
cart = redis_conn.hgetall('carts_%s' % user.id)
sku_id_list = cart.keys()
if selected:
# 全选
redis_conn.sadd('selected_%s' % user.id, *sku_id_list)
else:
# 取消全选
redis_conn.srem('selected_%s' % user.id, *sku_id_list)
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '全选购物车成功'})
else:
# 用户已登录,操作cookie购物车
pass
- 全选cookie购物车
class CartsSelectAllView(View):
"""全选购物车"""
def put(self, request):
# 接收和校验参数
......
# 判断用户是否登录
user = request.user
if user is not None and user.is_authenticated:
# 用户已登录,操作redis购物车
......
else:
# 用户已登录,操作cookie购物车
cart = request.COOKIES.get('carts')
response = http.JsonResponse({'code': RETCODE.OK, 'errmsg': '全选购物车成功'})
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()
response.set_cookie('carts', cookie_cart, max_age=constants.CARTS_COOKIE_EXPIRES)
return response
笔记
- 选中与否的数据来自于后端,需要发起请求
对于第二个参数为 *args的,传递list,应使用 *list 传递
删除多个数据
- 用户未登陆时,获取数据的代码直接进行拷贝
遍历字典的所有键值
- 详情页使用mysql做的,购物车使用redis 速度有明显差异
合并购物车
需求:用户登录时,将cookie购物车数据合并到Redis购物车数据中。
提示:
- QQ登录和账号登录时都要进行购物车合并操作。
1. 合并购物车逻辑分析
1.合并方向:cookie购物车数据合并到Redis购物车数据中。
2.合并数据:购物车商品数据和勾选状态。
3.合并方案:
3.1 Redis数据库中的购物车数据保留。
3.2 如果cookie中的购物车数据在Redis数据库中已存在,将cookie购物车数据覆盖Redis购物车数据。
3.3 如果cookie中的购物车数据在Redis数据库中不存在,将cookie购物车数据新增到Redis。
3.4 最终购物车的勾选状态以cookie购物车勾选状态为准。
2. 合并购物车逻辑实现
新建文件:carts.utils.py
def merge_cart_cookie_to_redis(request, user, response):
"""
登录后合并cookie购物车数据到Redis
:param request: 本次请求对象,获取cookie中的数据
:param response: 本次响应对象,清除cookie中的数据
:param user: 登录用户信息,获取user_id
:return: response
"""
# 获取cookie中的购物车数据
cookie_cart_str = request.COOKIES.get('carts')
# cookie中没有数据就响应结果
if not cookie_cart_str:
return response
cookie_cart_dict = pickle.loads(base64.b64decode(cookie_cart_str.encode()))
new_cart_dict = {}
new_cart_selected_add = []
new_cart_selected_remove = []
# 同步cookie中购物车数据
for sku_id, cookie_dict in cookie_cart_dict.items():
new_cart_dict[sku_id] = cookie_dict['count']
if cookie_dict['selected']:
new_cart_selected_add.append(sku_id)
else:
new_cart_selected_remove.append(sku_id)
# 将new_cart_dict写入到Redis数据库
redis_conn = get_redis_connection('carts')
pl = redis_conn.pipeline()
pl.hmset('carts_%s' % user.id, new_cart_dict)
# 将勾选状态同步到Redis数据库
if new_cart_selected_add:
pl.sadd('selected_%s' % user.id, *new_cart_selected_add)
if new_cart_selected_remove:
pl.srem('selected_%s' % user.id, *new_cart_selected_remove)
pl.execute()
# 清除cookie
response.delete_cookie('carts')
return response
3. 账号和QQ登录合并购物车
在users.views.py和oauth.views.py文件中调用合并购物车的工具方法
# 合并购物车
response = merge_cart_cookie_to_redis(request=request, user=user, response=response)
展示商品页面简单购物车
需求:用户鼠标悬停在商品页面右上角购物车标签上,以下拉框形式展示当前购物车数据。
笔记
- 对于不同公司,对于合并的需求有很多
- 这里使用cookie的数量和勾选状态覆盖redis的数量和勾选状态
只要有重复保留redis的 - 对于账号登录和qq登录都要进行合并,在登录过程中有三个地方要实现合并,应该使用代码封装
好像函数也可以这样注释
一次添加多个项到哈希表中- 空字符串,空列表,bool运算结果都为false
调用了redis底层的代码,应该是调用redis接口出现错误- 合并有多重方式实现
- base64 编码时只操作二进制数据
1. 简单购物车数据接口设计和定义
- 请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /carts/simple/ |
- 请求参数:
无
- 响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
cart_skus[ ] | 简单购物车SKU列表 |
id | 购物车SKU编号 |
name | 购物车SKU名称 |
count | 购物车SKU数量 |
default_image_url | 购物车SKU图片 |
{
"code":"0",
"errmsg":"OK",
"cart_skus":[
{
"id":1,
"name":"Apple MacBook Pro 13.3英寸笔记本 银色",
"count":1,
"default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrPB4GAWkTlAAGuN6wB9fU4220429"
},
......
]
}
4.后端接口定义
class CartsSimpleView(View):
"""商品页面右上角购物车"""
def get(self, request):
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,查询Redis购物车
pass
else:
# 用户未登录,查询cookie购物车
pass
# 构造简单购物车JSON数据
pass
2. 简单购物车数据后端逻辑实现
- 查询Redis购物车
class CartsSimpleView(View):
"""商品页面右上角购物车"""
def get(self, request):
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,查询Redis购物车
redis_conn = get_redis_connection('carts')
redis_cart = redis_conn.hgetall('carts_%s' % user.id)
cart_selected = redis_conn.smembers('selected_%s' % user.id)
# 将redis中的两个数据统一格式,跟cookie中的格式一致,方便统一查询
cart_dict = {}
for sku_id, count in redis_cart.items():
cart_dict[int(sku_id)] = {
'count': int(count),
'selected': sku_id in cart_selected
}
else:
# 用户未登录,查询cookie购物车
pass
# 构造简单购物车JSON数据
pass
- 查询Redis购物车
class CartsSimpleView(View):
"""商品页面右上角购物车"""
def get(self, request):
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,查询Redis购物车
......
else:
# 用户未登录,查询cookie购物车
cart_str = request.COOKIES.get('carts')
if cart_str:
cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
else:
cart_dict = {}
- 构造简单购物车JSON数据
class CartsSimpleView(View):
"""商品页面右上角购物车"""
def get(self, request):
# 判断用户是否登录
user = request.user
if user.is_authenticated:
# 用户已登录,查询Redis购物车
......
else:
# 用户未登录,查询cookie购物车
......
# 构造简单购物车JSON数据
cart_skus = []
sku_ids = cart_dict.keys()
skus = models.SKU.objects.filter(id__in=sku_ids)
for sku in skus:
cart_skus.append({
'id':sku.id,
'name':sku.name,
'count':cart_dict.get(sku.id).get('count'),
'default_image_url': sku.default_image.url
})
# 响应json列表数据
return http.JsonResponse({'code':RETCODE.OK, 'errmsg':'OK', 'cart_skus':cart_skus})
3. 展示商品页面简单购物车
- 商品页面发送Ajax请求
- index.js、list.js、detail.js
get_carts(){
let url = '/carts/simple/';
axios.get(url, {
responseType: 'json',
})
.then(response => {
this.carts = response.data.cart_skus;
this.cart_total_count = 0;
for(let i=0;i<this.carts.length;i++){
if (this.carts[i].name.length>25){
this.carts[i].name = this.carts[i].name.substring(0, 25) + '...';
}
this.cart_total_count += this.carts[i].count;
}
})
.catch(error => {
console.log(error.response);
})
},
- 商品页面渲染简单购物车数据
- index.html、list.html、detail.html
<div @mouseenter="get_carts" class="guest_cart fr" v-cloak>
<a href="{{ url('carts:info') }}" class="cart_name fl">我的购物车</a>
<div class="goods_count fl" id="show_count">[[ cart_total_count ]]</div>
<ul class="cart_goods_show">
<li v-for="sku in carts">
<img :src="sku.default_image_url" alt="商品图片">
<h4>[[ sku.name ]]</h4>
<div>[[ sku.count ]]</div>
</li>
</ul>
</div>
笔记
和查询购物车做的差不多,只是返回数据的规定不同,这段代码只是简单说了一下,前端代码也不用管
笔记
- gitbook 可以转换为pdf格式的
- update_or_create 有就更新,没有就新建,稀有语法自己去测试
为对象添加first_name:Bob 数据- 商品规格的查询难度大,搞清表以及表之间的关系