欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

django 商城项目之购物车以及python中的一些redis命令

程序员文章站 2023-11-28 18:48:58
最近在用django restframe框架做一个商城项目,有一个关于购物车的业务逻辑,是用cookie和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!!!