易课寄在线购课系统开发笔记(三十三)–完成购物车系统的开发
购物车的实现
功能分析
1、购物车是一个独立的表现层工程;
2、添加购物车不要求登录,可以指定购买课程的数量;
3、展示购物车列表页面;
4、修改购物车课程数量;
5、删除购物车课程。
工程搭建
ecourses-cart-web 打包方式 war
可以参考
ecourses-bms-web
pom 文件
<?xml version="1.0" encoding="UTF-8"?>
<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>cn.ecourses</groupId>
<artifactId>ecourses-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>cn.ecourses</groupId>
<artifactId>ecourses-cart-web</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>cn.ecourses</groupId>
<artifactId>ecourses-cart-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.ecourses</groupId>
<artifactId>ecourses-bms-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.ecourses</groupId>
<artifactId>ecourses-sso-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 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>
<!-- dubbo相关 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.netty</groupId>
<artifactId>netty</artifactId>
</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>
<!-- 配置tomcat插件 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<path>/</path>
<port>8090</port>
</configuration>
</plugin>
</plugins>
</build>
</project>
添加购物车
功能分析
在不登录的情况下也可以添加购物车,把购物车信息写入 Cookie 。
优点:
1、不占用服务端存储空间;
2、用户体验好;
3、代码实现简单。
缺点:
1、Cookie 中保存的容量有限,最大4k;
2、把购物车信息保存在 Cookie 中,更换设备购物车信息不能同步。
改造课程详情页面
请求的url:/cart/add/{itemId}
参数:
1)课程id: Long itemId 2)课程数量: int num
业务逻辑:
1、从 Cookie 中查询课程列表;
2、判断课程在课程列表中是否存在;
3、如果存在,课程数量相加;
4、不存在,根据课程 id 查询课程信息;
5、把课程添加到购车列表;
6、把购车课程列表写入cookie。
返回值:逻辑视图
Cookie 保存购物车
1)key:cart
2)Value:购物车列表转换成 JSON 数据,需要对数据进行编码。
3)Cookie的有效期:保存7天。
课程列表:
List<EcoursesItem>,每个课程数据使用 EcoursesItem 保存。当根据课程 id 查询课程信息后,取第一张图片保存到 image 属性中即可。
读写 Cookie 可以使用 CookieUtils 工具类实现。
Controller
package cn.ecourses.cart.controller;
//购物车处理Controller
@Controller
public class CartController {
@Value("${COOKIE_CART_EXPIRE}")
private Integer COOKIE_CART_EXPIRE;
@Autowired
private ItemService itemService;
@Autowired
private CartService cartService;
@RequestMapping("/cart/add/{itemId}")
public String addCart(@PathVariable Long itemId, @RequestParam(defaultValue="1")Integer num,
HttpServletRequest request, HttpServletResponse response) {
//判断用户是否登录
EcoursesUser user = (EcoursesUser) request.getAttribute("user");
//如果是登录状态,把购物车写入redis
if (user != null) {
//保存到服务端
cartService.addCart(user.getId(), itemId, num);
//返回逻辑视图
return "cartSuccess";
}
//如果未登录使用cookie
//从cookie中取购物车列表
List<EcoursesItem> cartList = getCartListFromCookie(request);
//判断课程在课程列表中是否存在
boolean flag = false;
for (EcoursesItem ecoursesItem : cartList) {
//如果存在数量相加
if (ecoursesItem.getId() == itemId.longValue()) {
flag = true;
//找到课程,数量相加
ecoursesItem.setNum(ecoursesItem.getNum() + num);
//跳出循环
break;
}
}
//如果不存在
if (!flag) {
//根据课程id查询课程信息。得到一个EcoursesItem对象
EcoursesItem ecoursesItem = itemService.getItemById(itemId);
//设置课程数量
ecoursesItem.setNum(num);
//取一张图片
String image = ecoursesItem.getImage();
if (StringUtils.isNotBlank(image)) {
ecoursesItem.setImage(image.split(",")[0]);
}
//把课程添加到课程列表
cartList.add(ecoursesItem);
}
//写入cookie
CookieUtils.setCookie(request, response, "cart", JsonUtils.objectToJson(cartList), COOKIE_CART_EXPIRE, true);
//返回添加成功页面
return "cartSuccess";
}
//从cookie中取购物车列表的处理
private List<EcoursesItem> getCartListFromCookie(HttpServletRequest request) {
String json = CookieUtils.getCookieValue(request, "cart", true);
//判断json是否为空
if (StringUtils.isBlank(json)) {
return new ArrayList<>();
}
//把json转换成课程列表
List<EcoursesItem> list = JsonUtils.jsonToList(json, EcoursesItem.class);
return list;
}
//展示购物车列表
@RequestMapping("/cart/cart")
public String showCatList(HttpServletRequest request, HttpServletResponse response) {
//从cookie中取购物车列表
List<EcoursesItem> cartList = getCartListFromCookie(request);
//判断用户是否为登录状态
EcoursesUser user = (EcoursesUser) request.getAttribute("user");
//如果是登录状态
if (user != null) {
//从cookie中取购物车列表
//如果不为空,把cookie中的购物车课程和服务端的购物车课程合并。
cartService.mergeCart(user.getId(), cartList);
//把cookie中的购物车删除
CookieUtils.deleteCookie(request, response, "cart");
//从服务端取购物车列表
cartList = cartService.getCartList(user.getId());
}
//把列表传递给页面
request.setAttribute("cartList", cartList);
//返回逻辑视图
return "cart";
}
//更新购物车课程数量
@RequestMapping("/cart/update/num/{itemId}/{num}")
@ResponseBody
public ECoursesResult updateCartNum(@PathVariable Long itemId, @PathVariable Integer num
, HttpServletRequest request ,HttpServletResponse response) {
//判断用户是否为登录状态
EcoursesUser user = (EcoursesUser) request.getAttribute("user");
if (user != null) {
cartService.updateCartNum(user.getId(), itemId, num);
return ECoursesResult.ok();
}
//从cookie中取购物车列表
List<EcoursesItem> cartList = getCartListFromCookie(request);
//遍历课程列表找到对应的课程
for (EcoursesItem ecoursesItem : cartList) {
if (ecoursesItem.getId().longValue() == itemId) {
//更新数量
ecoursesItem.setNum(num);
break;
}
}
//把购物车列表写回cookie
CookieUtils.setCookie(request, response, "cart", JsonUtils.objectToJson(cartList), COOKIE_CART_EXPIRE, true);
//返回成功
return ECoursesResult.ok();
}
//删除购物车课程
@RequestMapping("/cart/delete/{itemId}")
public String deleteCartItem(@PathVariable Long itemId, HttpServletRequest request,
HttpServletResponse response) {
//判断用户是否为登录状态
EcoursesUser user = (EcoursesUser) request.getAttribute("user");
if (user != null) {
cartService.deleteCartItem(user.getId(), itemId);
return "redirect:/cart/cart.html";
}
//从cookie中取购物车列表
List<EcoursesItem> cartList = getCartListFromCookie(request);
//遍历列表,找到要删除的课程
for (EcoursesItem ecoursesItem : cartList) {
if (ecoursesItem.getId().longValue() == itemId) {
//删除课程
cartList.remove(ecoursesItem);
//跳出循环
break;
}
}
//把购物车列表写入cookie
CookieUtils.setCookie(request, response, "cart", JsonUtils.objectToJson(cartList), COOKIE_CART_EXPIRE, true);
//返回逻辑视图
return "redirect:/cart/cart.html";
}
}
展示购物车课程列表
请求的 url: /cart/cart
参数:无
返回值:逻辑视图
业务逻辑:
1、从 Cookie 中取课程列表;
2、把课程列表传递给页面。
Controller
//展示购物车列表
@RequestMapping("/cart/cart")
public String showCatList(HttpServletRequest request, HttpServletResponse response) {
//从cookie中取购物车列表
List<EcoursesItem> cartList = getCartListFromCookie(request);
//判断用户是否为登录状态
EcoursesUser user = (EcoursesUser) request.getAttribute("user");
//如果是登录状态
if (user != null) {
//从cookie中取购物车列表
//如果不为空,把cookie中的购物车课程和服务端的购物车课程合并。
cartService.mergeCart(user.getId(), cartList);
//把cookie中的购物车删除
CookieUtils.deleteCookie(request, response, "cart");
//从服务端取购物车列表
cartList = cartService.getCartList(user.getId());
}
//把列表传递给页面
request.setAttribute("cartList", cartList);
//返回逻辑视图
return "cart";
}
修改购物车课程数量
功能分析
1、在页面中可以修改课程数量;
2、重新计算小计和总计;
3、修改需要写入 Cookie ;
4、每次修改都需要向服务端发送一个 AJAX 请求,在服务端修改 Cookie 中的课程数量。
请求的 url:/cart/update/num/{itemId}/{num}
参数:long itemId、int num
业务逻辑:
1、接收两个参数;
2、从 Cookie 中取课程列表;
3、遍历课程列表找到对应课程;
4、更新课程数量;
5、把课程列表写入 Cookie;
6、响应ecoursesResult。JSON 数据;
返回值:
ECoursesResult。JSON 数据
Controller
//更新购物车课程数量
@RequestMapping("/cart/update/num/{itemId}/{num}")
@ResponseBody
public ECoursesResult updateCartNum(@PathVariable Long itemId, @PathVariable Integer num
, HttpServletRequest request ,HttpServletResponse response) {
//判断用户是否为登录状态
EcoursesUser user = (EcoursesUser) request.getAttribute("user");
if (user != null) {
cartService.updateCartNum(user.getId(), itemId, num);
return ECoursesResult.ok();
}
//从cookie中取购物车列表
List<EcoursesItem> cartList = getCartListFromCookie(request);
//遍历课程列表找到对应的课程
for (EcoursesItem ecoursesItem : cartList) {
if (ecoursesItem.getId().longValue() == itemId) {
//更新数量
ecoursesItem.setNum(num);
break;
}
}
//把购物车列表写回cookie
CookieUtils.setCookie(request, response, "cart", JsonUtils.objectToJson(cartList), COOKIE_CART_EXPIRE, true);
//返回成功
return ECoursesResult.ok();
}
解决请求 *.html 后缀无法返回 JSON 数据的问题
在 SpringMVC 中请求 *.html 不可以返回 JSON 数据。
修改 web.xml,添加 url 拦截格式。
<servlet-mapping>
<servlet-name>ecourses-cart-web</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
删除购物车课程
功能分析
请求的 url:/cart/delete/{itemId}
参数:课程 id
返回值:展示购物车列表页面。url 需要做 redirect 跳转。
业务逻辑:
1、从 url 中取课程 id;
2、从 Cookie 中取购物车课程列表;
3、遍历列表找到对应的课程;
4、删除课程;
5、把课程列表写入 Cookie;
6、返回逻辑视图:在逻辑视图中做 redirect 跳转。
Controller
//删除购物车课程
@RequestMapping("/cart/delete/{itemId}")
public String deleteCartItem(@PathVariable Long itemId, HttpServletRequest request,
HttpServletResponse response) {
//判断用户是否为登录状态
EcoursesUser user = (EcoursesUser) request.getAttribute("user");
if (user != null) {
cartService.deleteCartItem(user.getId(), itemId);
return "redirect:/cart/cart.html";
}
//从cookie中取购物车列表
List<EcoursesItem> cartList = getCartListFromCookie(request);
//遍历列表,找到要删除的课程
for (EcoursesItem ecoursesItem : cartList) {
if (ecoursesItem.getId().longValue() == itemId) {
//删除课程
cartList.remove(ecoursesItem);
//跳出循环
break;
}
}
//把购物车列表写入cookie
CookieUtils.setCookie(request, response, "cart", JsonUtils.objectToJson(cartList), COOKIE_CART_EXPIRE, true);
//返回逻辑视图
return "redirect:/cart/cart.html";
}
小结
使用 Cookie 实现购物车:
优点:
1、实现简单;
2、不需要占用服务端存储空间。
缺点:
1、存储容量有限;
2、更换设备购车信息不能同步。
实现购车课程数据同步:
1、要求用户登录;
2、把购物车课程列表保存到数据库中,推荐使用 Redis;
3、Key:用户 id,value:购车课程列表。推荐使用 hash,hash 的 field:课程 id,value:课程信息;
4、在用户未登录情况下写 Cookie 。当用户登录后,访问购物车列表时,
a) 把 Cookie 中的数据同步到 Redis;
b) 把 Cookie 中的数据删除;
c) 展示购物车列表时以 Redis 为准;
d) 如果 Redis 中有数据 Cookie 中也有数据,需要做数据合并。相同课程数量相加,不同课程添加一个新课程;
5、如果用户登录状态,展示购物车列表以 Redis 为准。如果未登录,以 Cookie 为准。