django 商城项目之购物车以及python中的一些redis命令
最近在用django restframe框架做一个商城项目,有一个关于购物车的业务逻辑,是用cookie和redis存储的购物车信息,在这里记录一下。
完成一个商城项目,如果不做一个购物车,就是十分可惜的。我们先来分析一下业务逻辑,参照,京东、淘宝等大型电商网站,可以发现,对于登录用户以及未登录用户,都是可以使用购物车功能。所以首先我们将这两种情况区分开来,采用不同的存储方式。先来看一下已登录用户,购物车其实类似我们在游览网页时的收藏功能,用于收藏用户喜欢的一些商品,用户使用频率较高,所以我们应该优先使用内存型的数据库redis进行存储,这样查询起来会更快。确定了使用什么数据库,我们还要在思考一下用什么形式存储数据。我使用的是2.x版本的redis,共有string,hash,set,zset,list等几种存储格式。hash类似python中的字典,其他几种格式也和python中对应的list,set,string类似。需要额外注意的是,redis中所有数据都是以bytes方式存储。再来看一下我们的需求,对于一个购物车,我们可以看到商品以及商品数量,以及是否勾选商品。对于商品,我们可以只存储其商品id,需要用到商品信息时在进行查询,而对于勾选状态,我们则需要用一个额外的字段存储,由于每个人的购物车都应该是独立的个体,所有我们可以用用户的id进行存储,我们会发现要存储上述信息,我们只使用一种存储格式是很难完成的,所以我们可以考虑用两个分别存储。商品及数量我们可以考虑使用hash格式进行存储,用key存储商品id,value存储商品数量,用户id进行区分不同的购物车,而对于勾选状态,我们可以用set进行存储,对于不同的商品只有勾选和未勾选状态,我们可以考虑将已经勾选的商品的id进行存储,在set内的商品即为勾选,不在的即为未勾选。登录用户搞定了我们再来看看未登录用户,未登录用户的话,应该只在本机使用,而在登录时进行合并处理,所以我们没有必要存储到数据库中,同时,在登录时要进行合并操作,我们可以想到cookie,在登录请求时,游览器会自己带上cookie,所以我们可以考虑用cookie存储,存储格式可以用一个嵌套的大字典。分析完毕,就开始进行真正的操作把。
增删改查四个逻辑,首先来看看新增把。对于新增购物车,我们需要接受的参数为商品id和数量,而勾选状态可以默认为勾选,添加购物车后可以在进行修改,这里的商品我们采用sku的形式进行存储。新增操作的话是post请求,我们可以在视图类中定义一个post方法来接受新增请求,这里的视图类我们继承的是APIView。这里我只写视图方法,对于序列化器就不做描写。首先我们应该接受前端传过来的参数,并进行校验,校验完成后,对用户登录状态进行判断(我是采用JWT来进行用户登录状态存储),对于不同用户,采用不同方式存储购物车信息。
def post(self, request):
# 获取参数,校验参数 (使用序列化器)
serializer = CartSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
# 取出验证后的参数
sku_id = serializer.validated_data['sku_id']
count = serializer.validated_data['count']
selected = serializer.validated_data['selected']
# 判断用户是否登录,这里不明白的可以看一下我之前写的django中的user验证
try:
user = request.user
except Exception:
user = None
if user is not None and user.is_authenticated:
# 登录,将数据存到redis 默认勾选
redis_conn = get_redis_connection('cart') # 建立redis链接
pl = redis_conn.pipeline() # 建立管道,一次发送所有redis命令,不用多次连接redis
pl.hincrby('cart_%s'%user.id, sku_id, count) # 插入域名为'cart_%s'%user.id,key为sku_id,value为count的数据,域不存在会自己创建
if selected:
pl.sadd('cart_select_%s'%user.id, sku_id) # 若勾选,则会将商品id加入集合,若集合不存在则创建
pl.execute() # 将命令一次执行
return Response(serializer.validated_data, status=status.HTTP_201_CREATED) # 返回相应给前端
else:
# 未登录,将数据存到cookie
cart_dict = request.COOKIES.get('cart') # 从cookie中拿到购物车数据
if cart_dict is not None:
cookie_cart = pickle.loads(base64.b64decode(cart_dict.encode())) # 若存在,将数据转化为字典
else:
cookie_cart = {} # 若不存在,建立一个新的字典
if sku_id in cookie_cart:
# 若商品已在购物车中,则将数据进行更新
cookie_cart[sku_id]['count'] += count
cookie_cart[sku_id]['selected'] = selected
else:
# 若不存在,则建立新的数据
cookie_cart[sku_id] = {
'count':count,
'selected':selected
}
cart_cookie = base64.b64encode(pickle.dumps(cookie_cart)).decode() # 将数据进行加密,并转化为字符串
response = Response(serializer.validated_data, status=status.HTTP_201_CREATED)
response.set_cookie('cart', cart_cookie, max_age=constants.CART_COOKIE_EXPIRES) # 给相应设置cookie
return response
然后来看一下获取的逻辑,以get请求进行请求,获取不需要额外的参数,通过用户id查到商品的id和数量以及勾选状态,然后从数据库查到具体的商品信息,返回给前端即可。
def get(self, request):
try:
user = request.user
except Exception:
user = None
# 判断用户登录状态
if user is not None and user.is_authenticated:
redis_conn = get_redis_connection('cart')
redis_cart = redis_conn.hgetall('cart_%s'%user.id) # 获取购物车信息
redis_cart_selected = redis_conn.smembers('cart_select_%s'%user.id) # 获取勾选状态
cart_dict = {}
# 由于redis中所有信息都是bytes类型,所以我们需要进行转化
for sku_id, count in redis_cart.items():
cart_dict[int(sku_id)] = {
'count':int(count),
'selected':sku_id in redis_cart_selected
}
else:
cart_dict = request.COOKIES.get('cart') # 从cookie中获取购物车信息
if cart_dict is not None:
cart_dict = pickle.loads(base64.b64decode(cart_dict.encode()))
else:
cart_dict = {}
skus = SKU.objects.filter(id__in=cart_dict.keys())
for sku in skus:
sku.count = cart_dict[sku.id]['count']
sku.selected = cart_dict[sku.id]['selected']
serializer = CartSKUSerializer(skus, many=True)
return Response(serializer.data)
然后是修改的逻辑,以put形式请求,商品数量和勾选状态我们可以修改,所以我们需要接受这两个参数,并对redis或者cookie进行修改并返回
def put(self, request):
serializer = CartSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
sku_id = serializer.validated_data['sku_id']
count = serializer.validated_data['count']
selected = serializer.validated_data['selected']
try:
user = request.user
except Exception:
user = None
if user is not None and user.is_authenticated:
redis_conn = get_redis_connection('cart')
pl = redis_conn.pipeline() # 对于要进行多次的redis操作,我们就考虑使用管道
pl.hset('cart_%s'%user.id, sku_id, count) # 对哈希表进行数据插入,如果字段存在,就进行覆盖
if selected:
pl.sadd('cart_selected_%s'%user.id, sku_id) # 如果勾选,就在set中加入商品id
else:
pl.srem('cart_selected_%s'%user.id, sku_id) # 如果未勾选,则删除该商品id,若id不存在,则忽略操作
pl.execute()
return Response(serializer.validated_data)
else:
cart_dict = request.COOKIES.get('cart')
if cart_dict is not None:
cookie_cart = pickle.loads(base64.b64decode(cart_dict.encode()))
else:
cookie_cart = {}
cookie_cart[sku_id] = {
'count':count,
'selected':selected
}
cart_cookie = base64.b64encode(pickle.dumps(cookie_cart)).decode() # 加密cookie
response = Response(serializer.validated_data, status=status.HTTP_201_CREATED)
response.set_cookie('cart', cart_cookie, max_age=constants.CART_COOKIE_EXPIRES) # 在响应中设置新的cookie返回给前端
return response
最后是删除操作,删除需要接受的是对应的商品id,然后将对应的数据删除即可
def delete(self, request):
# 考虑到参数少,并不需要将数据返回,所以这里我自行对参数进行校验,这样比写序列化器代码量更少
sku_id = request.data.get('sku_id', None)
if not sku_id and not isinstance(sku_id, int):
return Response('请求方式错误',status=status.HTTP_400_BAD_REQUEST)
if not SKU.objects.filter(id=sku_id).first():
return Response('商品不存在',status=status.HTTP_400_BAD_REQUEST)
try:
user = request.user
except Exception:
user = None
if user is not None and user.is_authenticated:
redis_conn = get_redis_connection('cart')
pl = redis_conn.pipeline()
pl.hdel('cart_%s'%user.id, sku_id) # 删除单个域,如不存在则忽略操作
pl.srem('cart_selected_%s'%user.id, sku_id)
pl.execute()
return Response(status=status.HTTP_204_NO_CONTENT)
else:
response = Response(status=status.HTTP_204_NO_CONTENT)
cart_dict = request.COOKIES.get('cart')
if cart_dict:
cookie_cart = pickle.loads(base64.b64decode(cart_dict.encode()))
else:
cookie_cart = {}
if sku_id in cookie_cart:
del cookie_cart[sku_id]
cart_cookie = base64.b64encode(pickle.dumps(cookie_cart)).decode()
response.set_cookie('cart', cart_cookie, max_age=constants.CART_COOKIE_EXPIRES)
return response
由于涉及到登录和未登录,所以也就涉及到用户购物车合并的问题,而cookie中信息是最新的信息,我们合并时就以cookie中信息为准,在合并完成后,会删除cookie信息进行重置。由于合并购物车不涉及业务逻辑,仅仅在登录或注册等逻辑时触发,所以不必写额外的视图,而是写成工具函数的形式,哪里需要触发,就调用该函数
def merge_cart_cookie_to_redis(request, response, user):
"""
合并请求用户的购物车数据,将未登录保存在cookie里的保存到redis中
遇到cookie与redis中出现相同的商品时以cookie数据为主,覆盖redis中的数据
:param request: 用户的请求对象
:param response: 响应对象,用于清楚购物车cookie
:param user: 当前登录的用户
:return:
"""
# 首先在cookie中获取标准信息,若未登录状态下没有添加商品到购物车cookie中也就没有购物车,直接返回响应,不需要做合并处理
cart_dict = request.COOKIES.get('cart')
if not cart_dict:
return response
cookie_cart = pickle.loads(base64.b64decode(cart_dict.encode()))
redis_conn = get_redis_connection('cart')
redis_cart = redis_conn.hgetall('cart_%s'%user.id) # 从redis中取出过期的信息,并进行转化
cart = {}
for sku_id, count in redis_cart.items():
cart[int(sku_id)] = int(count)
# 首先将商品id和数量进行更新,并将selected为真的存在add的列表中,为假的存在remove的列表中,下面可以一次进行操作
redis_cart_selected_add = []
redis_cart_selected_remove = []
# 更新商品id和数量
for sku_id, count_selected_dict in cookie_cart.items():
cart[sku_id] = count_selected_dict['count']
if count_selected_dict['selected']:
redis_cart_selected_add.append(sku_id)
else:
redis_cart_selected_remove.append(sku_id) if cart:
pl = redis_conn.pipeline()
pl.hmset('cart_%s' % user.id, cart)
if redis_cart_selected_add:
pl.sadd('cart_selected_%s' % user.id, *redis_cart_selected_add) # sadd可以直接添加一组数据
if redis_cart_selected_remove:
pl.srem('cart_selected_%s' % user.id, *redis_cart_selected_remove) # srem可以直接删除一组数据
pl.execute() response.delete_cookie('cart') return response
至此,整个逻辑完成。
新人写博客锻炼自己,有错误欢迎大家指出,我的QQ:595395786!!!
django 商城项目之购物车以及python中的一些redis命令的更多相关文章
- Django商城项目笔记No.11用户部分-QQ登录1获取QQ登录网址
Django商城项目笔记No.11用户部分-QQ登录 QQ登录,亦即我们所说的第三方登录,是指用户可以不在本项目中输入密码,而直接通过第三方的验证,成功登录本项目. 若想实现QQ登录,需要成为QQ互联 ...
- Django商城项目笔记No.4用户部分-注册接口-图片验证码
Django商城项目笔记No.4用户部分-注册接口-图片验证码 1.首先分析注册业务接口 1.1.分析可得,至少这么几个接口 图片验证码 短信验证码 用户名是否存在 手机号是否存在 整体注册接口 图片 ...
- Django商城项目笔记No.3用户部分-用户模型类
Django商城项目笔记No.3用户部分-用户模型类 Django提供了认证系统,文档资料https://yiyibooks.cn/xx/Django_1.11.6/topics/auth/index ...
- Django商城项目笔记No.2项目准备工作
Django商城项目笔记No.2项目准备工作 接着上篇开始,创建好工程之后,随之而来的是怎么配置工程,这篇文章记录如何进行相关的配置 1.pycharm打开工程,进行相关的配置 通过pycharm打开 ...
- Django商城项目笔记No.12用户部分-QQ登录2获取QQ用户openid
Django商城项目笔记No.12用户部分-QQ登录2获取QQ用户openid 上一步获取QQ登录网址之后,测试登录之后本该跳转到这个界面 但是报错了: 新建oauth_callback.html & ...
- Django商城项目笔记No.10用户部分-登录接口
Django商城项目笔记No.10用户部分-登录接口 添加url路由 接下来第二步,增加返回内容: 增加结果如下: 配置:上边的方法定义了返回的内容都有哪些,那这个方法jwt还不知道,需要配置: 修改 ...
- Django商城项目笔记No.9用户部分-注册接口签发JWTtoken
Django商城项目笔记No.9用户部分-注册接口签发JWTtoken 我们在验证完用户的身份后(检验用户名和密码),需要向用户签发JWT,在需要用到用户身份信息的时候,还需核验用户的JWT. 关于签 ...
- Django商城项目笔记No.8用户部分-注册接口实现
Django商城项目笔记No.8用户部分-注册接口实现 users的view.py中增加如下代码 class RegisterUserView(CreateAPIView): "" ...
- Django商城项目笔记No.7用户部分-注册接口-判断用户名和手机号是否存在
Django商城项目笔记No.7用户部分-注册接口-判断用户名和手机号是否存在 判断用户名是否存在 后端视图代码实现,在users/view.py里编写如下代码 class UsernameCount ...
随机推荐
- Angular 如何修改启动的端口
在默认的情况下 Angular 启动使用的是端口 4200. 如果修改这个启动的端口,比如说我们希望再 4100 端口上启动? 可以在启动的时候添加端口参数 --port. 例如使用下面的启动命令: ...
- 5.聚类算法k-means
聚类与分类的区别在于,是在没有给定划分类别的情况下,更具数据相似度进行样本分组的一种办法,是一种非监督的学习算法,聚类的输入时一组未被标记的样本,聚类更具数据自身的距离或者相似度将其划分为若干组,划分 ...
- QtQtConcurrent 使用方式
说明:QtConcurrent 的线程函数启动方式略述. 1) 全局函数或静态函 ,作为线程函数 void threadFunc() { //...add } QtConcurrent::run(th ...
- CodeForces 352C Jeff and Rounding
题意 有一个含有\(2n(n \leqslant2000)\)个实数的数列,取出\(n\)个向上取整,另\(n\)个向下取整.问取整后数列的和与原数列的和的差的绝对值. 就是说,令\(a\)为原数列, ...
- 使用VSCODE开发UE4
完全可行,速度很快,智能提示.代码格式化.查找Symbol等等都不比VS+Visual AssistX 差. 准备 打开编辑器的Editor Preferences>Source Code,选择 ...
- AcWing:110. 防晒(贪心)
有C头奶牛进行日光浴,第i头奶牛需要minSPF[i]到maxSPF[i]单位强度之间的阳光. 每头奶牛在日光浴前必须涂防晒霜,防晒霜有L种,涂上第i种之后,身体接收到的阳光强度就会稳定为SPF[i] ...
- Oracle11g RAC+DG搭建
项目环境准备 3.1虚拟机配置 版本选择 注意Linux操作系统.此次项目我选择的版本是Oracle Enterprise Linux 5.4 内存的设置 本人电脑物理内存8G,由于此次实验要开三台虚 ...
- 上一个树形菜单的改进,增添了数据绑定功能而非仅仅的jq特效
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...
- plsql developer连接数据库时出现ORA-01033错误的解决方法
1.首先以管理员的身份登录本地数据库:sqlplus "/as sysdba"如下图: 2.卸载数据: shutdown normal 3. 重新装 ...
- LeetCode 40. 组合总和 II(Combination Sum II)
题目描述 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在每个组合中只能 ...