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

关于商城中购物车功能,天猫是必须登录才能将商品加入到购物车,京东则可以在不登录状态下也可以加入购物车,这里就模仿京东购物车功能。
购物车工程搭建:
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中如下
这里写图片描述
这里写图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值