购物车部分
基本功能:购物车的管理(区分用户登录和未登录)
业务需求
- 在用户登录与未登录状态下,都可以保存用户的购物车数据
- 用户可以对购物车数据进行增删改查
- 用户对于购物车的勾选也要保存,在订单结算页面会使用勾选数据
- 用户登录时,合并 cookie 中的购物车数据到 redis 中
技术实现
- 对于未登录的用户,购物车数据使用浏览器 cookie 保存
- 对于已登录的用户,购物车数据在后端使用 redis 保存
购物车数据存储设计
redis 保存已登录用户
分两个表进行保存
第一个表以哈希类型保存-购物车数据:
采用 hash 是因为每个用户要保存多个 sku_id 和 count 直接的对应关系
键: cart_用户id
值: {sku_id: count, sku_id: count, ……}
第二个表以 set 集合类型保存-购物车勾选记录
采用 set 是因为要对商品的勾选去重,但不需要保存商品的加入顺序,在表中只需要保存勾选状态就行
键: cart_selected_用户id
值: {sku_id, sku_id ….}
cookie 保存未登录用户
# 保存数据的格式
{
sku_id: {
"count": xxx, // 数量
"selected": True // 是否勾选
},
sku_id: {
"count": xxx,
"selected": False
},
...
}
在cookie中只能保存字符串数据,所以将上述数据使用pickle进行序列化转换 ,并使用base64编码为字符串,保存到cookie中。
pickle模块的使用
pickle模块是python的标准模块,提供了对于python数据的序列化操作,可以将数据转换为bytes类型,其序列化速度比json模块要高。
- pickle.dumps() 将python数据序列化为bytes类型
- pickle.loads() 将bytes类型数据反序列化为python的数据类型
base64模块的使用
Base64 是一种基于64个可打印字符来表示二进制数据的表示方法。由于2^6=64,所以每6个比特为一个单元,对应某个可打印字符。3个字节有24个比特,对应于4个Base64单元,即3个字节可由4个可打印字符来表示。在Base64中的可打印字符包括字母
A-Z
、a-z
、数字0-9
,这样共有62个字符,此外两个可打印符号在不同的系统中而不同。Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据,包括MIME的电子邮件及XML的一些复杂数据。
python标准库中提供了base64模块,用来进行转换
- base64.b64encode() 将bytes类型数据进行base64编码,返回编码后的bytes类型
- base64.b64deocde() 将base64编码的bytes类型进行解码,返回解码后的bytes类型
使用 pickle 模块对数据进行原始数据和字节之间的转换
使用 base64 模块对数据进行字节和字符串之间的转换
添加到购物车
注意:
因为前端请求时携带了 Authorization 请求头(主要是 JWT),而如果用户未登录,此请求头的 JWT 无意义(没有值),为了防止 REST framework 框架在验证此无意义的 JWT 时抛出 401 异常,在视图中需要做两个处理
- 重写perform_authentication()方法,此方法是REST framework检查用户身份的方法
- 在获取request.user属性时捕获异常,REST framework在返回user时,会检查Authorization请求头,无效的Authorization请求头会导致抛出异常
前端传 token 的三种情况
- 传了 token,而且 token 是正确的,那么通过 request.user 可以直接拿到用户对象
- 传了 token,但 token 是错误的,那么调用 request.user 会抛出异常
- 没传 token,调用 request.user 会返回 AnonymousUser(匿名用户)
-
写序列化器对前端传送过来的数据进行反序列化处理
-
Django 对 JWT 的验证可能会导致报错,我们需要重写 perform_authentication 方法,然后自己进行 JWT 验证(主要就是看 request 中能不能取出 user 对象),判断一下用户是否登录,然后根据登录状态进行不同的操作
-
判断用户是否登录且通过 is_authenticated 验证(也就是通过账户名和密码判断用户是否存在)
-
如果通过,用户已经登录,此时将数据保存到 redis 中,保存购物车数量数据,使用 hash 的命令
hincrby
,该命令可以对数量进行自增:-
HINCRBY key field increment
为哈希表
key
中的域field
的值加上增量increment
。
保存勾选状态,使用 set 的命令
sadd
,该命令可以自动去重:-
SADD key member [member …]
将一个或多个
member
元素加入到集合key
当中,已经存在于集合的member
元素将被忽略。
-
-
使用 pl 一次性进行提交并将数据返回
-
如果没有通过,就将数据保存在 cookie 中,cookie 中只能保存字符串,判断一下 cookie 中是否有数据
- 如果有数据,使用 encode() 对字符串进行编码,使用 base64 模块将编码过的字符串转换成二进制字节数据,使用 pickle 模块,将字节数据转换成字典数据方便操作,然后将当前要添加的数据添加到字典中,如果已经有这个商品,对数量进行增加操作
- 如果没有数据,创建空字典,将数据加入
最后使用 pickle 模块将字典转换为字节,base64 模块将字节转换为字符串,使用 decode() 将字符串解码,将字符串存入 cookie
-
返回 response
查询购物车数据
用户点击 我的购物车 ,跳转页面,对购物车数据进行查询,然后进行展示
- 还是区分登录和未登录状态,登录从 redis 中查询,未登录从 cookie 中查询
- 区别就是从 redis 中查询出来的数据要将数据格式转换成 cookie 中一样的数据格式,这个方便进行传递,
- 根据 sku_id 查询到所有的商品对象,为每个对象添加对应的 count 和 selected ;
- 新闻 sku 表中没有 数量和勾选状态这两个字段,在序列化时添加上
- 将数据序列化,返回前端需要的数据
修改购物车数据
用户对数据进行修改,进行保存,
在这里好像有个 幂等 和 非幂等 的区别:
幂等就是请求的数据和返回的数据一样,在这里就是进行数据覆盖操作;
非幂等就是请求的数据和返回的数据不一样,对数据进行增加操作
- 对传过来的数据反序列化
- 使用幂等模式,对数据进行覆盖操作,前端直接将商品的总数量传过来,进行覆盖操作
- 对勾选状态进行判断,用户勾选了就新增,取消将将状态删除
删除购物车数据
删除购物车数据,只需要将 sku_id 传过来即可,
- 反序列化数据,判断有没有当前这个商品
- 然后根据登录状态,将对应的数据删除即可
- 登录状态:
- 使用 hdel 命令删除商品数量表中的数据
- 使用 srem 命令删除勾选状态表中的数据
- 未登录:cookie 中的数据是字典,使用 del 方法将字典中的数据删除
登录合并购物车
用户登录时,需要将 cookie 中的购物车数据合并到 redis 中,并清除 cookie 中的购物车数据。
因为在这里有两种登录方式,普通登录和 QQ 登录,这两种登录都需要进行数据合并,所以将合并的逻辑放到公共函数里实现。
- 新建函数,函数需要三个参数,请求对象 request、响应对象 response、当前登录用户 user
- 尝试从 request 中取出 cookie 中的购物车数据,没有直接返回,有,进行下一步的操作
- 取出 redis 中的购物车数据
- 遍历 cookie 购物车数据字典,取到 sku_id 和 selected_count_dict 字典,按照 redis 存储的数据格式将数据加入,如果有 redis 中已经有 sku_id ,对 count 其进行覆盖操作
- 根据购物车状态进行操作,勾选添加,取消勾选,删除
- 使用 response.delete_cookie(‘cart’) 方法清除 cookie 数据
- 返回 response
修改普通登录视图
重写 rest_framework_jwt.views.ObtainJSONWebToken 类视图中的 post 方法来添加合并逻辑。
- 重写方法
- 不要重写 post 方法中的原始判断逻辑,使用 super 继承父类中的逻辑即可,该逻辑会返回 response 对象
- 添加合并购物车逻辑,函数要传三个参数,从 serializer 对象中取到 user 对象,将三个参数传过去,得到最终的 response 对象,返回即可。
- 修改登录的 url 路径,让登录时访问该视图
修改 QQ 登录视图
在最后登录成功前加上合并购物车逻辑