商城业务---购物车

本文涉及的产品
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
简介: 这篇文章讨论了商城业务中购物车功能的实现,包括用户登录状态下和未登录状态下购物车的处理方式。文章详细描述了购物车的基本需求、流程分析、判断用户登录状态的拦截器实现、临时购物车和登录购物车的数据操作,以及添加商品到购物车的过程。此外,还提到了可能遇到的问题和解决方案,以及在Redis中存储商品信息的不同数据结构选择。最后,文章展示了未登录和登录状态下购物车的使用效果和Redis中数据的存储情况。

前言

只做一个简单的思路提示,具体的业务逻辑实现,就是写接口,没啥好说的。

1、需求描述

  • 1、用户可以在登录状态下将商品添加到购物车【用户购物车/在线购物车】
  • 2、用户可以在未登录状态下将商品添加到购物车【游客购物车/离线购物车/临时购物车】
  • 3、用户可以使用购物车一起结算下单
  • 4、给购物车添加商品
  • 5、用户可以查询自己的购物车
  • 6、用户可以在购物车中修改购买商品的数量。
  • 7、用户可以在购物车中删除商品。
  • 8、选中不选中商品
  • 9、在购物车中展示商品优惠信息
  • 10、提示购物车商品价格变化

2、流程分析

2.1 新增商品:判断是否登录

是:则添加商品到后台 Redis 中,把 user 的唯一标识符作为 key。
否:则添加商品到后台 redis 中,使用随机生成的 user-key 作为 key。

2.2 查询购物车列表:判断是否登录

  • 否:直接根据 user-key 查询 redis 中数据并展示
  • 是:已登录,则需要先根据 user-key 查询 redis 是否有数据。
    • 有:需要提交到后台添加到 redis,合并数据,而后查询。
    • 否:直接去后台查询 redis,而后返回。

2.3 判断是否登录

这里使用拦截器进行预先处理,如果用户没有登录,就随机分配一个值。

    /***
     * 目标方法执行之前
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        UserInfoTo userInfoTo = new UserInfoTo();

        HttpSession session = request.getSession();
        //获得当前登录用户的信息
        MemberResponseVo memberResponseVo = (MemberResponseVo) session.getAttribute(LOGIN_USER);

        if (memberResponseVo != null) {
            //用户登录了
            userInfoTo.setUserId(memberResponseVo.getId());
        }

        Cookie[] cookies = request.getCookies();
        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
                //user-key
                String name = cookie.getName();
                if (name.equals(TEMP_USER_COOKIE_NAME)) {
                    userInfoTo.setUserKey(cookie.getValue());
                    //标记为已是临时用户
                    userInfoTo.setTempUser(true);
                }
            }
        }

        //如果没有临时用户一定分配一个临时用户
        if (StringUtils.isEmpty(userInfoTo.getUserKey())) {
            String uuid = UUID.randomUUID().toString();
            userInfoTo.setUserKey(uuid);
        }

        //目标方法执行之前
        toThreadLocal.set(userInfoTo);
        return true;
    }

3、临时购物车

    /**
     * 获取到我们要操作的购物车
     * @return
     */
    private BoundHashOperations<String, Object, Object> getCartOps() {
        //先得到当前用户信息
        UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();

        String cartKey = "";
        if (userInfoTo.getUserId() != null) {
            //gulimall:cart:1
            cartKey = CART_PREFIX + userInfoTo.getUserId();
        } else {
            cartKey = CART_PREFIX + userInfoTo.getUserKey();
        }

        //绑定指定的key操作Redis
        BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(cartKey);

        return operations;
    }

4、登录购物车

    /**
     * 获取用户登录或者未登录购物车里所有的数据
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @Override
    public CartVo getCart() throws ExecutionException, InterruptedException {

        CartVo cartVo = new CartVo();
        UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();
        if (userInfoTo.getUserId() != null) {
            //1、登录
            String cartKey = CART_PREFIX + userInfoTo.getUserId();
            //临时购物车的键
            String temptCartKey = CART_PREFIX + userInfoTo.getUserKey();

            //2、如果临时购物车的数据还未进行合并
            List<CartItemVo> tempCartItems = getCartItems(temptCartKey);
            if (tempCartItems != null) {
                //临时购物车有数据需要进行合并操作
                for (CartItemVo item : tempCartItems) {
                    addToCart(item.getSkuId(),item.getCount());
                }
                //清除临时购物车的数据
                clearCartInfo(temptCartKey);
            }

            //3、获取登录后的购物车数据【包含合并过来的临时购物车的数据和登录后购物车的数据】
            List<CartItemVo> cartItems = getCartItems(cartKey);
            cartVo.setItems(cartItems);

        } else {
            //没登录
            String cartKey = CART_PREFIX + userInfoTo.getUserKey();
            //获取临时购物车里面的所有购物项
            List<CartItemVo> cartItems = getCartItems(cartKey);
            cartVo.setItems(cartItems);
        }

        return cartVo;
    }

5、添加商品到购物车

这里直接将数据存储到redis中,这里牵涉到远程服务调用。

    @Override
    public CartItemVo addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {

        //拿到要操作的购物车信息
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();

        //判断Redis是否有该商品的信息
        String productRedisValue = (String) cartOps.get(skuId.toString());
        //如果没有就添加数据
        if (StringUtils.isEmpty(productRedisValue)) {

            //2、添加新的商品到购物车(redis)
            CartItemVo cartItemVo = new CartItemVo();
            //开启第一个异步任务
            CompletableFuture<Void> getSkuInfoFuture = CompletableFuture.runAsync(() -> {
                //1、远程查询当前要添加商品的信息
                R productSkuInfo = productFeignService.getInfo(skuId);
                SkuInfoVo skuInfo = productSkuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {});
                //数据赋值操作
                cartItemVo.setSkuId(skuInfo.getSkuId());
                cartItemVo.setTitle(skuInfo.getSkuTitle());
                cartItemVo.setImage(skuInfo.getSkuDefaultImg());
                cartItemVo.setPrice(skuInfo.getPrice());
                cartItemVo.setCount(num);
            }, executor);

            //开启第二个异步任务
            CompletableFuture<Void> getSkuAttrValuesFuture = CompletableFuture.runAsync(() -> {
                //2、远程查询skuAttrValues组合信息
                    List<String> skuSaleAttrValues = productFeignService.getSkuSaleAttrValues(skuId);
                cartItemVo.setSkuAttrValues(skuSaleAttrValues);
            }, executor);

            //等待所有的异步任务全部完成
            CompletableFuture.allOf(getSkuInfoFuture, getSkuAttrValuesFuture).get();

            String cartItemJson = JSON.toJSONString(cartItemVo);
            cartOps.put(skuId.toString(), cartItemJson);

            return cartItemVo;
        } else {
            //购物车有此商品,修改数量即可
            CartItemVo cartItemVo = JSON.parseObject(productRedisValue, CartItemVo.class);
            cartItemVo.setCount(cartItemVo.getCount() + num);
            //修改redis的数据
            String cartItemJson = JSON.toJSONString(cartItemVo);
            cartOps.put(skuId.toString(),cartItemJson);

            return cartItemVo;
        }
    }

6、提示

6.1 问题1

如果调用远程服务失败,页面也会报错。需要手动编写sql语句。在商品加入购物车前,判断该商品是否已经存在购物车。

6.3 问题2

在使用拦截器的时候,由于版本不同。写法有所不同。

        WebCallbackManager.setUrlBlockHandler(new UrlBlockHandler() {
            @Override
            public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {
                R error = R.error(BizCodeEnume.TO_MANY_REQUEST.getCode(), BizCodeEnume.TO_MANY_REQUEST.getMsg());
                response.setCharacterEncoding("UTF-8");
                response.setContentType("application/json");
                response.getWriter().write(JSON.toJSONString(error));

            }
        });

如果想要继续使用这个方式,需要在pom文件中加入新的依赖

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>

6.4 问题三

关于如何将商品的信息以何种方式存储到redis中

Redis 有 5 种不同数据结构,这里选择哪一种比较合适呢?Map

  • 首先不同用户应该有独立的购物车,因此购物车应该以用户的作为 key 来存储,Value 是用户的所有购物车信息。这样看来基本的k-v结构就可以了。
  • 但是,我们对购物车中的商品进行增、删、改操作,基本都需要根据商品 id 进行判断,为了方便后期处理,我们的购物车也应该是k-v结构,key 是商品 id,value 才是这个商品的购物车信息

综上所述,我们的购物车结构是一个双层 Map:Map<String,Map<String,String>>

  • 第一层 Map,Key 是用户 id
  • 第二层 Map,Key 是购物车中商品 id,值是购物项数据

7、实现的效果

7.1 未登录状态

7.1.1 商品加入购物车

在这里插入图片描述
在这里插入图片描述

7.1.2 查看购物车

在这里插入图片描述

7.1.3 查看redis中存储数据

在这里插入图片描述

7.2 登录状态

7.1 查看购物车

在这里插入图片描述
在这里插入图片描述

7.2 查看reids数据

在这里插入图片描述

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
SQL 前端开发
创建订单【项目 商城】
创建订单【项目 商城】
49 0
|
SQL JSON 前端开发
加入购物车【项目 商城】
加入购物车【项目 商城】
42 0
|
前端开发 容器
从零玩转系列之微信支付实战PC端装修下单页面2
从零玩转系列之微信支付实战PC端装修下单页面
71 0
|
前端开发 小程序 安全
从零玩转系列之微信支付实战PC端装修我的订单页面1
从零玩转系列之微信支付实战PC端装修我的订单页面
79 0
|
存储 JSON 前端开发
从零玩转系列之微信支付实战PC端装修我的订单页面2
从零玩转系列之微信支付实战PC端装修我的订单页面
96 0
|
JavaScript 前端开发 安全
从零玩转系列之微信支付实战PC端装修下单页面1
从零玩转系列之微信支付实战PC端装修下单页面
68 0
|
JavaScript
从零玩转系列之微信支付实战PC端装修下单页面3
从零玩转系列之微信支付实战PC端装修下单页面
34 0
|
存储 NoSQL MongoDB
商城业务:购物车
商城业务:购物车
|
前端开发 JavaScript
商城业务:商品详情
商城业务:商品详情