1、业务需求分析
无论用户是否处于登录状态,都可以把商品添加到购物车中,并操作购物车中的商品。没登录的情况下,可以先选择想要的商品到购物车中,当登录之后,直接把之前没登录的购物车的产品直接加入到登录之后那个人的购物车中。
2、登录用户购物车存储方案
1、存储位置
由于购物车数据量小,结构简单,更新频繁,所以我们选择内存型数据库redis进行存储
"carts": { # carts
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/5",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
2、需要存储的数据
在购物车的界面,我们需要展示产品的名称、单价、 购买的数量、产品总价、是否勾选等信息,而产品的名称、单价可以通过sku查询得到,所以这里只需存储一个sku商品的id就可以了,购买数量也需要我们自己存储,总价可以通过计算得到,不用存,勾选状态需要存储
一条完整的购物车记录包括:用户、商品、数量、勾选状态
存储数据:user_id、sku_id、count、selected
3、存储数据的结构
按照上面的逻辑来推,我会这么去设计存储数据的结构,
整个购物车用一个字典来保存,字典里面的一个key-value是一个用户的购物车数据,key是用户id,value也是一个字典,这个字典的key是商品id,value是一个列表,由商品数量和选中状态组成
但这样存在一个问题,redis中所有的数据都是key-value的形式存储的,value可以存储string、数字、布尔类型、list、set、hash,其中list、set的元素只能是string、数字、布尔类型,hash里面以key-value的形式组织,hash中的value也只能是string、数字、布尔类型
所以如果按上面的结构把数据存到redis中是不可行的,怎么办呢?
解决办法:
把商品数量和勾选状态分开存储
4、存储逻辑
当要添加到购物车的商品已存在时,对商品数量进行累加计算
当要添加到购物车的商品不存在时,向hash中新增field和value即可
5、redis相关命令
Hash类型
HSET key field value 增加、修改
HDEL key field1 field2 删除
HGETALL key 查询
Set类型
SMEMBERS key 查询
SADD key member 增加
SREM key member 删除
3、未登录用户购物车存储方案
1、存储位置
用户未登录的时候去操作购物车,服务器端无法拿到用户的id,服务器端不能使用用户id找到redis中的用户的购物车数据,也就不能把用户的购物车操作对应的数据存储在redis中
这里我们可以将登录用户的购物车操作对应的数据暂时存储到用户浏览器的cookie中,不同用户使用不同的浏览器,这样不同用户的浏览器的cookie也不同并且相互独立,互不影响,这样不同用户对自己购物车的操作对应的数据也可以相互独立、互不影响。
2、需要存储的数据
sku_id、count、selected
3、存储类型
浏览器的cookie中存储的数据类型是字符串,如何在字符串中描述一条购物车记录?
答:JSON字符串可以描述复杂结构的字符串数据,可以保证一条购物车记录不用分开保存。
4、存储逻辑
当要添加到购物车的商品已存在,需要先取出数据,对商品数量进行累加计算
当要添加的商品不存在时,向json中新增field和value即可
好,接下来,我们来思考一个问题,首先我们得知道浏览器cookie中存储的是字符串明文数据,很明显,这样是很不安全的,所以我们需要对购物车这类隐私数据进行密文存储
解决方案:pickle模块+base64模块
python对象->字节码数据->字符串数据->存储在cookie中
cookie中购物车数据->字符串数据->字节码数据->python对象
5、pickle模块介绍
pickle模块是python的标准模块,提供了对python数据的序列化操作,可以将数据转换为bytes类型,且序列化速度快。
pickle模块使用:
pickle.dumps() 将python数据序列化为bytes类型数据
pickle.loads()将bytes类型数据反序列化为python数据
4、实现代码
在商品详情页中点击添加购物车按钮,会调用Vue绑定的方法,使用ajax发起http请求,访问添加购物车接口。
服务器收到这个请求之后,会提取商品的id、商品的数量,然后校验商品id、数量是否正确,参数有无问题,根据用户的登录状态,修改redis或cookie里面的购物车数据。
返回响应,通知浏览器添加商品到购物车的结果。
# 1.提取参数
data_dict = json.loads(request.body.decode())
sku_id = data_dict.get("sku_id")
count = data_dict.get("count")
selected = data_dict.get("selected", True)
# 2.校验参数
if not all([sku_id, count]):
return HttpResponseForbidden("缺少必须参数")
try:
sku = SKU.objects.get(id=sku_id)
except SKU.DoesNotExist:
return HttpResponseForbidden("商品不存在")
count = int(count)
if count < 1:
return HttpResponseForbidden("商品数量不能小于1")
elif count > sku.stock:
return JsonResponse({"code": RETCODE.PARAMERR, "errmsg": "商品库存不足"})
# 3.处理逻辑
user = request.user
如果是登录的状态
if user.is_authenticated:
# 用户登录修改 redis 中的购物车数据
connection: Redis = get_redis_connection("carts")
connection.hincrby("carts_%s" % user.id, sku_id, count)
connection.sadd("selected_%s" % user.id, sku_id)
return JsonResponse({"code": RETCODE.OK, "errmsg": "添加购物车成功"})
如果没有登录
else:
# 用户未登录修改 cookie 中的购物车数据
# 1.获取 cookie 中的购物车字符串数据,转换为 python 字典类型的数据
carts_str = request.COOKIES.get("carts")
if carts_str:
carts_dict = base64_dict_loads(carts_str)
else:
carts_dict = {}
# 2.修改购物车中的商品的数量
if sku_id in carts_dict:
old_count = carts_dict[sku_id]["count"]
count += old_count
carts_dict[sku_id] = {
"count": count,
"selected": selected
}
# 3.把购物车数据转换为字符串,写入 cookie
carts_str = dict_base64_dumps(carts_dict)
response = JsonResponse({"code": RETCODE.OK, "errmsg": "添加购物车成功"})
response.set_cookie("carts", carts_str, constants.CARTS_COOKIE_EXPIRES)
return response