SpringBoot 集成 rabbitmq 简单实现创建订单、减少库存、创建物流即应用解耦
队列实现方式:
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作
物流系统:用户下单后,创建用户的物流信息
假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦
详细代码访问我的github:https://github.com/ningcs/reservemq
项目结构:
项目结构描述:
eureka :项目注册与发现。
所有项目都注册到这个地址,方便三个系统之间通过feignClient进行数据交互。(eureka详细注册与发现这里不过多描述)
eureka 配置文件:
spring.application.name=eureka-server server.port=1001 eureka.instance.hostname=localhost eureka.client.register-with-eureka=false eureka.client.fetch-registry=false ##禁用自我保护模式 #eureka.server.enable-self-preservation=false #eureka.instance.prefer-ip-address=true eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
reserve-provider-service:订单系统,消费者从订单系统发起订单,同时告知物流和库存系统。
reserve-receiver-service:库存系统。获取库存量,减少库存等。
reserve-wuliu-receiver-service: 物流系统。订单完成,告知物流系统创建该用户的物流信息。
项目所需jar包:
<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> <!-- 添加springboot对amqp的支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> <version>1.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
订单系统:
通过一个简单的购买页面,模拟用户购买时的状况。
package com.example.demo.controller; import com.example.demo.dto.OrderInfo; import com.example.demo.dto.ReserveInfo; import com.example.demo.entity.Product; import com.example.demo.service.OrderService; import com.example.demo.service.ReserveService; import com.example.demo.util.ResponseResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random; /** * Created by ningcs on 2017/10/30. */ @Controller @RequestMapping("rabbit") public class RabbitController { @Autowired private ReserveService reserveService; @Autowired private OrderService orderService; // // @RequestMapping(value = "/hello",method = {RequestMethod.GET, RequestMethod.POST}) // public String helloSender(){ // helloSender.send("hello,rabbit~"); // return "发送成功"; // } @RequestMapping(value = "/createOrderPage",method = {RequestMethod.GET, RequestMethod.POST}) public ModelAndView createOrderPage(Integer productId){ productId=1; ModelAndView modelAndView =new ModelAndView("/index"); //获取库存余量 ReserveInfo reserve =reserveService.getReserveCount(productId); Product product =orderService.getProductById(productId); if (reserve!=null && product!=null){ modelAndView.addObject("total",reserve.getTotalCount()-reserve.getCurrentCount()); modelAndView.addObject("product",product); modelAndView.addObject("userId",2); } return modelAndView; } @RequestMapping(value = "/createOrder",method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody public ResponseResult createOrder(Integer productId, Integer count,Integer userId){ if (count==null || count<=0){ return ResponseResult.errorResult("购买数量不能为空或为0"); } DecimalFormat df = new DecimalFormat("#.00"); Product product =orderService.getProductById(productId); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm"); OrderInfo orderInfo =new OrderInfo(); orderInfo.setProductId(productId); orderInfo.setBuyCount(count); orderInfo.setUserId(userId); //保留两位小数 orderInfo.setMonetary(""+df.format(Double.parseDouble(product.getProductPrice())*count)); orderInfo.setOrderId(getTenRandomLetter()+sdf.format(new Date())+"_"+userId+"_"+productId); orderInfo.setProductName(product.getProductName()); //创建订单 向库存系统 和物流系统发送消息 orderService.addOrder(orderInfo); return ResponseResult.successResult("生成订单成功"); } /** * 从26为字母中获得一个6位随机数(纯字母) * ok */ public static String getTenRandomLetter() { String word = "abcdefghijklmnopqrstuvwxyz"; String tmp = ""; for (int i = 0; i < 6; i++) { Random random = new Random(); Integer index = random.nextInt(word.length()); char c = word.charAt(index); tmp = tmp + c; } return tmp; } }
service: 将订单,和mq放在一个事物里面,创建订单成功后,会通过队列告知库存系统减少库存,物流系统创建该用户的物流信息。
package com.example.demo.service.impl; import com.example.demo.dao.OrderDao; import com.example.demo.dto.OrderInfo; import com.example.demo.entity.Product; import com.example.demo.sender.HelloSender; import com.example.demo.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * Created by ningcs on 2017/11/20. */ @Service public class OrderServiceImpl implements OrderService{ @Autowired private OrderDao orderDao; @Autowired private HelloSender helloSender; @Override public Product getProductById(Integer productId) { return orderDao.getProductById(productId); } @Override @Transactional public void addOrder(OrderInfo orderInfo) { orderDao.addOrder(orderInfo); helloSender.send(orderInfo); } }
队列配置:
package com.example.demo.conf; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Created by ningcs on 2017/10/30. */ @Configuration public class SenderConf { @Bean public Queue queueOrder() { return new Queue("queueOrder1"); } @Bean public Queue queueOrder2() { return new Queue("queueOrder2"); } }
订单系统的配置文件:页面框架使用的freemark
server.port=17073 eureka.client.service-url.defaultZone=http://127.0.0.1:1001/eureka/ spring.application.name=spirng-boot-rabbitmq-sender spring.rabbitmq.host=127.0.0.1 spring.rabbitmq.port=5672 spring.rabbitmq.username=ncs spring.rabbitmq.password=12345678 spring.rabbitmq.publisher-confirms=true spring.rabbitmq.virtual-host=/ spring.rabbitmq.listener.concurrency=1 spring.rabbitmq.listener.max-concurrency=5 spring.resources.static-locations=classpath:/static/ spring.freemarker.template-loader-path=classpath:/views/templates/ spring.freemarker.cache=false spring.freemarker.charset=UTF-8 spring.freemarker.check-template-location=true spring.freemarker.content-type=text/html spring.freemarker.expose-request-attributes=true spring.freemarker.expose-session-attributes=true spring.freemarker.request-context-attribute=request spring.freemarker.suffix=.ftl spring.datasource.url=jdbc:mysql://**.**.206.***:3306/order?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 spring.datasource.username=*** spring.datasource.password=pning***** spring.datasource.driver-class-name=com.mysql.jdbc.Driver feign.hystrix.enabled=false
库存系统:相当于消费者 ,接收创建订单成功后,减少库存。
package com.example.demo.receiver; import com.example.demo.dto.OrderInfo; import com.example.demo.service.ReserveService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * Created by ningcs on 2017/10/30. */ @Component public class HelloReceive { Log log = LogFactory.getLog(getClass()); @Autowired ReserveService reserveService; @RabbitListener(queues="queueOrder1") //监听器监听指定的Queue public void queueOrder1(OrderInfo orderInfo) { Integer count=0; if (orderInfo!=null){ count= reserveService.updateReserveCount(orderInfo.getBuyCount(),orderInfo.getProductId()); log.info("Receive:队列:queueOrder1 商品名字:"+orderInfo.getProductName()+",商品购买数量:"+orderInfo.getBuyCount()); } } @RabbitListener(queues="queueOrder2") //监听器监听指定的Queue public void queueOrder2(OrderInfo orderInfo) { Integer count=0; if (orderInfo!=null){ count= reserveService.updateReserveCount(orderInfo.getBuyCount(),orderInfo.getProductId()); log.info("Receive:队列:queueOrder2 商品名字:"+orderInfo.getProductName()+",商品购买数量:"+orderInfo.getBuyCount()); } } }
物流系统:相当于消费者 ,接收创建订单成功后,创建该用户的物流信息。
package com.example.demo.receiver; import com.example.demo.dto.OrderInfo; import com.example.demo.service.WuliuService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * Created by ningcs on 2017/10/30. */ @Component public class HelloReceive { Log log = LogFactory.getLog(getClass()); @Autowired private WuliuService wuliuService; @RabbitListener(queues="queueWuLiu1") //监听器监听指定的Queue public void queueOrder1(OrderInfo orderInfo) { if (orderInfo!=null){ wuliuService.addWuliu(orderInfo); log.info("Receive:队列:queueWuLiu1 商品名字:"+orderInfo.getProductName()+",商品购买数量:"+orderInfo.getBuyCount()); } } @RabbitListener(queues="queueWuLiu2") //监听器监听指定的Queue public void queueOrder2(OrderInfo orderInfo) { Integer count=0; if (orderInfo!=null){ wuliuService.addWuliu(orderInfo); log.info("Receive:队列:queueWuLiu2 商品名字:"+orderInfo.getProductName()+",商品购买数量:"+orderInfo.getBuyCount()); } } }
简单购买页面:
<!DOCTYPE html> <html lang="en"> <head> <script src="/js/jquery.js"></script> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form id="form"> <div class="form-group"> <label for="name">名称:</label> <input type="text" id="productName" disabled value="${product.productName}"> </div> <div class="form-group"> <label for="count">购买数量:</label> <input type="number" id="count" value=""> </div> <div class="form-group"> <label for="count">单价:</label> <p class="form-control-static" id="price">${product.productPrice}</p> </div> <div class="form-group"> <label for="count">库存:</label> <p class="form-control-static" id="total">${total}</p> </div> <div class="form-group"> <a href="javascript:void(0);" id="sub">购买</a> </div> </form> <script> $('#sub').click(function () { $.ajax({ url: '/rabbit/createOrder', type: 'post', data: { productId: ${product.id}, count: $('#count').val(), userId: ${userId} }, dataType: 'json', cache: false, success: function (json) { if(json.code==0){ window.location.reload(); }else { alert(json.msg); } }, error: function () { alert('出错'); } }); return false; }); </script> </body> </html>
本文只是自己经过简单的构思,对Springboot和rabbitmq的集成设计的订单、物流、库存写的一个简单的例子,只是构思了大抵的思路,并通过简单的代码实现,并没有用到什么先进的技术和严密的构思。甚至还有很多缺陷,望大家多多指导,也希望对初学者有所帮助。
详细代码地址访问我的github:https://github.com/ningcs/reservemq