概要
一个电商平台中一般都会有购物车,它能够存储顾客所选的商品,更加的方便了用户的选择。
实现购物车的存储有这几种方式:
1.数据库存储,将购物车数据存储在数据库中,用户的添加、删除等都会更新数据库,但是这样需要io操作,影响性能
2.会话存储,购物车数据保存在会话中,读写速度较快,实时性较高,但是会话过度依赖于服务器内存,而且当服务器重启或者会话过期时,购物车数据可能会丢失,不适用于多浏览器的情况
3.Cookie存储,购物车数据保存在用户的Cookie中,读写速度快,实时性较高,但是Cookie存储容量有限,可能无法存储大数据量的购物车数据,Cookie有被删除或者篡改的风险,安全性比较低
4.缓存存储,购物车数据保存在缓存中,读写速度快,实时性高,适用于高并发的场景,但需要考虑缓存的持久化问题
注意点:
本项目主要采用redis缓存的方式来存储购物车数据
当用户登录以后要和未登录中的购物车数据进行合并处理
主要实现功能
添加购物车功能
添加商品到购物车的时候需要注意两点:
1.可以在未登录的情况下添加到购物车,此时页面会生成一个临时用户id保存在cookie中,用来保存未登录的用户的数据,如果已经登录则优先获取已登录的用户id
2.添加的时候需要进行判断是否缓存中已经有了这个商品,如果已经有了,就将这个商品的数量进行增加,如果没有,就在缓存中添加一个新的数据
代码展示:
//controller层
@GetMapping("/addToCart/{skuId}/{skuNum}")
public Result addToCart(@PathVariable Long skuId, @PathVariable Long skuNum, HttpServletRequest request){
//获取经过网关过滤器后存储在header中的用户id
String userId = this.getUserId(request);
cartApiService.addToCart(skuId,skuNum,userId);
return Result.ok();
}
//service层
public void addToCart(Long skuId, Long skuNum, String userId) {
String cartKey = this.getCartKey(userId);
BoundHashOperations<String, String, CartInfo> boundHashOperations = redisTemplate.boundHashOps(cartKey);
CartInfo cartInfo = null;
//如果购物车已经有商品,添加数量
if (boundHashOperations.hasKey(skuId.toString())) {
cartInfo = boundHashOperations.get(skuId.toString());
cartInfo.setSkuNum(cartInfo.getSkuNum() + skuNum.intValue());
cartInfo.setUpdateTime(new Date());
cartInfo.setSkuPrice(productFeignClient.getSkuPrice(skuId));
cartInfo.setIsChecked(1);
} else {
SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
if (skuInfo != null) {
//生成购物车对象
cartInfo = new CartInfo();
cartInfo.setUserId(userId);
cartInfo.setSkuId(skuId);
cartInfo.setCartPrice(skuInfo.getPrice());
cartInfo.setSkuNum(skuNum.intValue());
cartInfo.setImgUrl(skuInfo.getSkuDefaultImg());
cartInfo.setSkuName(skuInfo.getSkuName());
cartInfo.setSkuPrice(productFeignClient.getSkuPrice(skuId));
cartInfo.setUpdateTime(new Date());
cartInfo.setCreateTime(new Date());
}
}
boundHashOperations.put(skuId.toString(), cartInfo);
}
//获取用户id的方法
public String getUserId(HttpServletRequest request){
//如果已经登录过了userId一般不为空,如果没有登录传过来的是临时id
String userId = AuthContextHolder.getUserId(request);
if (StringUtils.isEmpty(userId)){
userId=AuthContextHolder.getUserTempId(request);
}
return userId;
}
删除购物车功能
删除购物车,只需要根据前台传过来的用户id和商品id在redis中进行删除即可
代码展示:
//controller层
@DeleteMapping("/deleteCart/{skuId}")
public Result deleteCart(@PathVariable Long skuId,HttpServletRequest request){
String userId = this.getUserId(request);
cartApiService.deleteCart(skuId,userId);
return Result.ok();
}
//service层
public void deleteCart(Long skuId, String userId) {
String cartKey = this.getCartKey(userId);
BoundHashOperations<String, String, CartInfo> boundHashOperations = redisTemplate.boundHashOps(cartKey);
boundHashOperations.delete(skuId.toString());
}
更新购物车选中状态
用户在购物车列表进行商品的勾选,后台需要记录下此状态,这样等下一次用户再来访问购物车列表的时候,还是用户上一次进行操作所保存的状态
代码展示:
//controller层
@GetMapping("/checkCart/{skuId}/{isChecked}")
public Result checkCart(@PathVariable Long skuId,@PathVariable Long isChecked,HttpServletRequest request){
//获取从过滤器中存储的用户id
String userId = this.getUserId(request);
cartApiService.checkCart(skuId,isChecked,userId);
return Result.ok();
}
//service层
public void checkCart(Long skuId, Long isChecked, String userId) {
String cartKey = this.getCartKey(userId);
BoundHashOperations<String, String, CartInfo> boundHashOperations = redisTemplate.boundHashOps(cartKey);
CartInfo cartInfo = boundHashOperations.get(skuId.toString());
cartInfo.setIsChecked(isChecked.intValue());
boundHashOperations.put(skuId.toString(), cartInfo);
}
购物车列表功能
在查询购物车列表的时候,要稍微复杂一点,因为需要判断用户是已登录还是未登录的状态来决定购物车列表的展现,在已登录的情况下要将未登录和已登录的购物车数据进行合并并删除未登录的购物车数据
代码展示:
//controller层
@GetMapping("/cartList")
public Result cartList(HttpServletRequest request){
String userId = AuthContextHolder.getUserId(request);
String userTempId = AuthContextHolder.getUserTempId(request);
List<CartInfo> cartInfoList= cartApiService.cartList(userId,userTempId);
return Result.ok(cartInfoList);
}
//service层
public List<CartInfo> cartList(String userId, String userTempId) {
/**
* 获取购物车列表,这里需要考虑四种情况:1.用户id和临时用户id都不存在
* 2.用户id和临时用户id都存在
* 3.用户id存在,临时用户id不存在
* 4.用户id不存在,临时用户id存在
*存在用户id表示已登录,存在临时用户id说明添加过商品到购物车
* 只要存在用户id并且访问了购物车列表,临时用户的购物车列表就要删除,因为登录以后两者将会合并
*/
//定义未登录的购物车集合
List<CartInfo> noLoginList = new ArrayList<>();
//首先判断临时用户id是否存在
if (!StringUtils.isEmpty(userTempId)) {
//如果临时用户id存在,取出它的购物车列表
String cartKeyTempUser = this.getCartKey(userTempId);
BoundHashOperations<String, String, CartInfo> boundHashOperations = redisTemplate.boundHashOps(cartKeyTempUser);
noLoginList = boundHashOperations.values();
}
//判断用户id是否存在
if (!StringUtils.isEmpty(userId)) {
/**
* //如果用户id存在 有这几种情况
* case1.用户的购物车中有商品,临时用户的购物车中没有商品
* case2.用户的购物车中没有商品,临时用户的购物车中也没有商品
* case3.用户的购物车中有商品。临时用户的购物车中也有商品
* case4.用户的购物车中没有商品,临时用户的购物车中有商品
*/
//获取已登录用户的购物车列表
String cartKeyUser = this.getCartKey(userId);
BoundHashOperations<String, String, CartInfo> boundHashOperations = redisTemplate.boundHashOps(cartKeyUser);
//loginList=boundHashOperations.values();
//如果临时用户的购物车列表有值
if (!CollectionUtils.isEmpty(noLoginList)){
//这是临时用户购物车列表中肯定有商品
noLoginList.forEach(noLoginCartInfo->{
//如果已登录用户中包含临时用户的商品,将它们的数量相加保存到已登录用户中
//在此情况下case3得到解决
if (boundHashOperations.hasKey(noLoginCartInfo.getSkuId().toString())){
CartInfo cartInfo = boundHashOperations.get(noLoginCartInfo.getSkuId().toString());
cartInfo.setSkuNum(cartInfo.getSkuNum()+noLoginCartInfo.getSkuNum());
cartInfo.setUpdateTime(new Date());
cartInfo.setSkuPrice(productFeignClient.getSkuPrice(noLoginCartInfo.getSkuId()));
if (noLoginCartInfo.getIsChecked()==1){
cartInfo.setIsChecked(1);
}
boundHashOperations.put(cartInfo.getSkuId().toString(),cartInfo);
}else{
//否则另外添加一条新的商品记录保存到已登录用户中
//在此状态下case3和case4的情况得到解决
noLoginCartInfo.setCreateTime(new Date());
noLoginCartInfo.setUpdateTime(new Date());
noLoginCartInfo.setUserId(userId);
boundHashOperations.put(noLoginCartInfo.getSkuId().toString(),noLoginCartInfo);
}
});
}
//合并完成后将未登录用户的购物车数据进行删除
redisTemplate.delete(this.getCartKey(userTempId));
//case1和2也已经解决,直接返回已登录的购物车列表即可
//由于在redis中的boundHashOps得到的购物车列表即便没有商品,也已经有地址值,不用担心空指针
return this.cartInfoListSort(boundHashOperations.values());
} else {
//如果用户id不存在返回临时用户的购物车集合
return noLoginList;
}
}
结果展示
添加商品到购物车列表
查看redis,里面已经保存了此条数据</font
从购物车列表中删除商品
查看redis,里面已经删除了此条数据</font
更该购物车列表中商品的选中状态
刷新页面后发现此条商品的状态被保存
购物车列表
这是未登录时购物车列表
这是登录之后购物车列表,可以发现登陆以后的购物车列表和未登录的购物车列表进行了合并
退出登录以后,未登录状态下的购物车列表都被删除
总结
主要涉及到了购物车模块的增删改查,其中在查询购物车列表的时候,要注意根据已登录用户存储商品和未登录用户存储的商品情况进行不同的分析
1.在已登录用户和未登录用户都没有商品的情况下,直接返回空的集合即可
2.在已登录用户有商品、未登录用户没有商品的情况下,也不需要过多分析,直接返回已登录用户的商品列表即可
3.在已登录用户有商品、未登录用户也有商品的情况下,需要进行判断已登录用户是否已经含有未登录用户的商品,如果有,则将已登录用户的商品数量进行加1,如果没有就在已登录用户中保存一个新的商品
4.在已登录用户没有商品,未登录用户有商品的情况下,将未登录用户的商品都赋给已登录用户即可。
5.只要用户已经登录,最后要将未登录用户的购物车商品进行删除