商城业务---购物车

前言

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

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<String, List>

  • 首先不同用户应该有独立的购物车,因此购物车应该以用户的作为 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数据

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43304253/article/details/130175863