課程計划
- 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<String, String> map = jedisClient.hgetAll(TT_CART_REDIS_PRE_KEY + ":" + userId);
Set<Entry<String, String>> 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<String, String> 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、訪問測試
省略。。。