Cookie及Redis在商城购物车系统中的使用

xiaoxiao2021-02-28  107

关于商城中购物车功能,天猫是必须登录才能将商品加入到购物车,京东则可以在不登录状态下也可以加入购物车,这里就模仿京东购物车功能。 购物车工程搭建: e3-cart(pom) |–e3-cart-interface(jar) |-e3-cart-service(war) e3-cart-web(war) 参照”redis实现单点登录系统”搭建

需求: 商品详情页面如下:

选择好商品,确定数量后,点击“加入购物车”按钮,发送请求。 请求地址:8090/cart/add/{itemId}.html,参数:商品id跟商品数量 返回逻辑视图:”cartSuccess”;

一、未登录状态下购物车功能实现 1、未登录状态下添加商品到购物车 在不登陆的情况下也可以添加购物车。把购物车信息写入cookie。 优点: 1、不占用服务端存储空间 2、用户体验好。 3、代码实现简单。 缺点: 1、cookie中保存的容量有限。最大4k 2、把购物车信息保存在cookie中,更换设备购物车信息不能同步。

分析:页面传来的是商品id跟商品数量 (1) 从cookie中获取商品列表信息(单独提出来写成个通用的方法) (2) 遍历购物车列表,判断需要添加的商品在购物车列表是否存在 (3) 商品存在的话,那么取出该商品原来的数量+添加的数量作为该商品现在的数量 (4) 如果商品不存在,那么调用服务,根据传来的商品id查询商品数量,设置商品的数量为页面传来的数量,取商品的第一张图片(购物车列表只展示一张图片)。 (5) 把修改后的购物车列表重新存入到cookie中 (6) 返回逻辑视图”cartSuccess”

实现: 在表现层工程e3-cart-web中引用商品服务工程提供的服务

<!-- 加载配置文件 --> <context:property-placeholder location="classpath:conf/resource.properties" /> <context:component-scan base-package="cn.e3mall.cart.controller" /> <mvc:annotation-driven /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <!-- 引用dubbo服务 --> <dubbo:application name="e3-cart-web"/> <dubbo:registry protocol="zookeeper" address="192.168.25.128:2181"/> <dubbo:reference interface="cn.e3mall.service.ItemService" id="itemService" />

ItemService提供了根据id获取商品信息的方法:getItemById(Long itemId)

@Controller public class CartController { @Autowired private ItemService itemService; @Value("${COOKIE_MAX_TIME}") private Integer COOKIE_MAX_TIME; /* * 1.未登录状态下添加购物车商品 */ @RequestMapping("/cart/add/{itemId}") public String addCartNum(@PathVariable Long itemId, Integer num, HttpServletRequest request,HttpServletResponse response){ //获取购物车列表 List<TbItem> cartList = getCartListFromCookie(request); //判断购物车中是否有该商品 boolean flag = false; for (TbItem tbItem : cartList) { if(tbItem.getId()==itemId.longValue()){ flag = true; //存在该商品,数量相加 tbItem.setNum(tbItem.getNum()+num); //跳出循环 break; } } if(!flag){ //没有的话,调用服务查询该商品 TbItem tbItem = itemService.getItemById(itemId); //设置数量 tbItem.setNum(num); //取一张图片 String image = tbItem.getImage(); if(StringUtils.isNotBlank(image)){ tbItem.setImage(image.split(",")[0]); } //商品添加到购物车列表 cartList.add(tbItem); } //购物车信息写入cookie CookieUtils.setCookie(request, response, "cart1", JsonUtils.objectToJson(cartList), COOKIE_MAX_TIME, true); //返回逻辑视图 return "cartSuccess"; } /* *从cookie中获取购物车列表 */ public List<TbItem> getCartListFromCookie(HttpServletRequest request){ String string = CookieUtils.getCookieValue(request, "cart1", true); //判断是否为空 if(StringUtils.isBlank(string)){ //空的话也不能返回null return new ArrayList<>(); } //转为商品列表 List<TbItem> list = JsonUtils.jsonToList(string, TbItem.class); return list; } }

其中商品实体类TbItem里面的属性image存放的是多张照片。

COOKIE_MAX_TIME便是cookie中cart1最大存在时间,true表示采用utf-8编码

测试: 其实并不能看出来效果。展示购物车列表功能实现后就能看到了。

2、展示购物车列表 单击“去购物车结算按钮”向服务端发送请求,服务端应该返回逻辑视图”cart” 请求地址:8090/cart/cart.html 返回逻辑视图:”cart”也就是购物车列表页面

实现:同样是在CartController中添加

/* * 2.未登录状态下展示商品列表 */ @RequestMapping("/cart/cart") public String showCartList(HttpServletRequest request){ //获取购物车列表 List<TbItem> cartList = getCartListFromCookie(request); //绑定参数 request.setAttribute("cartList", cartList); //返回逻辑视图 return "cart"; }

注:cartList是根据cart.jsp的需要绑定的。该页面拿到cartList后会进行遍历,取各个商品的信息。 测试:

3、为登录状态下购物车列表页面修改商品数量 购物车列表页面单击”+”,”-”会向服务端发送ajax请求。 页面需要根据调整的数量重新显示商品总计(已经实现了也就是输入框的值*价格)和小计(用js,待实现) 服务端要求修改cookie中对应商品的数量。 请求地址:/cart/update/num/{itemId}/{num} 参数:商品id,商品数量 返回结果:E3Result

/* * 未登录状态下更新商品数量 */ @RequestMapping("/cart/update/num/{itemId}/{num}") @ResponseBody public E3Result updateCartNum(@PathVariable Long itemId,@PathVariable Integer num, HttpServletRequest request,HttpServletResponse response){ //获取购物车列表 List<TbItem> cartList = getCartListFromCookie(request); //取所选择的需要更新的商品 for (TbItem tbItem : cartList) { if(tbItem.getId()==itemId.longValue()){ //更新商品数量 tbItem.setNum(num); //跳出循环 break; } } //购物车信息写入cookie CookieUtils.setCookie(request, response, "cart1", JsonUtils.objectToJson(cartList), COOKIE_MAX_TIME, true); return E3Result.ok(); }

测试: 注:商品总金额的js没有去写所以还是只显示单价。 E3Result为自定义响应结构

public class E3Result implements Serializable{ // 定义jackson对象 private static final ObjectMapper MAPPER = new ObjectMapper(); // 响应业务状态 private Integer status; // 响应消息 private String msg; // 响应中的数据 private Object data; public static E3Result build(Integer status, String msg, Object data) { return new E3Result(status, msg, data); } public static E3Result ok(Object data) { return new E3Result(data); } public static E3Result ok() { return new E3Result(null); } public E3Result() { } public static E3Result build(Integer status, String msg) { return new E3Result(status, msg, null); } public E3Result(Integer status, String msg, Object data) { this.status = status; this.msg = msg; this.data = data; } public E3Result(Object data) { this.status = 200; this.msg = "OK"; this.data = data; } get、set方法 }

4、未登录状态下删除购物车商品 请求地址:/cart/delete/{itemId} 请求参数:商品id 响应:重定向到购物车列表。

实现: (1)从cookie中获取购物车列表 (2)遍历,查找到要删除的商品 (3)将该商品从购物车列表移除 (4)更新后的购物车列表重新写入cookie (5)重定向到购物车列表页面

/* * 未登录状态下删除购物车商品 */ @RequestMapping("/cart/delete/{itemId}") public String deleteCartById(@PathVariable Long itemId, HttpServletRequest request,HttpServletResponse response){ //获取商品列表 List<TbItem> cartList = getCartListFromCookie(request); //遍历商品列表,找到该商品 for (TbItem tbItem : cartList) { if(tbItem.getId() == itemId.longValue()){ //删除该商品 cartList.remove(tbItem); break; } } //购物车信息写入cookie CookieUtils.setCookie(request, response, "cart1", JsonUtils.objectToJson(cartList), COOKIE_MAX_TIME, true); //重定向到列表页面 return "redirect:/cart/cart.html"; }

测试: 上面的图,点击删除后

二、登录状态下购物车功能的实现 功能分析: 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) 没过期登录状态。

1、登录拦截器 几乎在购物车所有功能执行 都要判断用户是否登录。利用aop思想,应该编写个拦截器,来判断用户是否登录。登录的话用户信息需要存在request域中 (1) 从cookie中取token (2) 判断token是否存在 (3) 不存在,说明用于未登录,放行 (4) 如果token存在,调用服务,根据token从redis中取用户信息 (5) 取不到用户信息,说明已经过期,放行 (6) 取到了用户信息,说明用户已经登录,用户信息存到request中 (7) 放行 实现: 首先需要在购物车系统表现层工程中(e3-cart-web)调用单点登录系统(sso)的服务,以及拦截器的配置。

<!-- 加载配置文件 --> <context:property-placeholder location="classpath:conf/resource.properties" /> <context:component-scan base-package="cn.e3mall.cart.controller" /> <mvc:annotation-driven /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <!-- 拦截器配置 --> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="cn.e3mall.cart.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors> <!-- 引用dubbo服务 --> <dubbo:application name="e3-cart-web"/> <dubbo:registry protocol="zookeeper" address="192.168.25.128:2181"/> <dubbo:reference interface="cn.e3mall.service.ItemService" id="itemService" /> <dubbo:reference interface="cn.e3mall.sso.service.TokenService" id="tokenService" /> /* * 用户登录处理 */ public class LoginInterceptor implements HandlerInterceptor { @Autowired private TokenService tokenService; public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //前处理,执行handler之前执行此方法 //返回true:放行 false:拦截 //1.从cookie中取token String token = CookieUtils.getCookieValue(request, "token"); //2.如果没有token,未登录状态 if(StringUtils.isBlank(token)){ return true; } //3.如果取到token,需要调用sso系统的服务,根据token取用户信息 E3Result e3Result = tokenService.getUserByToken(token); if (e3Result.getStatus()!=200){ //4.没有取到用户信息,登录已经过期,直接放行 return true; } //5.取到用户信息。登录状态。 TbUser user = (TbUser) e3Result.getData(); //6.把用户信息放到request中,只需要在controller中判断request中是否包含user信息。 request.setAttribute("user", user); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { //handler执行之后,返回modelAndView之前 } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //完成处理,返回modelAndView之后(已经响应了) //可以再次处理异常 } }

拦截器写完之后,对于购物车功能只需要在表现层判断用户是否登录,从而进行不同的处理。

2、登录状态下,商品添加功能实现

(1)、服务层e3-cart-service中: 服务层用到了redis,所以需要将redis和spring整合。

<!-- 连接redis单机版 --> <bean id="jedisClientPool" class="cn.e3mall.common.jedis.JedisClientPool"> <property name="jedisPool" ref="jedisPool"></property> </bean> <bean id="jedisPool" class="redis.clients.jedis.JedisPool"> <!-- 一定要用name,构造方法太多用index容易错 --> <constructor-arg name="host" value="192.168.25.128"/> <constructor-arg name="port" value="6379"/> </bean>

JedisClient只是自己对jedis操作redis的api的封装。服务层当然还得添加其他配置,如组件扫描,引入数据源,事务。

/* * 购物车处理服务 */ @Service public class CartServiceImpl implements CartService{ @Autowired private JedisClient jedisClient; @Value("${REDIS_CART_PRE}")//属性配置文件中,值为cart1 private String REDIS_CART_PRE; @Autowired private TbItemMapper itemMapper; public E3Result addCart(Long userId, Long itemId, int num) { //向redis中添加购物车 //数据类型是hash key:用户id field:商品id value:商品信息 //判断商品是否存在 Boolean hexists = jedisClient.hexists(REDIS_CART_PRE+":"+userId, itemId+""); if(hexists){ //如果存在,数量相加 String json = jedisClient.hget(REDIS_CART_PRE+":"+userId, itemId+""); //把json转换成TbItem TbItem tbItem = JsonUtils.jsonToPojo(json, TbItem.class); tbItem.setNum(tbItem.getNum()+num); //写回redis jedisClient.hset(REDIS_CART_PRE+":"+userId, itemId+"",JsonUtils.objectToJson(tbItem)); return E3Result.ok(); } //如果不存在,根据商品id取商品信息,服务层尽量别相互调用 TbItem item = itemMapper.selectByPrimaryKey(itemId); //设置购物车数量 item.setNum(num); //取一张图片 String image = item.getImage(); if(StringUtils.isNotBlank(image)){ item.setImage(image.split(",")[0]); } //添加到购物车列表 jedisClient.hset(REDIS_CART_PRE+":"+userId, itemId+"",JsonUtils.objectToJson(item)); //返回成功 return E3Result.ok(); } }

发布服务:

<context:component-scan base-package="cn.e3mall.cart.service"/> <!-- 使用dubbo发布服务 --> <!-- 提供方应用信息,用于计算依赖关系 --> <dubbo:application name="e3-cart" /> <dubbo:registry protocol="zookeeper" address="192.168.25.128:2181" /> <!-- 用dubbo协议在20880端口暴露服务 --> <dubbo:protocol name="dubbo" port="20884" /><!-- 一个服务对应一个端口 --> <!-- 声明需要暴露的服务接口 --> <dubbo:service interface="cn.e3mall.cart.service.CartService" ref="cartServiceImpl" timeout="600000"/>

(2)、表现层工程e3-cart-web中 调用e3-car-service刚发布的服务

<!-- 引用dubbo服务 --> <dubbo:application name="e3-cart-web"/> <dubbo:registry protocol="zookeeper" address="192.168.25.128:2181"/> <dubbo:reference interface="cn.e3mall.service.ItemService" id="itemService" /> <dubbo:reference interface="cn.e3mall.sso.service.TokenService" id="tokenService" /> <dubbo:reference interface="cn.e3mall.cart.service.CartService" id="cartService" />

只需要再原来的添加商品功能中做判断处理

@RequestMapping("/cart/add/{itemId}") public String addCart(@PathVariable Long itemId, @RequestParam(defaultValue="1") Integer num, HttpServletRequest request, HttpServletResponse response){ //判断用户是否为登录状态 TbUser user = (TbUser) request.getAttribute("user"); if(user != null){ //保存到服务端 cartService.addCart(user.getId(), itemId, num); //返回逻辑视图 return "cartSuccess"; } //如果是登录状态,把购物车写入redis //如果未登录使用cookie ...未登录状态下代码 }

测试: Tidy用户登录,买了一个thinkpad电脑,单击加入购物车 查看redis,商品信息已经添加

2、登录状态下,商品列表展示 分析: (1)从cookie中取购物车列表 (2)判断用户是否登录 (3)用户已经登录的话,则调用服务层,合并cookie中的列表和redis中的列表。存入到redis中。 (4)同时删除cookie中的购物车列表 (5)根据用户id,调用服务查询redis中所有的商品,返回购物车列表。 (6)未登录状态还是跟前面一样 (7)将列表绑定到参数,返回购物车列表页面。

在服务层e3-cart-service中

/* * 合并购物车 */ public E3Result mergeCart(Long userId, List<TbItem> itemList) { //遍历商品列表 //把列表添加到购物车 //判断购物车中是否有此商品 //如果有,数量相加 //如果没有添加新的商品 for (TbItem tbItem : itemList) { //等同于上面的添加商品到redis中 addCart(userId, tbItem.getId(), tbItem.getNum()); } return E3Result.ok(); } /* * 取购物车列表 */ public List<TbItem> getCartList(long userId) { //根据用户id查询购物车列表 List<String> jsonList = jedisClient.hvals(REDIS_CART_PRE+":"+userId); List<TbItem> itemList = new ArrayList<>(); for (String string : jsonList) { //创建一个TbItem TbItem item = JsonUtils.jsonToPojo(string, TbItem.class); //添加到列表 itemList.add(item); } return itemList; }

表现层工程 e3-cart-web中

/* * 展示购物车列表 */ @RequestMapping("/cart/cart") public String showCartList(HttpServletRequest request,HttpServletResponse response){ //从cookie中取购物车列表 List<TbItem> list = getCartListFromCookie(request); //判断用户是否为登录状态 TbUser user = (TbUser) request.getAttribute("user"); //如果是登录状态 if(user!=null){ //从cookie中取购物车列表 //如果不为空,把cookie中的购物车商品和服务端的购物车商品合并。 cartService.mergeCart(user.getId(), list); //把cookie中的购物车删除 CookieUtils.deleteCookie(request, response, "cart"); //从服务端取购物车列表 list = cartService.getCartList(user.getId()); } //未登录状态 //把列表传递给页面 request.setAttribute("cartList", list); //返回逻辑视图 return "cart"; }

测试: 先不登录状态下添加商品都购物车,再登录添加商品到购物车。 再登录tidy账号(之前买了个电脑放入到了购物车) 发现已经合并成功了,再看cookie中

发现购物车已经为空了。 也可以看下redis中,商品合并了

3、登录状态下修改购物车商品数量 分析 单击”+”,”-”修改商品的数量的时候,要求redis中该商品的数量发生改变 (1) 根据用户id,商品id从redis中取出对应的商品 (2) 设置商品的数量 (3) 该商品更新到redis中 (4) 返回E3Result 实现: 服务层e3-cart-service中

/* * 登录状态下更新购物车数量 */ public E3Result updateCartNum(Long userId, Long itemId, int num) { //从redis中取商品信息 String json = jedisClient.hget(REDIS_CART_PRE+":"+userId, itemId+""); //更新商品数量 TbItem tbItem = JsonUtils.jsonToPojo(json, TbItem.class); tbItem.setNum(num); //写入redis jedisClient.hset(REDIS_CART_PRE+":"+userId, itemId+"",JsonUtils.objectToJson(tbItem)); return E3Result.ok(); }

表现层工程e3-cart-web中

/* * 更新购物车商品数量 */ @RequestMapping("/cart/update/num/{itemId}/{num}") @ResponseBody public E3Result updateCartNum(@PathVariable Long itemId, @PathVariable Integer num, HttpServletRequest request, HttpServletResponse response){ //判断用户是否为登录状态 TbUser user = (TbUser) request.getAttribute("user"); if (user != null){ cartService.updateCartNum(user.getId(), itemId, num); return E3Result.ok(); } //从cookie中取购物车列表 List<TbItem> cartList = getCartListFromCookie(request); //遍历商品列表找到对应的商品 for (TbItem tbItem : cartList) { //包装类型直接==比的是内存地址 if(tbItem.getId() == itemId.longValue()){ //跟新数量 tbItem.setNum(num); break; } } //把购物车列表写回cookie CookieUtils.setCookie(request, response, "cart", JsonUtils.objectToJson(cartList), COOKIE_CART_EXPIRE, true); //返回成功 return E3Result.ok(); }

5、登录状态下,删除购物车商品 分析 单击删除的时候,删除redis中该商品。重定向到列表页面 (1) 直接用jedisClient的del的方法根据用户id跟商品id 商品 (2) 返回成功

服务层e3-cart-service中

/* * 登录状态下删除 */ public E3Result deleteCartItem(long userId, long itemId) { //删除购物车商品 jedisClient.hdel(REDIS_CART_PRE+":"+userId, itemId+""); return E3Result.ok(); }

表现层工程e3-cart-web中 在原先的删除方法中添加即可

/* * 从购物车删除商品 */ @RequestMapping("/cart/delete/{itemId}") public String deleteCartItem(@PathVariable Long itemId,HttpServletRequest request, HttpServletResponse response){ //判断用户是否为登录状态 TbUser user = (TbUser) request.getAttribute("user"); if (user != null){ cartService.deleteCartItem(user.getId(), itemId); return "redirect:/cart/cart.html"; } 未登录状态下删除购物车 ... }

修改删除测试: 初始情况 现在:删除手机,笔记本的数量改为2,操作后页面跟redis中如下

转载请注明原文地址: https://www.6miu.com/read-52547.html

最新回复(0)