0
点赞
收藏
分享

微信扫一扫

购物车的实现(未登录时也可以使用)

最近在公司做的一个项目我负责的模块之一,做的时候可谓是踩了无数个坑,所以我决定重写捋一捋然后写出来,在为了下次再做类似业务的时候更加方便的同时,希望也能帮到大家。里面很多字段都是我举例随便编的,就像一个现编的小demo,不涉及公司任何事务。

一、未登录的购物车实现:

新建购物车目录对象: CartItem 属性 int productId; int count;date updateTime;

CartItemVO 属性 TProduct product; int count;date updateTime;

(一)加入购物车:

1)当用户访问客 户端时,判断其uuid是否存在,不存在的话,则创建一个uuid给当前用户;

2)在添加商品的时候, 执行addToCart方法把uuid作为key和cartItem作为value存入redis 此时方法中有三种情况:

①当前redis中没有此uuid对应的集合 :

new一个list,再创建CartItem对象存入到该集合中,再存入到redis中然后设置超时时间为一个星期 ; 即return ResultVO(true,”添加购物车成功“,list)

②当前redis中有uuid对应的集合 :

先遍历集合list

1)如果集合中有和当前要存的product的id是一样的,就将其product的数量和更新时间更新;

cartItemVO.setUpdateTime(cartItem.getUpdateTime());
                cartItemVO.setCount(cartItem.getCount());

再把list重新更新到redis中(覆盖原来list);

2)如果集合中的每一条CartItem的produtId都和传进来的productId不一样的话,就表示是一个新商品,则重新创建一个CartItem,存入list集合,更新redis;

上面完成后,在controller中new一个cookie( key:user:cart value:uuid) 发送给客户端 response.addCookie,完成添加;

(二)查看购物车

Controller:

getCart(@CookieValue(name=CookieConstant.USER_CART,required = false)String uuid,
                            HttpServletRequest request,
                            HttpServletResponse response)://获取购物车中的信息

Service:
先写一个方法:
cartService.getCart(String key)  //此方法通过传入的cart_uuid去redis中查找对应的value也就是List<Item>
再通过:
cartService.getCartVO(String key) //在此方法内先调用上面的方法获得cartItem集合,再遍历集合,拿到每个productId所对应的的TProduct对象,再存入List<cartItemVO>中
这样,就可以拿到cart展示所需所有信息

getCartVO(String key)中需要注意:

对于通过productId查询对应商品,先做一个商品预热,遵循2/8定律,把热门商品先加载到缓存中,即查询过程是: 1、先去redis中拿

String product_key = RedisUtil.getProductKey(productId);`
                `Object o = redisTemplate.opsForValue().get(product_key);`
                TProduct product = null;

此时注意要加上分布式锁避免数据库压力过大导致缓存击穿

2、若redis中没有该商品对象,去数据库查然后存入cartItemVO然后再存入redis中

if(o==null){
                    product = tProductMapper.selectByPrimaryKey(productId);

                    cartItemVO.setProduct(product);

                    redisTemplate.opsForValue().set(product_key,product);`
                }else{
                    product = (TProduct) o;
                    cartItemVO.setProduct(product);
                }

之后记得更新一下redis中该key的超时时间

redisTemplate.expire(product_key,30,TimeUnit.MINUTES);

再把该对象存入到集合中,并更新redis

cartItemVOS.add(cartItemVO);

让商品按照更新时间进行排序

//让cartItems处于排序状态  -1  0  1
        Collections.sort(cartItems, new Comparator<CartItem>() {
            @Override
            public int compare(CartItem o1, CartItem o2) {
                return (int)(o2.getUpdateTime().getTime()-o1.getUpdateTime().getTime());
            }
        });

这样,才算真正完成了查看购物车信息的功能。

(三)改变购物车中商品的数量

Controller:

public ResultVO resetCart(@PathVariable Long productId,
                          @PathVariable Integer count,
                          @CookieValue(name=CookieConstant.USER_CART,required = 								  false)String uuid,
                          HttpServletRequest request,
                          HttpServletResponse response)
//通过uuid拿到对应的key值,然后传入并调用 resetCart方法

Service:

public ResultVO resetCart(Long productId, Integer count, String key)
//通过key在redis中找到对应的value即List<cartItem>,然后遍历通过productId找到要修改的商品,重新set其count值
//重置数量后 
redistemplete.opsForValue.set(key,List<CartItems>)
//进行覆盖
//重置过期时间

(四)删除购物车中的商品

这一部分很简单,通过uuid找到List,然后再遍历通过productId找到对应商品删除掉就可以了,删除完要更新redis数据和重新设置过期时间。

以上就是未登录购物车CRUD的实现

=======================================================================================

二、已登录的购物车的实现

一开始拟定了两种实现已登录购物车CRUD的方案:

方案一:将商品的CRUD存入数据库中

弊端:当每一次修改或则删除商品时,都要访问db,会导致db压力过大,db可能会崩

方案二:将添加的商品存到redis缓存中,redis也有持久化机制,可以订制一个定时任务,定时往数据库中添加购物车中的商品信息

为了减轻数据库的压力,我选择了第二种方案;

具体业务实现逻辑:

(一)添加商品到购物车

同样在addToCart中在将商品添加到购物车之前调用userService中的IsLogin方法进行判断用户是否登录,

如果未登录的话,就按未登录购物车添加商品流程走;

如果是已登录状态的话,就可以拿到当前用户的userId,然后以User:Cart:uid作为key, CartItem作为value存入redis中。

如何判断是否已登录:在购物车商品的添加修改和删除都需要判断是否已经登录,所以通过拦截器来达到只需要一次请求的判断就可以的通用的效果

一次请求request到拦截器(HandlerInterceptor),验证是否登录(拦截器在preHandler方法中对cookie进行遍历,得到key为USER_TOKEN的cookie的value,即为用户的uuid,再通过调用Uerservice中的checkLogin来验证用户是否登录),无论是否登录,都予以放行,拦截器收到请求后request.setAttribute("user",user),

然后requset到达添加购物车接口request.getAttribute(“user”)就可以拿到user对象

得到的user对象如果是空的,就代表未登录,非空则为已登录

购物车的实现(未登录时也可以使用)_List

(二)增删改购物车

有上面的添加购物车为例子,再执行业务前判断下用户是否已登陆。若已登录的话,就将用户的uid代替uuid生成属于已登录的用户的专属key值,把对应的cartItem作为value存到以此key为键的redis中,实现流程都是一样的,就不再重复描述了。

最后,对已登录和未登录的购物车进行合并

合并的时机:在登录成功的时候,会把(USER_TOKEN,用户的uuid)存入redis中,这时调用CartController中的merge方法去实现对已登录和未登录的购物车进行合并

为什么调用的是Controller层的方法实现合并呢?

因为Controller层的merge方法声明是这样的:

public ResultVO merge(@CookieValue(name=CookieConstant.USER_CART,required = false)String uuid,HttpServletRequest request,HttpServletResponse response){方法体}

在参数列表中有当前未登录的cart所对应的的cookie值,

如果通过调用service层的merge方法

cartService.merge(loginedKey,noLoginKey);

要去拿cookie的值的话是要改动很多的,要在checkLogin()里拿到未登录的cart的uuid

所以,我决定用httpClient去直接调用Controller中的merge,它会自动拿到Cookie

Controller中merge的具体实现:

1.通过从拦截器放行过来的request中get到user对象是否为空来判断用户是否登录

2.如果没有登录的话则直接返回一个ResultVO(false);

3.如果已登录的话则调用cartService中的merge方法进行购物车合并

public ResultVO merge(@CookieValue(name=CookieConstant.USER_CART,required = false)String 						   uuid,
                          HttpServletRequest request,
                          HttpServletResponse response)
                          //该方法中提供已登录的loginedKey,和未登录用户的noLoginKey

4.合并完成后,通过未登录对应的uuid删除对应的cookie

cartService中merge方法的具体实现:

//1.获取已登录状态下的购物车
        List<CartItem> loginedCartItems = (List<CartItem>) 				  		   redisTemplate.opsForValue().get(loginedKey);
//2.获取未登录状态下的购物车
        List<CartItem> noLoginCartItems = (List<CartItem>) redisTemplate.opsForValue().get(noLoginKey);
//3.未登录状态下没有购物车情况
        if(noLoginCartItems==null||noLoginCartItems.size()==0){
            return new ResultVO(true,"未登录状态下没有购物车,合并成功",loginedCartItems);
        }
//4.已登录状态下没有购物车:未登录状态下的购物车直接交给已登录状态下的购物车
        if(loginedCartItems==null||loginedCartItems.size()==0){
            redisTemplate.opsForValue().set(loginedKey,noLoginCartItems);
            redisTemplate.expire(loginedKey,30,TimeUnit.DAYS);
            //删除未登录状态下的购物车
            redisTemplate.delete(noLoginKey);
            return new ResultVO(true,"合并成功",noLoginCartItems);
        }
//5.彼此都在。
        HashMap<Long,CartItem> map = new HashMap<>();
//1)先把未登录的存进去
        for (CartItem cartItem : noLoginCartItems) {
            map.put(cartItem.getProductId(),cartItem);
        }
//2)再存已登录
        for (CartItem loginedCartItem : loginedCartItems) {
//当前loginedCartItem是否在map中已经存在
            CartItem currentCartItem = map.get(loginedCartItem.getProductId());
            if(currentCartItem==null){
//不存在
                map.put(loginedCartItem.getProductId(),loginedCartItem);
            }else{
//存在
//数量相加          currentCartItem.setCount(currentCartItem.getCount()+loginedCartItem.getCount());
                map.put(currentCartItem.getProductId(),currentCartItem);
            }
        }
//6.获取map中的List<CartItem>==合并结果
        Collection<CartItem> values = map.values();
        List<CartItem> mergedList = new ArrayList<>(values);
//7.存入到redis
        redisTemplate.opsForValue().set(loginedKey,mergedList);
        redisTemplate.expire(loginedKey,30,TimeUnit.DAYS);
//8.删除未登录状态下的购物车
        redisTemplate.delete(noLoginKey);
        return new ResultVO(true,"合并成功",mergedList);
    }
    ues = map.values();
        List<CartItem> mergedList = new ArrayList<>(values);
//7.存入到redis
        redisTemplate.opsForValue().set(loginedKey,mergedList);
        redisTemplate.expire(loginedKey,30,TimeUnit.DAYS);
//8.删除未登录状态下的购物车
        redisTemplate.delete(noLoginKey);
        return new ResultVO(true,"合并成功",mergedList);
    }

这样整个购物车就完成了,它可以在未登录的时候添加商品,当你登录成功时,购物车中的商品会自动添加到你的账号中。有什么不足的地方请多多指教。

举报

相关推荐

0 条评论