美文网首页
Cookie及Redis在商城购物车系统中的使用

Cookie及Redis在商城购物车系统中的使用

作者: 奇点一氪 | 来源:发表于2018-12-18 16:31 被阅读57次

    关于商城中购物车功能,天猫是必须登录才能将商品加入到购物车,京东则可以在不登录状态下也可以加入购物车,这里就模仿京东购物车功能。
    购物车工程搭建:
    e3-cart(pom)
    |–e3-cart-interface(jar)
    |-e3-cart-service(war)
    e3-cart-web(war)
    参照”redis实现单点登录系统”搭建

    需求:
    商品详情页面如下:


    image.png

    选择好商品,确定数量后,点击“加入购物车”按钮,发送请求。
    请求地址:8090/cart/add/{itemId}.html,参数:商品id跟商品数量
    返回逻辑视图:”cartSuccess”;

    一、未登录状态下购物车功能实现

    1、未登录状态下添加商品到购物车
    在不登陆的情况下也可以添加购物车。把购物车信息写入cookie。
    优点:
    1、不占用服务端存储空间
    2、用户体验好。
    3、代码实现简单。
    缺点:
    1、cookie中保存的容量有限。最大4k
    2、把购物车信息保存在cookie中,更换设备购物车信息不能同步。

    分析:页面传来的是商品id跟商品数量

    (1) 从cookie中获取商品列表信息(单独提出来写成个通用的方法)
    (2) 遍历购物车列表,判断需要添加的商品在购物车列表是否存在
    (3) 商品存在的话,那么取出该商品原来的数量+添加的数量作为该商品现在的数量
    (4) 如果商品不存在,那么调用服务,根据传来的商品id查询商品数量,设置商品的数量为页面传来的数量,取商品的第一张图片(购物车列表只展示一张图片)。
    (5) 把修改后的购物车列表重新存入到cookie中
    (6) 返回逻辑视图”cartSuccess”

    实现:

    在表现层工程e3-cart-web中引用商品服务工程提供的服务

    <!-- 加载配置文件 -->
        <context:property-placeholder location="classpath:conf/resource.properties" />
    
        <context:component-scan base-package="cn.e3mall.cart.controller" />
        <mvc:annotation-driven />
        <bean
            class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/" />
            <property name="suffix" value=".jsp" />
        </bean>
        <!-- 引用dubbo服务 -->
        <dubbo:application name="e3-cart-web"/>
        <dubbo:registry protocol="zookeeper" address="192.168.25.128:2181"/>    
        <dubbo:reference interface="cn.e3mall.service.ItemService" id="itemService" />
    

    ItemService提供了根据id获取商品信息的方法:getItemById(Long itemId)

    @Controller
    public class CartController {
    
        @Autowired
        private ItemService itemService;
    
        @Value("${COOKIE_MAX_TIME}")
        private Integer COOKIE_MAX_TIME;
    
        /*
         * 1.未登录状态下添加购物车商品
         */
        @RequestMapping("/cart/add/{itemId}")
        public String addCartNum(@PathVariable Long itemId, Integer num,
                    HttpServletRequest request,HttpServletResponse response){
            //获取购物车列表
            List<TbItem> cartList = getCartListFromCookie(request);
            //判断购物车中是否有该商品
            boolean flag = false;
            for (TbItem tbItem : cartList) {
            if(tbItem.getId()==itemId.longValue()){
                    flag = true;
                    //存在该商品,数量相加
                    tbItem.setNum(tbItem.getNum()+num);
                    //跳出循环
                    break;
                }
            }
            if(!flag){
                //没有的话,调用服务查询该商品
                TbItem tbItem = itemService.getItemById(itemId);
                //设置数量
                tbItem.setNum(num);
                //取一张图片
                String image = tbItem.getImage();
                if(StringUtils.isNotBlank(image)){
                    tbItem.setImage(image.split(",")[0]);
                }
                //商品添加到购物车列表
                cartList.add(tbItem);
            }
            //购物车信息写入cookie
            CookieUtils.setCookie(request, response, "cart1", 
                    JsonUtils.objectToJson(cartList), COOKIE_MAX_TIME, true);
            //返回逻辑视图
            return "cartSuccess";
        }
        /*
         *从cookie中获取购物车列表
         */
        public List<TbItem> getCartListFromCookie(HttpServletRequest request){
            String string = CookieUtils.getCookieValue(request, "cart1", true);
            //判断是否为空
            if(StringUtils.isBlank(string)){
                //空的话也不能返回null
                return new ArrayList<>();
            }
            //转为商品列表
            List<TbItem> list = JsonUtils.jsonToList(string, TbItem.class);
            return list;
        }
    
    }
    

    其中商品实体类TbItem里面的属性image存放的是多张照片。
    COOKIE_MAX_TIME便是cookie中cart1最大存在时间,true表示采用utf-8编码
    测试:


    image.png

    其实并不能看出来效果。展示购物车列表功能实现后就能看到了。

    2、展示购物车列表

    单击“去购物车结算按钮”向服务端发送请求,服务端应该返回逻辑视图”cart”
    请求地址:8090/cart/cart.html
    返回逻辑视图:”cart”也就是购物车列表页面

    实现:同样是在CartController中添加

    /*
         * 2.未登录状态下展示商品列表 
         */
        @RequestMapping("/cart/cart")
        public String showCartList(HttpServletRequest request){
            //获取购物车列表
            List<TbItem> cartList = getCartListFromCookie(request);
            //绑定参数
            request.setAttribute("cartList", cartList);
            //返回逻辑视图
            return "cart";
        }
    

    注:cartList是根据cart.jsp的需要绑定的。该页面拿到cartList后会进行遍历,取各个商品的信息。
    测试:


    image.png

    3、为登录状态下购物车列表页面修改商品数量

    购物车列表页面单击”+”,”-”会向服务端发送ajax请求。
    页面需要根据调整的数量重新显示商品总计(已经实现了也就是输入框的值*价格)和小计(用js,待实现)
    服务端要求修改cookie中对应商品的数量。
    请求地址:/cart/update/num/{itemId}/{num}
    参数:商品id,商品数量
    返回结果:E3Result

    /*
         * 未登录状态下更新商品数量
         */
        @RequestMapping("/cart/update/num/{itemId}/{num}")
        @ResponseBody
        public E3Result updateCartNum(@PathVariable Long itemId,@PathVariable Integer num, 
                HttpServletRequest request,HttpServletResponse response){
            //获取购物车列表
            List<TbItem> cartList = getCartListFromCookie(request);
            //取所选择的需要更新的商品
            for (TbItem tbItem : cartList) {
                if(tbItem.getId()==itemId.longValue()){
                    //更新商品数量
                    tbItem.setNum(num);
                    //跳出循环
                    break;
                }
            }
            //购物车信息写入cookie
            CookieUtils.setCookie(request, response, "cart1", 
                    JsonUtils.objectToJson(cartList), COOKIE_MAX_TIME, true);
            return E3Result.ok();
        }
    

    测试:


    image.png

    注:商品总金额的js没有去写所以还是只显示单价。
    E3Result为自定义响应结构

    public class E3Result implements Serializable{
        // 定义jackson对象
        private static final ObjectMapper MAPPER = new ObjectMapper();
        // 响应业务状态
        private Integer status;
        // 响应消息
        private String msg;
        // 响应中的数据
        private Object data;
        public static E3Result build(Integer status, String msg, Object data) {
            return new E3Result(status, msg, data);
        }
        public static E3Result ok(Object data) {
            return new E3Result(data);
        }
        public static E3Result ok() {
            return new E3Result(null);
        }
        public E3Result() {
        }
        public static E3Result build(Integer status, String msg) {
            return new E3Result(status, msg, null);
        }
        public E3Result(Integer status, String msg, Object data) {
            this.status = status;
            this.msg = msg;
            this.data = data;
        }
        public E3Result(Object data) {
            this.status = 200;
            this.msg = "OK";
            this.data = data;
        }
        get、set方法
       }
    

    4、未登录状态下删除购物车商品

    请求地址:/cart/delete/{itemId}
    请求参数:商品id
    响应:重定向到购物车列表。

    实现:
    (1)从cookie中获取购物车列表
    (2)遍历,查找到要删除的商品
    (3)将该商品从购物车列表移除
    (4)更新后的购物车列表重新写入cookie
    (5)重定向到购物车列表页面

    /*
         * 未登录状态下删除购物车商品
         */
        @RequestMapping("/cart/delete/{itemId}")
    
        public String deleteCartById(@PathVariable Long itemId,
                HttpServletRequest request,HttpServletResponse response){
            //获取商品列表
            List<TbItem> cartList = getCartListFromCookie(request);
            //遍历商品列表,找到该商品
            for (TbItem tbItem : cartList) {
                if(tbItem.getId() == itemId.longValue()){
                    //删除该商品
                    cartList.remove(tbItem);
                    break;
                }
            }
            //购物车信息写入cookie
            CookieUtils.setCookie(request, response, "cart1", 
                    JsonUtils.objectToJson(cartList), COOKIE_MAX_TIME, true);
            //重定向到列表页面
            return "redirect:/cart/cart.html";
        }
    

    测试:
    上面的图,点击删除后


    image.png

    二、登录状态下购物车功能的实现

    功能分析:
    1、购物车数据保存的位置:
    未登录状态下,把购物车数据保存到cookie中。
    登录状态下,需要把购物车数据保存到服务端。需要永久保存,可以保存到数据库中。可以把购物车数据保存到redis中。
    2、redis使用的数据类型
    a) 使用hash数据类型
    b) Hash的key应该是用户id。Hash中的field是商品id,value可以是把商品信息转换成json
    3、添加购物车
    登录状态下直接把商品数据保存到redis中。
    未登录状态保存到cookie中。
    4、如何判断是否登录?
    a) 从cookie中取token
    b) 取不到未登录
    c) 取到token,到redis中查询token是否过期。
    d) 如果过期,未登录状态
    e) 没过期登录状态。

    1、登录拦截器

    几乎在购物车所有功能执行 都要判断用户是否登录。利用aop思想,应该编写个拦截器,来判断用户是否登录。登录的话用户信息需要存在request域中
    (1) 从cookie中取token
    (2) 判断token是否存在
    (3) 不存在,说明用于未登录,放行
    (4) 如果token存在,调用服务,根据token从redis中取用户信息
    (5) 取不到用户信息,说明已经过期,放行
    (6) 取到了用户信息,说明用户已经登录,用户信息存到request中
    (7) 放行
    实现:
    首先需要在购物车系统表现层工程中(e3-cart-web)调用单点登录系统(sso)的服务,以及拦截器的配置。

    <!-- 加载配置文件 -->
        <context:property-placeholder location="classpath:conf/resource.properties" />
    
        <context:component-scan base-package="cn.e3mall.cart.controller" />
        <mvc:annotation-driven />
        <bean
            class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/" />
            <property name="suffix" value=".jsp" />
        </bean>
        <!-- 拦截器配置 -->
        <mvc:interceptors>
            <mvc:interceptor>
                <mvc:mapping path="/**"/>
                <bean class="cn.e3mall.cart.interceptor.LoginInterceptor"/>
            </mvc:interceptor>
        </mvc:interceptors>
        <!-- 引用dubbo服务 -->
        <dubbo:application name="e3-cart-web"/>
        <dubbo:registry protocol="zookeeper" address="192.168.25.128:2181"/>    
        <dubbo:reference interface="cn.e3mall.service.ItemService" id="itemService" />
        <dubbo:reference interface="cn.e3mall.sso.service.TokenService" id="tokenService" />
    
    /*
     * 用户登录处理
     */
    public class LoginInterceptor implements HandlerInterceptor {
    
        @Autowired
        private TokenService tokenService;
    
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
            //前处理,执行handler之前执行此方法
            //返回true:放行  false:拦截
            //1.从cookie中取token
            String token = CookieUtils.getCookieValue(request, "token");
            //2.如果没有token,未登录状态
            if(StringUtils.isBlank(token)){
                return true;
            }
            //3.如果取到token,需要调用sso系统的服务,根据token取用户信息
            E3Result e3Result = tokenService.getUserByToken(token);
            if (e3Result.getStatus()!=200){
                //4.没有取到用户信息,登录已经过期,直接放行
                return true;
            }
            //5.取到用户信息。登录状态。
            TbUser user = (TbUser) e3Result.getData();
            //6.把用户信息放到request中,只需要在controller中判断request中是否包含user信息。
            request.setAttribute("user", user);
            return true;
        }
    
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                ModelAndView modelAndView) throws Exception {
            //handler执行之后,返回modelAndView之前
        }
    
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
                throws Exception {
            //完成处理,返回modelAndView之后(已经响应了)
            //可以再次处理异常
        }   
    }
    

    拦截器写完之后,对于购物车功能只需要在表现层判断用户是否登录,从而进行不同的处理。

    2、登录状态下,商品添加功能实现

    (1)、服务层e3-cart-service中:
    服务层用到了redis,所以需要将redis和spring整合。

    <!-- 连接redis单机版 -->
        <bean id="jedisClientPool" class="cn.e3mall.common.jedis.JedisClientPool">
            <property name="jedisPool" ref="jedisPool"></property>
        </bean>
        <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
        <!-- 一定要用name,构造方法太多用index容易错 -->
            <constructor-arg name="host" value="192.168.25.128"/>
            <constructor-arg name="port" value="6379"/>
        </bean>
    

    JedisClient只是自己对jedis操作redis的api的封装。服务层当然还得添加其他配置,如组件扫描,引入数据源,事务。

    /*
     * 购物车处理服务
     */
    @Service
    public class CartServiceImpl implements CartService{
    
        @Autowired
        private JedisClient jedisClient;
    
        @Value("${REDIS_CART_PRE}")//属性配置文件中,值为cart1
        private String REDIS_CART_PRE;
    
        @Autowired
        private TbItemMapper itemMapper;
        public E3Result addCart(Long userId, Long itemId, int num) {
            //向redis中添加购物车
            //数据类型是hash  key:用户id   field:商品id  value:商品信息
            //判断商品是否存在
            Boolean hexists = jedisClient.hexists(REDIS_CART_PRE+":"+userId, itemId+"");
            if(hexists){
                //如果存在,数量相加
                String json = jedisClient.hget(REDIS_CART_PRE+":"+userId, itemId+"");
                //把json转换成TbItem
                TbItem tbItem = JsonUtils.jsonToPojo(json, TbItem.class);
                tbItem.setNum(tbItem.getNum()+num);
                //写回redis
                jedisClient.hset(REDIS_CART_PRE+":"+userId, itemId+"",JsonUtils.objectToJson(tbItem));
                return E3Result.ok();
            }
            //如果不存在,根据商品id取商品信息,服务层尽量别相互调用
            TbItem item = itemMapper.selectByPrimaryKey(itemId);
            //设置购物车数量
            item.setNum(num);
            //取一张图片
            String image = item.getImage();
            if(StringUtils.isNotBlank(image)){
                item.setImage(image.split(",")[0]);
            }
            //添加到购物车列表
            jedisClient.hset(REDIS_CART_PRE+":"+userId, itemId+"",JsonUtils.objectToJson(item));
            //返回成功
            return E3Result.ok();
        }
    }
    

    发布服务:

    <context:component-scan base-package="cn.e3mall.cart.service"/>
    
        <!-- 使用dubbo发布服务 -->
        <!-- 提供方应用信息,用于计算依赖关系 -->
        <dubbo:application name="e3-cart" />
        <dubbo:registry protocol="zookeeper"
            address="192.168.25.128:2181" />
        <!-- 用dubbo协议在20880端口暴露服务 -->
        <dubbo:protocol name="dubbo" port="20884" /><!-- 一个服务对应一个端口 -->
        <!-- 声明需要暴露的服务接口 -->
        <dubbo:service interface="cn.e3mall.cart.service.CartService" ref="cartServiceImpl" timeout="600000"/>
    

    (2)、表现层工程e3-cart-web中
    调用e3-car-service刚发布的服务

    <!-- 引用dubbo服务 -->
        <dubbo:application name="e3-cart-web"/>
        <dubbo:registry protocol="zookeeper" address="192.168.25.128:2181"/>    
        <dubbo:reference interface="cn.e3mall.service.ItemService" id="itemService" />
        <dubbo:reference interface="cn.e3mall.sso.service.TokenService" id="tokenService" />
        <dubbo:reference interface="cn.e3mall.cart.service.CartService" id="cartService" />
    

    只需要再原来的添加商品功能中做判断处理

    @RequestMapping("/cart/add/{itemId}")
        public String addCart(@PathVariable Long itemId, @RequestParam(defaultValue="1") Integer num,
                HttpServletRequest request, HttpServletResponse response){
            //判断用户是否为登录状态
            TbUser user = (TbUser) request.getAttribute("user");
            if(user != null){
                //保存到服务端
                cartService.addCart(user.getId(), itemId, num);
                //返回逻辑视图
                return "cartSuccess";
            }
            //如果是登录状态,把购物车写入redis
            //如果未登录使用cookie
            ...未登录状态下代码
        }
    

    测试:
    Tidy用户登录,买了一个thinkpad电脑,单击加入购物车


    image.png

    查看redis,商品信息已经添加


    image.png

    2、登录状态下,商品列表展示

    分析:
    (1)从cookie中取购物车列表
    (2)判断用户是否登录
    (3)用户已经登录的话,则调用服务层,合并cookie中的列表和redis中的列表。存入到redis中。
    (4)同时删除cookie中的购物车列表
    (5)根据用户id,调用服务查询redis中所有的商品,返回购物车列表。
    (6)未登录状态还是跟前面一样
    (7)将列表绑定到参数,返回购物车列表页面。
    在服务层e3-cart-service中

    /*
         * 合并购物车
         */
        public E3Result mergeCart(Long userId, List<TbItem> itemList) {
            //遍历商品列表 
            //把列表添加到购物车
            //判断购物车中是否有此商品
            //如果有,数量相加
            //如果没有添加新的商品
            for (TbItem tbItem : itemList) {
                //等同于上面的添加商品到redis中
                addCart(userId, tbItem.getId(), tbItem.getNum());
            }
            return E3Result.ok();
        }
        /*
         * 取购物车列表
         */
        public List<TbItem> getCartList(long userId) {
            //根据用户id查询购物车列表
            List<String> jsonList = jedisClient.hvals(REDIS_CART_PRE+":"+userId);
            List<TbItem> itemList = new ArrayList<>();
            for (String string : jsonList) {
                //创建一个TbItem
                TbItem item = JsonUtils.jsonToPojo(string, TbItem.class);
                //添加到列表
                itemList.add(item);
            }
            return itemList;
        }
    

    表现层工程 e3-cart-web中

    /*
         * 展示购物车列表
         */
        @RequestMapping("/cart/cart")
        public String showCartList(HttpServletRequest request,HttpServletResponse response){
            //从cookie中取购物车列表
            List<TbItem> list = getCartListFromCookie(request);
    
            //判断用户是否为登录状态
            TbUser user = (TbUser) request.getAttribute("user");
            //如果是登录状态
            if(user!=null){
                //从cookie中取购物车列表
                //如果不为空,把cookie中的购物车商品和服务端的购物车商品合并。
                cartService.mergeCart(user.getId(), list);
                //把cookie中的购物车删除
                CookieUtils.deleteCookie(request, response, "cart");
                //从服务端取购物车列表
                list = cartService.getCartList(user.getId());
    
            }
    
            //未登录状态 
            //把列表传递给页面
            request.setAttribute("cartList", list);
            //返回逻辑视图
            return "cart";
        }
    

    测试:
    先不登录状态下添加商品都购物车,再登录添加商品到购物车。


    image.png

    再登录tidy账号(之前买了个电脑放入到了购物车)


    image.png
    发现已经合并成功了,再看cookie中
    image.png
    发现购物车已经为空了。

    也可以看下redis中,商品合并了


    image.png

    3、登录状态下修改购物车商品数量

    分析
    单击”+”,”-”修改商品的数量的时候,要求redis中该商品的数量发生改变
    (1) 根据用户id,商品id从redis中取出对应的商品
    (2) 设置商品的数量
    (3) 该商品更新到redis中
    (4) 返回E3Result
    实现:
    服务层e3-cart-service中

    /*
         * 登录状态下更新购物车数量
         */
        public E3Result updateCartNum(Long userId, Long itemId, int num) {
            //从redis中取商品信息
            String json = jedisClient.hget(REDIS_CART_PRE+":"+userId, itemId+"");
            //更新商品数量
            TbItem tbItem = JsonUtils.jsonToPojo(json, TbItem.class);
            tbItem.setNum(num);
            //写入redis
            jedisClient.hset(REDIS_CART_PRE+":"+userId, itemId+"",JsonUtils.objectToJson(tbItem));
            return E3Result.ok();
        }
    

    表现层工程e3-cart-web中

    /*
         * 更新购物车商品数量
         */
        @RequestMapping("/cart/update/num/{itemId}/{num}")
        @ResponseBody
        public E3Result updateCartNum(@PathVariable Long itemId, @PathVariable Integer num,
                    HttpServletRequest request, HttpServletResponse response){
            //判断用户是否为登录状态
            TbUser user = (TbUser) request.getAttribute("user");
            if (user != null){
                cartService.updateCartNum(user.getId(), itemId, num);
                return E3Result.ok();
            }
    
            //从cookie中取购物车列表
            List<TbItem> cartList = getCartListFromCookie(request);
            //遍历商品列表找到对应的商品
            for (TbItem tbItem : cartList) {
                //包装类型直接==比的是内存地址
                if(tbItem.getId() == itemId.longValue()){
                    //跟新数量
                    tbItem.setNum(num);
                    break;
                }
            }
            //把购物车列表写回cookie
            CookieUtils.setCookie(request, response, "cart", 
                    JsonUtils.objectToJson(cartList), COOKIE_CART_EXPIRE, true);
            //返回成功
            return E3Result.ok();
        }
    

    5、登录状态下,删除购物车商品

    分析
    单击删除的时候,删除redis中该商品。重定向到列表页面
    (1) 直接用jedisClient的del的方法根据用户id跟商品id 商品
    (2) 返回成功

    服务层e3-cart-service中

    /*
         * 登录状态下删除
         */
        public E3Result deleteCartItem(long userId, long itemId) {
            //删除购物车商品
            jedisClient.hdel(REDIS_CART_PRE+":"+userId, itemId+"");
            return E3Result.ok();
        }
    

    表现层工程e3-cart-web中
    在原先的删除方法中添加即可

    /*
         * 从购物车删除商品
         */
        @RequestMapping("/cart/delete/{itemId}")
        public String deleteCartItem(@PathVariable Long itemId,HttpServletRequest request,
                HttpServletResponse response){
            //判断用户是否为登录状态
            TbUser user = (TbUser) request.getAttribute("user");
            if (user != null){
                cartService.deleteCartItem(user.getId(), itemId);
                return "redirect:/cart/cart.html";
            }
            未登录状态下删除购物车
            ...
        }
    

    修改删除测试:
    初始情况


    image.png
    image.png

    现在:删除手机,笔记本的数量改为2,操作后页面跟redis中如下


    image.png
    image.png

    相关文章

      网友评论

          本文标题:Cookie及Redis在商城购物车系统中的使用

          本文链接:https://www.haomeiwen.com/subject/upxlkqtx.html