文章目录

  • 十、购物车模块
    • 1.需求分析
    • 2.封装vo
    • 3.添加商品
    • 4.查询购物车
    • 5.选中商品
    • 6.在购物车修改商品数量
    • 7.在购物车删除商品
  • 十一、消息队列RabbitMQ
    • 1.场景分析
    • 2.概述
    • 3.核心概念
    • 4.docker安装
    • 5.RabbitMQ 几种消息模式的测试
    • 6.SpringBoot整合RabbitMQ

十、购物车模块

1.需求分析

用户在登录状态下将商品添加到购物车

  • 放入数据库(经常访问、修改不显示)
  • 放入 mongodb(Nosql 数据库 比上面的好一点点)
  • 放入 redis (采用)

用户在未登录状态下点击购物车会转到登录页面

存储:

  • 使用 hash 数据类型来存储商品信息,方便商品的CRUD

用户在购物车中可以

  • 添加商品
  • 查询购物车
  • 修改购买商品的数量
  • 删除商品

2.封装vo

package com.henu.soft.merist.cart.vo;import java.math.BigDecimal;
import java.util.List;/*** 单个购物项内容*/
public class CartItemVo {private Long skuId;private Boolean check = true;private String title;private String image;private List<String> skuAttrValue;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;}public void setTotalPrice(BigDecimal totalPrice) {this.totalPrice = totalPrice;}/*** 计算当前项的总价* @return*/public BigDecimal getTotalPrice(){return this.price.multiply(new BigDecimal(""+this.count));}
}package com.henu.soft.merist.cart.vo;import java.math.BigDecimal;
import java.util.List;/*** 整个购物车*/
public class CartVo {List<CartItemVo> items;private Integer countNum;//商品数量private Integer countType;//商品类型数量private BigDecimal totalAmount;//商品总价private BigDecimal reduce = new BigDecimal("0.00");//减免总价public List<CartItemVo> getItems() {return items;}public void setItems(List<CartItemVo> items) {this.items = items;}public Integer getCountNum() {int count = 0;if (items != null && items.size() > 0){for (CartItemVo item : items) {count += item.getCount();}}return count;}public void setCountNum(Integer countNum) {this.countNum = countNum;}public Integer getCountType() {int count = 0;if (items != null && items.size() > 0){for (CartItemVo item : items) {count ++;}}return count;}public void setCountType(Integer countType) {this.countType = countType;}public BigDecimal getTotalAmount() {BigDecimal total = new BigDecimal(0);if (items != null && items.size() > 0){for (CartItemVo item : items) {total = total.add(item.getTotalPrice());}}total = total.subtract(reduce);return total;}public void setTotalAmount(BigDecimal totalAmount) {this.totalAmount = totalAmount;}public BigDecimal getReduce() {return reduce;}public void setReduce(BigDecimal reduce) {this.reduce = reduce;}
}package com.henu.soft.merist.cart.vo;
import lombok.Data;import java.math.BigDecimal;@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;}

3.添加商品

添加完成之后重定向避免刷新页面重复添加商品

若是将添加、跳转的逻辑都放在addCartItem一个controller的话,刷新页面会重复提交cartService.addCartItem(skuId, num);

因此需要添加完成重定向到 addCartItemSuccess 的controller 查询redis 在跳转到显示页面

/*** 添加商品到购物车* RedirectAttributes.addFlashAttribute():将数据放在session中,可以在页面中取出,但是只能取一次* RedirectAttributes.addAttribute():将数据放在url后面* @return*/
@GetMapping("/addCartItem")
public String addCartItem(@RequestParam("skuId") Long skuId, @RequestParam("num") Integer num, RedirectAttributes attributes,HttpSession session){MemberResponseTo memberResponseTo = (MemberResponseTo) session.getAttribute(AuthServerConstant.LOGIN_USER);cartService.addCartItem(skuId,num,memberResponseTo.getId());attributes.addAttribute("skuId",skuId);attributes.addAttribute("userId",memberResponseTo.getId());return "redirect:http://cart.gulimall.com/addCartItemSuccess";
}
@GetMapping("/addCartItemSuccess")
public String addCartItemSuccess(@RequestParam("skuId") Long skuId,@RequestParam("userId") Long userId, Model model){//添加成功 返回根据skuid在购物车中查到的数据CartItemVo cartItemVo = cartService.getCartItem(skuId,userId);model.addAttribute("cartItem",cartItemVo);return "success";
}

使用线程池 异步查询sku 的属性(远程调用 product模块),节省时间,CompletableFuture.allOf(future1,future2).get();在两个异步操作完成之前阻塞线程,确保获取到数据再添加。

@Override
public CartItemVo addCartItem(Long skuId, Integer num, Long userId) {BoundHashOperations<String,Object,Object> ops = redisTemplate.boundHashOps(CartConstant.CART_PREFIX + userId);// 判断当前商品是否已经存在购物车String cartJson = (String) ops.get(skuId.toString());// 1.已经存在购物车,将数据提取出并添加商品数量if (!StringUtils.isEmpty(cartJson)){//1.1 将json 转对象 并数据加CartItemVo cartItemVo = JSON.parseObject(cartJson, CartItemVo.class);cartItemVo.setCount(cartItemVo.getCount() + num);//1.2 将更新后的对象转为json 并存入redisString jsonString = JSON.toJSONString(cartItemVo);ops.put(skuId.toString(),jsonString);return cartItemVo;}else {//不存在 添加新商品CartItemVo cartItemVo = new CartItemVo();CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {//2.1 远程查询sku基本信息R info = productFeignService.info(skuId);SkuInfoVo skuInfo = info.getData("skuInfo", new TypeReference<SkuInfoVo>() {});cartItemVo.setCheck(true);cartItemVo.setCount(num);cartItemVo.setImage(skuInfo.getSkuDefaultImg());cartItemVo.setPrice(skuInfo.getPrice());cartItemVo.setSkuId(skuId);cartItemVo.setTitle(skuInfo.getSkuTitle());}, executor);//2.2 远程查询sku属性组合信息CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {List<String> attrValuesAsString = productFeignService.getSkuSaleAttrValuesAsString(skuId);cartItemVo.setSkuAttrValue(attrValuesAsString);}, executor);//当前两个异步任务执行完这里才会放行try {CompletableFuture.allOf(future1,future2).get();} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}//2.3 将该属性封装并存入redis,登录用户使用userId为keyString jsonString = JSON.toJSONString(cartItemVo);ops.put(skuId.toString(),jsonString);return cartItemVo;}
}@Override
public CartItemVo getCartItem(Long skuId, Long userId) {BoundHashOperations<String,Object,Object> ops = redisTemplate.boundHashOps(CartConstant.CART_PREFIX + userId);String s = (String) ops.get(skuId.toString());CartItemVo cartItemVo = JSON.parseObject(s, CartItemVo.class);return cartItemVo;
}

4.查询购物车

需要说的是,这里购物车的总价部分,应该是由前端来计算的,因为前端可以统计用户此时选中的商品。

由于博主对前端几乎是一窍不通,所以只是简单的在后端vo里修改了get方法进行计算。

@GetMapping("/cart.html")
public String cartListPage(HttpSession session,Model model){MemberResponseTo memberResponseTo = (MemberResponseTo) session.getAttribute(AuthServerConstant.LOGIN_USER);if (memberResponseTo == null){//未登录 转到登录页面return "redirect:http://gulimall.com/login.html";}else {//查询购物车CartVo cartVo = cartService.getCart(memberResponseTo.getId());model.addAttribute("cart",cartVo);return "cartList";}
}
@Override
public CartVo getCart(Long userId) {BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(CartConstant.CART_PREFIX + userId);CartVo cartVo = new CartVo();//查询 userId 对应的购物车//对购物车的货物进行遍历封装到CartVoList<Object> values = ops.values();//list 中每个数据代表一件商品if (values != null && values.size() > 0){List<CartItemVo> cartItemVos = values.stream().map(obj -> {String json = (String) obj;return JSON.parseObject(json, CartItemVo.class);}).collect(Collectors.toList());cartVo.setItems(cartItemVos);}return cartVo;
}

5.选中商品

@GetMapping("/checkCart")
public String checkCart(@RequestParam("isChecked") Integer isChecked,@RequestParam("skuId") Long skuId,HttpSession session){MemberResponseTo memberResponseTo = (MemberResponseTo) session.getAttribute(AuthServerConstant.LOGIN_USER);cartService.checkCart(skuId,isChecked,memberResponseTo.getId());return "redirect:http://cart.gulimall.com/cart.html";
}
@Override
public void checkCart(Long skuId, Integer isChecked, Long userId) {BoundHashOperations<String,Object,Object> ops = redisTemplate.boundHashOps(CartConstant.CART_PREFIX + userId);String cartJson = (String) ops.get(skuId.toString());CartItemVo cartItemVo = JSON.parseObject(cartJson, CartItemVo.class);cartItemVo.setCheck(isChecked == 1);ops.put(skuId.toString(),JSON.toJSONString(cartItemVo));
}

6.在购物车修改商品数量

@GetMapping("/countItem")
public String changeItemCount(@RequestParam("skuId") Long skuId, @RequestParam("num") Integer num, HttpSession session){MemberResponseTo memberResponseTo = (MemberResponseTo) session.getAttribute(AuthServerConstant.LOGIN_USER);cartService.changeItemCount(skuId,num,memberResponseTo.getId());return "redirect:http://cart.gulimall.com/cart.html";
}
@Override
public void changeItemCount(Long skuId, Integer num, Long userId) {BoundHashOperations<String,Object,Object> ops = redisTemplate.boundHashOps(CartConstant.CART_PREFIX + userId);String cartJson = (String) ops.get(skuId.toString());CartItemVo cartItemVo = JSON.parseObject(cartJson, CartItemVo.class);cartItemVo.setCount(num);ops.put(skuId.toString(),JSON.toJSONString(cartItemVo));
}

7.在购物车删除商品

@GetMapping("/deleteItem")
public String deleteItem(@RequestParam("skuId") Long skuId, HttpSession session){MemberResponseTo memberResponseTo = (MemberResponseTo) session.getAttribute(AuthServerConstant.LOGIN_USER);cartService.deleteItem(skuId,memberResponseTo.getId());return "redirect:http://cart.gulimall.com/cart.html";
}
@Override
public void deleteItem(Long skuId, Long userId) {BoundHashOperations<String,Object,Object> ops = redisTemplate.boundHashOps(CartConstant.CART_PREFIX + userId);ops.delete(skuId.toString());
}

十一、消息队列RabbitMQ

RabbitMQ 学习参考:RabbitMQ_HotRabbit.的博客-CSDN博客

1.场景分析

前面的代码中,为了节省时间,使用了异步编排的操作。而现在,通过中间件消息队列,可以快速地相应客户并把待处理的消息放入队列供其他消费者消费。

**应用解耦:**在之前,订单系统会直接调用库存系统的库存接口,但是如果库存系统升级库存接口改变,那么修改订单系统会显得非常麻烦。所以我们在这俩中间加个中间消息队列,将订单系统调用的消息发送给RabbitMQ,库存系统直接订阅消息队列,完成操作。

**流量控制:**在秒杀业务中,后端接收到大量的请求导致资源耗尽,现在可以使用消息队列存储这些消息,慢慢放给后台,保证后台不会宕机。

2.概述

  1. 大多应用中,可通过消息服务中间件来提升系统异步通信、扩展解耦能力

  2. 消息服务中两个重要概念: 消息代理(message broker)和目的地(destination) 当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定目的地。

  3. 消息队列主要有两种形式的目的地

    1. 队列(queue):点对点消息通信(point-to-point)
    2. 主题(topic):发布(publish)/订阅(subscribe)消息通信
  4. 点对点式:

    • 消息发送者发送消息,消息代理将其放入一个队列中,消息接收者从队列中获 取消息内容,消息读取后被移出队列

    • 消息只有唯一的发送者和接受者,但并不是说只能有一个接收者

  5. 发布订阅式:

    • 发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个 主题,那么就会在消息到达时同时收到消息

  6. JMS(Java Message Service)JAVA消息服务:

    • 基于JVM消息代理的规范。ActiveMQ、HornetMQ是JMS实现

  7. AMQP(Advanced Message Queuing Protocol)

    • 高级消息队列协议,也是一个消息代理的规范,兼容JMS

    • RabbitMQ是AMQP的实现

  8. Spring支持

    • spring-jms提供了对JMS的支持 • spring-rabbit提供了对AMQP的支持

    • 需要ConnectionFactory的实现来连接消息代理

    • 提供JmsTemplate、RabbitTemplate来发送消息

    • @JmsListener(JMS)、@RabbitListener(AMQP)注解在方法上监听消息 代理发布的消息

    • @EnableJms、@EnableRabbit开启支持

  9. Spring Boot自动配置

    • JmsAutoConfiguration

    • RabbitAutoConfiguration

  10. 市面的MQ产品

    • ActiveMQ、RabbitMQ、RocketMQ、Kafka

3.核心概念

RabbitMQ简介: RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现。

核心概念:

  • Message

消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成, 这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可 能需要持久性存储)等。

  • Publisher

    消息的生产者,也是一个向交换器发布消息的客户端应用程序。

  • Exchange

    交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。 Exchange有4种类型:direct(默认),fanout, topic, 和headers,不同类型的Exchange转发消息的策略有所区别

  • Queue

    消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直 在队列里面,等待消费者连接到这个队列将其取走。

  • Binding

    绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交 换器理解成一个由绑定构成的路由表。 Exchange 和Queue的绑定可以是多对多的关系。

  • Connection

    网络连接,比如一个TCP连接。

  • Channel

    信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP 命令都是通过信道 发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都 是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。

  • Consumer

    消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。

  • Virtual Host

    虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加 密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥 有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时 指定,RabbitMQ 默认的 vhost 是 / 。

  • Broker

    表示消息队列服务器实体

4.docker安装

docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management
//设置自启动
docker update rabbitmq --restart=always
  • 4369, 25672 (Erlang发现&集群端口)
  • 5672, 5671 (AMQP端口)
  • 15672 (web管理后台端口)
  • 61613, 61614 (STOMP协议端口)
  • 1883, 8883 (MQTT协议端口

5.RabbitMQ 几种消息模式的测试

参考:RabbitMQ-03(实战 、RabbitMQ 的六种消息模式、SpringBoot整合RabbitMQ)_HotRabbit.的博客-CSDN博客

6.SpringBoot整合RabbitMQ

参考:RabbitMQ-04(SpringBoot整合RabbitMQ,基本使用)_HotRabbit.的博客-CSDN博客

谷粒商城12——购物车模块、消息队列RabbitMQ相关推荐

  1. 快速掌握消息队列RabbitMQ

    ※快速掌握消息队列RabbitMQ 一.RabbitMQ概述 (一)什么是消息队列MQ 消息队列(Message Queue),后文称MQ,是一种 跨进程的通信机制,用于上下游传递消息. MQ作为消息 ...

  2. 初识消息队列/RabbitMQ详解

    欢迎大家阅读<朝夕Net社区技术专刊> 我们致力于.NetCore的推广和落地,为更好的帮助大家学习,方便分享干货,特创此刊!很高兴你能成为忠实读者,文末福利不要错过哦! 今天来给大家分享 ...

  3. 消息队列RabbitMQ的使用

    最近在学习spring cloud微服务,当学习到spring cloud bus的时候,涉及到了消息队列,需要学习RabbitMQ. 一.消息队列 1.1介绍消息队列 消息队列,即MQ,Messag ...

  4. 消息队列RabbitMQ入门与PHP实战

    消息队列介绍以及消息队列应用场景 RabbitMQ 说明 MQ(Message Queue) 即消息队列,是应用间的通信方式,消息发送后可立即返回,由消息系统来确保消息的可靠传递."消息队列 ...

  5. RabbitMQ总结(一)--消息队列RabbitMQ应答模式(自动、手动)

    原文链接 消息队列RabbitMQ应答模式(自动.手动) 为了确保消息不会丢失,RabbitMQ支持消息应答.消费者发送一个消息应答,告诉RabbitMQ这个消息已经接收并且处理完毕了.RabbitM ...

  6. 消息队列RabbitMQ之初学者

    文章目录 消息队列 什么是消息队列 生产者和消费者 AMQP和JMS AMQP和JMS的区别 常见的MQ产品 RabbitMQ Erlang语言 RabbitMQ下载 什么是消息队列RabbitMQ? ...

  7. SpringCloud源码探析(六)-消息队列RabbitMQ

    1.概述 RabbitMQ是一个开源的消息代理和队列服务器,它是基于Erlang语言开发,并且是基于AMQP协议的.由于Erlang语言最初使用与交换机领域架构,因此使得RabbitMQ在Broker ...

  8. 消息队列——RabbitMQ消息队列集群

    RabbitMQ消息队列集群 消息队列/中间件 RabbitMQ详解 RabbitMQ单机部署 RabbitMQ集群部署 消息队列/中间件 一.前言 在我们秒杀抢购商品的时候,系统会提醒我们稍等排队中 ...

  9. Linux中级实战专题篇:rabbitmq(消息中间件p2p模式和pub模式,消息队列rabbitmq详解,单机安装,集群部署以及配置实战)

    一.消息中间件相关概念 1.简介 消息中间件也可以称消息队列,是指用高效可靠的消息传递机制进行与平台相关 的数据交流,并基于数据通信来进行分布式系统的集成.通过提供消息传递和消息 队列模型,可以在分布 ...

最新文章

  1. 强化学习vs遗传算法-人工智能在模拟领域的应用
  2. 【深度学习】nnU-Net(优秀的前处理和后处理框架)
  3. web相关概念回顾|| 部署项目的方式
  4. Spark查找某个IP的归属地,二分算法,try{}catch{}的使用,将结果存MySQL数据库
  5. Linux 文件系统在线扩容实战
  6. html:(17):img标签和表单标签
  7. OpenCV与图像处理学习二——图像直方图与色彩空间
  8. Elasticsearch 的使用,看这一篇就够了!
  9. OK6410裸机调试
  10. 使用Windows 8 的“任务计划”令HydraVision更加精彩
  11. c#事件的发布-订阅模型_NET Core 3 WPF MVVM框架 Prism系列之事件聚合器
  12. 一个优秀的Android应用从建项目开始
  13. linux 串口 抓包工具下载,Device Monitoring Studio串口抓包工具使用教程
  14. cimage和gdi绘图效率比较_使用MFC CImage类和GDI+ Image加载并绘制PNG图片
  15. 求职时,怎样判断一家公司是否靠谱?
  16. C++定时器CTimer的实现
  17. HiveSql面试题11详解(count(1)、count(*)和count(列名)的区别)
  18. CPA2021_税法_中华叶青_01_税法概念
  19. teechart的addarray_C# TeeChart的一些用法
  20. 免杀veil的简单使用

热门文章

  1. Bug现形记(一):一个多重继承程序的查错
  2. 护眼灯真能护眼睛吗?2022护眼台灯怎么样选择好
  3. WebService客户端三种调用方式整理
  4. 100 个知名网站源码
  5. 两相四线步进电机的驱动方法/驱动芯片用法
  6. CSS样式表的引入方式
  7. 打印机灯闪和解决方法
  8. 大专生出身?java如何导入excel数据
  9. 凝固永恒瞬间的艺术 纪实摄影到底怎么拍
  10. BUUCTF [极客大挑战 2019] PHP