功能分析

功能需求

需求描述:

  • 用户可以在登录状态下将商品添加到购物车
  • 用户可以在未登录状态下将商品添加到购物车
  • 用户可以使用购物车一起结算下单
  • 用户可以查询自己的购物车
  • 用户可以在购物车中修改购买商品的数量。
  • 用户可以在购物车中删除商品。
  • 在购物车中展示商品优惠信息
  • 提示购物车商品价格变化

提示购物车商品价格变化,数据结构,首先分析一下购物车的数据结构

数据结构

首先分析一下购物车的数据结构

购物车功能 - 图1

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

  1. {
  2. id: 1,
  3. userId: '2',
  4. skuId: 2131241,
  5. check: true, // 选中状态
  6. title: "Apple iphone.....",
  7. image: "...",
  8. price: 4999,
  9. count: 1,
  10. store: true, // 是否有货
  11. saleAttrs: [{..},{..}], // 销售属性
  12. sales: [{..},{..}] // 营销信息
  13. }

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

  1. [
  2. {...},{...},{...}
  3. ]

怎么保存

由于购物车是一个读多写多的场景,为了应对高并发场景,所有购物车采用的存储方案也和其他功能,有所差别。

主流的购物车数据存储方案

  1. redis(登录/未登录):性能高,代价高,不利于数据分析
  2. mysql(登录/未登录):性能低,成本低,利于数据分析
  3. cookie(未登录):未登录时,不需要和服务器交互,性能提高。其他请求会占用带宽
  4. localStorage/IndexedDB/WebSQL(未登录):不需要和服务器交互,不占用带宽

数据需要持久化,购物车为什么不使用MySQL,原因是此场景读多写多,MySQL性能并不是特别高。读多写少场景才适合。redis同样也可以开启持久化操作。MongoDB性能也提升不了多大,所以不使用MongoDB。

一般情况下,企业级购物车通常采用组合方案

  1. cookie(未登录时) + mysql(登录时)
  2. cookie(未登录) + redis(登录时)
  3. localStorage/IndexedDB/WebSQL(未登录) + redis(登录)
  4. localStorage/IndexedDB/WebSQL(未登录) + mysql(登录)

随着数据价值的提升,企业越来越重视用户数据的收集,现在以上4种方案使用的越来越少。

当前大厂普遍采用:redis + mysql

不管是否登录都把数据保存到mysql,为了提高性能可以搭建mysql集群,并引入redis。

查询时,从redis查询提高查询速度,写入时,采用双写模式

mysql保存购物车很简单,创建一张购物车表即可。

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,值是购物车数据
  1. key:用户标示
  2. 登录态:gulimall:cart:userId
  3. 非登录态:gulimall:cart:userKey
  4. value
  5. 存储一个Hash结构的值,其中该hash结构的keySkuIdhash结构的value是商品信息,以json字符串格式存储

购物车功能 - 图2

创建gulimall-cart工程

创建工程也就是动静分离的配置

购物车VO

  1. /**
  2. * 购物车VO
  3. * 需要计算的属性需要重写get方法,保证每次获取属性都会进行计算
  4. */
  5. public class CartVo {
  6. private List<CartItemVo> items; // 购物项集合
  7. private Integer countNum; // 商品件数(汇总购物车内商品总件数)
  8. private Integer countType; // 商品数量(汇总购物车内商品总个数)
  9. private BigDecimal totalAmount; // 商品总价
  10. private BigDecimal reduce = new BigDecimal("0.00");// 减免价格
  11. public List<CartItemVo> getItems() {
  12. return items;
  13. }
  14. public void setItems(List<CartItemVo> items) {
  15. this.items = items;
  16. }
  17. public Integer getCountNum() {
  18. int count = 0;
  19. if (items != null && items.size() > 0) {
  20. for (CartItemVo item : items) {
  21. count += item.getCount();
  22. }
  23. }
  24. return count;
  25. }
  26. public Integer getCountType() {
  27. return CollectionUtils.isEmpty(items) ? 0 : items.size();
  28. }
  29. public BigDecimal getTotalAmount() {
  30. BigDecimal amount = new BigDecimal("0");
  31. // 1、计算购物项总价
  32. if (!CollectionUtils.isEmpty(items)) {
  33. for (CartItemVo cartItem : items) {
  34. if (cartItem.getCheck()) {
  35. amount = amount.add(cartItem.getTotalPrice());
  36. }
  37. }
  38. }
  39. // 2、计算优惠后的价格
  40. return amount.subtract(getReduce());
  41. }
  42. public BigDecimal getReduce() {
  43. return reduce;
  44. }
  45. public void setReduce(BigDecimal reduce) {
  46. this.reduce = reduce;
  47. }
  48. }

购物项VO

  1. /**
  2. * 购物项VO(购物车内每一项商品内容)
  3. */
  4. public class CartItemVo {
  5. private Long skuId; // skuId
  6. private Boolean check = true; // 是否选中
  7. private String title; // 标题
  8. private String image; // 图片
  9. private List<String> skuAttrValues; // 销售属性
  10. private BigDecimal price; // 单价
  11. private Integer count; // 商品件数
  12. private BigDecimal totalPrice; // 总价
  13. public Long getSkuId() {
  14. return skuId;
  15. }
  16. public void setSkuId(Long skuId) {
  17. this.skuId = skuId;
  18. }
  19. public Boolean getCheck() {
  20. return check;
  21. }
  22. public void setCheck(Boolean check) {
  23. this.check = check;
  24. }
  25. public String getTitle() {
  26. return title;
  27. }
  28. public void setTitle(String title) {
  29. this.title = title;
  30. }
  31. public String getImage() {
  32. return image;
  33. }
  34. public void setImage(String image) {
  35. this.image = image;
  36. }
  37. public List<String> getSkuAttrValues() {
  38. return skuAttrValues;
  39. }
  40. public void setSkuAttrValues(List<String> skuAttrValues) {
  41. this.skuAttrValues = skuAttrValues;
  42. }
  43. public BigDecimal getPrice() {
  44. return price;
  45. }
  46. public void setPrice(BigDecimal price) {
  47. this.price = price;
  48. }
  49. public Integer getCount() {
  50. return count;
  51. }
  52. public void setCount(Integer count) {
  53. this.count = count;
  54. }
  55. /**
  56. * 计算当前购物项总价
  57. */
  58. public BigDecimal getTotalPrice() {
  59. return this.price.multiply(new BigDecimal("" + this.count));
  60. }
  61. public void setTotalPrice(BigDecimal totalPrice) {
  62. this.totalPrice = totalPrice;
  63. }
  64. }

流程

参照jd:

购物车功能 - 图3

user-key是游客id,不管有没有登录都会有这个cookie信息。

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

新增商品:判断是否登录

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

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

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

配置拦截器

  1. 业务逻辑:
  2. 1)第一次使用购物车功能,创建user-key(分配临时用户身份)
  3. 2)访问购物车时,判断当前是否登录状态(session是否存在用户信息)
  4. 登录状态则获取用户购物车信息
  5. 3)未登录状态,则获取临时用户身份,获取游客购物车
  6. 拦截器功能:
  7. 过滤器(URL拦截)=》拦截器(URL拦截)=》切面(方法拦截)
  8. 1.preHandle
  9. 1)获取用户登录信息userId,封装到ThreadLocal中,controller可以拿到
  10. 2)用户未登录,分配userKey封装到ThreadLocal中,controller可以拿到
  11. 2.postHandle
  12. 1)判断客户端是否存在游客用户标识
  13. 不存在则创建cookie,命令客户端保存游客信息user-key

购物车系统根据用户的登录状态,购物车的增删改处理方式不同,因此需要添加登录校验。而登录状态的校验如果在每个方法中进行校验,会造成代码的冗余,不利于维护。所以这里使用拦截器统一处理。

springboot自定义拦截器:

  1. 编写自定义拦截器类实现HandlerInterceptor接口(前置方法 后置方法 完成方法)
  2. 编写配置类(添加@Configuration注解)实现WebMvcConfigurer接口(重写addInterceptors方法)
  1. @Component
  2. public class CartInterceptor implements HandlerInterceptor {
  3. public static ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal<>();
  4. @Override
  5. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  6. UserInfoTo userInfoTo = new UserInfoTo();
  7. HttpSession session = request.getSession();
  8. MemberRespVo member = (MemberRespVo) session.getAttribute(AuthConstant.LOGIN_USER);
  9. if (member != null) {
  10. // 登录状态,封装用户ID,供controller使用
  11. userInfoTo.setUserId(member.getId());
  12. }
  13. // 获取当前请求游客用户标识user-key
  14. Cookie[] cookies = request.getCookies();
  15. if (cookies != null && cookies.length > 0) {
  16. for (Cookie cookie : cookies) {
  17. // 如果cookie有user_key
  18. if (cookie.getName().equals(CartConstant.TEMP_USER_COOKIE_NAME)) {
  19. // 获取user-key值封装到user,供controller使用
  20. userInfoTo.setUserKey(cookie.getValue());
  21. // 表示已经登录,不是零时用户
  22. userInfoTo.setTempUser(true);
  23. break;
  24. }
  25. }
  26. }
  27. // 没有零时用户一定分配一个零时用户
  28. if (StringUtils.isEmpty(userInfoTo.getUserKey())) {
  29. // 无游客标识,分配游客标识
  30. userInfoTo.setUserKey(UUID.randomUUID().toString());
  31. }
  32. // 封装用户信息(登录状态userId非空,游客状态userId空)
  33. threadLocal.set(userInfoTo);
  34. return true;
  35. }
  36. /**
  37. * 业务执行之后,让浏览器保存临时用户信息
  38. *
  39. * @param request
  40. * @param response
  41. * @param handler
  42. * @param modelAndView
  43. * @throws Exception
  44. */
  45. @Override
  46. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  47. UserInfoTo userInfoTo = threadLocal.get();
  48. // 如果是零时用户
  49. if (!userInfoTo.isTempUser()) {
  50. Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
  51. cookie.setDomain("gulimalls.com");
  52. cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);
  53. response.addCookie(cookie);
  54. }
  55. }
  56. }

拦截器定义好了,将来怎么把拦截器中获取的用户信息传递给后续的每个业务逻辑:

  1. public类型的公共变量。线程不安全
  2. request对象。不够优雅
  3. ThreadLocal线程变量。推荐

所以将用户信息放入ThreadLocal中

  1. /**
  2. * 业务执行之后,让浏览器保存临时用户信息
  3. *
  4. * @param request
  5. * @param response
  6. * @param handler
  7. * @param modelAndView
  8. * @throws Exception
  9. */
  10. @Override
  11. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  12. UserInfoTo userInfoTo = threadLocal.get();
  13. // 如果是零时用户
  14. if (!userInfoTo.isTempUser()) {
  15. Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
  16. cookie.setDomain("gulimalls.com");
  17. cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);
  18. response.addCookie(cookie);
  19. }
  20. }

注册拦截器

  1. /**
  2. * 配置拦截器
  3. */
  4. @Configuration
  5. public class GulimallWebConfig implements WebMvcConfigurer {
  6. @Override
  7. public void addInterceptors(InterceptorRegistry registry) {
  8. registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");
  9. }
  10. }

添加商品到购物车

  1. /**
  2. * 添加商品到购物车
  3. * RedirectAttributes ra
  4. * ra.addFLashAttribute();将数据放在session里面可以在页面取出,但是只能取一次
  5. * ra.addAttribute( "shuId" , skuId);将数据放在urL后面
  6. *
  7. * @return
  8. */
  9. @GetMapping("addToCart")
  10. public String addToCart(@RequestParam("skuId") Long skuId,
  11. @RequestParam("num") Integer num,
  12. RedirectAttributes redirectAttributes) throws ExecutionException, InterruptedException {
  13. cartService.addCart(skuId, num);
  14. // 重定向域(会自动拼接在路径后面)
  15. redirectAttributes.addAttribute("skuId", skuId);
  16. return "redirect:http://cart.gulimalls.com/addToCartSuccess.html";
  17. }

Hash数据类型操作对象

此时操作redis中的数据结构需要绑定hash键,抽取一个方法来绑定哈希键,以后都是操作它

这里区分了登录了的用户还是临时用户,那么作为hash的key就会不一样

  1. /**
  2. * 查询购物车
  3. *
  4. * @return
  5. */
  6. @Override
  7. public CartVo getCart() throws ExecutionException, InterruptedException {
  8. CartVo cartVo = new CartVo();
  9. UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
  10. String cartKey = "";
  11. if (userInfoTo.getUserId() != null) {
  12. // 先判断零时购物车是否有商品
  13. String tempCartKey = CartConstant.CART_PREFIX + userInfoTo.getUserKey();
  14. List<CartItemVo> tempCartItemList = getCartItems(tempCartKey);
  15. if (tempCartItemList != null && tempCartItemList.size() > 0) {
  16. for (CartItemVo cartItemVo : tempCartItemList) {
  17. // 合并在用户购物车中
  18. addCart(cartItemVo.getSkuId(), cartItemVo.getCount());
  19. }
  20. // 删除临时购物车
  21. clearCart(tempCartKey);
  22. }
  23. // 使用用户的id作为购物车,此时购物车已经合并了,所以直接查
  24. cartKey = CartConstant.CART_PREFIX + userInfoTo.getUserId();
  25. cartVo.setItems(getCartItems(cartKey));
  26. } else {
  27. // 此时没登录,就用cookie,游客购物车
  28. cartKey = CartConstant.CART_PREFIX + userInfoTo.getUserKey();
  29. // 此时游客购物车直接查询遍历即可
  30. List<CartItemVo> cartItemVoList = getCartItems(cartKey);
  31. cartVo.setItems(cartItemVoList);
  32. }
  33. return cartVo;
  34. }

实现类

注意使用了异步编排查询商品的销售属性和基本信息,此时如果redis中有数据,那么更新数据,如果没有添加数据。

  1. @Override
  2. public CartItemVo addCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
  3. BoundHashOperations<String, Object, Object> cartOps = getCartOps();
  4. String res = (String) cartOps.get(skuId.toString());
  5. if (StringUtils.isEmpty(res)) {
  6. // 如果redis中没有购物车,添加购物车
  7. CartItemVo cartItem = new CartItemVo();
  8. // 使用异步编排
  9. CompletableFuture<Void> getSkuInfoFuture = CompletableFuture.runAsync(() -> {
  10. // 远程调用查询sku基本信息
  11. R r = productFeignService.info(skuId);
  12. SkuInfoTo skuInfo = r.getData("skuInfo", new TypeReference<SkuInfoTo>() {
  13. });
  14. cartItem.setSkuId(skuInfo.getSkuId());// 商品ID
  15. cartItem.setTitle(skuInfo.getSkuTitle());// 商品标题
  16. cartItem.setImage(skuInfo.getSkuDefaultImg());// 商品默认图片
  17. cartItem.setPrice(skuInfo.getPrice());// 商品单价
  18. cartItem.setCount(num);// 商品件数
  19. cartItem.setCheck(true);// 是否选中
  20. }, threadPoolExecutor);
  21. CompletableFuture<Void> getSkuAttrValuesFuture = CompletableFuture.runAsync(() -> {
  22. // 远程调用查询sku销售属性
  23. List<String> skuSaleAttrValues = productFeignService.getSkuSaleAttrValues(skuId);
  24. cartItem.setSkuAttrValues(skuSaleAttrValues);
  25. }, threadPoolExecutor);
  26. // 等待两个线程都完成才保存到redis
  27. CompletableFuture.allOf(getSkuInfoFuture, getSkuAttrValuesFuture).get();
  28. // 以json格式保存在redis中
  29. String jsonString = JSON.toJSONString(cartItem);
  30. cartOps.put(skuId.toString(), jsonString);
  31. return cartItem;
  32. } else {
  33. // 如果redis中有数据,那么就更新数量
  34. CartItemVo cartItem = JSON.parseObject(res, CartItemVo.class);
  35. cartItem.setCount(cartItem.getCount() + num);
  36. cartOps.put(skuId.toString(), JSON.toJSONString(cartItem));
  37. return cartItem;
  38. }
  39. }

接口防刷

如果刷新cart.gulimall.com/addToCart?skuId=7&num=1该页面,会导致购物车中此商品的数量无限新增
解决方案:
/addToCart请求使用重定向给/addToCartSuccessPage.html
由/addToCartSuccessPage.html这个请求跳转”商品已成功加入购物车页面”(浏览器url请求已更改),达到防刷的目的,此时刷新是重定向过的页面,所以一致刷新也只是查询redis中的内容。

  1. /**
  2. * 添加商品到购物车
  3. * RedirectAttributes ra
  4. * ra.addFLashAttribute();将数据放在session里面可以在页面取出,但是只能取一次
  5. * ra.addAttribute( "shuId" , skuId);将数据放在urL后面
  6. *
  7. * @return
  8. */
  9. @GetMapping("addToCart")
  10. public String addToCart(@RequestParam("skuId") Long skuId,
  11. @RequestParam("num") Integer num,
  12. RedirectAttributes redirectAttributes) throws ExecutionException, InterruptedException {
  13. cartService.addCart(skuId, num);
  14. // 重定向域(会自动拼接在路径后面)
  15. redirectAttributes.addAttribute("skuId", skuId);
  16. return "redirect:http://cart.gulimalls.com/addToCartSuccess.html";
  17. }
  18. // 使用重定向保证接口防刷,如果一致发送请求只是查询,而不是新增
  19. @GetMapping("/addToCartSuccess.html")
  20. public String addToCartSuccessPage(@RequestParam("skuId") Long skuId, Model model) {
  21. // 查询skuId的数据
  22. CartItemVo cartItem = cartService.getCartBySkuId(skuId);
  23. model.addAttribute("cartItem", cartItem);
  24. return "success";
  25. }

查询内容实现类

  1. @Override
  2. public CartItemVo getCartBySkuId(Long skuId) {
  3. BoundHashOperations<String, Object, Object> hashOps = getCartOps();
  4. String data = (String) hashOps.get(skuId.toString());
  5. return JSON.parseObject(data, CartItemVo.class);
  6. }

购物车列表

  1. /**
  2. * 浏览器有一个cookie; user-key;标识用户身份,一个月后过期;
  3. * 如果第一次使用jd的购物车功能,都会给一个临时的用户身份;
  4. * 浏览器以后保存,每次访间都会带上这个cookie;
  5. * <p>
  6. * 登录: session有
  7. * 没登录:按照cookie里面带来user-key来做。
  8. * 第一次:如果没有临时用户,帮忙创建—个临时用户。
  9. *
  10. * @return
  11. */
  12. @GetMapping("/cart.html")
  13. public String cartListPage(Model model) throws ExecutionException, InterruptedException {
  14. CartVo cartVo = cartService.getCart();
  15. model.addAttribute("cart", cartVo);
  16. return "cartList";
  17. }

获取购物车列表时:

  • 如果用户购物车有两个,一个游客购物车,key是user-key,一个是用户购物车,key是user-id,那么此时需要合并购物车在用户购物车中,并删除游客购物车
  • 如果只有一个购物车,那么直接查询遍历即可
  1. /**
  2. * 查询购物车
  3. *
  4. * @return
  5. */
  6. @Override
  7. public CartVo getCart() throws ExecutionException, InterruptedException {
  8. CartVo cartVo = new CartVo();
  9. UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
  10. String cartKey = "";
  11. if (userInfoTo.getUserId() != null) {
  12. // 先判断零时购物车是否有商品
  13. String tempCartKey = CartConstant.CART_PREFIX + userInfoTo.getUserKey();
  14. List<CartItemVo> tempCartItemList = getCartItems(tempCartKey);
  15. if (tempCartItemList != null && tempCartItemList.size() > 0) {
  16. for (CartItemVo cartItemVo : tempCartItemList) {
  17. // 合并在用户购物车中
  18. addCart(cartItemVo.getSkuId(), cartItemVo.getCount());
  19. }
  20. // 删除临时购物车
  21. clearCart(tempCartKey);
  22. }
  23. // 使用用户的id作为购物车,此时购物车已经合并了,所以直接查
  24. cartKey = CartConstant.CART_PREFIX + userInfoTo.getUserId();
  25. cartVo.setItems(getCartItems(cartKey));
  26. } else {
  27. // 此时没登录,就用cookie,游客购物车
  28. cartKey = CartConstant.CART_PREFIX + userInfoTo.getUserKey();
  29. // 此时游客购物车直接查询遍历即可
  30. List<CartItemVo> cartItemVoList = getCartItems(cartKey);
  31. cartVo.setItems(cartItemVoList);
  32. }
  33. return cartVo;
  34. }
  35. /**
  36. * 根据cartKey获取购物车所有商品
  37. *
  38. * @param cartKey
  39. * @return
  40. */
  41. private List<CartItemVo> getCartItems(String cartKey) {
  42. // 绑定购物车的key操作Redis
  43. BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(cartKey);
  44. List<Object> values = hashOps.values();
  45. if (values != null && values.size() > 0) {
  46. List<CartItemVo> cartItemVoList = values.stream().map((obj) -> {
  47. CartItemVo cartItemVo = JSON.parseObject(String.valueOf(obj), CartItemVo.class);
  48. return cartItemVo;
  49. }).collect(Collectors.toList());
  50. return cartItemVoList;
  51. }
  52. return null;
  53. }
  54. /**
  55. * 根据cartKey删除购物车
  56. *
  57. * @param cartKey
  58. */
  59. public void clearCart(String cartKey) {
  60. redisTemplate.delete(cartKey);
  61. }

多选、更新数量、删除购物项

购物车功能 - 图4

前端页面略,后端都是直接操作redis中的数据,对对象进行修改即可,所以流程大致一样

controller

  1. @GetMapping("/deleteItem")
  2. public String deleteItem(@RequestParam("skuId") Long skuId) {
  3. cartService.deleteItemBySkuId(skuId);
  4. return "redirect:http://cart.gulimalls.com/cart.html";
  5. }
  6. @GetMapping("/countItem")
  7. public String countItem(@RequestParam("skuId") Long skuId,
  8. @RequestParam("num") Integer num) {
  9. cartService.changeItemCount(skuId, num);
  10. return "redirect:http://cart.gulimalls.com/cart.html";
  11. }
  12. @GetMapping("/countItem")
  13. public String checkItem(@RequestParam("skuId") Long skuId,
  14. @RequestParam("checked") Integer checked) {
  15. cartService.checkItem(skuId, checked);
  16. return "redirect:http://cart.gulimalls.com/cart.html";
  17. }

实现类

  1. @Override
  2. public void checkItem(Long skuId, Integer checked) {
  3. BoundHashOperations<String, Object, Object> cartOps = getCartOps();
  4. CartItemVo cartBySkuId = getCartBySkuId(skuId);
  5. cartBySkuId.setCheck(checked == 1 ? true : false);
  6. cartOps.put(skuId.toString(), JSON.toJSONString(cartBySkuId));
  7. }
  8. @Override
  9. public void changeItemCount(Long skuId, Integer num) {
  10. BoundHashOperations<String, Object, Object> cartOps = getCartOps();
  11. CartItemVo cartBySkuId = getCartBySkuId(skuId);
  12. cartBySkuId.setCount(num);
  13. cartOps.put(skuId.toString(), JSON.toJSONString(cartBySkuId));
  14. }
  15. @Override
  16. public void deleteItemBySkuId(Long skuId) {
  17. BoundHashOperations<String, Object, Object> cartOps = getCartOps();
  18. cartOps.delete(skuId.toString());
  19. }