• day79_淘淘商城项目_12_购物车流程 + 商城购物车系统的搭建 + 商城购物车系统的实现分析(cookie+redis方案) + 购物车实现增删改查_匠心笔记


    课程计划

    • 1、购物车的流程
    • 2、购物车的实现
      • a) 登录状态下的购物车实现
      • b) 未登录状态下的购物车实现

    1、购物车流程

    1.1、以前的购物车流程

    用户将商品添加到购物车时,判断用户是否登录,如果已经登录将购物车放入session中。


    存在的问题:
      购物车使用了session,而session是存在于内存资源中,消耗了大量的内存资源。非常不好。
      购物车存在session当中,如果session销毁(浏览器关闭),购物车就没有了。
      session无法共享,无法进行水平扩展。
        解决方案:给购物车做持久化。
        持久化,需要用到数据库(把数据存储到服务端,较安全)。
        a、mysql数据库(数据完整性比较好)
        b、redis数据库(读写速度快)

      用户未登录的时候不能添加购物车。
        解决方案:未登录的状态下,可以把购物车放在cookie中。
      在不登陆的情况下添加购物车。把购物车信息写入cookie。(数据保存在客户端,数据完整性差)。
      优点:
        1、不占用服务端存储空间。
        2、用户体验好。
        3、代码实现简单。
      缺点:
        1、cookie中保存的容量有限。最大4k
        2、把购物车信息保存在cookie中,更换设备购物车信息不能同步。
      这里我们不使用这种方法。

    1.2、现在的购物车流程


    对于未登陆用户,将购物车放到cookie中。对于已登陆用户将购物车放入redis缓存中。可以实现,用户未登录或者登录状况下的添加购物车(并进行购物车的增删查改)。

    2、商城购物车系统的搭建

    2.1、购物车系统的架构

    购物车系统架构:
    taotao-cart(pom聚合工程)
      |--taotao-cart-interface(jar)
      |--taotao-cart-Service(war)
    taotao-cart-web(war)
    可以参考taotao-manager、taotao-manager-web创建。

    2.2、服务层工程搭建

    2.2.1、taotao-cart

    taotao-cart打包方式pom。
    可以参考taotao-manager工程的创建。
    New --> Maven Project --> 不使用骨架创建工程 --> Next


    pom.xml
    <project xmlns="http://maven.apache.org/POM/4.0.0" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
        http://maven.apache.org/xsd/maven-4.0.0.xsd"
    >

        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>com.taotao</groupId>
            <artifactId>taotao-parent</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </parent>
        <artifactId>taotao-cart</artifactId>
        <packaging>pom</packaging>
        <dependencies>
            <!-- 配置对taotao-common的依赖-->
            <dependency>
                <groupId>com.taotao</groupId>
                <artifactId>taotao-common</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <!-- 配置Tomcat插件  -->
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <configuration>
                        <port>8089</port>
                        <path>/</path>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>

    2.2.2、taotao-cart-interface

    New --> Other--> Maven Module --> 不使用骨架创建
    千万不要创建成Maven Project。
    taotao-cart-interface打包方式jar。
    pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
        http://maven.apache.org/xsd/maven-4.0.0.xsd"
    >

        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>com.taotao</groupId>
            <artifactId>taotao-cart</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </parent>
        <artifactId>taotao-cart-interface</artifactId>
        <dependencies>
            <!-- 配置对taotao-manager-pojo的依赖 -->
            <dependency>
                <groupId>com.taotao</groupId>
                <artifactId>taotao-manager-pojo</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </project>

    2.2.3、taotao-cart-service

    New --> Other--> Maven Module --> 不使用骨架创建
    千万不要创建成Maven Project。
    taotao-cart-service打包方式war。
    pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
        http://maven.apache.org/xsd/maven-4.0.0.xsd"
    >

        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>com.taotao</groupId>
            <artifactId>taotao-cart</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </parent>
        <artifactId>taotao-cart-service</artifactId>
        <packaging>war</packaging>
        <dependencies>
            <!-- 配置对taotao-manager-dao的依赖 -->
            <dependency>
                <groupId>com.taotao</groupId>
                <artifactId>taotao-manager-dao</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
            <!-- 配置对taotao-cart-interface的依赖:服务层发布服务要通过该接口 -->
            <dependency>
                <groupId>com.taotao</groupId>
                <artifactId>taotao-cart-interface</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
            <!-- 配置对spring的依赖 -->
            <!-- Spring -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jms</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
            </dependency>
            <!-- 配置对dubbo的依赖 -->
            <!-- dubbo相关 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>dubbo</artifactId>
                <!-- 排除对低版本jar包的依赖 -->
                <exclusions>
                    <exclusion>
                        <artifactId>spring</artifactId>
                        <groupId>org.springframework</groupId>
                    </exclusion>
                    <exclusion>
                        <artifactId>netty</artifactId>
                        <groupId>org.jboss.netty</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>zookeeper</artifactId>
            </dependency>
            <dependency>
                <groupId>com.github.sgroschupf</groupId>
                <artifactId>zkclient</artifactId>
            </dependency>
            <!-- 配置对Redis的Java客户端jedis的依赖 -->
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
            </dependency>
        </dependencies>
    </project>

    由于没有web.xml,打包方式为war的项目会报错。
    右键项目taotao-cart-service --> Java EE Tools --> Generate … xxx,会自动帮我们生成web.xml,
    在web.xml中配置spring的监听器,内容如下:
    web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
        http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

        id="WebApp_ID" version="2.5">

        <display-name>taotao-cart-service</display-name>
        <welcome-file-list>
            <welcome-file>index.jsp</welcome-file>
        </welcome-file-list>
        <!-- 初始化spring容器:也即加载spring容器 -->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/applicationContext*.xml</param-value>
        </context-param>
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    </web-app>

    2.2.4、框架整合

    将taotao-manager-service的src/main/resources下的配置文件全部复制到taotao-cart-service的src/main/resources中


    删除或清空与数据库相关的配置文件,因为购物车主要使用redis缓存,所以需要将redis的工具类添加进来:

    修改applicationContext-service.xml中spring注解包扫描的类与dubbo暴露服务的端口:

    在taotao-cart-interface工程的src/main/java中创建com.taotao.cart.service包;
    在taotao-cart-service工程的src/main/java中创建com.taotao.cart.service.impl包。

    2.3、表现层工程搭建

    2.3.1、taotao-cart-web

    表现层为一个单独的工程,所以需要创建Maven Project。
    New --> Maven Project
    千万不要创建成Maven Module。
    taotao-cart-web的打包方式是war。


    pom.xml
    <project xmlns="http://maven.apache.org/POM/4.0.0" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
        http://maven.apache.org/xsd/maven-4.0.0.xsd"
    >

        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>com.taotao</groupId>
            <artifactId>taotao-parent</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </parent>
        <artifactId>taotao-cart-web</artifactId>
        <packaging>war</packaging>
        <dependencies>
            <!-- 配置对taotao-common的依赖 -->
            <dependency>
                <groupId>com.taotao</groupId>
                <artifactId>taotao-common</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
            <!-- 配置对taotao-manager-interface的依赖:表现层调用服务要通过该接口 -->
            <dependency>
                <groupId>com.taotao</groupId>
                <artifactId>taotao-manager-interface</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
            <!-- 配置对spring的依赖 -->
            <!-- Spring -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jms</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
            </dependency>
            <!-- JSP相关 -->
            <dependency>
                <groupId>jstl</groupId>
                <artifactId>jstl</artifactId>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jsp-api</artifactId>
                <scope>provided</scope>
            </dependency>
            <!-- 文件上传组件 -->
            <dependency>
                <groupId>commons-fileupload</groupId>
                <artifactId>commons-fileupload</artifactId>
            </dependency>
            <!-- 配置对分页插件pagehelper的依赖 -->
            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper</artifactId>
            </dependency>
            <!-- 配置对dubbo的依赖 -->
            <!-- dubbo相关 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>dubbo</artifactId>
                <!-- 排除对低版本jar包的依赖 -->
                <exclusions>
                    <exclusion>
                        <artifactId>spring</artifactId>
                        <groupId>org.springframework</groupId>
                    </exclusion>
                    <exclusion>
                        <artifactId>netty</artifactId>
                        <groupId>org.jboss.netty</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>zookeeper</artifactId>
            </dependency>
            <dependency>
                <groupId>com.github.sgroschupf</groupId>
                <artifactId>zkclient</artifactId>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <!-- 配置Tomcat插件  -->
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <configuration>
                        <port>8090</port>
                        <path>/</path>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>

    web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
        http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

        version="2.5">

        <display-name>taotao-cart-web</display-name>
        <welcome-file-list>
            <welcome-file>index.jsp</welcome-file>
        </welcome-file-list>
        <!-- 配置解决post乱码的过滤器 -->
        <filter>
            <filter-name>characterEncodingFilter</filter-name>
            <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
            <init-param>
                <param-name>encoding</param-name>
                <param-value>utf-8</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>characterEncodingFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <!-- 配置springmvc的前端控制器 -->
        <servlet>
            <servlet-name>taotao-cart-web</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:spring/springmvc.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>taotao-cart-web</servlet-name>
            <!-- 拦截以(*.html)请求,jsp除外 -->
            <url-pattern>*.html</url-pattern>
        </servlet-mapping>
    </web-app>

    2.3.2、框架整合

    将表现层taotao-manager-web工程下的src/main/resources下面的配置文件复制到taotao-cart-web下的src/main/resources中
    删除或清空相关的配置文件:


    修改springmvc.xml中controller注解扫描的位置:

    在taotao-manager-web工程下创建com.taotao.cart.controller包。

    3、商城购物车系统的实现分析

    3.1、购物车如何表示?

    由于购物车中的商品属性与TbItem类似,所以可以将TbItem当做购买的商品。TbItem中的num属性原用作后台管理系统的商品库存数量,在这里可以将num属性用作购买商品的数量,所以List<TbItem>就是一个购物车。一个用户对应一个购物车,一个购物车对应多个商品。

    3.2、怎么将购物车存入cookie中?

    对于存入cookie中,我们可以自定义key,然后将购物车List<TbItem>转为json串,存到cookie。

    3.3、使用哪种redis数据结构存放购物车?

    由于一个用户对应一个购物车,一个购物车购买多个商品。相当于一个用户可以购买多个商品。
    对于存入redis中,我们有两种实现方案:
    1.方案一:
      使用String类型,将用户userId作为key,将购物List<TbItem>转为json串作为value


    2.方案二:
      使用hash类型,将用户userId作为key,将商品itemId作为filed,将购物车List<TbItem>转为json串作为value

      推荐使用hash,可以提高修改的效率(性能的提升),如果是String存储大数据量的时候速度较慢。redis是单线程。
    分析需要存储的商品的信息的属性有:
      商品的ID、商品的名称、商品的价格、购买商品的数量、商品的图片(取一张即可)。

    3.4、创建POJO

      可以直接使用TbItem。

    4、商城购物车系统实现的准备工作

    由于购物车系统的运行,依赖很多其他系统,为了防止登录注册等其他情况,url错误报404导致不好测试,需要修改整个淘淘商城系统中的url为正确的url。

    4.1、修改所有登录注册的url


    Ctrl+H,搜索所有的:http://localhost:8084/page,将8084改为8088

    搜索结果如下:

    4.2、修改所有回显用户名的url

    将所有的$.cookie()取值的name改为自己定义的值。
    将ajax跨域请求的url改为自己的。

    4.3、修改所有安全退出的url

    Ctrl+H,搜索所有的:<a href="http://www.taotao.com/user/logout.html" class="link-logout">[退出]</a>
    改为:<a href="http://localhost:8088/user/logout.html" class="link-logout">[退出]</a>

    5、实现登录与未登录状态下添加商品到购物车

    5.1、导入静态资源

    将参考资料中的购物车静态页面下的js、css、images导入webapp下,将jsp导入WEB-INF下:

    5.2、修改商品详情页

    商品详情在taotao-item-web系统的item.jsp中,给加入购物车添加一个事件javascript:addCartItem();


    在前端点击添加购物车,就会触发addCartItem函数,跳转url:cart/add/itemId.html?num=123,我们需要接收itemId与num,同步到redis或者cookie中。
    添加购物车功能的分析:
      url:/cart/add/{itemId}?num=2
      参数:商品的id以及num
      另外:用户的id(因为要保证redis中存储数据的正确性,必须要求要存放于哪一个用户的购物车数据)
      返回值:逻辑视图,cartSuccess.jsp 。

    5.3、登录状态下添加购物车

    5.3.1、Dao层

      直接通过JedisClient对象操作redis数据库。

    5.3.2、Service层

    业务逻辑:
      1、根据商品的ID查询商品的信息。
      2、设置商品的数量和图片(只需要设置图片的一张即可,数量使用num字段来表示,因为原来的num存放的是库存,所以需要重新设置一下)。
      3、根据商品id和用户id从redis数据库中查询用户的购物车的商品的列表,看是否存在(需要将JSON转成java对象)。
      4、如果没有存在,则直接添加到redis中(需要将java对象转成JSON)。
      5、如果存在,则更新商品购买的数量即可。
    首先在taotao-cart-interface下创建接口包com.taotao.cart.service,在接口包下创建接口CartService.java。

    /**
     * 购物车管理接口
     * @author chenmingjun
     * @date 2018年12月5日 下午11:21:43
     * @version V1.0
     */

    public interface CartService {

        /**
         * 添加商品至redis购物车或者cookie购物车
         * @param userId 购物车所属的用户
         * @param item 添加的商品
         * @param num 添加商品的数量
         * @return
         */

        TaotaoResult addItemCart(Long userId, TbItem tbItem, Integer num);

        /**
         * 根据用户ID和商品的ID查询购物车是否存储在redis中
         * @param userId
         * @param itemId
         * @return null 说明购物车不存在,如果不为空说明购物车存在
         */

        TbItem queryTbItemByUserIdAndItemId(Long userId, Long itemId);
    }

    使用hash类型,可以给key设置一个前缀用于分类。在taotao-cart-service的src/main/resources下创建resource.properties文件,文件内容如下:

    #购物车的前缀
    TT_CART_REDIS_PRE_KEY=TT_CART_REDIS_PRE_KEY

    在taotao-cart-service下创建实现类包com.taotao.cart.service.impl,在实现类包下创建CartServiceImpl实现CartService:

    /**
     * 购物车管理Service
     * @author chenmingjun
     * @date 2018年12月5日 下午11:23:46
     * @version V1.0
     */

    @Service
    public class CartServiceImpl implements CartService {

        // 注入jedisClient对象
        @Autowired
        private JedisClient jedisClient;

        // 获取redis中购物车的前缀
        @Value("${TT_CART_REDIS_PRE_KEY}")
        private String TT_CART_REDIS_PRE_KEY;

        @Override
        public TaotaoResult addItemCart(Long userId, TbItem tbItem, Integer num) {
            // 1、从redis数据库中查询该用户的购物车中的某一个商品
            TbItem tbItem2 = queryTbItemByUserIdAndItemId(userId, tbItem.getId());
            // 利用了redis中hash类型赋值的特性:当字段不存在时赋值
            // 2、判断要添加的商品是否存在于列表中
            if (tbItem2 != null) {
                // 3、说明存在,则将商品的数量取出后相加再设置回去(将修改过的java对象转为JSON)
                tbItem2.setNum(tbItem2.getNum() + num);
                // 存入redis
                jedisClient.hset(TT_CART_REDIS_PRE_KEY + ":" + userId, tbItem.getId() + "", JsonUtils.objectToJson(tbItem2));
            } else {
                // 4、说明不存在,则设置商品数量,并设置照片(只需要设置图片的一张即可)
                tbItem.setNum(num);
           String image = tbItem.getImage();
                if (StringUtils.isNotBlank(image)) {
                    tbItem.setImage(image.split(",")[0]);
                }
                // 存入redis
                jedisClient.hset(TT_CART_REDIS_PRE_KEY + ":" + userId, tbItem.getId() + "", JsonUtils.objectToJson(tbItem));
            }
            return TaotaoResult.ok();
        }

        @Override
        public TbItem queryTbItemByUserIdAndItemId(Long userId, Long itemId) {
            // 通过用户id和商品的id查询redis数据库所对应的商品的数据,如果存在则不为空
            String string = jedisClient.hget(TT_CART_REDIS_PRE_KEY + ":" + userId, itemId + "");
            if (StringUtils.isNotBlank(string)) {
                // 将JSON转成java对象
                TbItem tbItem = JsonUtils.jsonToPojo(string, TbItem.class);
                return tbItem;
            } else {
                return null;
            }
        }
    }

    5.3.3、发布服务

    先在taotao-cart-service工程中的pom.xml文件中配置对taotao-cart-interface的依赖,因为服务层发布服务要通过该接口,
    再在taotao-cart-service工程中的applicationContext-service.xml文件中发布服务:要注意图中标注的地方

    5.3.4、引用服务

    先在taotao-cart-web工程中的pom.xml文件中配置对taotao-cart-interface的依赖,因为表现层引用服务要通过该接口,
    再在taotao-cart-web工程中的springmvc.xml文件中引用服务:要注意图中标注的地方

    5.3.5、Controller

    url: /cart/add/{itemId}
    参数:itemId, num
    返回值:添加购物车成功页面。
    业务逻辑:
      1、调用SSO的服务,获取用户相关的信息。
      2、调用商品(manage)的服务,获取商品的相关的信息。
      3、判断如果是登录的状态,则调用登录的添加购物车的service。
      4、如果是未登录的状态,则调用的是未登录的添加购物车的方法。

    1、由于要表现层taotao-cart-web要调用taotao-sso与taotao-manager的服务,所以需要在该工程的pom.xml中添加依赖:

        <!-- 配置对taotao-cart-interface的依赖:表现层调用服务要通过该接口 -->
        <dependency>
            <groupId>com.taotao</groupId>
            <artifactId>taotao-cart-interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- 配置对taotao-manager-interface的依赖:表现层调用服务要通过该接口 -->
        <dependency>
            <groupId>com.taotao</groupId>
            <artifactId>taotao-manager-interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- 配置对taotao-sso-interface的依赖:表现层调用服务要通过该接口 -->
        <dependency>
            <groupId>com.taotao</groupId>
            <artifactId>taotao-sso-interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

    2、由于要将购物车存入cookie中,所以需要给购物车存入cookie时设置一个name,获取token时正好也需要一个name,可以将这两个name放在resource.properties文件中,我们还需要设置cookie中存放token的key的过期时间,代码截图如下:


    代码如下:
    #cookie中存放token的key
    COOKIE_TOKEN_KEY=COOKIE_TOKEN_KEY

    #cookie中存放购物车的key
    COOKIE_CART_KEY=COOKIE_CART_KEY

    #cookie中存放token的key的过期时间,7天
    COOKIE_CART_EXPIRE_TIME=604800

    在taotao-cart-web下创建controller的包com.taotao.cart.controller,在包controller中创建CartController.java:

    @Controller
    public class CartController {
        // 引入服务 ,注入依赖 
        @Autowired
        private CartService cartService;

        @Autowired
        private ItemService itemService;

        @Autowired
        private UserLoginService userLoginService;

        // cookie中存放token的key
        @Value("${COOKIE_TOKEN_KEY}")
        private String COOKIE_TOKEN_KEY;

        // cookie中存放购物车的key
        @Value("${COOKIE_CART_KEY}")
        private String COOKIE_CART_KEY;

        @RequestMapping("/cart/add/{itemId}")   // /cart/add/149204693130763.html?num=4
        public String addItemCart(@PathVariable Long itemId, Integer num, HttpServletRequest request, HttpServletResponse response) {
            // 1、从cookie中获取token
            String token = CookieUtils.getCookieValue(request, COOKIE_TOKEN_KEY);

            // 2、根据token调用SSO的服务,获取用户的信息
            TaotaoResult result = userLoginService.getUserByToken(token);

            // 3、判断
            // 3.1、首先根据商品id调用商品(manager)服务的方法,获取商品的数据TbItem
            TbItem tbItem = itemService.getItemById(itemId);
            if (result.getStatus() == 200) { // 如果用户存在,说明已经登录
                // 3.2、调用添加购物车的方法,将商品数据添加到redis中
                TbUser tbUser = (TbUser) result.getData();
                cartService.addItemCart(tbUser.getId(), tbItem, num);
            } else {
                // 4、判断,如果用户不存在,说明未登录,将商品数据添加到cookie中,并设置过期时间
                // 4.1、首先根据cookie获取购物车的列表
            }
            return "cartSuccess";
        }
    }

    5.3.6、访问测试

      安装taotao-cart。
      由于要调用taotao-sso与taotao-manager查询用户与商品信息,所以需要启动taotao-sso、taotao-manager。
      需要登录在cookie中写入toekn,所以要启动taotao-sso-web。
      需要搜索商品,所以要启动taotao-search、taotao-search-web。(如果手动输入url进入商品详情页,可以不启动。)
      需要将商品详情页加入购物车,所以需要启动taotao-item-web。
      最后购物车的taotao-cart、taotao-cart-web也要启动。

    5.4、未登录状态下添加购物车

    5.4.1、服务层

      服务层不变,存入cookie,需要要使用servlet原生response对象,跟service没什么关系,所以放在controller中。

    5.4.2、发布服务

    5.4.3、引用服务

    5.4.5、表现层

    在addItemCart判断用户没登录中添加如下,需要判断cookie中是否已存在该商品,存在的话商品数量需要相加,不存在的话直接设置商品数量,还需要设置图片为第一张图片,最后设置cookie存放时间为一个星期(7243600)。


    代码如下:
    @Controller
    public class CartController {
        // 引入服务 ,注入依赖 
        @Autowired
        private CartService cartService;

        @Autowired
        private ItemService itemService;

        @Autowired
        private UserLoginService userLoginService;

        // cookie中存放token的key
        @Value("${COOKIE_TOKEN_KEY}")
        private String COOKIE_TOKEN_KEY;

        // cookie中存放购物车的key
        @Value("${COOKIE_CART_KEY}")
        private String COOKIE_CART_KEY;

        // cookie中存放token的key的过期时间
        @Value("${COOKIE_CART_EXPIRE_TIME}")
        private Integer COOKIE_CART_EXPIRE_TIME;

        @RequestMapping("/cart/add/{itemId}")   // /cart/add/149204693130763.html?num=4
        public String addItemCart(@PathVariable Long itemId, Integer num, HttpServletRequest request, HttpServletResponse response) {
            // 1、从cookie中获取token
            String token = CookieUtils.getCookieValue(request, COOKIE_TOKEN_KEY);
            // 2、根据token调用SSO的服务,获取用户的信息
            TaotaoResult result = userLoginService.getUserByToken(token);
            // 3、判断
            // 3.1、首先根据商品id调用商品(manager)服务的方法,获取商品的数据TbItem
            TbItem tbItem = itemService.getItemById(itemId);
            if (result.getStatus() == 200) { // 如果用户存在,说明已经登录
                // 3.2、调用添加购物车的方法,将商品数据添加到redis中
                TbUser tbUser = (TbUser) result.getData();
                cartService.addItemCart(tbUser.getId(), tbItem, num);
            } else {
                // 4、判断,如果用户不存在,说明未登录,将商品数据添加到cookie中,并设置过期时间
                // 4.1、首先根据cookie获取购物车的列表
                List<TbItem> cartList = getCartListFromCookie(request);

                boolean flag = false;
                for (TbItem tbItem2 : cartList) {
                    // 4.2、说明购物车cookie上有该商品,就获取购物车列表中的商品,更新商品数量
                    if (tbItem2.getId() == itemId.longValue()) { // 两个对象比的是内存地址值,longValue()取出的是基本类型的值
                        tbItem2.setNum(tbItem2.getNum() + num);
                        flag = true;
                        break;
                    }
                }
                if (!flag) {
                    // 4.3、说明购物车cookie中没有该商品,就设置该商品数量和图片
                    tbItem.setNum(num);
                    if (tbItem.getImage() != null) {
                        tbItem.setImage(tbItem.getImage().split(",")[0]);
                    }
                    // 把商品添加至购物车列表
                    cartList.add(tbItem);
                }
                // 5、将商品添加至购物车cookie,并设置过期时间
                CookieUtils.setCookie(request, response, COOKIE_CART_KEY, JsonUtils.objectToJson(cartList), COOKIE_CART_EXPIRE_TIME, true);
            }
            return "cartSuccess";
        }

        /**
         * 从cookie中获取购物车列表
         * @param request
         * @return
         */

        private List<TbItem> getCartListFromCookie(HttpServletRequest request) {
            // 从cookie中获取购物车商品列表
            String cartJson = CookieUtils.getCookieValue(request, COOKIE_CART_KEY, true);
            // 将cartJson转换为List<TbItem>
            if (StringUtils.isNotBlank(cartJson)) {
                List<TbItem> list = JsonUtils.jsonToList(cartJson, TbItem.class);
                return list;
            }
            return new ArrayList<>();
        }
    }

    注意:由于从cookie中获取购物车方法会经常使用,所以单独抽取成一个私有方法。

    5.4.3、访问测试

      首先退出用户登录状态,使用我们已经做好的查看购物车列表的功能(在下节),查看cookie中是否存入了购物车,可以看到未登录的情况下,添加购物车成功。

    6、实现登录与未登录状态下展示购物车列表

      在 5.3、登录状态下添加购物车5.4、未登录状态下添加购物车 准备工作已经做完的情况下。
      如果用户登录状态,展示购物车列表以redis为准。如果未登录,以cookie为准。

    6.1、功能分析

    添加商品到购物车后,会提示【去购物车结算】,点击【去购物车结算】,会跳转到http://localhost:8090/cart/cart.html页面,可以看到购物车中商品列表。
    在cart.jsp,我们可以看到需要准备一个cartList商品集合到model中。需要修改${cart.images[0]}${cart.image}


    url: /cart/cart
    参数:用户id
    返回值:购物车页面,需要传递模型数据List<Tbitem>

    6.2、Dao层

      直接通过JedisClient对象操作redis数据库。

    6.3、Service层

    在taotao-cart-interface创建接口

        /**
         * 根据用户的ID查询redis数据库中用户的购物车的商品列表
         * @param userId
         * @return
         */

        List<TbItem> queryCartListByUserId(Long userId);

    在taotao-cart-service编写实现类
    业务逻辑:
      1、根据用户的ID查询redis中所有的field的值(map)。
      2、遍历map对象,将其添加到List中。
      3、返回一个List<tbitem>

        @Override
        public List<TbItem> queryCartListByUserId(Long userId) {
            // 1、根据用户的ID查询redis中所有的field的值(map)
            Map<StringString> map = jedisClient.hgetAll(TT_CART_REDIS_PRE_KEY + ":" + userId);
            Set<Entry<StringString>> set = map.entrySet(); // map.entrySet() 是把Map类型的数据转换成集合类型
            // Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。它表示Map中的一个实体(一个key-value对)。接口中有getKey()、getValue()方法。
            // 2、遍历map对象,将其添加到List中
            if (set != null) { // 判断是否为空
                List<TbItem> list = new ArrayList<>();
                // 迭代器只针对集合类型的数据,因此map类型的必须先转换成集合类型才能使用迭代器去获取元素
                for (Entry<StringString> entry : set) {
                    TbItem tbItem = JsonUtils.jsonToPojo(entry.getValue(), TbItem.class);
                    list.add(tbItem);
                }
                // 3、返回一个List<tbitem>
                return list;
            }
            return null;
        }

    6.4、发布服务与引用服务

      同上5.4.2、发布服务5.4.3、引用服务,这里不再赘图!

    6.5、Controller

    url: /cart/cart
    参数:无
    返回值:购物车展示列表的页面
    业务逻辑:
      1、根据token调用SSO的服务,获取用户的信息。
      2、判断,如果有用户的信息,说明用户已登录,调用CartService服务中查询购物车的商品列表的方法。
      3、如果没有用户信息,说明用户未登录,调用从cookie中获取购物车商品列表的方法。
      4、将购物车对象放入request域。
      5、返回逻辑页面cart.jsp。


    代码如下:
        @RequestMapping("/cart/cart"// http://localhost:8090/cart/cart.html
        public String showCart(HttpServletRequest request) {
            // 1、从cookie中获取token
            String token = CookieUtils.getCookieValue(request, COOKIE_TOKEN_KEY);
            // 2、根据token调用SSO的服务,获取用户的信息
            TaotaoResult result = userLoginService.getUserByToken(token);
            // 3、判断,如果用户存在,说明已经登录
            if (result.getStatus() == 200) {
                // 如果用户已经登录,从redis中查询购物车列表
                TbUser tbUser = (TbUser) result.getData();
                List<TbItem> redisCartlist = cartService.queryCartListByUserId(tbUser.getId());
                request.setAttribute("cartList", redisCartlist);
            } else {
                // 如果用户未登录,从cookie中查询购物车列表
                List<TbItem> cookieCartList = getCartListFromCookie(request);
                request.setAttribute("cartList", cookieCartList);
            }
            return "cart";
        }

    6.6、访问测试

      安装taotao-cart。
      由于要调用taotao-sso与taotao-manager查询用户与商品信息,所以需要启动taotao-sso、taotao-manager。
      需要登录在cookie中写入toekn,所以要启动taotao-sso-web。
      需要搜索商品,所以要启动taotao-search、taotao-search-web。(如果手动输入url进入商品详情页,可以不启动)
      需要将商品详情页加入购物车,所以需要启动taotao-item-web。
      最后购物车的taotao-cart、taotao-cart-web也要启动。

    7、实现登录与未登录状态下更新购物车的商品数量

    7.1、更新购物车的商品数量的js分析

    在taotao-cart-web的cart.js中有更新商品时js事件处理。
    商品数量加一、减一时会触发对应的事件,修改dom,从而修改前端展示的商品价格。
    然后会异步请求url:/cart/update/num/" + _thisInput.attr("itemId") + "/" + _thisInput.val()
    也就是url:/cart/update/num/itemId/num,修改服务端的数据。
    refreshTotalPrice函数用于重新计算总价。


    注意:我们的请求是以.action结尾的。为什么呢?
    答:因为在springmvc.xml中拦截*.html结尾的请求不可以返回json数据。

    7.2、Dao层

      直接通过JedisClient对象操作redis数据库。

    7.3、Service层

    在taotao-cart-interface创建接口

        /**
         * 根据用户ID和商品的ID更新redis购物车中商品的数量
         * @param userId
         * @param itemId
         * @param num
         * @return
         */

        TaotaoResult updateTbItemCartByUserIdAndItemId(Long userId, Long itemId, Integer num);

    在taotao-cart-service创建实现类
    业务逻辑:
    从redis中获取到对应的商品的对象,设置对象的商品数量,转成JSON,存入redis中。

        @Override
        public TbItem queryTbItemByUserIdAndItemId(Long userId, Long itemId) {
            // 通过用户id和商品的id查询redis数据库所对应的商品的数据,如果存在则不为空
            String string = jedisClient.hget(TT_CART_REDIS_PRE_KEY + ":" + userId, itemId + "");
            if (StringUtils.isNotBlank(string)) {
                // 将JSON转成java对象
                TbItem tbItem = JsonUtils.jsonToPojo(string, TbItem.class);
                return tbItem;
            } else {
                return null;
            }
        }

        @Override
        public TaotaoResult updateTbItemCartByUserIdAndItemId(Long userId, Long itemId, Integer num) {
            // 通过用户id和商品的id查询redis数据库所对应的商品的数据,如果存在则不为空
            TbItem tbItem = queryTbItemByUserIdAndItemId(userId, itemId);
            if (tbItem != null) {
                tbItem.setNum(num);
                jedisClient.hset(TT_CART_REDIS_PRE_KEY + ":" + userId, itemId + "", JsonUtils.objectToJson(tbItem));
            }
            return TaotaoResult.ok();
        }

    7.4、发布服务与引用服务

      同上5.4.2、发布服务5.4.3、引用服务,这里不再赘图!

    7.5、Controller

    url:/cart/update/num/{itemId}/{num}
    参数:itemId、num
    从cookie中获取token,根据token查询redis,判断用户是否登录,已登录更新购物车到redis中,未登录更新到cookie中。
    更新cookie中的购物车思路比较简单:从cookie中获取所有购物车,遍历购物车找到对应商品更新数量,重新存入cookie即可。

        @RequestMapping("/cart/update/num/{itemId}/{num}")
        @ResponseBody // 响应json
        public TaotaoResult updateTbItemCartByUserIdAndItemId(@PathVariable Long itemId, @PathVariable Integer num, 
                HttpServletRequest request, HttpServletResponse response) {
            // 1、从cookie中获取token
            String token = CookieUtils.getCookieValue(request, COOKIE_TOKEN_KEY);
            // 2、根据token调用SSO的服务,获取用户的信息
            TaotaoResult result = userLoginService.getUserByToken(token);
            // 3、判断用户是否登录
            if (result.getStatus() == 200) {
                // 用户已登录,在redis中更新数据
                TbUser tbUser = (TbUser) result.getData();
                cartService.updateTbItemCartByUserIdAndItemId(tbUser.getId(), itemId, num);
            } else {
                // 用户没有登录,在cookie中更新数据
                updateCookieItemCart(itemId, num, request, response);
            }
            // 4、返回TaotaoResult.ok()
            return TaotaoResult.ok();
        }

        /**
         * 从cookie中获取购物车列表的方法
         * @param request
         * @return
         */

        private List<TbItem> getCartListFromCookie(HttpServletRequest request) {
            // 从cookie中获取购物车列表
            String cartJson = CookieUtils.getCookieValue(request, COOKIE_CART_KEY, true);
            // 将cartJson转换为List<TbItem>
            if (StringUtils.isNotBlank(cartJson)) {
                List<TbItem> list = JsonUtils.jsonToList(cartJson, TbItem.class);
                return list;
            }
            return new ArrayList<>();
        }

        /**
         * 更新购物车cookie中的商品数量的方法
         * @param itemId
         * @param num
         * @param request
         * @param response
         */

        private void updateCookieItemCart(Long itemId, Integer num, 
                HttpServletRequest request, HttpServletResponse response) {
            // 1、从cookie中获取商品的列表
            List<TbItem> cartList = getCartListFromCookie(request);
            // 2、判断,如果列表中有商品的id和传递过来的itemId一致 ,说明商品找到,更新商品的数量
            for (TbItem tbItem : cartList) {
                if (tbItem.getId() == itemId.longValue()) { // 两个对象比的是内存地址值,longValue()取出的是基本类型的值
                    tbItem.setNum(num); // 设置新的商品数量
                    break;
                }
            }
            // 3、重新设置cookie,将商品的列表转换成JSON设置回cookie中
            CookieUtils.setCookie(request, response, COOKIE_CART_KEY, JsonUtils.objectToJson(cartList), COOKIE_CART_EXPIRE_TIME, true);
            // 4、找不到什么不处理
        }

    7.6、访问测试

    解决请求*.html后缀无法返回json格式的数据的问题:
    问题原因:因为在springmvc.xml中拦截*.html结尾的请求不可以返回json数据。
    解决方式:由于我们的请求是以.action结尾的,所以我们修改web.xml,添加url拦截格式。

    8、实现登录与未登录状态下删除购物车中的商品

    8.1、功能分析

    用户点击删除,未登录从cookie中删除该商品、已登录从redis中删除该商品。
    url:/cart/delete/${cart.id}.html
    参数:cartid,其实是就是itemId
    根据商品id,从cookie或者redis中删除商品。
    返回值:展示购物车列表页面。url需要做redirect跳转(重定向)

    8.2、Dao层

      直接通过JedisClient对象操作redis数据库。

    8.3、Service层

    在taotao-cart-interface创建接口

        /**
         * 根据用户ID和商品的ID删除redis购物车中的商品
         * @param userId
         * @param itemId
         * @param num
         * @return
         */

        TaotaoResult deleteTbItemCartByUserIdAndItemId(Long userId, Long itemId);

    在taotao-cart-service创建实现类
    业务逻辑:
    根据userid、itemid删除redis中购物车列表的商品

        @Override
        public TaotaoResult deleteTbItemCartByUserIdAndItemId(Long userId, Long itemId) {
            jedisClient.hdel(TT_CART_REDIS_PRE_KEY + ":" + userId, itemId + "");
            return TaotaoResult.ok();
        }

    8.4、发布服务与引用服务

      同上5.4.2、发布服务5.4.3、引用服务,这里不再赘图!

    8.5、Controller

    url:/cart/delete/${cart.id}.html
    参数:cartid,其实是就是itemId
    根据商品id,从cookie或者redis中删除商品
    返回值:展示购物车列表页面。url需要做redirect跳转到商品列表展示的controller。

        @RequestMapping("/cart/delete/{itemId}")
        public String deleteTbItemCartByUserIdAndItemId(@PathVariable Long itemId, 
                HttpServletRequest request, HttpServletResponse response) {
            // 1、从cookie中获取token
            String token = CookieUtils.getCookieValue(request, COOKIE_TOKEN_KEY);
            // 2、根据token调用SSO的服务,获取用户的信息
            TaotaoResult result = userLoginService.getUserByToken(token);
            if (result.getStatus() == 200) {
                // 3、如果用户已经登录,则删除redis中对应商品
                TbUser tbUser = (TbUser) result.getData();
                cartService.deleteTbItemCartByUserIdAndItemId(tbUser.getId(), itemId);
            } else {
                // 4.如果用户没登录,则删除cookie中对应商品
                deleteCookieCartItem(itemId, request, response);
            }
            return "redirect:/cart/cart.html"// 重定向
        }

        /**
         * 从cookie中删除购物车中的商品的方法
         * @param itemId
         * @param request
         * @param response
         */

        private void deleteCookieCartItem(Long itemId, 
                HttpServletRequest request, HttpServletResponse response) {
            // 1、 从cookie中获取商品的列表
            List<TbItem> cartList = getCartListFromCookie(request);
            // 2、判断是否商品在列表中,如果列表中有商品的id和传递过来的itemId一致 ,说明商品找到,刪除商品
            for (TbItem tbItem : cartList) {
                if (tbItem.getId() == itemId.longValue()) { // 两个对象比的是内存地址值,longValue()取出的是基本类型的值
                    // 删除商品
                    cartList.remove(tbItem); // 在循环中删除,再遍历的话会有问题,所以我们删除完后应立即跳出循环
                    break;
                }
            }
            // 3、重新设置购物车列表到cookie中
            CookieUtils.setCookie(request, response, COOKIE_CART_KEY, JsonUtils.objectToJson(cartList), COOKIE_CART_EXPIRE_TIME, true);
            // 4、如果不在不处理
        }

    8.6、访问测试

      省略。。。

  • 相关阅读:
    Windows / Linux / MacOS 设置代理上网的方法汇总
    Centos7 配置 sendmail、postfix 端口号25、465
    CentOS 6/7 配置 sendEmail 发送邮件
    Python 发送 email 的三种方式
    Linux curl 命令模拟 POST/GET 请求
    Python + Selenium + Chrome 使用代理 auth 的用户名密码授权
    Python + Selenium + Firefox 使用代理 auth 的用户名密码授权
    Jenkins+JMeter+Ant 接口持续集成
    接口自动化、移动端、web端自动化如何做?
    pytest--命令行参数
  • 原文地址:https://www.cnblogs.com/chenmingjun/p/10078503.html
Copyright © 2020-2023  润新知