1. 学习计划
第十二天:
1、购物车实现
2、订单确认页面展示
2. 购物车的实现
2.1. 功能分析
1、购物车是一个独立的表现层工程。
2、添加购物车不要求登录。可以指定购买商品的数量。
3、展示购物车列表页面
4、修改购物车商品数量
5、删除购物车商品
2.2. 工程搭建
e3-cart-web打包方式war
可以参考e3-portal-web
2.2.1. Pom文件
<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.e3mall</groupId> <artifactId>e3-parent</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <groupId>cn.e3mall</groupId> <artifactId>e3-cart-web</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>cn.e3mall</groupId> <artifactId>e3-manager-interface</artifactId> <version>0.0.1-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> <port>8089</port> <path>/</path> </configuration> </plugin> </plugins> </build> </project>
3. 未登录状态下使用购物车
3.1. 添加购物车
3.1.1. 功能分析
在不登陆的情况下也可以添加购物车。把购物车信息写入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:TT_CART
2)Value:购物车列表转换成json数据。需要对数据进行编码。
3)Cookie的有效期:保存7天。
商品列表:
List<TbItem>,每个商品数据使用TbItem保存。当根据商品id查询商品信息后,取第一张图片保存到image属性中即可。
读写cookie可以使用CookieUtils工具类实现。
3.1.2. Controller
@Controller public class CartController { @Value("${TT_CART}") private String TT_CART; @Value("${CART_EXPIRE}") private Integer CART_EXPIRE; @Autowired private ItemService itemService; @RequestMapping("/cart/add/{itemId}") public String addCartItem(@PathVariable Long itemId, Integer num, HttpServletRequest request, HttpServletResponse response) { // 1、从cookie中查询商品列表。 List<TbItem> cartList = getCartList(request); // 2、判断商品在商品列表中是否存在。 boolean hasItem = false; for (TbItem tbItem : cartList) { //对象比较的是地址,应该是值的比较 if (tbItem.getId() == itemId.longValue()) { // 3、如果存在,商品数量相加。 tbItem.setNum(tbItem.getNum() + num); hasItem = true; break; } } if (!hasItem) { // 4、不存在,根据商品id查询商品信息。 TbItem tbItem = itemService.getItemById(itemId); //取一张图片 String image = tbItem.getImage(); if (StringUtils.isNoneBlank(image)) { String[] images = image.split(","); tbItem.setImage(images[0]); } //设置购买商品数量 tbItem.setNum(num); // 5、把商品添加到购车列表。 cartList.add(tbItem); } // 6、把购车商品列表写入cookie。 CookieUtils.setCookie(request, response, TT_CART, JsonUtils.objectToJson(cartList), CART_EXPIRE, true); return "cartSuccess"; } /** * 从cookie中取购物车列表 * <p>Title: getCartList</p> * <p>Description: </p> * @param request * @return */ private List<TbItem> getCartList(HttpServletRequest request) { //取购物车列表 String json = CookieUtils.getCookieValue(request, TT_CART, true); //判断json是否为null if (StringUtils.isNotBlank(json)) { //把json转换成商品列表返回 List<TbItem> list = JsonUtils.jsonToList(json, TbItem.class); return list; } return new ArrayList<>(); } }
3.2. 展示购物车商品列表
请求的url:/cart/cart
参数:无
返回值:逻辑视图
业务逻辑:
1、从cookie中取商品列表。
2、把商品列表传递给页面。
3.2.1. Controller
@RequestMapping("/cart/cart") public String showCartList(HttpServletRequest request, Model model) { //取购物车商品列表 List<TbItem> cartList = getCartList(request); //传递给页面 model.addAttribute("cartList", cartList); return "cart"; }
3.3. 修改购物车商品数量
3.3.1. 功能分析
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、响应e3Result。Json数据。
返回值:
e3Result。Json数据
3.3.2. Controller
@RequestMapping("/cart/update/num/{itemId}/{num}") @ResponseBody public e3Result updateNum(@PathVariable Long itemId, @PathVariable Integer num, HttpServletRequest request, HttpServletResponse response) { // 1、接收两个参数 // 2、从cookie中取商品列表 List<TbItem> cartList = getCartList(request); // 3、遍历商品列表找到对应商品 for (TbItem tbItem : cartList) { if (tbItem.getId() == itemId.longValue()) { // 4、更新商品数量 tbItem.setNum(num); } } // 5、把商品列表写入cookie。 CookieUtils.setCookie(request, response, TT_CART, JsonUtils.objectToJson(cartList), CART_EXPIRE, true); // 6、响应e3Result。Json数据。 return e3Result.ok(); }
3.3.3. 解决请求*.html后缀无法返回json数据的问题
在springmvc中请求*.html不可以返回json数据。
修改web.xml,添加url拦截格式。
3.4. 删除购物车商品
3.4.1. 功能分析
请求的url:/cart/delete/{itemId}
参数:商品id
返回值:展示购物车列表页面。Url需要做redirect跳转。
业务逻辑:
1、从url中取商品id
2、从cookie中取购物车商品列表
3、遍历列表找到对应的商品
4、删除商品。
5、把商品列表写入cookie。
6、返回逻辑视图:在逻辑视图中做redirect跳转。
3.4.2. Controller
@RequestMapping("/cart/delete/{itemId}") public String deleteCartItem(@PathVariable Long itemId, HttpServletRequest request, HttpServletResponse response) { // 1、从url中取商品id // 2、从cookie中取购物车商品列表 List<TbItem> cartList = getCartList(request); // 3、遍历列表找到对应的商品 for (TbItem tbItem : cartList) { if (tbItem.getId() == itemId.longValue()) { // 4、删除商品。 cartList.remove(tbItem); break; } } // 5、把商品列表写入cookie。 CookieUtils.setCookie(request, response, TT_CART, JsonUtils.objectToJson(cartList), CART_EXPIRE, true); // 6、返回逻辑视图:在逻辑视图中做redirect跳转。 return "redirect:/cart/cart.html"; }
4. 登录状态下的购物车处理
4.1. 功能分析
1、购物车数据保存的位置:
未登录状态下,把购物车数据保存到cookie中。
登录状态下,需要把购物车数据保存到服务端。需要永久保存,可以保存到数据库中。可以把购物车数据保存到redis中。
2、redis使用的数据类型
a) 使用hash数据类型
b) Hash的key应该是用户id。Hash中的field是商品id,value可以把商品信息转换成json
3、添加购物车
登录状态下直接包商品数据保存到redis中。
未登录状态保存到cookie中。
4、如何判断是否登录?
a) 从cookie中取token
b) 取不到未登录
c) 取到token,到redis中查询token是否过期。
d) 如果过期,未登录状态
e) 没过期登录状态。
4.2. 判断用户是否登录
4.2.1. 功能分析
应该使用拦截器实现。
1、实现一个HandlerInterceptor接口。
2、在执行handler方法之前做业务处理
3、从cookie中取token。使用CookieUtils工具类实现。
4、没有取到token,用户未登录。放行
5、取到token,调用sso系统的服务,根据token查询用户信息。
6、没有返回用户信息。登录已经过期,未登录,放行。
7、返回用户信息。用户是登录状态。可以把用户对象保存到request中,在Controller中可以通过判断request中是否包含用户对象,确定是否为登录状态。
4.2.2. LoginInterceptor
/** * 判断用户是否登录的拦截器 * <p>Title: LoginInterceptor</p> * <p>Description: </p> * <p>Company: www.itcast.cn</p> * @version 1.0 */ public class LoginInterceptor implements HandlerInterceptor { @Value("${COOKIE_TOKEN_KEY}") private String COOKIE_TOKEN_KEY; @Autowired private UserService userService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 执行handler方法之前执行此方法 // 1、实现一个HandlerInterceptor接口。 // 2、在执行handler方法之前做业务处理 // 3、从cookie中取token。使用CookieUtils工具类实现。 String token = CookieUtils.getCookieValue(request, COOKIE_TOKEN_KEY); // 4、没有取到token,用户未登录。放行 if (StringUtils.isBlank(token)) { return true; } // 5、取到token,调用sso系统的服务,根据token查询用户信息。 E3Result e3Result = userService.getUserByToken(token); // 6、没有返回用户信息。登录已经过期,未登录,放行。 if (e3Result.getStatus() != 200) { return true; } // 7、返回用户信息。用户是登录状态。可以把用户对象保存到request中,在Controller中可以通过判断request中是否包含用户对象,确定是否为登录状态。 TbUser user = (TbUser) e3Result.getData(); request.setAttribute("user", user); //返回true放行 //返回false拦截 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // 执行handler方法之后,并且是返回ModelAndView对象之前 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 返回ModelAndView之后。可以捕获异常。 } }
4.2.3. Springmvc.xml配置拦截器
<!-- 拦截器配置 --> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="cn.e3mall.cart.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>
4.3. 添加购物车
4.3.1. 功能分析
登录状态下添加购物车,直接把数据保存到redis中。需要调用购物车服务,使用redis的hash来保存数据。
Key:用户id
Field:商品id
Value:商品对象转换成json
参数:
1、用户id
2、商品id
3、商品数量
业务逻辑:
1、根据商品id查询商品信息
2、把商品信息保存到redis
a) 判断购物车中是否有此商品
b) 如果有,数量相加
c) 如果没有,根据商品id查询商品信息。
d) 把商品信息添加到购物车
3、返回值。E3Result
4.3.2. dao层
根据商品id查询商品信息,单表查询。可以使用逆向工程。
4.3.3. Service层
@Service public class CartServiceImpl implements CartService { @Value("${CART_REDIS_KEY}") private String CART_REDIS_KEY; @Autowired private TbItemMapper itemMapper; @Autowired private JedisClient jedisClient; @Override public E3Result addCart(long userId, long itemId, int num) { // a)判断购物车中是否有此商品 Boolean flag = jedisClient.hexists(CART_REDIS_KEY + ":" + userId, itemId + ""); // b)如果有,数量相加 if (flag) { //从hash中取商品数据 String json = jedisClient.hget(CART_REDIS_KEY + ":" + userId, itemId + ""); //转换成java对象 TbItem tbItem = JsonUtils.jsonToPojo(json, TbItem.class); //数量相加 tbItem.setNum(tbItem.getNum() + num); //写入hash jedisClient.hset(CART_REDIS_KEY + ":" + userId, itemId + "", JsonUtils.objectToJson(tbItem)); //返回添加成功 return E3Result.ok(); } // c)如果没有,根据商品id查询商品信息。 TbItem tbItem = itemMapper.selectByPrimaryKey(itemId); //设置商品数量 tbItem.setNum(num); String image = tbItem.getImage(); //取一张图片 if (StringUtils.isNotBlank(image)) { tbItem.setImage(image.split(",")[0]); } // d)把商品信息添加到购物车 jedisClient.hset(CART_REDIS_KEY + ":" + userId, itemId + "", JsonUtils.objectToJson(tbItem)); return E3Result.ok(); } }
发布服务:
4.3.4. Controller
@RequestMapping("/cart/add/{itemId}") public String addCart(@PathVariable Long itemId, Integer num, HttpServletRequest request, HttpServletResponse response) { //判断用户是否为登录状态 Object object = request.getAttribute("user"); if (object != null) { TbUser user = (TbUser) object; //取用户id Long userId = user.getId(); //添加到服务端 E3Result e3Result = cartService.addCart(userId, itemId, num); return "cartSuccess"; } //如果登录直接把购物车信息添加到服务端 //如果未登录保存到cookie中 // 1、从cookie中取购物车列表。 List<TbItem> cartList = getItemListFromCookie(request); // 2、判断商品列表是否存在此商品。 boolean falg = false; for (TbItem tbItem : cartList) { if (tbItem.getId() == itemId.longValue()) { //数量相加 // 4、如果存在,数量相加。 tbItem.setNum(tbItem.getNum() + num); falg = true; break; } } // 3、如果不存在添加到列表 if (!falg) { //根据商品id取商品信息 TbItem tbItem = itemService.getItemById(itemId); //设置数量 tbItem.setNum(num); String image = tbItem.getImage(); //取一张图片 if (StringUtils.isNotBlank(image)) { tbItem.setImage(image.split(",")[0]); } //添加到列表 cartList.add(tbItem); } // 5、把购车列表写入cookie CookieUtils.setCookie(request, response, COOKIE_CART_KEY, JsonUtils.objectToJson(cartList), COOKIE_CART_EXPIRE, true); // 6、返回逻辑视图,提示添加成功 return "cartSuccess"; }
4.4. 登录状态下访问购物车列表
4.4.1. 功能分析
1、未登录状态下购物车列表是从cookie中取。
2、登录状态下购物车应该是从服务端取。
3、如果cookie中有购物车数据,应该吧cookie中的购物车和服务端合并,合并后删除cookie中的购物车数据。
4、合并购物车时,如果商品存在,数量相加,如果不存在,添加一个新的商品。
5、从服务端取购物车列表展示
4.4.2. Dao层
不需要访问数据库,只需要访问redis。
4.4.3. Service层
1、合并购物车
参数:用户id
List<TbItem>
返回值:E3Result
业务逻辑:
1)遍历商品列表
2)如果服务端有相同商品,数量相加
3)如果没有相同商品,添加一个新的商品
2、取购物车列表
参数:用户id
返回值:List<TbItem>
业务逻辑:
1)从hash中取所有商品数据
2)返回
/** * 合并购物车 * <p>Title: mergeCart</p> * <p>Description: </p> * @param userId * @param itemList * @return * @see cn.e3mall.cart.service.CartService#mergeCart(long, java.util.List) */ @Override public E3Result mergeCart(long userId, List<TbItem> itemList) { //遍历商品列表 for (TbItem tbItem : itemList) { addCart(userId, tbItem.getId(), tbItem.getNum()); } return E3Result.ok(); } /** * 取购物车列表 * <p>Title: getCartList</p> * <p>Description: </p> * @param userId * @return * @see cn.e3mall.cart.service.CartService#getCartList(long) */ @Override public List<TbItem> getCartList(long userId) { //从redis中根据用户id查询商品列表 List<String> strList = jedisClient.hvals(CART_REDIS_KEY + ":" + userId); List<TbItem> resultList = new ArrayList<>(); //把json列表转换成TbItem列表 for (String string : strList) { TbItem tbItem = JsonUtils.jsonToPojo(string, TbItem.class); //添加到列表 resultList.add(tbItem); } return resultList; }
4.4.4. 表现层
1、判断用户是否登录。
2、如果已经登录,判断cookie中是否有购物车信息
3、如果有合并购物车,并删除cookie中的购物车。
4、如果是登录状态,应从服务端取购物车列表。
5、如果是未登录状态,从cookie中取购物车列表
@RequestMapping("/cart/cart") public String showCartList(HttpServletRequest request, HttpServletResponse response) { //从cookie中取购物车列表 List<TbItem> cartList = getItemListFromCookie(request); //判断用户是否登录 Object object = request.getAttribute("user"); if (object != null) { TbUser user = (TbUser) object; //用户已经登录 System.out.println("用户已经登录,用户名为:" + user.getUsername()); //判断给我吃列表是否为空 if (!cartList.isEmpty()) { //合并购物车 cartService.mergeCart(user.getId(), cartList); //删除cookie中的购物车 CookieUtils.setCookie(request, response, COOKIE_CART_KEY, ""); } //从服务端取购物车列表 List<TbItem> list = cartService.getCartList(user.getId()); request.setAttribute("cartList", list); return "cart"; } else { System.out.println("用户未登录"); } //传递给页面 request.setAttribute("cartList", cartList); return "cart"; }
4.5. 修改购物车数量
只需要更新hash中商品的数量即可。
不需要对数据库进行操作,只需要对redis操作即可。
4.5.1. Server
参数:
1、用户id
2、商品id
3、数量
返回值:
E3Result
业务逻辑:
1、根据商品id从hash中取商品信息。
2、把json转换成java对象
3、更新商品数量
4、把商品数据写回hash
@Override public E3Result updateCartItemNum(long userId, long itemId, int num) { //从hash中取商品信息 String json = jedisClient.hget(CART_REDIS_KEY + ":" + userId, itemId + ""); //转换成java对象 TbItem tbItem = JsonUtils.jsonToPojo(json, TbItem.class); //更新数量 tbItem.setNum(num); //写入hash jedisClient.hset(CART_REDIS_KEY + ":" + userId, itemId + "", JsonUtils.objectToJson(tbItem)); return E3Result.ok(); }
4.5.2. Controller
1、判断是否为登录状态
2、如果是登录状态,更新服务端商品数量
3、如果未登录,更新cookie中是商品数量
/** * 更新商品数量 * <p>Title: updateCartItemNum</p> * <p>Description: </p> * @param itemId * @param num * @return */ @RequestMapping("/cart/update/num/{itemId}/{num}") @ResponseBody public E3Result updateCartItemNum(@PathVariable Long itemId, @PathVariable Integer num, HttpServletRequest request, HttpServletResponse response) { //判断是否为登录状态 Object object = request.getAttribute("user"); if (object != null) { TbUser user = (TbUser) object; //更新服务端的购物车 cartService.updateCartItemNum(user.getId(), itemId, num); return E3Result.ok(); } // 1、从cookie中取购物车列表 List<TbItem> cartList = getItemListFromCookie(request); // 2、遍历列表找到对应的商品 for (TbItem tbItem : cartList) { if (tbItem.getId() == itemId.longValue()) { // 3、更新商品数量 tbItem.setNum(num); break; } } // 4、把购物车列表写入cookie CookieUtils.setCookie(request, response, COOKIE_CART_KEY, JsonUtils.objectToJson(cartList), COOKIE_CART_EXPIRE, true); //返回OK return E3Result.ok(); }
4.6.1. 业务逻辑4.6. 删除购物车商品
1、判断是否为登录状态
2、如果是登录状态,直接删除hash中的商品。
3、如果不是登录状态,对cookie中的购物车进行操作
4.6.2. Service
参数:用户id
商品id
返回值:E3Result
业务逻辑:
根据商品id删除hash中对应的商品数据。
@Override public E3Result deleteCartItem(long userId, long itemId) { // 根据商品id删除hash中对应的商品数据。 jedisClient.hdel(CART_REDIS_KEY + ":" + userId, itemId + ""); return E3Result.ok(); }
4.6.3. Controller
1、判断用户登录状态
2、如果登录删除服务端
3、如果未登录删除cookie中的购物车商品
/** * 删除购物车商品 * <p>Title: deleteCartItem</p> * <p>Description: </p> * @param itemId * @return */ @RequestMapping("/cart/delete/{itemId}") public String deleteCartItem(@PathVariable Long itemId, HttpServletRequest request, HttpServletResponse response) { //判断用户登录状态 Object object = request.getAttribute("user"); if (object != null) { TbUser user = (TbUser) object; //删除服务端的购物车商品 cartService.deleteCartItem(user.getId(), itemId); return "redirect:/cart/cart.html"; } // 1、从url 中取商品id // 2、从cookie 中取购物车列表 List<TbItem> cartList = getItemListFromCookie(request); // 3、遍历列表找到商品 for (TbItem tbItem : cartList) { if (tbItem.getId() == itemId.longValue()) { // 4、删除商品 cartList.remove(tbItem); //退出循环 break; } } // 5、把购物车列表写入cookie CookieUtils.setCookie(request, response, COOKIE_CART_KEY, JsonUtils.objectToJson(cartList), COOKIE_CART_EXPIRE, true); // 6、返回逻辑视图。做redirect跳转到购物车列表页面。 return "redirect:/cart/cart.html"; }