微服务实战——购物车模块实战

购物车

1. 数据模型分析

1.1. 需求描述

  • 用户可以在登录状态下将商品添加到购物车【用户购物车/在线购物车】
    • 放入数据库
    • mongodb
    • 放入 redis(采用)
      • 登录以后,会将临时购物车的数据全部合并过来,并清空临时购物车;
  • 用户可以在未登录状态下将商品添加到购物车【游客购物车/离线购物车/临时购物车】
    • 放入 localstorage(客户端存储,后台不存)
    • cookie
    • WebSQL
    • 放入 redis(采用)
      • 浏览器即使关闭,下次进入,临时购物车数据都在
  • 用户可以使用购物车一起结算下单
  • 给购物车添加商品
  • 用户可以查询自己的购物车
  • 用户可以在购物车中修改购买商品的数量。
  • 用户可以在购物车中删除商品。
  • 选中不选中商品
  • 在购物车中展示商品优惠信息
    • 提示购物车商品价格变化

1.2. 数据存储

购物车是一个读多写多的场景,因此放入数据库并不合适,但购物车又是需要持久化,因此这里我们选用redis存储购物车数据。

1.3. 数据结构

购物项

因此每一个购物项信息,都是一个对象,基本字段包括:

{
    skuId: 2131241, 
    check: true, 
    title: "Apple iphone.....", 
    defaultImage: "...", 
    price: 4999, 
    count: 1, 
    totalPrice: 4999, 
    skuSaleVO: {...}
}

另外,购物车中不止一条数据,因此最终会是对象的数组。即:

[
    {...},{...},{...}
]

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

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

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

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

一个购物车是由各个购物项组成的,但是我们用 List进行存储并不合适,因为使用 List查找某个购物项时需要挨个遍历每个购物项,会造成大量时间损耗,为保证查找速度,我们使用 hash进行存储。

1.4. 流程

参照京东

user-key 是随机生成的 id,不管有没有登录都会有这个 cookie 信息。

两个功能:新增商品到购物车、查询购物车。

新增商品:判断是否登录

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

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

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

2. VO编写

2.1. 因此每一个购物项信息,都是一个对象,基本字段包括:

添加"com.cwh.gulimall.cart.vo.CartItem"类,代码如下:

package com.cwh.gulimall.cart.vo;
 
import java.math.BigDecimal;
import java.util.List;
 
/**
 * @Description: 购物项
 * @Date: 2024/5/19 19:04
 * @Version 1.0
 */
public class CartItem {
    /**
     * 商品id
     */
    private Long skuId;
 
    /**
     * 是否选中
     */
    private Boolean check = true;
 
    /**
     * 标题
     */
    private String title;
 
    /**
     * 图片
     */
    private String image;
 
    /**
     * 商品套餐属性
     */
    private List<String> skuAttr;
 
    /**
     * 价格
     */
    private BigDecimal price;
 
    /**
     * 数量
     */
    private Integer count;
 
    /**
     * 总价
     */
    private BigDecimal totalPrice;
 
    public Long getSkuId() {
        return skuId;
    }
 
    public void setSkuId(Long skuId) {
        this.skuId = skuId;
    }
 
    public Boolean getCheck() {
        return check;
    }
 
    public void setCheck(Boolean check) {
        this.check = check;
    }
 
    public String getTitle() {
        return title;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
 
    public String getImage() {
        return image;
    }
 
    public void setImage(String image) {
        this.image = image;
    }
 
    public List<String> getSkuAttr() {
        return skuAttr;
    }
 
    public void setSkuAttr(List<String> skuAttr) {
        this.skuAttr = skuAttr;
    }
 
    public BigDecimal getPrice() {
        return price;
    }
 
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
 
    public Integer getCount() {
        return count;
    }
 
    public void setCount(Integer count) {
        this.count = count;
    }
 
    /**
     * 计算当前购物项总价
     *
     * @return
     */
    public BigDecimal getTotalPrice() {
        return this.price.multiply(new BigDecimal("" + this.count));
    }
 
    public void setTotalPrice(BigDecimal totalPrice) {
        this.totalPrice = totalPrice;
    }
}

添加"com.cwh.gulimall.cart.vo.Cart"类,代码如下:

package com.cwh.gulimall.cart.vo;
 
import java.math.BigDecimal;
import java.util.List;
 
/**
 * @Description: 整体购物车  需要计算的属性,必须重写他的get方法,保证每次获取属性都会进行计算
 * @Date: 2024/5/19 19:07
 * @Version 1.0
 */
public class Cart {
    /**
     * 购物车子项信息
     */
    List<CartItem> items;
 
    /**
     * 商品数量
     */
    private Integer countNum;
 
    /**
     * 商品类型数量
     */
    private Integer countType;
 
    /**
     * 商品总价
     */
    private BigDecimal totalAmount;
 
    /**
     * 减免价格
     */
    private BigDecimal reduce = new BigDecimal("0.00");
 
    public List<CartItem> getItems() {
        return items;
    }
 
    public void setItems(List<CartItem> items) {
        this.items = items;
    }
 
    public Integer getCountNum() {
        int count = 0;
        if (items != null && items.size() > 0) {
            for (CartItem item : items) {
                count += item.getCount();
            }
        }
        return count;
    }
 
    public Integer getCountType() {
        int count = 0;
        if (items != null && items.size() > 0) {
            for (CartItem item : items) {
                count += 1;
            }
        }
        return count;
    }
 
    public BigDecimal getTotalAmount() {
        BigDecimal amount = new BigDecimal("0");
        // 1、计算购物项总价
        if (items != null && items.size() > 0) {
            for (CartItem item : items) {
                if (item.getCheck()) {
                    BigDecimal totalPrice = item.getTotalPrice();
                    amount = amount.add(totalPrice);
                }
            }
        }
        // 2、减去优惠总价
        BigDecimal subtract = amount.subtract(getReduce());
        return subtract;
    }
 
 
    public BigDecimal getReduce() {
        return reduce;
    }
 
    public void setReduce(BigDecimal reduce) {
        this.reduce = reduce;
    }
}

2.2. 导入redis和SpringSession的依赖

<!--整合SpringSession完成session共享问题-->
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.3. 配置redis和SpringSession存储类型

#配置redis
spring.redis.host=192.168.119.127
spring.redis.port=6379
 
spring.session.store-type=redis

2.4. 添加SpringSession配置类

添加"com.cwh.gulimall.cart.config.GulimallSessionConfig"类,代码如下:

@EnableRedisHttpSession  //自动开启RedisHttpSession
@Configuration
public class GulimallSessionConfig {
 
    @Bean
    public CookieSerializer cookieSerializer(){
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        cookieSerializer.setDomainName("gulimall.com");
        cookieSerializer.setCookieName("GULISESSION");
        return cookieSerializer;
    }
 
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer(){
        return new GenericJackson2JsonRedisSerializer();
    }
}

3. ThreadLocal用户身份鉴别

3.1. ThreadLocal同一个线程共享数据

3.2. 核心原理

Map<Thread,Object> threadLocal

3.3. 用户身份鉴别方式

参考京东,在点击购物车时,会为临时用户生成一个name为user-key的cookie临时标识,过期时间为一个月,如果手动清除user-key,那么临时购物车的购物项也被清除,所以user-key是用来标识和存储临时购物车数据的。

3.4. 使用ThreadLocal进行用户身份鉴别信息传递

  • 在调用购物车的接口前,先通过session信息判断是否登录,并分别进行用户身份信息的封装,并把user-key放在cookie中
  • 这个功能使用拦截器进行完成

添加"com.cwh.gulimall.cart.vo.UserInfoTo"类,代码如下:

@ToString
@Data
public class UserInfoTo {
 
    private Long userId;
 
    private String userKey; //一定封装
 
    private boolean tempUser = false;  //判断是否有临时用户
}

添加"com.cwh.gulimall.cart.interceptor.CartInterceptor"类,代码如下:

package com.cwh.gulimall.cart.interceptor;
 
import com.cwh.common.constant.AuthServerConstant;
import com.cwh.common.constant.CartConstant;
import com.cwh.common.vo.MemberResponseVO;
import com.cwh.gulimall.cart.vo.UserInfoTo;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
 
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.UUID;
 
/**
 * @Description: 在执行目标方法之前,判断用户的登录状态。并封装传递给目标请求
 * @Date: 2024/5/19 19:39
 * @Version 1.0
 */
public class CartInterceptor implements HandlerInterceptor {
    // ThreadLocal同一个线程共享数据
    public static ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal<>();
 
    /**
     * 在目标方法执行之前拦截
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        UserInfoTo userInfoTo = new UserInfoTo();
        HttpSession session = request.getSession();
        MemberResponseVO member = (MemberResponseVO) session.getAttribute(AuthServerConstant.LOGIN_USER);
        // 1、用户登录,封装用户id
        if (member != null) {
            userInfoTo.setUserId(member.getId());
 
        }
        // 2、如果有临时用户,封装临时用户
        Cookie[] cookies = request.getCookies();
        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
                String name = cookie.getName();
                if (name.equals(CartConstant.TEMP_USER_COOKIE_NAME)) {
                    userInfoTo.setUserKey(cookie.getValue());
                    userInfoTo.setTempUser(true);
                }
            }
        }
 
        // 3、如果没有临时用户,一定保存一个临时用户
        if (StringUtils.isEmpty(userInfoTo.getUserKey())) {
            String uuid = UUID.randomUUID().toString();
            userInfoTo.setUserKey(uuid);
        }
        // 目标方法执行之前,将用户信息保存到ThreadLocal
        threadLocal.set(userInfoTo);
        return true;
    }
 
    /**
     * 业务执行之后 分配临时用户,让浏览器保存
     *
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        UserInfoTo userInfoTo = threadLocal.get();
        // 如果没有临时用户,第一次访问购物车就添加临时用户
        if (!userInfoTo.isTempUser()) {
            // 持续的延长用户的过期时间
            Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
            cookie.setDomain("gulimall.com");
            cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);
            response.addCookie(cookie);
        }
    }
}

添加拦截器的配置,不能只把拦截器加入容器中,不然拦截器不生效的

添加"com.cwh.gulimall.cart.config.GulimallWebConfig"类,代码如下:

@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {
    /**
     * 添加拦截器的配置,不能只把拦截器加入容器中,不然拦截器不生效的
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");
    }
}

添加"com.cwh.gulimall.cart.controller.CartController"类,代码如下:

@Controller
public class CartController {
 
    /**
     * 浏览器有一个cookie;user-key:标识用户身份,一个月后过期
     * 如果第一次使用jd购物车功能,都会给一个临时的用户身份
     * 浏览器保存,每次访问都会带上有这个cookies
     *
     * 登录  session有
     * 没登录,按照cookie里面带来的user-key来做
     * 第一次,如果没有临时用户,帮忙创建一个临时用户
     */
    @GetMapping("/cart.html")
    public String cartListPage(){
 
        //快速得到用户信息,id,user-key
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        System.out.println(userInfoTo);
        return "cartList";
    }
}

4. 添加商品到购物车

在gulimall-product模块,修改"加入购物车"按钮

修改"com.cwh.gulimall.cart.controller.CartController"类,代码如下:

    /**
     * 添加商品到购物车
     */
    @GetMapping("/addToCart")
    public String addToCart(@RequestParam("skuId") Long skuId,
                            @RequestParam("num") Integer num,
                            Model model) {
        CartItem cartItem = cartService.addToCart(skuId,num);
        model.addAttribute("item", cartItem);
        return "success";
    }

修改"com.cwh.gulimall.cart.service.CartService"类,代码如下:

public interface CartService {
    CartItem addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException;
}

业务逻辑

  • 若当前商品已经存在购物车,只需增添数量
  • 否则需要查询商品购物项所需信息,并添加新商品至购物车

修改"com.cwh.gulimall.cart.service.impl.CartServiceImpl"类,代码如下:

@Slf4j
@Service
public class CartServiceImpl implements CartService {
    private final String CART_PREFIX = "gulimall:cart";
    @Autowired
    StringRedisTemplate stringRedisTemplate;
 
    @Autowired
    ProductFeignService productFeignService;
 
    @Autowired
    ThreadPoolExecutor executor;
 
    @Override
    public CartItem addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
        // 获取我们要操作的购物车,临时购物车、用户购物车
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
 
        String res = (String) cartOps.get(skuId.toString());
        // 1、添加新商品到购物车(购物车无此商品)
        if (StringUtils.isEmpty(res)) {
            CartItem cartItem = new CartItem();
            /**
             * 异步查询
             */
            CompletableFuture<Void> getSkuInfo = CompletableFuture.runAsync(() -> {
                // 1.1、远程查询要添加的商品信息
                R skuInfo = productFeignService.getSkuInfo(skuId);
                SkuInfoVo data = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                });
                cartItem.setCheck(true);
                cartItem.setCount(1);
                cartItem.setImage(data.getSkuDefaultImg());
                cartItem.setTitle(data.getSkuTitle());
                cartItem.setSkuId(skuId);
                cartItem.setPrice(data.getPrice());
            }, executor);
            CompletableFuture<Void> getSkuSaleAttrValues = CompletableFuture.runAsync(() -> {
                // 1.2、远程查询sku的组合信息
                List<String> values = productFeignService.getSkuSaleAttrValues(skuId);
                cartItem.setSkuAttr(values);
            }, executor);
            CompletableFuture.allOf(getSkuInfo, getSkuSaleAttrValues).get();
            String jsonString = JSON.toJSONString(cartItem);
            cartOps.put(skuId.toString(), jsonString);
            return cartItem;
        } else {
            // 2、购物车有此商品,将数据取出修改数量即可
            CartItem cartItem = JSON.parseObject(res, CartItem.class);
            cartItem.setCount(cartItem.getCount() + num);
            cartOps.put(skuId.toString(), JSON.toJSONString(cartItem));
            return cartItem;
        }
    }
    
    /**
     * 获取我们要操作的购物车,临时购物车、用户购物车
     *
     * @return
     */
    private BoundHashOperations<String, Object, Object> getCartOps() {
        // 得到用户信息 账号用户 、临时用户
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        // 1、userInfoTo.getUserId()不为空表示账号用户,反之临时用户  然后决定用临时购物车还是用户购物车
        // 放入缓存的key
        String cartKey = "";
        if (userInfoTo.getUserId() != null) {
            cartKey = CART_PREFIX + userInfoTo.getUserId();
        } else {
            cartKey = CART_PREFIX + userInfoTo.getUserKey();
        }
        BoundHashOperations<String, Object, Object> operations = stringRedisTemplate.boundHashOps(cartKey);
        return operations;
    }
}
4.1.1. 配置线程池

application.properties添加线程池配置

gulimall.thread.core= 20
gulimall.thread.max-size= 200
gulimall.thread.keep-alive-time= 10

添加"com.cwh.gulimall.cart.config.ThreadPoolConfigProperties"类,代码如下:

@ConfigurationProperties(prefix = "gulimall.thread")
@Component
@Data
public class ThreadPoolConfigProperties {
    private Integer core;
    private Integer maxSize;
    private Integer keepAliveTime;
}

添加"com.cwh.gulimall.cart.config.MyThreadConfig"类,代码如下:

//如果ThreadPoolConfigProperties.class类没有加上@Component注解,那么我们在需要的配置类里开启属性配置的类加到容器中
//@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class MyThreadConfig {
    @Bean
    public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool){
        return new ThreadPoolExecutor(pool.getCore(),
                pool.getMaxSize(),
                pool.getKeepAliveTime(),
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(100000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
    }
}
4.1.2. 远程查询要添加的商品信息

添加"com.cwh.gulimall.cart.vo.SkuInfoVo"类,代码如下

@Data
public class SkuInfoVo {
    private Long skuId;
    /**
     * spuId
     */
    private Long spuId;
    /**
     * sku名称
     */
    private String skuName;
    /**
     * sku介绍描述
     */
    private String skuDesc;
    /**
     * 所属分类id
     */
    private Long catalogId;
    /**
     * 品牌id
     */
    private Long brandId;
    /**
     * 默认图片
     */
    private String skuDefaultImg;
    /**
     * 标题
     */
    private String skuTitle;
    /**
     * 副标题
     */
    private String skuSubtitle;
    /**
     * 价格
     */
    private BigDecimal price;
    /**
     * 销量
     */
    private Long saleCount;
}

添加"com.cwh.gulimall.cart.feign.ProductFeignService"类,代码如下:

@FeignClient("gulimall-product")
public interface ProductFeignService {
 
    @RequestMapping("/product/skuinfo/info/{skuId}")
    R getSkuInfo(@PathVariable("skuId") Long skuId);
 
    @GetMapping("product/skusaleattrvalue/stringlist/{skuId}")
    public List<String> getSkuSaleAttrValues(@PathVariable("skuId") Long skuId);
}
4.1.3. 远程查询sku的组合信息

修改"com.cwh.gulimall.product.app.SkuSaleAttrValueController"类,代码如下:

    @GetMapping("stringlist/{skuId}")
    public List<String> getSkuSaleAttrValues(@PathVariable("skuId") Long skuId){
        return skuSaleAttrValueService.getSkuSaleAttrValuesAsStringList(skuId);
    }

修改"com.cwh.gulimall.product.service.SkuSaleAttrValueService",代码如下:

List<String> getSkuSaleAttrValuesAsStringList(Long skuId);

修改"com.cwh.gulimall.product.service.impl.SkuSaleAttrValueServiceImpl"类,代码如下:

     @Override
    public List<String> getSkuSaleAttrValuesAsStringList(Long skuId) {
        return this.baseMapper.getSkuSaleAttrValuesAsStringList(skuId);
    }

修改"com.cwh.gulimall.product.dao.SkuSaleAttrValueDao"类,代码如下:

List<String> getSkuSaleAttrValuesAsStringList(@Param("skuId") Long skuId);

    <select id="getSkuSaleAttrValuesAsStringList" resultType="java.lang.String">
        select CONCAT(attr_name,":",attr_value) from pms_sku_sale_attr_value
        where sku_id = #{skuId}
    </select>
4.1.4. bug:不断刷新页面会一直增加数量

不断刷新页面会一直增加数量,所以我们修改逻辑在controller的addToCart方法里添加商品,商品添加完跳转到成功页面我们改为改成重定向另一个方法,专门查询数据跳转到成功页面

修改"com.cwh.gulimall.cart.controller.CartController"类,代码如下:

    /**
     * 添加商品到购物车
     * <p>
     * RedirectAttributes attributes
     * attributes.addFlashAttribute();将数据放在session里面可以在页面取出,但只能取一次
     * attributes.addAttribute("skuId",skuId); 将数据放在url后面
     *
     * @return
     */
    @GetMapping("/addToCart")
    public String addToCart(@RequestParam("skuId") Long skuId,
                            @RequestParam("num") Integer num,
                            RedirectAttributes attributes) throws ExecutionException, InterruptedException {
        cartService.addToCart(skuId, num);
        attributes.addAttribute("skuId", skuId);
 
        return "redirect:http://cart.gulimall.com/addToCartSuccess.html";
    }
 
    /**
     * 跳转到成功页
     *
     * @param skuId
     * @param model
     * @return
     */
    @GetMapping("/addToCartSuccess.html")
    public String addToCartSuccessPage(@RequestParam("skuId") Long skuId, Model model) {
        CartItem cartItem = cartService.getCartItem(skuId);
        model.addAttribute("item", cartItem);
        return "success";
    }

修改"com.cwh.gulimall.cart.service.CartService"类,代码如下:

    /**
     * 获取购物车中某个购物项
     *
     * @param skuId
     * @return
     */
    CartItem getCartItem(Long skuId);

修改"com.cwh.gulimall.cart.service.impl.CartServiceImpl"类,代码如下:

    @Override
    public CartItem getCartItem(Long skuId) {
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        String s = (String) cartOps.get(skuId.toString());
        CartItem cartItem = JSON.parseObject(s, CartItem.class);
        return cartItem;
    }

5. 获取&合并购物车

5.1. 获取

  • 若用户未登录,则直接使用user-key获取购物车数据
  • 否则使用userId获取购物车数据,并将user-key对应临时购物车数据与用户购物车数据合并,并删除临时购物车

修改"com.cwh.gulimall.cart.controller.CartController"类,代码如下

    @GetMapping("/cart.html")
    public String cartListPage(Model model) throws ExecutionException, InterruptedException {
 
        // 快速得到用户信息,id,user-key
//        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        Cart cart = cartService.getCart();
        model.addAttribute("cart", cart);
        return "cartList";
    }

修改"com.cwh.gulimall.cart.service.CartService"类,代码如下:

    /**
     * 获取整个购物车
     * 
     * @return
     */
    Cart getCart() throws ExecutionException, InterruptedException;
 修改"com.cwh.gulimall.cart.service.impl.CartServiceImpl"类,代码如下:

    @Override
    public Cart getCart() throws ExecutionException, InterruptedException {
        Cart cart = new Cart();
        // 1、登录
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        if (userInfoTo.getUserId() != null) {
            String cartKey = CART_PREFIX + userInfoTo.getUserId();
            // 1.1、如果临时购物车的数据还没有合并【合并购物车】
            String tempCartKey = CART_PREFIX + userInfoTo.getUserKey();
            List<CartItem> tempsCartItems = getCartItems(tempCartKey);
            if (tempsCartItems != null) {
                // 临时购物车有数据,需要合并
                for (CartItem item : tempsCartItems) {
                    addToCart(item.getSkuId(), item.getCount());
                }
                // 清除临时购物车的数据
                clearCart(tempCartKey);
            }
            // 1.2、获取登录后的购物车数据【包含合并过来的临时购物车的数据,和登录后的购物车数据】
            List<CartItem> cartItems = getCartItems(cartKey);
            cart.setItems(cartItems);
        } else {
            // 2、没登录
            String cartKey = CART_PREFIX + userInfoTo.getUserKey();
            // 获取临时购物车的所有购物项
            List<CartItem> cartItems = getCartItems(cartKey);
            cart.setItems(cartItems);
        }
        return cart;
    }
 
    /**
     * 获取购物项
     * 
     * @param cartKey
     * @return
     */
    private List<CartItem> getCartItems(String cartKey){
        BoundHashOperations<String, Object, Object> operations = stringRedisTemplate.boundHashOps(cartKey);
        List<Object> values = operations.values();
        if (values != null && values.size() > 0){
            List<CartItem> collect = values.stream().map(obj -> {
                String str = (String) obj;
                CartItem cartItem = JSON.parseObject(str, CartItem.class);
                return cartItem;
            }).collect(Collectors.toList());
            return collect;
        }
        return null;
    }

5.2. 清空购物车数据

修改"com.cwh.gulimall.cart.service.CartService"类,代码如下:

    /**
     * 清空购物车数据
     * 
     * @param cartKey
     */
    void clearCart(String cartKey);
  修改"com.cwh.gulimall.cart.service.CartService"类,代码如下:

    @Override
    public void clearCart(String cartKey) {
        stringRedisTemplate.delete(cartKey);
    }

6. 选中购物车项

修改"com.cwh.gulimall.cart.service.CartService"类,代码如下:

/**

     * 勾选购物项
     * @param skuId
     * @param check
     */
    void checkItem(Long skuId, Integer check);
修改"com.cwh.gulimall.cart.service.impl.CartServiceImpl"类,代码如下:

    @Override
    public void checkItem(Long skuId, Integer check) {
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        CartItem cartItem = getCartItem(skuId);
        cartItem.setCheck(check == 1 ? true : false);
        String jsonString = JSON.toJSONString(cartItem);
        cartOps.put(skuId.toString(), jsonString);
    }
 
    /**
     * 获取我们要操作的购物车,临时购物车、用户购物车
     *
     * @return
     */
    private BoundHashOperations<String, Object, Object> getCartOps() {
        // 得到用户信息 账号用户 、临时用户
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        // 1、userInfoTo.getUserId()不为空表示账号用户,反之临时用户  然后决定用临时购物车还是用户购物车
        // 放入缓存的key
        String cartKey = "";
        if (userInfoTo.getUserId() != null) {
            cartKey = CART_PREFIX + userInfoTo.getUserId();
        } else {
            cartKey = CART_PREFIX + userInfoTo.getUserKey();
        }
        BoundHashOperations<String, Object, Object> operations = stringRedisTemplate.boundHashOps(cartKey);
        return operations;
    }

7. 修改购物项数量

修改"com.cwh.gulimall.cart.controller.CartController"类,代码如下:

    @GetMapping("/changeItemCount")
    public String changeItemCount(@RequestParam("skuId") Long skuId, @RequestParam("num") Integer num){
        cartService.changeItemCount(skuId,num);
        return "redirect:http://cart.gulimall.com/cart.html";
    }

修改"com.cwh.gulimall.cart.service.CartService"类,代码如下:

     /**
     * 修改购物项数量
     * 
     * @param skuId
     * @param num
     */
    void changeItemCount(Long skuId, Integer num);

修改"com.cwh.gulimall.cart.service.impl.CartServiceImpl"类,代码如下:

    @Override
    public void changeItemCount(Long skuId, Integer num) {
        CartItem cartItem = getCartItem(skuId);
        cartItem.setCount(num);
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        cartOps.put(skuId.toString(),JSON.toJSONString(cartItem));
    }

8. 删除购物车项

修改"com.cwh.gulimall.cart.controller.CartController"类,代码如下:

    @GetMapping("/deleteItem")
    public String deleteItem(@RequestParam("skuId") Long skuId){
        cartService.deleteItem(skuId);
        return "redirect:http://cart.gulimall.com/cart.html";
    }

修改"com.cwh.gulimall.cart.service.CartService"类,代码如下:

    /**
     * 删除购物项
     * @param skuId
     */
    void deleteItem(Long skuId);

修改"com.cwh.gulimall.cart.service.impl.CartServiceImpl"类,代码如下:

@Override
public void deleteItem(Long skuId) {
    BoundHashOperations<String, Object, Object> cartOps = getCartOps();
    cartOps.delete(skuId.toString());
}
相关推荐
白露与泡影21 分钟前
Spring Boot中的 6 种API请求参数读取方式
java·spring boot·后端
CodeClimb21 分钟前
【华为OD-E卷 - 服务失效判断 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
CodeClimb23 分钟前
【华为OD-E卷 - 九宫格按键输入 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
_Soy_Milk29 分钟前
Golang,Let‘s GO!
开发语言·后端·golang
豪宇刘30 分钟前
MyBatis 与 MyBatis-Plus 的区别
java·tomcat
1-programmer33 分钟前
【Go研究】Go语言脚本化的可行性——yaegi项目体验
开发语言·后端·golang
一个儒雅随和的男子38 分钟前
Spring为什么要用三级缓存解决循环依赖?
java·spring·缓存
梦想是成为Java高手38 分钟前
ThreadLocal的介绍与使用规范,初学者必看
java
StevenGerrad40 分钟前
【读书笔记/源码】How Tomcat Works 笔记 - c1~c10
java·笔记·tomcat
pumpkin845141 小时前
C++移动语义
开发语言·c++