taotao购物车2 解决购物车本地cookie和服务器redis不同步的问题
下面的思路逻辑一定要理清楚,比较绕
思路;
前面已经实现了在cookie本地维护购物车的功能,
这次加入和服务器同步功能,
因为 购物车 操作比较频繁,所以,后台服务器 用redis存储用户的购物车信息
逻辑是:
写一个后台操作redis的接口(也可以写两个):要实现的功能是
1.通过用户id从redis中取出用户的购物车信息(购物车商品集合)
2.通过用户id向redis中写入用户的购物车信息
一、用户向购物车中添加商品时的逻辑:
判断用户是否登录,
如果没有登录,则继续只操作cookie
如果用户登录了,则调用远程接口获取用户的 redis中的购物车信息 redisList,再获取cookie的购物车信息cookieList
将这两个购物车中的商品合并(相同商品数量相加,不同商品都添加)组成新的购物车集合 newLIst
然后再 将当期 要加入购物车的商品 和 newList 比较,如果已经存在,则增加数量,如果不存在,则增加商品,
最后将 newList 调用远程接口,写入redis中
二、用户修改购物车中商品数量时的逻辑:
修改购物车中商品的数量,一定是在已经打开购物车列表页面,那么也就是说,这时,肯定已经明确过了用户是否登录,
也就是说,如果用户已经登录了,那么这时本地cookie和redis中的购物车一定是同步的。
所以,这时,
不用先和远程redis同步,可以先修改本地cookie的商品数量,即cookieList,
等修改完成后,再只执行远程向redis中写入购物车newList即可
三、删除购物车中的商品,逻辑 基本 等同于 修改 购物车商品数量,即,只在修改完本地 cookie的list后,向redis写入即可
四、展示购物车列表逻辑:
先判断用户是否登录,
如果用户没有登录,则只从cookie中取出cookieList 返回前台展示即可;
如果用户已经登录,则只需要从redis中取出 reidsList,返回前台展示即可;
(原因是:我们这个方法只是展示购物车列表,并不会操作购物车列表,即不会产生本地cookie和redis中不同步的问题,而前面说过,只要会产生本地cookie和远程redis不同步的方法,我们在方法结束前都已经做过了同步,所以,这里我们要展示购物车列表,只需要从redis中取redisList即可,因为这时redisList和cookieList一定是同步的,所以,如果用户已经登录,那么展示列表我们根本不用考虑本地cookieList)
portal门户项目:
Controller代码:
package com.taotao.portal.controller; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.taotao.common.pojo.CartItem; import com.taotao.common.pojo.TaotaoResult; import com.taotao.portal.service.CartService; /** * 购物车的Controller * @author Administrator */ @Controller @RequestMapping("/cart") public class CartController { @Autowired private CartService cartService; /** * 添加商品到购物车 * @param itemId * @param num * @param request * @param response * @return */ @RequestMapping("/add/{itemId}") public String addCartItem(@PathVariable Long itemId, @RequestParam(defaultValue="1")Integer num, HttpServletRequest request,HttpServletResponse response) { cartService.addCartItem(itemId, num, request, response); // return "cartSuccess"; //这样会打开新的页面,所以不用 //为了 避免每次添加完商品都打开新的页面,所以这里这样重定向 return "redirect:/cart/success.html"; // return "forward:/cart/success.html"; //这种写法也可以(请求转发) } //接收重定向的请求返回jsp页面 @RequestMapping("/success") public String showSuccess(){ return "cartSuccess"; } /** * 查询购物车中的商品列表返回前台展示 * @param request * @param response * @param model * @return */ @RequestMapping("/cart") public String showCart(HttpServletRequest request,HttpServletResponse response,Model model){ List<CartItem> cartItemList = cartService.getCartItemList(request, response); model.addAttribute("cartList", cartItemList); return "cart"; } /** * 修改商品数量(包括点加减号和收到写值) * @param itemId * @param num * @param request * @param response * @return */ @RequestMapping("/update/{itemId}") @ResponseBody public String updateCartItemNum(@PathVariable Long itemId, @RequestParam(defaultValue="0")Integer num, HttpServletRequest request,HttpServletResponse response) { cartService.updateCartItmeNum(itemId, num, request, response); // return "redirect:/cart/cart.html"; return ""; } /** * 删除购物车中的指定商品 * @param itemId * @param request * @param response * @return */ @RequestMapping("/delete/{itemId}") public String delCartItem(@PathVariable Long itemId, HttpServletRequest request,HttpServletResponse response){ cartService.delCartItem(itemId, request, response); return "redirect:/cart/cart.html"; } }
Service层代码:
package com.taotao.portal.service.impl; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.taotao.common.pojo.CartItem; import com.taotao.common.pojo.TaotaoResult; import com.taotao.common.utils.CookieUtils; import com.taotao.common.utils.HttpClientUtil; import com.taotao.common.utils.JsonUtils; import com.taotao.pojo.TbItem; import com.taotao.pojo.TbUser; import com.taotao.portal.service.CartService; import com.taotao.portal.service.UserService; /** * 购物车service * @author Administrator */ @Service public class CartServiceImpl implements CartService { //rest服务基础url @Value("${REST_BASE_URL}") private String REST_BASE_URL; //获取商品信息的url @Value("${ITEM_INFO_URL}") private String ITEM_INFO_URL; //同步购物车的url @Value("${REST_CART_SYNC_URL}") private String REST_CART_SYNC_URL; @Autowired private UserService userService; /** * 添加购物车商品 * 1、接收controller传递过来的商品id,根据商品id查询商品信息。 2、从cookie中取出购物车信息,转换成商品pojo列表。 3、把商品信息添加到商品列表中。 参数: 1、商品id 2、Request 3、response 返回值: TaoTaoResult */ @Override public TaotaoResult addCartItem(long itemId, int num, HttpServletRequest request,HttpServletResponse response) { //合并购物车 Map<String, Object> map = mergeCart(request); List<CartItem> newList = (List<CartItem>) map.get("list"); //当前id对应的购物车商品 CartItem cartItem = null; //先判断原购物车列表中是否有当前商品 for (CartItem item : newList) { //如果存在 if (item.getId()==itemId) { //增加商品数量 item.setNum(item.getNum()+num); cartItem = item; break; } } //如果原来没有此商品,则新建 if (cartItem==null) { cartItem = new CartItem(); //调用rest服务获取商品信息 String doGetResult = HttpClientUtil.doGet(REST_BASE_URL+ITEM_INFO_URL+itemId); TaotaoResult taoTaoResult = TaotaoResult.formatToPojo(doGetResult, TbItem.class); if (taoTaoResult.getStatus()==200) { //封装成购物车商品对象 TbItem item = (TbItem) taoTaoResult.getData(); cartItem.setId(itemId); cartItem.setImage(item.getImage()==null?"":item.getImage().split(",")[0]); cartItem.setPrice(item.getPrice()); cartItem.setTitle(item.getTitle()); cartItem.setSellPoint(item.getSellPoint()); cartItem.setNum(num); //将商品加入到购物车列表中 newList.add(cartItem); } } //如果登录则向redis发送数据同步购物车 Boolean isLogin = (Boolean) map.get("isLogin"); if (isLogin) { Long userId = (Long) map.get("userId"); syncCart(userId, JsonUtils.objectToJson(newList)); } //将购物车列表写回cookie String listJson = JsonUtils.objectToJson(newList); //最后一个参数 true,对数据进行编码,这样在 cookie中就看不到中文原始数据了 CookieUtils.setCookie(request, response, "TT_CART", listJson,true); return TaotaoResult.ok(); } //合并购物车的方法 private Map<String, Object> mergeCart(HttpServletRequest request) { List<CartItem> cookieCartList = getCartItemList(request); Map<String, Object> map = new HashMap<>(); //是否登录 Boolean isLogin = false; Long userId = null; List<CartItem> newList; //准备合并redis和当前cookie的购物车 //先判断用户是否登录 TbUser currentUser = getCurrentUser(request); if (currentUser!=null) { //如果登录,先获取redis中的购物车 userId = currentUser.getId(); isLogin = true; List<CartItem> redisCart = syncCart(userId, null); if (redisCart!=null&&redisCart.size()>0) { //将两个购物车列表合并 Iterator<CartItem> it = redisCart.iterator(); //遍历redis购物车,对比,如果有和cookie一样的商品,则把数量加到Cookie中,删除自身 while (it.hasNext()) { CartItem redisItem = it.next(); for (CartItem cookieItem : cookieCartList) { if (redisItem.getId().equals(cookieItem.getId())) { System.out.println(redisItem.getId()); System.out.println(cookieItem.getId()); cookieItem.setNum(redisItem.getNum()+cookieItem.getNum()); //从resisList中删除 it.remove(); break; } } } } newList = new ArrayList<CartItem>(); //合并 if (redisCart!=null && redisCart.size()>0) { newList.addAll(redisCart); } if (cookieCartList!=null && cookieCartList.size()>0) { newList.addAll(cookieCartList); } //向redis发送数据同步购物车 syncCart(userId, JsonUtils.objectToJson(newList)); }else{ //用户没有登录时 newList = cookieCartList; } map.put("list", newList); map.put("isLogin", isLogin); map.put("userId", userId); return map; } /** * 从cookie中取商品列表 * @param request * @return */ private List<CartItem> getCartItemList(HttpServletRequest request) { //从cookie中取商品列表 String cartJson = CookieUtils.getCookieValue(request, "TT_CART",true); if (cartJson==null) { return new ArrayList<CartItem>(); } //把json转换为商品列表 try { List<CartItem> list = JsonUtils.jsonToList(cartJson, CartItem.class); return list; } catch (Exception e) { e.printStackTrace(); } return new ArrayList<CartItem>(); } /** * 获取 购物车商品列表供前台展示 */ @Override public List<CartItem> getCartItemList(HttpServletRequest request, HttpServletResponse response) { //展示列表的逻辑: //判断用户是否登录,如果未登录,直接取cookie中的数据 //如果已登录,直接取redis中的数据(不涉及到合并及同步问题,因为这个只是展示,合并只有在 用户添加商品到购物车时、用户修改购物车时才需要) //定义要返回前台的数据 List<CartItem> list = null; //判断用户是否已经登录,如果登录了,再同步 TbUser currentUser = getCurrentUser(request); if (currentUser!=null) { //如果登录则从redis中取数据到前台 list = syncCart(currentUser.getId(),null); }else{ list = getCartItemList(request); } return list; } /** * 直接输入数量,更新购物车中商品的数量(在购物车列表展示页面中使用) */ @Override public TaotaoResult updateCartItmeNum(long itemId, int num, HttpServletRequest request, HttpServletResponse response){ //执行此方法是,一定是已经打开了购物车列表页,也就是一定可以确定了用户是否已经登录 //先进行未登录的正常逻辑 //当前id对应的购物车商品 CartItem cartItem = null; //从cookie中获取购物车列表 List<CartItem> cartList = getCartItemList(request); //先判断原购物车列表中是否有当前商品 for (CartItem item : cartList) { //如果存在 if (item.getId()==itemId) { //修改商品数量 item.setNum(num); cartItem = item; break; } } //判断用户是否已经登录,如果登录了,再同步 TbUser currentUser = getCurrentUser(request); if (currentUser!=null) { //如果登录则向redis发送数据同步购物车 syncCart(currentUser.getId(), JsonUtils.objectToJson(cartList)); } //将购物车列表写回cookie String listJson = JsonUtils.objectToJson(cartList); //最后一个参数 true,对数据进行编码,这样在 cookie中就看不到中文原始数据了 CookieUtils.setCookie(request, response, "TT_CART", listJson,true); return TaotaoResult.ok(); } /** * 删除购物车中商品 * @param itemId * @param request * @param response * @return */ @Override public TaotaoResult delCartItem(long itemId, HttpServletRequest request, HttpServletResponse response){ //执行此方法是,一定是已经打开了购物车列表页,也就是一定可以确定了用户是否已经登录 //当前id对应的购物车商品 CartItem cartItem = null; //从cookie中获取购物车列表 List<CartItem> cartList = getCartItemList(request); Iterator<CartItem> iterator = cartList.iterator(); //遍历 while (iterator.hasNext()) { CartItem item = iterator.next(); //找到对应的商品 if (item.getId()==itemId) { //执行删除动作 iterator.remove(); break; } } //判断用户是否已经登录,如果登录了,再同步 TbUser currentUser = getCurrentUser(request); if (currentUser!=null) { //如果登录则向redis发送数据同步购物车 syncCart(currentUser.getId(), JsonUtils.objectToJson(cartList)); } //将购物车列表写回cookie String listJson = JsonUtils.objectToJson(cartList); //最后一个参数 true,对数据进行编码,这样在 cookie中就看不到中文原始数据了 CookieUtils.setCookie(request, response, "TT_CART", listJson,true); return TaotaoResult.ok(); } /** * 从redis中获取购物车信息/同步购物车 * @param userId * @param cartList 当此参数为null时,为获取redis中的购物车信息;否则为向redis中同步购物车信息 * @return */ private List<CartItem> syncCart(long userId, String cartList){ //定义返回值 List<CartItem> returnList = new ArrayList<CartItem>(); HashMap<String, String> map = new HashMap<String,String>(); map.put("userId", userId+""); map.put("cartList", cartList); String url = REST_BASE_URL+REST_CART_SYNC_URL; String json = HttpClientUtil.doPost(url, map); if (StringUtils.isNotEmpty(json)) { TaotaoResult taotaoResult = TaotaoResult.formatToList(json, CartItem.class); if (taotaoResult.getStatus()==200) { returnList = (ArrayList<CartItem>) taotaoResult.getData(); } } return returnList; } //获取当前用户信息 public TbUser getCurrentUser(HttpServletRequest request) { //判断用户是否登录 //从cookie中取token String token = CookieUtils.getCookieValue(request, "TT_TOKEN"); //根据token换取用户信息,调用sso系统的接口 ///这里需要写一些业务 逻辑,不要在拦截器中写,单写一个service,只在这里注入并调用 TbUser user = userService.getUserByToken(token); return user; } }
配置文件:
#服务层属性定义
#服务层基础 url
REST_BASE_URL = http://localhost:8081/rest
#首页大 广告位
REST_INDEX_AD_URL = /content/list/89
#同步购物车的url
REST_CART_SYNC_URL=/cart/syncCart
cart.js代码:
var TTCart = { load : function(){ // 加载购物车数据 }, itemNumChange : function(){ $(".increment").click(function(){//+ var _thisInput = $(this).siblings("input"); _thisInput.val(eval(_thisInput.val()) + 1); $.post("/cart/update/"+_thisInput.attr("itemId")+".html?num="+_thisInput.val(),function(data){ TTCart.refreshTotalPrice(); }); }); $(".decrement").click(function(){//- var _thisInput = $(this).siblings("input"); if(eval(_thisInput.val()) == 1){ return ; } _thisInput.val(eval(_thisInput.val()) - 1); $.post("/cart/update/"+_thisInput.attr("itemId")+".html?num="+_thisInput.val(),function(data){ TTCart.refreshTotalPrice(); }); }); $(".quantity-form .quantity-text").rnumber(1);//限制只能输入数字 $(".quantity-form .quantity-text").change(function(){ var _thisInput = $(this); $.post("/cart/update/"+_thisInput.attr("itemId")+".html?num="+_thisInput.val(),function(data){ debugger TTCart.refreshTotalPrice(); }); }); }, refreshTotalPrice : function(){ //重新计算总价 var total = 0; $(".quantity-form .quantity-text").each(function(i,e){ var _this = $(e); total += (eval(_this.attr("itemPrice")) * 10000 * eval(_this.val())) / 10000; }); $(".totalSkuPrice").html(new Number(total/100).toFixed(2)).priceFormat({ //价格格式化插件 prefix: '¥', thousandsSeparator: ',', centsLimit: 2 }); } }; $(function(){ TTCart.load(); TTCart.itemNumChange(); });
然后是远程接口服务层代码:
Controller层:
package com.taotao.rest.controller; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.taotao.common.pojo.CartItem; import com.taotao.common.pojo.TaotaoResult; import com.taotao.rest.service.CartService; /**购物车Controller * @author Administrator * *当用户登录时,同步cookie购物车和redis中购物车的 商品list *也可以在每次购物车中商品数量、添加商品、删除商品、清空购物车时,都调用这个同步方法 */ @Controller @RequestMapping("/cart") public class CartCotroller { @Autowired private CartService cartService; @RequestMapping("/syncCart") @ResponseBody public TaotaoResult syncCart(long userId,String cartList) { /** 先拿到cookie中的购物车商品列表listCookie,然后再拿到redis中的购物车商品列表listRedis 然后遍历比对,将两者合并,合并时以cookie中的为准, 具体规则: 如果c中有而r中没有,则合并后都有 如果c中没有而r中有,则合并后都有 两者都有时,把数量相加 */ /** * 因为这里是rest项目只提供接口,不方便操作cookie,所以这里只提供如下方法,其他逻辑在 portal项目中完成 一种是前台根据用户id获取redis中存储的 购物车信息(适用于用户登录时) 一种是前台向后台提供 购物车中的商品集合,后台写入redis */ List<CartItem> list = cartService.syncCart(userId, cartList); return TaotaoResult.ok(list); } }
service层:
package com.taotao.rest.service.impl; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.taotao.common.pojo.CartItem; import com.taotao.common.utils.JsonUtils; import com.taotao.rest.dao.JedisClient; import com.taotao.rest.service.CartService; @Service public class CartServiceImpl implements CartService { @Value("REDIS_CART_LIST_KEY") private String REDIS_CART_LIST_KEY; @Autowired private JedisClient jedisClient; @Override public List<CartItem> syncCart(long userId, String cartList) { /** * 因为这里是rest项目只提供接口,不方便操作cookie,所以这里只提供如下方法,其他逻辑在 portal项目中完成 一种是前台根据用户id获取redis中存储的 购物车信息(适用于用户登录时) 一种是前台向后台提供 购物车中的商品集合,后台写入redis */ //定义返回值 List<CartItem> list = new ArrayList<CartItem>(); if (StringUtils.isEmpty(cartList)) { //说明是用户请求list //从redis中取出用户的购物车信息返回给前台 String json = jedisClient.hget(REDIS_CART_LIST_KEY, userId+""); if (StringUtils.isNotEmpty(json)) { list = JsonUtils.jsonToList(json, CartItem.class); } }else{ //说明是用户向后台提供购物成信息,准备保存到redis中 jedisClient.hset(REDIS_CART_LIST_KEY, userId+"", cartList); } return list; } }
配置文件:
#购物车商品列表在redis中保存的key
REDIS_CART_LIST_KEY=REDIS_CART_LIST_KEY