1. 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2. 配置ymal
spring:
rabbitmq:
host: 192.168.56.10
port: 5672
virtual-host: /
publisher-confirms: true # 开启发送端确认
publisher-returns: true # 开启发送端消息抵达队列确认
template:
mandatory: true # 只要抵达队列,以异步发送优先回调我们这个returnconnfirms
listener:
direct:
acknowledge-mode: manual # 手动ack消息
3. 使用java操作mq
package com.systop.gulimall.order;
import com.rabbitmq.client.ConnectionFactory;
import com.systop.common.utils.Query;
import com.systop.gulimall.order.entity.OrderEntity;
import com.systop.gulimall.order.entity.RefundInfoEntity;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@Slf4j
@SpringBootTest
class GulimallOrderApplicationTests {
@Autowired
AmqpAdmin amqpAdmin;
@Autowired
RabbitTemplate rabbitTemplate;
// 发布消息
@Test
void sendMessage(){
String msg = "java.hello";
OrderEntity orderEntity = new OrderEntity();
orderEntity.setReceiverName("杜宜洲");
orderEntity.setOrderSn("213");
rabbitTemplate.convertAndSend("java.exchange.direct","java.hello",orderEntity);
log.info("消息发布成功!!");
}
// 创建交换机
@Test
void createExchanges() {
DirectExchange directExchange = new DirectExchange("java.exchange.direct", true, false);
amqpAdmin.declareExchange(directExchange);
log.info("交换机[{}]创建成功", "java.exchange.direct");
}
// 创建队列
@Test
void createQueue() {
// public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
Queue queue = new Queue("hello-java-queue", true, false, false);
amqpAdmin.declareQueue(queue);
log.info("队列[{}]创建成功", "hello-java-queue");
}
// 交换机绑定队列
@Test
void binding(){
Binding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE, "java.exchange.direct", "java.hello", null);
amqpAdmin.declareBinding(binding);
log.info("交换机[{}]绑定成功", "java.exchange.direct");
}
}
4. 使用请求发布消息
Autowired
private RabbitTemplate rabbitTemplate;
@ResponseBody
@GetMapping("/send")
public String sendMsg (){
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
OrderEntity orderEntity = new OrderEntity();
orderEntity.setReceiverName("杜宜洲"+i);
orderEntity.setOrderSn("213"+i);
rabbitTemplate.convertAndSend("java.exchange.direct","java.hello",orderEntity,new CorrelationData(UUID.randomUUID().toString()));
log.info("消息发布成功!!");
}else {
OrderItemEntity orderItemEntity = new OrderItemEntity();
orderItemEntity.setSkuAttrsVals("dasdf");
orderItemEntity.setOrderId(1L);
rabbitTemplate.convertAndSend("java.exchange.direct","java.hello22",orderItemEntity,new CorrelationData(UUID.randomUUID().toString()));
}
}
return "ok";
}
5. 接收消息
@RabbitListener(queues = {"hello-java-queue"})
@Service("orderItemService")
public class OrderItemServiceImpl extends ServiceImpl<OrderItemDao, OrderItemEntity> implements OrderItemService {
/**
* 参数可以写一下类型
* 1. Message message : 原生消息详细信息. 头 + 体
* 2. T<发送的消息的类型> : OrderReturnReasonEntity content;
* 3. Channel channel : 当前传输数据的通道
*
* Queue : 可以很多人都来监听. 只要收到消息,队列删除消息,而且只能有一个收到此消息
* 场景:
* 1> 订单服务启动多个; 同一个消息,之鞥你有一个客户端收到
* 2> 只有一个消息完全处理完,方法运行结束,我们就可以接收到下一个消息
* @param message
* @param orderEntity
*/
// @RabbitListener(queues = {"hello-java-queue"})
@RabbitHandler
public void recieveMessage(Message message, OrderEntity orderEntity, Channel channel) throws IOException {
System.out.println("接收到消息" + orderEntity);
byte[] body = message.getBody();
// channel 内按顺序自增的
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("deliveryTag==>" + deliveryTag);
// 手动签收,非批量模式
try {
if (deliveryTag % 2 == 0){
// 收货
channel.basicAck(deliveryTag,false);
System.out.println("签收了货物..." + deliveryTag);
}else {
// 退货
// requeue(b1) = false 丢弃 requeue=true 发回服务器,服务器重新入队
// b: 是否批量拒收 b1: 拒收后是否重新入队
channel.basicNack(deliveryTag,false,false);
System.out.println("没有签收了货物");
}
}catch (Exception e){
// 网络中断了
}
}
}
5. 因为默认的序列化机制是jdk默认的所以需要自己配置序列化,创建一个config类
package com.systop.gulimall.order.config;
import com.mysql.cj.protocol.MessageListener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.text.SimpleDateFormat;
/**
* @author dyz
* @date 2022/1/11 13:40
*/
@Configuration
public class MyRabbitConfig {
@Autowired
RabbitTemplate rabbitTemplate;
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
/**
* 定制RabbitTemplate
* 1. 服务收到消息就回调
* 1. spring.rabbitmq.publisher-confirms=true
* 2. 设置确认回调 ConfirmCallback
* 2. 消息正确抵达队列进行回调
* 1. spring.rabbitmq.publisher-returns=true
* spring.rabbitmq.template.mandatory=true
* 2. ReturnCallback
* 3. 消费端确认(保证每个消息被正确消费,此时才可以broker删除这个消息)
* 1. 默认时自动确认的,只要消息接收到,客户端会自动确认,服务端就会移除这个消息.
* 问题:
* 我们收到很多消息,自动回复给服务器ack,只有一个消息处理成功,宕机了.发生数据丢失;
* 手动确认模式. spring.rabbitmq.listener.direct.acknowledge-mode: manual # 手动ack消息
* 只要我们没有明确的告诉MQ,货物被签收.没有Ack, 消息就一直是unacked状态, 即使Consumer宕机,
* 消息不会丢失,会重新变为Ready,下次有新的Consumer连接进来就发给他
*/
@PostConstruct
public void initRabbitTemplate(){
System.out.println(123);
System.out.println(rabbitTemplate);
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* 只要消息抵达Broker就ack(b)=true
* @param correlationData 当前消息的唯一管来拿数据(这个是消息的唯一id)
* @param b 消息是否成功收到
* @param s 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
System.out.println("confirm...correlationData["+correlationData+"] ==> ack" +"["+b+"]");
}
});
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* 只要消息没有投递给指定的队列,就触发这个失败的回调
* @param message 投递失败的消息详细信息
* @param i 回复的状态码
* @param s 回复的文本内容
* @param s1 当时这个消息发给那个交换机
* @param s2 当时这个消息用的那个路由键
*/
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("Fail Message [" + message +"]" + "==>" + "回复的文本内容[" + s + "]");
}
});
}
// 这个是接收数据的时候需要配置
@Bean
public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(new Jackson2JsonMessageConverter());
return factory;
}
}