Design and Implementation of an Online Course Purchase System(33)

Complete the development of shopping cart system

Posted by Chen Xingxu on June 3, 2020

易课寄在线购课系统开发笔记(三十三)–完成购物车系统的开发

购物车的实现

功能分析

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 中,更换设备购物车信息不能同步。

改造课程详情页面

12-01

12-02

请求的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 中的课程数量。

12-03

请求的 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 为准。