SpringBoot整合RabbitMQ实现电商销售商品功能模块
1、本文主讲一下目前比较主流的信息中间件——rabbitmq
在当前电商横行的年代,对我们来说一年一度的“双十一”购物节应该不为陌生吧。“限时、秒杀、拼团、抢红包”这些高流量的词汇不正是由此而生。不过在这里重点不是这些,而是站在技术的角度来解析为什么这些电商网站能够在十几亿甚至几十亿流量中屹立不倒呢。这其中支撑这些高流量、高并发功能模块的主角也就是本文将要讲述的技术——消息中间件。当然消息中间件有很多种,比如rabbitmq、activemq、rocketmq以及大数据中常用到的kafka这些都是比较优秀的消息中间件,而本文是讲述rabbitmq。
2、rabbitmq简介
rabbit——兔子,官方估计想着这个中间件的信息传输速度能和兔子一样跑得飞快吧。。O(∩_∩)O哈哈~。还是回归主题比较好,目前软件应用都有一个趋势就是:微服务、大数据、高并发。显而易见微服务就是把原来一个大应用拆分成若干个小应用,然后使用再把这些小应用组合起来形成大应用。这是多么天才般的设计啊。这样做不仅降低了应用开发的复杂度和后期的可维护性,更是提升了应用模块的复用性,使得应用之间可以像排插一样高灵活。微服务虽然使应用的灵活性增高了,但是也带来了一个缺陷那就是微服务之间的联系就变得困难了,特别是在高并发的情况下很容易造成服务数据出错严重的就服务宕机。大势所趋,消息中间件恰恰就是为了解决这个问题所产生。rabbitmq 其实就是一种典型的生产者/消费者模型。它就像一根通信管道一个应用在一头生产消息,另一个应用在另一头消费消息,这样微服务的应用间通信困难问题就迎刃而解了。当然 rabbitmq 不仅仅只是消息传递的功能,它还有存储消息的服务器。当消费者忙不过来的时候它就会把待消费的信息保存起来,等待消费者空闲的时候再来取。正是其具有这样的功能才使得微服务应用在高并发下可以坑得住亿级的访问流量。A应用在正面迎接客户的访问不间断地把访问日志生产到队列中。B应用则需要在队列中取出这些访问日志分析进行下一步可能耗时的操作,所以B应用的消费消息速度就比A应用生成消息速度慢。如果 rabbitmq 没有存储消息的能力就会造成大量消息因为未被及时处理而造成数据丢失的情况,所以说消息中间件有个一个重要功能就是高并发削峰。如下图所示,rabbitmq内部运行机制。
3、rabbitmq 优点
- 开源免费、性能卓越,高稳定性
- 提供可靠性消息投递模式、返回模式
- 与Spring AMQP完美整合,API丰富
- 集群模式丰富,表达式配置,HA模式,镜像队列模型
- 保证数据不丢失的前提做到高可靠性、可用性
4、MQ典型应用场景(引用:https://www.cnblogs.com/sgh1023/p/11217017.html)
- 异步处理。把消息放入消息中间件中,等到需要的时候再去处理。
- 流量削峰。例如秒杀活动,在短时间内访问量急剧增加,使用消息队列,当消息队列满了就拒绝响应,跳转到错误页面,这样就可以使得系统不会因为超负载而崩溃。
- 日志处理
- 应用解耦。假设某个服务A需要给许多个服务(B、C、D)发送消息,当某个服务(例如B)不需要发送消息了,服务A需要改代码再次部署;当新加入一个服务(服务E)需要服务A的消息的时候,也需要改代码重新部署;另外服务A也要考虑其他服务挂掉,没有收到消息怎么办?要不要重新发送呢?是不是很麻烦,使用MQ发布订阅模式,服务A只生产消息发送到MQ,B、C、D从MQ中读取消息,需要A的消息就订阅,不需要了就取消订阅,服务A不再操心其他的事情,使用这种方式可以降低服务或者系统之间的耦合。
5、RabbitMQ的技术主干点(引用:https://www.cnblogs.com/sgh1023/p/11217017.html)
提到RabbitMQ,就不得不提AMQP协议。AMQP协议是具有现代特征的二进制协议。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
先了解一下AMQP协议中间的几个重要概念:
- Server:接收客户端的连接,实现AMQP实体服务。
- Connection:连接,应用程序与Server的网络连接,TCP连接。
- Channel:信道,消息读写等操作在信道中进行。客户端可以建立多个信道,每个信道代表一个长连接会话任务。
- Message:消息,应用程序和服务器之间传送的数据,消息可以非常简单,也可以很复杂。有Properties和Body组成。Properties为外包装,可以对消息进行修饰,比如消息的优先级、延迟等高级特性;Body就是消息体内容。
- Virtual Host:虚拟主机,用于逻辑隔离。一个虚拟主机里面可以有若干个Exchange和Queue,同一个虚拟主机里面不能有相同名称的Exchange或Queue。
- Exchange:交换器,接收消息,按照路由规则将消息路由到一个或者多个队列。如果路由不到,或者返回给生产者,或者直接丢弃。RabbitMQ常用的交换器常用类型有direct、topic、fanout、headers四种,后面详细介绍。
- Binding:绑定,交换器和消息队列之间的虚拟连接,绑定中可以包含一个或者多个RoutingKey。
- RoutingKey:路由键,生产者将消息发送给交换器的时候,会发送一个RoutingKey,用来指定路由规则,这样交换器就知道把消息发送到哪个队列。路由键通常为一个“.”分割的字符串,例如“com.rabbitmq”。
- Queue:消息队列,用来保存消息,供消费者消费。
6、本文要实现的功能模块,如下图所示。
功能的业务逻辑:
- 买家在网上选择商店,并进去查看商品列表,选择商品加入购物车。
- 买家选择完商品之后提交订单,系统会把订单信息生产到订单队列中。
- 订单队列监听器监听到队列有消息进行消费消息把订单信息入库,并生成该订单商品的减库存信息到商品队列中。
- 订单生成之后,系统会提示买家进行付款操作。
- 付款成功,则会把订单信息生成到发货队列中。付款失败,则会把订单商品信息生成到商品队列中恢复商品库存。
- 物流监听器监听到发货队列中存在发货信息,则把订单信息生成物流信息进行入库。
- 物流在运送途中实时更新物流信息到订单队列中做物流订单状态联动
7、安装并运行软件
在虚拟机上运行rabbitmq服务器,这里我采用的时候docker的方式跑rabbitmq服务
// 第一步:先拉去rabbitmq镜像 docker pull rabbitmq:3-management // 第二步:检查镜像情况 docker images // 第三步:运行rabbitmq容器 docker run -d --restart=always -p 5672:5672 -p 15672:15672 --nane mymq rabbitmq:3-management // 检查容器运行情况 docker ps
在浏览器上打开rabbitmq的客户端(http://虚拟机ip:15672/#/queues)
进去之后可以自行添加用户信息、分配权限、创建虚拟主机、交换机、队列、查看消费情况、效率等
8、整合springboot项目
首先在我们创建项目的时候就可以选择 rabbitmq 插件,也可以自己通过 maven 的方式导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency>
8.1、设置 rabbitmq 的初始参数
## 配置rabbitMQ 系统参数 spring.rabbitmq.host=虚拟机ip地址 spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest # 虚拟主机,默认主机"/",也可以自己创建主机 #spring.rabbitmq.virtual-host="/"
8.2、使用注解方式自动装配 rabbitmq 的插件
/** * 自动配置 * 1、RabbitAutoConfiguration * 2、有自动配置了连接工厂 ConnectionFactory * 3、RabbitProperties 封装了RabbitMQ 的配置信息 * 4、RabbitTemplate:操作RabbitMQ 发送和接收消息 * 5、AmqpAdmin:RabbitMQ 系统管理功能组件 * ——创建和删除queue(队列)、Exchange(交换器)、binding(连接) * 6、使用注解版消息中间件:@EnableRabbit + @RabbitListener */ @EnableRabbit // 开启消息队列机制 @SpringBootApplication public class SpringbootAmqpApplication { public static void main(String[] args) { SpringApplication.run(SpringbootAmqpApplication.class, args); } }
8.3、初始化交换机、队列和关联
①商品配置类
package com.example.amqp.config; import org.springframework.amqp.core.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Author: chaoyou * Date: 2021/1/10 0010 21:53 * Content:商品交换机(直连式)配置类 */ @Configuration public class GoodExchangeConfig { // 商品交换机 public static final String GOOD_EXCHANGE = "good.exchange"; // 减库存队列 public static final String GOOD_SUB_QUEUE = "good.sub"; // 恢复库存队列 public static final String GOOD_ADD_QUEUE = "good.add"; // 减库存队列与交换机的联系路由键 public static final String GOOD_SUB_ROUTINGKEY = "sub"; // 恢复库存队列与交换机的联系路由键 public static final String GOOD_ADD_ROUTINGKEY = "add"; /** * 初始化一个直连式交换机 */ @Bean public DirectExchange createDirectExchange(){ /** * @param name 交换机名称 * @param durable 是否持久化,默认是true,持久化交换机:会被存储在磁盘上,当消息代理重启时仍然存在,(false)暂存交换机:当前连接有效 * @param autoDelete 是否自动删除,当没有生产者或者消费者使用此交换机,该交换机会自动删除。 */ return new DirectExchange(GOOD_EXCHANGE, true, false); } /** * 初始化一个订单付款成功,商品减库存队列 */ @Bean public Queue createSubQueue(){ /** * @param name 队列名称 * @param durable 是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效 * @param exclusive 默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable * @param autoDelete 是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。 */ return new Queue(GOOD_SUB_QUEUE, true, false, false); } /** * 初始化一个订单付款失败,商品恢复库存队列 */ @Bean public Queue createAddQueue(){ /** * @param name 队列名称 * @param durable 是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效 * @param exclusive 默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable * @param autoDelete 是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。 */ return new Queue(GOOD_ADD_QUEUE, true, false, false); } /** * 把减库存队列和商品交换机搭建联系,并设置一个路由键(sub) */ @Bean public Binding createSubBinding(){ return BindingBuilder.bind(createSubQueue()).to(createDirectExchange()).with(GOOD_SUB_ROUTINGKEY); } /** * 把恢复库存队列和商品交换机搭建联系,并设置一个路由键(add) */ @Bean public Binding createAddBinding(){ return BindingBuilder.bind(createAddQueue()).to(createDirectExchange()).with(GOOD_ADD_ROUTINGKEY); } }
②订单配置类
package com.example.amqp.config; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Author: chaoyou * Date: 2021/1/10 0010 22:30 * Content:订单交换机(主题式)配置类 */ @Configuration public class OrderExchangeConfig { public static final String ORDER_EXCHANGE = "order.exchange"; public static final String ORDER_SAVE_QUEUE = "order.save"; public static final String ORDER_UPDATE_QUEUE = "order.update"; public static final String ORDER_LOG_QUEUE = "order.log"; public static final String ORDER_SAVE_ROUTINGKEY = "save"; public static final String ORDER_UPDATE_ROUTINGKEY = "update"; public static final String ORDER_LOG_ROUTINGKEY = "log_order"; @Bean public TopicExchange createOrderExchange(){ /** * @param name 交换机名称 * @param durable 是否持久化,默认是true,持久化交换机:会被存储在磁盘上,当消息代理重启时仍然存在,(false)暂存交换机:当前连接有效 * @param autoDelete 是否自动删除,默认是false,当没有生产者或者消费者使用此交换机,该交换机会自动删除。 */ return new TopicExchange(ORDER_EXCHANGE, true, false, null); } @Bean public Queue createOrderSaveQueue(){ /** * @param name 队列名称 * @param durable 是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效 * @param exclusive 默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable * @param autoDelete 是否自动删除, 默认false,当没有生产者或者消费者使用此队列,该队列会自动删除。 */ return new Queue(ORDER_SAVE_QUEUE, true, false, false); } @Bean public Queue createOrderUpdateQueue(){ /** * @param name 队列名称 * @param durable 是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效 * @param exclusive 默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable * @param autoDelete 是否自动删除, 默认false,当没有生产者或者消费者使用此队列,该队列会自动删除。 */ return new Queue(ORDER_UPDATE_QUEUE, true, false, false); } @Bean public Queue createOrderLogeQueue(){ /** * @param name 队列名称 * @param durable 是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效 * @param exclusive 默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable * @param autoDelete 是否自动删除, 默认false,当没有生产者或者消费者使用此队列,该队列会自动删除。 */ return new Queue(ORDER_LOG_QUEUE, true, false, false); } /** * 把减库存队列和商品交换机搭建联系,并设置一个路由键(order.save) */ @Bean public Binding createSaveOrderBinding(){ return BindingBuilder.bind(createOrderSaveQueue()).to(createOrderExchange()).with(ORDER_SAVE_ROUTINGKEY); } /** * 把减库存队列和商品交换机搭建联系,并设置一个路由键(order.save) */ @Bean public Binding createUpdateOrderBinding(){ return BindingBuilder.bind(createOrderUpdateQueue()).to(createOrderExchange()).with(ORDER_UPDATE_ROUTINGKEY); } /** * 把恢复库存队列和商品交换机搭建联系,并设置一个路由键(add) */ @Bean public Binding createLogOrderBinding(){ return BindingBuilder.bind(createOrderLogeQueue()).to(createOrderExchange()).with(ORDER_LOG_ROUTINGKEY); } }
③物流配置类
package com.example.amqp.config; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Author: chaoyou * Date: 2021/1/10 0010 23:07 * Content:物流交换机(主题式)配置类 */ @Configuration public class LogExchangeConfig { public static final String LOG_EXCHANGE = "log.exchange"; public static final String LOG_SAVE_QUEUE = "log.save"; public static final String LOG_UPDATE_QUEUE = "log.update"; public static final String LOG_SAVE_ROUTINGKEY = "save"; public static final String LOG_UPDATE_ROUTINGKEY = "update"; @Bean public TopicExchange createLogExchange(){ /** * @param name 交换机名称 * @param durable 是否持久化,默认是true,持久化交换机:会被存储在磁盘上,当消息代理重启时仍然存在,(false)暂存交换机:当前连接有效 * @param autoDelete 是否自动删除,默认是false,当没有生产者或者消费者使用此交换机,该交换机会自动删除。 */ return new TopicExchange(LOG_EXCHANGE, true, false, null); } @Bean public Queue createLogSaveQueue(){ /** * @param name 队列名称 * @param durable 是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效 * @param exclusive 默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable * @param autoDelete 是否自动删除, 默认false,当没有生产者或者消费者使用此队列,该队列会自动删除。 */ return new Queue(LOG_SAVE_QUEUE, true, false, false); } @Bean public Queue createLogUpdateQueue(){ /** * @param name 队列名称 * @param durable 是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效 * @param exclusive 默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable * @param autoDelete 是否自动删除, 默认false,当没有生产者或者消费者使用此队列,该队列会自动删除。 */ return new Queue(LOG_UPDATE_QUEUE, true, false, false); } /** * 把减库存队列和商品交换机搭建联系,并设置一个路由键(order.save) */ @Bean public Binding createLogSaveBinding(){ return BindingBuilder.bind(createLogSaveQueue()).to(createLogExchange()).with(LOG_SAVE_ROUTINGKEY); } /** * 把减库存队列和商品交换机搭建联系,并设置一个路由键(order.save) */ @Bean public Binding createLogUpdateBinding(){ return BindingBuilder.bind(createLogUpdateQueue()).to(createLogExchange()).with(LOG_UPDATE_ROUTINGKEY); } }
8.4、项目涉及到的实体类
①商品实体类
package com.example.amqp.domain; import com.example.amqp.enums.CodeTypeEnum; import com.example.amqp.utils.CodeUtil; import java.util.Date; /** * Author: chaoyou * Date: 2021/1/9 0009 16:34 * Content:商品信息表 */ public class Good { /** * 商品编码 */ private String code = CodeUtil.getCodeMsg(CodeTypeEnum.good); /** * 商品名称 */ private String name; /** * 商品库存 */ private Long nums; /** * 商品销售价 */ private Double price; /** * 商品类型:1、书籍,2、食品,3、电器,4、衣服,5、其他 */ private Integer type = 5; /** * 商品生产时间 */ private Date produceTime; /** * 商品颜色 */ private String color; public Good() { } public Good(String name, Long nums, Double price, Integer type, Date produceTime, String color) { this.name = name; this.nums = nums; this.price = price; this.type = type; this.produceTime = produceTime; this.color = color; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getType() { return type; } public void setType(Integer type) { this.type = type; } public Date getProduceTime() { return produceTime; } public void setProduceTime(Date produceTime) { this.produceTime = produceTime; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public Long getNums() { return nums; } public void setNums(Long nums) { this.nums = nums; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } @Override public String toString() { return "Good{" + "code='" + code + '\'' + ", name='" + name + '\'' + ", type=" + type + ", produceTime=" + produceTime + ", color='" + color + '\'' + '}'; } }
②订单实体类
package com.example.amqp.domain; import com.example.amqp.enums.CodeTypeEnum; import com.example.amqp.utils.CodeUtil; import jdk.nashorn.internal.objects.annotations.Setter; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Author: chaoyou * Date: 2021/1/9 0009 16:34 * Content:订单信息表 */ public class Order { /** * 订单编码 */ private String code = CodeUtil.getCodeMsg(CodeTypeEnum.order); /** * 客户ID */ private String customerId; /** * 绑定商品信息:key(商品编码)、value(购买数量) */ private Map<String, Long> goodCodes = new LinkedHashMap<>(); /** * 订单金额 */ private Double sales; /** * 联系方式 */ private String phone; /** * 配送地址 */ private String address; /** * 是否付款:0、未付款,1、已付款 */ private Integer isPay = 0; /** * 是否付款:0、未完成,1、已完成 */ private Integer isSuccess = 0; public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getCustomerId() { return customerId; } public void setCustomerId(String customerId) { this.customerId = customerId; } public Map<String, Long> getGoodCodes() { return goodCodes; } public void setGoodCodes(Map<String, Long> goodCodes) { this.goodCodes = goodCodes; } public Double getSales() { return sales; } public void setSales(Double sales) { this.sales = sales; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public Integer getIsPay() { return isPay; } public Integer getIsSuccess() { return isSuccess; } public void setIsSuccess(Integer isSuccess) { this.isSuccess = isSuccess; } public void setIsPay(Integer isPay) { this.isPay = isPay; } public Order() { } public Order(String customerId, Map<String, Long> goodCodes, Double sales, String phone, String address, Integer isPay, Integer isSuccess) { this.customerId = customerId; this.goodCodes = goodCodes; this.sales = sales; this.phone = phone; this.address = address; this.isPay = isPay; this.isSuccess = isSuccess; } @Override public String toString() { return "Order{" + "code='" + code + '\'' + ", customerId='" + customerId + '\'' + ", goodCodes=" + goodCodes + ", sales=" + sales + ", phone='" + phone + '\'' + ", address='" + address + '\'' + ", isPay=" + isPay + ", isSuccess=" + isSuccess + '}'; } }
③物流实体类
package com.example.amqp.domain; import com.example.amqp.enums.CodeTypeEnum; import com.example.amqp.utils.CodeUtil; import java.util.ArrayList; import java.util.List; /** * Author: chaoyou * Date: 2021/1/9 0009 20:30 * Content:物流信息库 */ public class Logistics { /** * 物流编码 */ private String code = CodeUtil.getCodeMsg(CodeTypeEnum.logistics); /** * 物流类型:1、韵达,2、中通,3、申通,4、圆通,5、顺丰,6、邮政,7、其他 */ private Integer type = 7; /** * 订单编码 */ private String orderCode; /** * 物流状态:1、揽件中,2、转运中,3、到达目的城市,4、派件中,5、完成 */ private Integer status = 1; /** * 追踪各物流状态状态下负责人信息 */ private List<LogisticsStatusMsg> logisticsStatusMsgs = new ArrayList<>(); public Logistics() { } public Logistics(String code, Integer type, String orderCode, Integer status, List<LogisticsStatusMsg> logisticsStatusMsgs) { this.code = code; this.type = type; this.orderCode = orderCode; this.status = status; this.logisticsStatusMsgs = logisticsStatusMsgs; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public Integer getType() { return type; } public void setType(Integer type) { this.type = type; } public String getOrderCode() { return orderCode; } public void setOrderCode(String orderCode) { this.orderCode = orderCode; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public List<LogisticsStatusMsg> getLogisticsStatusMsgs() { return logisticsStatusMsgs; } public void setLogisticsStatusMsgs(List<LogisticsStatusMsg> logisticsStatusMsgs) { this.logisticsStatusMsgs = logisticsStatusMsgs; } public LogisticsStatusMsg getLogisticsStatusMsg(){ return new LogisticsStatusMsg(); } @Override public String toString() { return "Logistics{" + "code='" + code + '\'' + ", type=" + type + ", orderCode='" + orderCode + '\'' + ", status=" + status + ", logisticsStatusMsgs=" + logisticsStatusMsgs + '}'; } }
④物流运输状态实体类
package com.example.amqp.domain; /** * Author: chaoyou * Date: 2021/1/10 0010 17:55 * Content:物流运输状态信息表 */ public class LogisticsStatusMsg { /** * 负责人 */ private String username; /** * 联系方式 */ private String phone; /** * 所在地 */ private String address; /** * 物流状态标识 */ private Integer status; public LogisticsStatusMsg() { } public LogisticsStatusMsg(String username, String phone, String address, Integer status) { this.username = username; this.phone = phone; this.address = address; this.status = status; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } @Override public String toString() { return "LogisticsStatusMsg{" + "username='" + username + '\'' + ", phone='" + phone + '\'' + ", address='" + address + '\'' + ", status=" + status + '}'; } }
⑤商店实体类
package com.example.amqp.domain; import java.util.ArrayList; import java.util.List; /** * Author: chaoyou * Date: 2021/1/9 0009 21:06 * Content:商品店信息表 */ public class Store { /** * 商店名称 */ private String name = "惠民商店"; /** * 负责人 */ private String username = "chaoyou"; /** * 商品信息列表 */ List<Good> goodsList = new ArrayList<>(); public String getName() { return name; } public void setName(String name) { this.name = name; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public List<Good> getGoodsList() { return goodsList; } public void setGoodsList(List<Good> goodsList) { this.goodsList = goodsList; } /** * 商店对象 */ private volatile static Store store = null; private Store() { } private Store(String name) { this.name = name; } /** * 线程安全的懒汉式(双重检查加锁) */ public static Store getStoreObj(String name){ if (null == store){ synchronized (Store.class){ if (null == store){ if (null != name && !"".equals(name)){ store = new Store(name); } else { store = new Store(); } } } } return store; } @Override public String toString() { return "Store{" + "name='" + name + '\'' + ", username='" + username + '\'' + ", goodsList=" + goodsList + '}'; } }
8.5、service 层接口和实现类
①商品
package com.example.amqp.service; import com.example.amqp.domain.Good; import com.example.amqp.domain.Order; import java.util.List; /** * Author: chaoyou * Date: 2021/1/10 0010 15:36 * Content:商品的service接口类 */ public interface GoodService { String save(Good good); String update(Good good); String delete(String...codes); Good select(String code); List<Good> selectList(String...codes); /** * @消息监听接口 * * 订单入库:减持该订单商品的库存数量 */ void subGoodNums(Order order); /** * @消息监听接口 * * 付款失败:恢复此订单商品的库存数量 */ void addGoodNums(Order order); }
package com.example.amqp.service.impl; import com.example.amqp.config.GoodExchangeConfig; import com.example.amqp.domain.Good; import com.example.amqp.domain.Order; import com.example.amqp.service.GoodService; import org.apache.commons.lang3.StringUtils; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Author: chaoyou * Date: 2021/1/10 0010 15:39 * Content: */ @Service public class GoodServiceImpl implements GoodService { private volatile static Map<String, Good> goods = null; public static void setGoods(Map<String, Good> goods) { GoodServiceImpl.goods = goods; } public GoodServiceImpl() { } public static Map<String, Good> getGoodList(){ if (null == goods){ synchronized (GoodServiceImpl.class){ if (null == goods){ goods = new LinkedHashMap<>(); } } } return goods; } @Override public String save(Good good) { if (null == good || StringUtils.isBlank(good.getCode())){ throw new NullPointerException("the good object or good.code field is not null for save"); } String result = ""; Map<String, Good> goodMap = getGoodList(); Good value = goodMap.get(good.getCode()); if (null == value){ goodMap.put(good.getCode(), good); result = "success"; } else { result = update(good); } return result; } @Override public String update(Good good) { if (null == good || StringUtils.isBlank(good.getCode())){ throw new NullPointerException("the good object or good.code field is not null for update"); } String result = ""; Map<String, Good> goodMap = getGoodList(); Good value = goodMap.get(good.getCode()); if (null == value){ result = "error"; } else { BeanUtils.copyProperties(good, value); goodMap.put(good.getCode(), value); result = "success"; } return result; } @Override public String delete(String... codes) { if (null == codes){ throw new NullPointerException("the good.code fields is not null for delete"); } Map<String, Good> temp = new LinkedHashMap<>(getGoodList()); Map<String, Good> goodMap = getGoodList(); for (String code : codes){ if (StringUtils.isBlank(code)){ throw new NullPointerException("the good.code fields is not null or '' for delete"); } else { if (null == goodMap.get(code)){ throw new NullPointerException("this key of good.code not exist in this goodMap"); } else { temp.remove(code); } } } setGoods(temp); return "success"; } @Override public Good select(String code) { if (StringUtils.isBlank(code)){ throw new NullPointerException("the good.code fields is not null for select"); } Map<String, Good> goodList = getGoodList(); return goodList.get(code); } @Override public List<Good> selectList(String... codes) { if (null == codes){ throw new NullPointerException("the good.code fields is not null for select"); } Map<String, Good> goodMap = getGoodList(); List<Good> goods = new ArrayList<>(); for (String code : codes){ Good good = goodMap.get(code); if (null != good){ goods.add(good); } } return goods; } @RabbitListener(queues = GoodExchangeConfig.GOOD_SUB_QUEUE) @Override public void subGoodNums(Order order) { if (null == order){ throw new NullPointerException("the order is not null for subGoodNums"); } Map<String, Long> codeMapNum = order.getGoodCodes(); if (codeMapNum.size() < 1){ return ; } Map<String, Good> goodMap = getGoodList(); System.out.println("生成订单,去库存后的商品信息:"); for (String code : codeMapNum.keySet()){ Good good = goodMap.get(code); if (null == good){ throw new NullPointerException("the store not exist this good"); } Long nums = good.getNums(); Long sub = codeMapNum.get(code); if (nums < sub){ throw new IndexOutOfBoundsException("the good.nums less than order.nums"); } else { good.setNums(nums - sub); } goodMap.put(code, good); System.out.println("商品名:" + good.getName() + ", 库存量:" + good.getNums()); } } @RabbitListener(queues = "good.add") @Override public void addGoodNums(Order order) { if (null == order){ throw new NullPointerException("the order is not null for addGoodNums"); } Map<String, Long> codeMapNum = order.getGoodCodes(); if (codeMapNum.size() < 1){ return; } Map<String, Good> goodMap = getGoodList(); System.out.println("订单未完成付款,恢复库存后的商品信息:"); for (String code : codeMapNum.keySet()){ Good good = goodMap.get(code); if (null == good){ throw new NullPointerException("the store not exist this good"); } good.setNums(good.getNums() + codeMapNum.get(code)); goodMap.put(code, good); System.out.println("商品名:" + good.getName() + ", 库存量:" + good.getNums()); } } }
②订单
package com.example.amqp.service; import com.example.amqp.domain.Logistics; import com.example.amqp.domain.Order; import java.util.List; /** * Author: chaoyou * Date: 2021/1/10 0010 16:43 * Content:订单的service接口类 */ public interface OrderService { String save(Order order); String update(Order order); String delete(String...orders); Order select(String order); List<Order> selectList(String...orders); /** * @消息监听接口 * * 完成下单:持久化该订单信息入库 */ void addOrder(Order order); /** * @消息监听接口 * * 订单发货:客户完成订单付款之后会通知该订单进行发货处理 */ void ship(String code); /** * @消息监听接口 * * 物流联动订单:根据物流状态列反馈信息实时更新订单 */ void logOrder(Logistics logistics); }
package com.example.amqp.service.impl; import com.example.amqp.config.GoodExchangeConfig; import com.example.amqp.config.LogExchangeConfig; import com.example.amqp.config.OrderExchangeConfig; import com.example.amqp.domain.Logistics; import com.example.amqp.domain.Order; import com.example.amqp.service.OrderService; import com.sun.org.apache.xpath.internal.operations.Or; import org.apache.commons.lang3.StringUtils; import org.springframework.amqp.core.AmqpAdmin; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Author: chaoyou * Date: 2021/1/10 0010 16:46 * Content: */ @Service public class OrderServiceImpl implements OrderService { @Autowired private RabbitTemplate rabbitTemplate; private volatile static Map<String, Order> orderMap = null; public static void setOrderMap(Map<String, Order> orderMap) { OrderServiceImpl.orderMap = orderMap; } public OrderServiceImpl() { } public static Map<String, Order> getOrderMap(){ if (null == orderMap){ synchronized (OrderServiceImpl.class){ if (null == orderMap){ orderMap = new LinkedHashMap<>(); } } } return orderMap; } @Override public String save(Order order) { if (null == order || StringUtils.isBlank(order.getCode())){ throw new NullPointerException("the order object or order.code field is not null for save"); } String result = ""; Map<String, Order> orderMap = getOrderMap(); Order value = orderMap.get(order.getCode()); if (null == value){ orderMap.put(order.getCode(), order); result = "success"; } else { result = update(order); } return result; } @Override public String update(Order order) { if (null == order || StringUtils.isBlank(order.getCode())){ throw new NullPointerException("the order object or order.code field is not null for update"); } String result = ""; Map<String, Order> orderMap = getOrderMap(); Order value = orderMap.get(order.getCode()); if (null == value){ result = "error"; } else { BeanUtils.copyProperties(order, value); orderMap.put(order.getCode(), value); result = "success"; } return result; } @Override public String delete(String... codes) { if (null == codes){ throw new NullPointerException("the order.code fields is not null for delete"); } Map<String, Order> temp = new LinkedHashMap<>(getOrderMap()); Map<String, Order> orderMap = getOrderMap(); for (String code : codes){ if (StringUtils.isBlank(code)){ throw new NullPointerException("the order.code fields is not null or '' for delete"); } else { if (null == orderMap.get(code)){ throw new NullPointerException("this key of order.code not exist in this orderMap"); } else { temp.remove(code); } } } setOrderMap(temp); return "success"; } @Override public Order select(String code) { if (StringUtils.isBlank(code)){ throw new NullPointerException("the order.code fields is not null for select"); } return getOrderMap().get(code); } @Override public List<Order> selectList(String... codes) { if (null == codes){ throw new NullPointerException("the order.code fields is not null for select"); } Map<String, Order> orderMap = getOrderMap(); List<Order> orders = new ArrayList<>(); for (String code : codes){ Order order = orderMap.get(code); if (null != order){ orders.add(order); } } return orders; } @RabbitListener(queues = OrderExchangeConfig.ORDER_SAVE_QUEUE) @Override public void addOrder(Order order) { save(order); System.out.println("生成订单:" + order); rabbitTemplate.convertAndSend(GoodExchangeConfig.GOOD_EXCHANGE, GoodExchangeConfig.GOOD_SUB_ROUTINGKEY, order); } @RabbitListener(queues = OrderExchangeConfig.ORDER_UPDATE_QUEUE) @Override public void ship(String code) { if (StringUtils.isBlank(code)){ throw new NullPointerException("the order.code fields is not null for select"); } Map<String, Order> orderMap = getOrderMap(); Order order = orderMap.get(code); if (null != order){ order.setIsPay(1); rabbitTemplate.convertAndSend(LogExchangeConfig.LOG_EXCHANGE, LogExchangeConfig.LOG_SAVE_ROUTINGKEY, order); System.out.println("订单发货:" + order); } orderMap.put(code, order); } @RabbitListener(queues = OrderExchangeConfig.ORDER_LOG_QUEUE) @Override public void logOrder(Logistics logistics) { if (null == logistics || StringUtils.isBlank(logistics.getCode())){ throw new NullPointerException("the log object or log.code field is not null for update"); } if (5 == logistics.getStatus()){ Order order = select(logistics.getOrderCode()); order.setIsSuccess(1); update(order); System.out.println("完成订单:" + order); } else { System.out.println("订单在运送中:" + logistics); } } }
③物流
package com.example.amqp.service; import com.example.amqp.domain.Logistics; import com.example.amqp.domain.Order; import com.sun.org.apache.xpath.internal.operations.Or; import java.util.List; /** * Author: chaoyou * Date: 2021/1/10 0010 17:21 * Content:物流信息的service接口类 */ public interface LogisticsService { String save(Logistics log); String update(Logistics log); String delete(String...codes); Logistics select(String code); List<Logistics> selectList(String...codes); /** * @消息监听接口 * * 物流入库:物流收到发货队列传递的发货信息后,持久化该物流信息 */ void addLogistics(Order order); /** * @消息监听接口 * * 物流联动订单:物资在配送后会定时发送物流状态到订单进行信息联动 */ void connectOrder(Logistics logistics); /** * @消息监听接口 * * 物流下一站点:启动物流配送下一步流程 */ void nextStep(String...codes); }
package com.example.amqp.service.impl; import com.example.amqp.config.LogExchangeConfig; import com.example.amqp.config.OrderExchangeConfig; import com.example.amqp.domain.Logistics; import com.example.amqp.domain.LogisticsStatusMsg; import com.example.amqp.domain.Order; import com.example.amqp.service.LogisticsService; import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Author: chaoyou * Date: 2021/1/10 0010 17:23 * Content: */ @Service public class LogisticsServiceImpl implements LogisticsService { private static final String[] usernames = {"张三", "李四", "王五", "赵六"}; private static final String[] phones = {"123456", "234567", "345678", "456789"}; private static final String[] addresss = {"上海", "北京", "广州", "深圳"}; @Autowired private RabbitTemplate rabbitTemplate; private volatile static Map<String, Logistics> logisticsMap = null; public static void setLogisticsMap(Map<String, Logistics> logisticsMap) { LogisticsServiceImpl.logisticsMap = logisticsMap; } public LogisticsServiceImpl() { } public static Map<String, Logistics> getLogisticsMap(){ if (null == logisticsMap){ synchronized (OrderServiceImpl.class){ if (null == logisticsMap){ logisticsMap = new LinkedHashMap<>(); } } } return logisticsMap; } @Override public String save(Logistics log) { if (null == log || StringUtils.isBlank(log.getCode())){ throw new NullPointerException("the log object or log.code field is not null for save"); } String result = ""; Map<String, Logistics> logisticsMap = getLogisticsMap(); Logistics value = logisticsMap.get(log.getCode()); if (null == value){ logisticsMap.put(log.getCode(), log); result = "success"; } else { result = update(log); } return result; } @Override public String update(Logistics log) { if (null == log || StringUtils.isBlank(log.getCode())){ throw new NullPointerException("the log object or log.code field is not null for update"); } String result = ""; Map<String, Logistics> orderMap = getLogisticsMap(); Logistics value = orderMap.get(log.getCode()); if (null == value){ result = "error"; } else { BeanUtils.copyProperties(log, value); orderMap.put(log.getCode(), value); result = "success"; } return result; } @Override public String delete(String... codes) { if (null == codes){ throw new NullPointerException("the log.code fields is not null for delete"); } Map<String, Logistics> temp = new LinkedHashMap<>(getLogisticsMap()); Map<String, Logistics> logisticsMap = getLogisticsMap(); for (String code : codes){ if (StringUtils.isBlank(code)){ throw new NullPointerException("the log.code fields is not null or '' for delete"); } else { if (null == logisticsMap.get(code)){ throw new NullPointerException("this key of log.code not exist in this logisticsMap"); } else { temp.remove(code); } } } setLogisticsMap(temp); return "success"; } @Override public Logistics select(String code) { if (StringUtils.isBlank(code)){ throw new NullPointerException("the log.code fields is not null for select"); } return getLogisticsMap().get(code); } @Override public List<Logistics> selectList(String... codes) { if (null == codes){ throw new NullPointerException("the logistics.code fields is not null for select"); } Map<String, Logistics> orderMap = getLogisticsMap(); List<Logistics> logistics = new ArrayList<>(); for (String code : codes){ Logistics log = orderMap.get(code); if (null != logistics){ logistics.add(log); } } return logistics; } @RabbitListener(queues = LogExchangeConfig.LOG_SAVE_QUEUE) @Override public void addLogistics(Order order) { if (null == order || StringUtils.isBlank(order.getCode())){ throw new NullPointerException("the order object or order.code field is not null for update"); } Logistics logistics = new Logistics(); logistics.setOrderCode(order.getCode()); logistics.setStatus(RandomUtils.nextInt(1, 4)); logistics.setType(RandomUtils.nextInt(1, 7)); List<LogisticsStatusMsg> logisticsStatusMsgs = logistics.getLogisticsStatusMsgs(); LogisticsStatusMsg statusMsg = logistics.getLogisticsStatusMsg(); statusMsg.setUsername(usernames[RandomUtils.nextInt(1, 4)]); statusMsg.setPhone(phones[RandomUtils.nextInt(1,4)]); statusMsg.setAddress(addresss[RandomUtils.nextInt(1, 4)]); statusMsg.setStatus(RandomUtils.nextInt(1, 4)); logisticsStatusMsgs.add(statusMsg); logistics.setLogisticsStatusMsgs(logisticsStatusMsgs); save(logistics); rabbitTemplate.convertAndSend(OrderExchangeConfig.ORDER_EXCHANGE, OrderExchangeConfig.ORDER_LOG_ROUTINGKEY, logistics); System.out.println("生成物流信息:" + logistics); } @RabbitListener(queues = LogExchangeConfig.LOG_UPDATE_QUEUE) @Override public void connectOrder(Logistics logistics) { List<LogisticsStatusMsg> logisticsStatusMsgs = logistics.getLogisticsStatusMsgs(); LogisticsStatusMsg statusMsg = logistics.getLogisticsStatusMsg(); statusMsg.setUsername(usernames[RandomUtils.nextInt(1, 4)]); statusMsg.setPhone(phones[RandomUtils.nextInt(1,4)]); statusMsg.setAddress(addresss[RandomUtils.nextInt(1, 4)]); statusMsg.setStatus(logistics.getStatus()); logisticsStatusMsgs.add(statusMsg); logistics.setLogisticsStatusMsgs(logisticsStatusMsgs); update(logistics); rabbitTemplate.convertAndSend(OrderExchangeConfig.ORDER_EXCHANGE, OrderExchangeConfig.ORDER_LOG_ROUTINGKEY, logistics); System.out.println("更新物流信息:" + logistics); } @Override public void nextStep(String...codes) { if (null == codes){ throw new NullPointerException("the logistics.code fields is not null for select"); } for (String code : codes){ Logistics logistics = select(code); if (null != logistics){ rabbitTemplate.convertAndSend(LogExchangeConfig.LOG_EXCHANGE, LogExchangeConfig.LOG_UPDATE_ROUTINGKEY, logistics); System.out.println("转运下一站物流信息:" + logistics); } } } }
④商店
package com.example.amqp.service; import com.example.amqp.domain.Store; /** * Author: chaoyou * Date: 2021/1/9 0009 21:09 * Content: */ public interface StoreService { Store getStoreMsg(String name); }
package com.example.amqp.service.impl; import com.example.amqp.domain.Good; import com.example.amqp.domain.Store; import com.example.amqp.enums.GoodType; import com.example.amqp.service.GoodService; import com.example.amqp.service.StoreService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; /** * Author: chaoyou * Date: 2021/1/9 0009 21:11 * Content: */ @Service public class StoreServiceImpl implements StoreService { @Autowired private GoodService goodService; @Override public Store getStoreMsg(String name) { Store store = Store.getStoreObj(name); Good good01 = new Good(); good01.setName("java核心技术"); good01.setNums(20l); good01.setPrice(25.6); good01.setType(GoodType.book.getIndex()); good01.setProduceTime(new Date(2019, 04, 12, 14, 23, 34)); good01.setColor("red"); goodService.save(good01); Good good02 = new Good(); good02.setName("JavaScript艺术经典"); good02.setNums(45l); good02.setPrice(23.2); good02.setType(GoodType.book.getIndex()); good02.setProduceTime(new Date(2017, 10, 15, 14, 23, 34)); good02.setColor("blue"); goodService.save(good02); Good good03 = new Good(); good03.setName("电饭煲"); good03.setNums(100l); good03.setPrice(258.0); good03.setType(GoodType.device.getIndex()); good03.setProduceTime(new Date(2020, 10, 15, 14, 23, 34)); good03.setColor("break"); goodService.save(good03); store.setGoodsList(new ArrayList<>(GoodServiceImpl.getGoodList().values())); return store; } }
8.6、工具类和枚举类
①生成编码的工具类
package com.example.amqp.utils; import com.example.amqp.enums.CodeTypeEnum; import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.StringUtils; import java.net.InetAddress; import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.IntStream; /** * Author: chaoyou * Date: 2021/1/9 0009 21:29 * Content:生成各种编码工具类 */ public class CodeUtil { /** * 设置一个并发量数值范围 */ private static final Integer CONCURRENTS = 1000; /** * 设置一个四位数的原子性数字(这个数字定义的位数决定并发量的扩展) **/ private static final AtomicInteger SEQ = new AtomicInteger(CONCURRENTS); /** * 时间格式 */ private static final DateTimeFormatter DF_FMT_PREFIX = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSS"); /** * 时区 */ private static ZoneId ZONE_ID = ZoneId.of("Asia/Shanghai"); /** * 应用所在网络的IP号 */ private volatile static String IP_SUFFIX = null; /** * 设置一个可重用锁对象 */ private static ReentrantLock lock = new ReentrantLock(); /** * 编码 */ private static StringBuffer code = null; public static String getCodeMsg(CodeTypeEnum typeEnum) { if (null == typeEnum){ throw new NullPointerException("The `CodeTypeEnum` enum is not null!"); } if (null == code){ code = new StringBuffer(); } else { code.setLength(0); } String value = typeEnum.getValue(); if (null == value && "".equals(value)){ throw new NullPointerException("The `CodeTypeEnum.value` field is not null!"); } code.append((value.hashCode()+"").substring(1, 5)).append(generateOrderNo()); return code.toString(); } /** * 获取当前服务器ip地址后四位数字 */ public static String getIpSuffix() { if (IP_SUFFIX != null){ return IP_SUFFIX; } try { lock.lock(); if(null != IP_SUFFIX){ return IP_SUFFIX; } InetAddress addr = InetAddress.getLocalHost(); String hostAddress = addr.getHostAddress(); if (null != hostAddress && hostAddress.length() > 4) { String ipString = hostAddress.trim().replace(".", ""); IP_SUFFIX = ipString.substring(ipString.length() - 4, ipString.length()); return IP_SUFFIX; } } catch (Exception e){ e.printStackTrace(); } finally { lock.unlock(); } return IP_SUFFIX; } /** * 生成一个订单号 = 当前时间(yyyyMMddHHmmssSS) + 四位数字 + 服务器ip地址后四位数字 * * @return */ public static String generateOrderNo() { // 订单号 String orderNum = null; // jdk8 获取时间的方式 LocalDateTime dataTime = LocalDateTime.now(ZONE_ID); // 使得 SEQ 始终保持在四位数之间 StringBuffer buffer = new StringBuffer(); for (int i=0; i<CONCURRENTS.toString().length(); i++){ buffer.append(9); } if (SEQ.intValue() > Integer.parseInt(buffer.toString())) { // 重新初始化为1000 SEQ.getAndSet(CONCURRENTS); } orderNum = dataTime.format(DF_FMT_PREFIX) + SEQ.getAndIncrement() + getIpSuffix(); if (StringUtils.isBlank(orderNum)){ orderNum = generateOrderNo(); } return orderNum; } /** * OD单号生成 * 订单号生成规则:OD + yyMMddHHmmssSSS + 5位数(商户ID3位+随机数2位) 22位 */ public static String getYYMMDDHHNumber(String merchId) { StringBuffer orderNo = new StringBuffer(new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date())); if (StringUtils.isNotBlank(merchId)) { if (merchId.length() > 3) { orderNo.append(merchId.substring(0, 3)); } else { orderNo.append(merchId); } } int orderLength = orderNo.toString().length(); String randomNum = getRandomByLength(20 - orderLength); orderNo.append(randomNum); return orderNo.toString(); } /** * 生成3~8位数的随机数 **/ public static String getRandomByLength(int size) { if (size > 8 || size < 3) { return ""; } StringBuffer endNumStr = new StringBuffer("10"); StringBuffer staNumStr = new StringBuffer("9"); for (int i = 1; i < size; i++) { endNumStr.append("0"); staNumStr.append("0"); } int randomNum = RandomUtils.nextInt(Integer.valueOf(staNumStr.toString()), Integer.valueOf(endNumStr.toString())); return String.valueOf(randomNum); } public static void main(String[] args){ String code = getCodeMsg(CodeTypeEnum.good); System.out.println("商品:" + code); code = getCodeMsg(CodeTypeEnum.order); System.out.println("订单:" + code); code = getCodeMsg(CodeTypeEnum.logistics); System.out.println("物流:" + code); } }
②编码类型枚举类
package com.example.amqp.enums; /** * Author: chaoyou * Date: 2021/1/9 0009 21:35 * Content:编码类型枚举信息类 */ public enum CodeTypeEnum { good(1, "商品"), order(2, "订单"), logistics(3, "物流"); private Integer index; private String value; private CodeTypeEnum(Integer index,String value){ this.index=index; this.value=value; } public static CodeTypeEnum getByIndex(Integer index) { for(CodeTypeEnum typeEnum : CodeTypeEnum.values()) { if(typeEnum.index == index) { return typeEnum; } } throw new IllegalArgumentException("No element matches " + index); } public Integer getIndex() { return index; } public void setIndex(Integer index) { this.index = index; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } }
③商品类型枚举类
package com.example.amqp.enums; /** * Author: chaoyou * Date: 2021/1/9 0009 23:53 * Content:商品类型枚举类:1、书籍,2、食品,3、电器,4、衣服,5、其他 */ public enum GoodType { book(1, "书籍"), food(2, "食品"), device(3, "电器"), clothes(4, "衣服"), other(5, "其他"); private Integer index; private String value; private GoodType(Integer index,String value){ this.index=index; this.value=value; } public static GoodType getByIndex(Integer index) { for(GoodType typeEnum : GoodType.values()) { if(typeEnum.index == index) { return typeEnum; } } throw new IllegalArgumentException("No element matches " + index); } public Integer getIndex() { return index; } public void setIndex(Integer index) { this.index = index; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } }
8.7、解决消息传递过程中序列化问题
package com.example.amqp.config; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Author: chaoyou * Date: 2019/8/18 0018 18:34 * Content:这是用来自定义 AMQP 的配置参数的配置类 */ @Configuration public class MyAMQPConfig { /** * @Description 解决消息传递过程中序列化问题 * @return */ @Bean public MessageConverter messageConverter(){ return new Jackson2JsonMessageConverter(); } }
8.8、设置一个测试controller
package com.example.amqp.controller; import com.example.amqp.config.GoodExchangeConfig; import com.example.amqp.config.LogExchangeConfig; import com.example.amqp.config.OrderExchangeConfig; import com.example.amqp.domain.Good; import com.example.amqp.domain.Logistics; import com.example.amqp.domain.Order; import com.example.amqp.domain.Store; import com.example.amqp.service.BookService; import com.example.amqp.service.GoodService; import com.example.amqp.service.LogisticsService; import com.example.amqp.service.StoreService; import com.example.amqp.service.impl.BookServiceImpl; import com.example.amqp.service.impl.LogisticsServiceImpl; import com.example.amqp.service.impl.StoreServiceImpl; import org.apache.commons.lang3.RandomUtils; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.*; import java.util.stream.Collectors; /** * Author: chaoyou * Date: 2021/1/10 0010 23:57 * Content: */ @RestController public class TestController { @Autowired private StoreService storeService; @Autowired private GoodService goodService; @Autowired private LogisticsService logisticsService; @Autowired RabbitTemplate rabbitTemplate; @RequestMapping("/test") public void test() throws InterruptedException { Scanner scanner = new Scanner(System.in); System.out.println("================================进入商店================================"); System.out.println("请搜索商店名称:"); String storeName = scanner.nextLine(); Store store = storeService.getStoreMsg(storeName); if (null != store){ System.out.println("================================展示商店的商品信息================================"); System.out.println("welcome to " + storeName + ", 下列为本商店的商品:"); List<Good> goodList = store.getGoodsList(); for (int i=0; i<goodList.size(); i++){ System.out.println(i + 1 + "、" + "商品名:" + goodList.get(i).getName() + ", 库存量:" + goodList.get(i).getNums()); } System.out.println("================================选择商品================================"); System.out.println("请输入要购买的商品编号和数量(多商品选择以空格隔开):如1号商品购买2个(1,2)"); String line = scanner.nextLine(); String[] strings = line.split(" "); System.out.println("您选择了如下商品:"); Map<String, Long> temp = new LinkedHashMap<>(); for (String str : strings){ String[] split = str.split(","); Good good = goodList.get(Integer.valueOf(split[0]) - 1); System.out.println("商品名:" + good.getName() + ", 购买数量:" + split[1]); temp.put(good.getCode(), Long.valueOf(split[1])); } System.out.println("================================下订单================================"); System.out.println("请确认是否下订单:1、是,2、否"); int num = scanner.nextInt(); if (1 == num){ Order order = new Order(); order.setCustomerId("zhangsan123"); order.setAddress("广东省广州市海珠区"); order.setPhone("123456789"); order.setGoodCodes(temp); double sales = 0; for (String code : temp.keySet()){ Good good = goodService.select(code); sales += good.getPrice() * temp.get(code); } order.setSales(sales); rabbitTemplate.convertAndSend(OrderExchangeConfig.ORDER_EXCHANGE, OrderExchangeConfig.ORDER_SAVE_ROUTINGKEY, order); Thread.sleep(1000); System.out.println("================================订单付款================================"); System.out.println("请确认订单付款:1、是,0、否"); while (true){ int anInt = scanner.nextInt(); if (1 == anInt){ order.setIsPay(1); rabbitTemplate.convertAndSend(OrderExchangeConfig.ORDER_EXCHANGE, OrderExchangeConfig.ORDER_UPDATE_ROUTINGKEY, order.getCode()); break; } else if (0 == anInt){ rabbitTemplate.convertAndSend(GoodExchangeConfig.GOOD_EXCHANGE, GoodExchangeConfig.GOOD_ADD_QUEUE, order); break; } System.out.println("输入有误,请重新输入:"); } Thread.sleep(1000); System.out.println("================================物流转运================================"); Collection<Logistics> logistics = LogisticsServiceImpl.getLogisticsMap().values(); Logistics logistic = logistics.stream().filter(log -> log.getOrderCode().equals(order.getCode())).collect(Collectors.toList()).get(0); while (true){ System.out.println("请确认物流转运状态:1、下一站,0、到达"); int anInt = scanner.nextInt(); if (1 == anInt){ logistic.setStatus(RandomUtils.nextInt(1, 4)); } else if (0 == anInt){ logistic.setStatus(5); break; } System.out.println("输入有误,请重新输入:"); } rabbitTemplate.convertAndSend(LogExchangeConfig.LOG_EXCHANGE, LogExchangeConfig.LOG_UPDATE_ROUTINGKEY, logistic); } else { System.out.println("欢迎下次再来!!!"); } } } }
9、测试流程
①在浏览器中输入:https://localhost:8080/test
②返回 IDE 的控制台中进行接口的操作
编码类型枚举信息类