最近在公司做的一个项目我负责的模块之一,做的时候可谓是踩了无数个坑,所以我决定重写捋一捋然后写出来,在为了下次再做类似业务的时候更加方便的同时,希望也能帮到大家。里面很多字段都是我举例随便编的,就像一个现编的小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对象如果是空的,就代表未登录,非空则为已登录
(二)增删改购物车
有上面的添加购物车为例子,再执行业务前判断下用户是否已登陆。若已登录的话,就将用户的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);
}
这样整个购物车就完成了,它可以在未登录的时候添加商品,当你登录成功时,购物车中的商品会自动添加到你的账号中。有什么不足的地方请多多指教。