zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    免密码远程登录和远程操作
    1、linux网络服务实验 用PuTTY连接Linux
    巧用CAS解决数据一致性问题
    第一天
    图像处理02
    图像处理01
    Poem 01(转)
    CS229 Lecture 01
    日本語1
    latex测试
  • 原文地址:https://www.cnblogs.com/libin6505/p/9889399.html
Copyright © 2011-2022 走看看