RabbitMQ学习之旅

1587640966654

Author:Eric

Version:9.0.0

文章目录

一、引言

模块之间的耦合度过高,导致一个模块宕机后,全部功能都不能用了,并且同步通讯的成本过高,用户体验差。

RabbitMQ引言
1587650344256

二、RabbitMQ介绍


市面上比较火爆的几款MQ:

ActiveMQ,RocketMQ,Kafka,RabbitMQ。

  • 语言的支持:ActiveMQ,RocketMQ只支持Java语言,Kafka可以支持多们语言,RabbitMQ支持多种语言。
  • 效率方面:ActiveMQ,RocketMQ,Kafka效率都是毫秒级别,RabbitMQ是微秒级别的(RabbitMQ基于Erlang编写的,面向并发编程)。
  • 消息丢失,消息重复问题: RabbitMQ针对消息的持久化,和重复问题都有比较成熟的解决方案。

RabbitMQ是由Rabbit公司去研发和维护的,最终是在Pivotal。

RabbitMQ严格的遵循AMQP协议,高级消息队列协议,帮助我们在进程之间传递异步消息。

三、RabbitMQ安装


version: "3.1"
services:
  rabbitmq:
  #management图形化界面 15672图形画面端口
    image: daocloud.io/library/rabbitmq:management
    restart: always
    container_name: rabbitmq
    ports:
      - 5672:5672
      - 15672:15672
    volumes:
      - ./data:/var/lib/rabbitmq

四、RabbitMQ架构【重点


4.1 官方的简单架构图
  • Publisher - 生产者:发布消息到RabbitMQ中的Exchange

  • Consumer - 消费者:监听RabbitMQ中的Queue中的消息

  • Exchange - 交换机:和生产者建立连接并接收生产者的消息

  • Queue - 队列:Exchange会将消息分发到指定的Queue,Queue和消费者进行交互

  • Routes - 路由:交换机以什么样的策略将消息发布到Queue

简单架构图
1587703812776
4.2 RabbitMQ的完整架构图

完整架构图

完整架构图
1587705504342
4.3 查看图形化界面并创建一个Virtual Host

创建一个全新的用户和全新的Virtual Host,并且将test用户设置上可以操作/test的权限

监控界面
1587707241741

image-20201030094813848

添加用户

image-20201030095210147

设置使用主机

image-20201030095407229

删除

image-20201030095704493

返回首页界面

image-20201030095742991

五、RabbitMQ的使用【重点


5.1 RabbitMQ的通讯方式
通讯方式
15891044628671587707774754
5.2 Java连接RabbitMQ
5.2.1 创建maven项目

…………

5.2.2 导入依赖
<dependencies>
    <dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.6.0</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>
5.2.3 创建工具类连接RabbitMQ
public static Connection getConnection(){
    // 创建Connection工厂
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("192.168.206.142");
    factory.setPort(5672);
    factory.setUsername("test");
    factory.setPassword("123");
    factory.setVirtualHost("/test");

    // 创建Connection
    Connection conn = null;
    try {
        conn = factory.newConnection();
    } catch (Exception e) {
        e.printStackTrace();
    }
    // 返回
    return conn;
}
效果图
1587714183124
5.3 Hello-World,点对点,简单模式

一个生产者,一个默认的交换机,一个队列,一个消费者

结构图
1587715551088

创建生产者,创建一个channel,发布消息到exchange,指定路由规则。

@Test
public void publish() throws Exception {
    //1. 获取rabbit连接
    Connection connection = RabbitMQClient.getConnection();

    //2. 创建Channel
    Channel channel = connection.createChannel();

    //3. 发布消息到exchange,同时指定路由的规则
    String msg = "Hello-World!";
    //发送消息
    // 参数1:指定exchange,使用""。
    // 参数2:指定路由的规则,使用具体的队列名称。
    // 参数3:指定传递的消息所携带的properties,使用null。
    // 参数4:指定发布的具体消息,byte[]类型
    channel.basicPublish("","HelloWorld",null,msg.getBytes());
    // Ps:exchange是不会帮你将消息持久化到本地的,Queue才会帮你持久化消息。
    System.out.println("生产者发布消息成功!");
    //4. 释放资源
    channel.close();
    connection.close();
}

创建消费者,创建一个channel,创建一个队列,并且去消费当前队列

@Test
public void consume() throws Exception {
    //1. 获取连接对象
    Connection connection = RabbitMQClient.getConnection();

    //2. 创建channel
    Channel channel = connection.createChannel();

    //3. 声明队列-HelloWorld
    //参数1:queue - 指定队列的名称
    //参数2:durable - 当前队列是否需要持久化(true)
    //参数3:exclusive - 是否排外(conn.close() - 当前队列会被自动删除,当前队列只能被一个消费者消费)
    //参数4:autoDelete - 如果这个队列没有消费者在消费,队列自动删除
    //参数5:arguments - 指定当前队列的其他信息
    channel.queueDeclare("HelloWorld",true,false,false,null);

    //4. 开启监听Queue
    DefaultConsumer consume = new DefaultConsumer(channel){
        @Override//body消费者传过来的内容
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
            System.out.println("接收到消息:" + new String(body,"UTF-8"));
        }
    };

    //参数1:queue - 指定消费哪个队列
    //参数2:autoAck - 指定是否自动ACK (true,接收到消息后,会立即告诉RabbitMQ)
    //参数3:consumer - 指定消费回调
    channel.basicConsume("HelloWorld",true,consume);

    System.out.println("消费者开始监听队列!");
    // System.in.read();
    System.in.read();

    //5. 释放资源
    channel.close();
    connection.close();
}
5.4 Work Queue 工作队列模式

一个生产者,一个默认的交换机,一个队列,两个消费者

结构图
1587718055260

只需要在消费者端,添加Qos能力以及更改为手动ack即可让消费者,根据自己的能力去消费指定的消息,而不是默认情况下由RabbitMQ平均分配了,生产者不变,正常发布消息到默认的exchange,并指定routing

消费者指定Qoa和手动ack

//1 指定当前消费者,一次消费多少个消息
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel){
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("消费者1号接收到消息:" + new String(body,"UTF-8"));

        //2. 手动ack
        channel.basicAck(envelope.getDeliveryTag(),false);
    }
};
//3. 指定手动ack
channel.basicConsume("Work",false,consumer);
5.5 Publish/Subscribe,发布订阅的方式

一个生产者,一个交换机,两个队列,两个消费者

结构图
1587720941513

声明一个Fanout类型的exchange,并且将exchange和queue绑定在一起,绑定的方式就是直接绑定。

让生产者创建一个exchange并且指定类型,和一个或多个队列绑定到一起。

//3. 创建exchange - 绑定某一个队列
//参数1: exchange的名称
//参数2: 指定exchange的类型  FANOUT - pubsub ,   DIRECT - Routing , TOPIC - Topics
//参数3:指定路由key
channel.exchangeDeclare("pubsub-exchange", BuiltinExchangeType.FANOUT);
channel.queueBind("pubsub-queue1","pubsub-exchange","");//队列绑定到交换机
channel.queueBind("pubsub-queue2","pubsub-exchange","");

消费者还是正常的监听对应队列,consumer的消息队列名要一一对应上。

5.6 Routing,路由的方式

一个生产者,一个交换机,两个队列,两个消费者

结构图
1587724886159

生产者在创建DIRECT类型的exchange后,根据RoutingKey去绑定相应的队列,并且在发送消息时,指定消息的具体RoutingKey即可。

//3. 创建exchange, routing-queue-error,routing-queue-info,队列绑定
//参数1: exchange的名称
//参数2: 指定exchange的类型  FANOUT - pubsub ,   DIRECT - Routing , TOPIC - Topics
//参数3:指定路由key
channel.exchangeDeclare("routing-exchange", BuiltinExchangeType.DIRECT);
channel.queueBind("routing-queue-error","routing-exchange","ERROR");
channel.queueBind("routing-queue-info","routing-exchange","INFO");

//4. 发布消息到exchange,同时指定路由的规则
// 参数1:交换机名称
// 参数2:路由key,与绑定队列的路由key一致
// 参数3:指定传递的消息所携带的properties,使用null。
// 参数4:指定发布的具体消息,byte[]类型
channel.basicPublish("routing-exchange","ERROR",null,"ERROR".getBytes());
channel.basicPublish("routing-exchange","INFO",null,"INFO1".getBytes());
channel.basicPublish("routing-exchange","INFO",null,"INFO2".getBytes());
channel.basicPublish("routing-exchange","INFO",null,"INFO3".getBytes());

消费者没有变化

5.7 Topic ,RoutingKey通配符方式

一个生产者,一个交换机,两个队列,两个消费者

结构图
1587727436898

生产者创建Topic的exchange并且绑定到队列中,这次绑定可以通过*和#关键字,对指定RoutingKey内容,编写时注意格式 xxx.xxx.xxx去编写, * 一个xxx,而# 代表多个xxx.xxx,在发送消息时,指定具体的RoutingKey到底是什么。

//2. 创建exchange并指定绑定方式
//  fast.red.cat  绑定队列可以写成以下其中一种对应方式:*.red.*
//  fast.white.dog 绑定队列可以写成以下其中一种对应方式:fast.# 或fast.*.*
//  slow.yello.dog
channel.exchangeDeclare("topic-exchange", BuiltinExchangeType.TOPIC);
channel.queueBind("topic-queue-1","topic-exchange","*.red.*");
channel.queueBind("topic-queue-2","topic-exchange","fast.#");
channel.queueBind("topic-queue-2","topic-exchange","*.*.rabbit");

//3. 发布消息到exchange,同时指定路由的规则
channel.basicPublish("topic-exchange","fast.red.monkey",null,"红快猴子".getBytes());
channel.basicPublish("topic-exchange","slow.black.dog",null,"黑漫狗".getBytes());
channel.basicPublish("topic-exchange","fast.white.cat",null,"快白猫".getBytes());

消费者只是监听队列,没变化。

六、RabbitMQ整合SpringBoot【重点


6.1 SpringBoot整合RabbitMQ
6.1.1 创建SpringBoot工程
6.1.2 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
6.1.3 编写配置文件
spring:
  rabbitmq:
    host: 192.168.199.109
    port: 5672
    username: test
    password: test
    virtual-host: /test
6.1.4 声明exchange、queue
@Configuration
public class RabbitMQConfig {
    //1. 创建交换机exchange - topic
    @Bean
    public TopicExchange getTopicExchange(){
        return new TopicExchange("boot-topic-exchange",true,false);
    }

    //2. 创建队列queue
    @Bean
    public Queue getQueue(){
        return new Queue("boot-queue",true,false,false,null);
    }

    //3. 将交换机和队列绑定在一起
    @Bean
    public Binding getBinding(TopicExchange topicExchange,Queue queue){
        return BindingBuilder.bind(queue).to(topicExchange).with("*.red.*");
    }
}
6.1.5 发布消息到RabbitMQ
@Autowired
private RabbitTemplate rabbitTemplate;

@Test
void contextLoads() {
    //通过模板发送消息
    rabbitTemplate.convertAndSend("boot-topic-exchange","slow.red.dog","红色大狼狗!!");
}
6.1.6 创建消费者监听消息
@Component
public class Consumer {

    @RabbitListener(queues = "boot-queue")//监听队列
    public void getMessage(Object message){
        System.out.println("接收到消息:" + message);
    }

}
6.2 手动Ack
6.2.1 添加配置文件
spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual #手动ack方式
6.2.2 手动ack
//手动回复
@RabbitListener(queues = "boot-queue")
public void getMessage(String msg, Channel channel, Message message) throws IOException {
    System.out.println("接收到消息:" + msg);
    int i = 1 / 0;
    // 手动ack
    channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}

七、RabbitMQ的其他操作


7.1 消息的可靠性

a.是否到达交换机

b.是否到达队列

使用确认机制

RabbitMQ的事务:事务可以保证消息100%传递,可以通过事务的回滚去记录日志,后面定时再次发送当前消息。事务的操作,效率太低,加了事务操作后,比平时的操作效率至少要慢100倍以上。

RabbitMQ除了事务,还提供了Confirm的确认机制,这个效率比事务高很多。

7.1.1 普通Confirm方式
//在生产者类中添加

//3.1 开启confirm
channel.confirmSelect();
//3.2 发送消息
String msg = "Hello-World!";
channel.basicPublish("","HelloWorld",null,msg.getBytes());
//3.3 判断消息是否成功发送到交换机Exchange
if(channel.waitForConfirms()){
    System.out.println("消息发送到交换机成功");
}else{
    System.out.println("发送消息到交换机失败");
}
7.1.2 批量Confirm方式
//3.1 开启confirm
channel.confirmSelect();
//3.2 批量发送消息
for (int i = 0; i < 1000; i++) {
    String msg = "Hello-World!" + i;
    channel.basicPublish("","HelloWorld",null,msg.getBytes());
}
//3.3 确定批量操作是否成功
channel.waitForConfirmsOrDie();     // 当你发送的全部消息,有一个失败的时候,就直接全部失败 抛出异常IOException

7.1.3 异步Confirm方式。
//3.1 开启confirm
channel.confirmSelect();
//3.2 批量发送消息
for (int i = 0; i < 1000; i++) {
    String msg = "Hello-World!" + i;
    channel.basicPublish("","HelloWorld",null,msg.getBytes());
}
//3.3 开启异步回调
channel.addConfirmListener(new ConfirmListener() {

    @Override
    public void handleAck(long deliveryTag, boolean multiple) throws IOException {
        System.out.println("消息发送成功,标识:" + deliveryTag + ",是否是批量" + multiple);
    }

    @Override
    public void handleNack(long deliveryTag, boolean multiple) throws IOException {
        System.out.println("消息发送失败,标识:" + deliveryTag + ",是否是批量" + multiple);
    }
});
消息传递可靠性
1587746943156
7.1.4 Return机制

Confirm只能保证消息到达exchange,无法保证消息可以被exchange分发到指定queue。

而且exchange是不能持久化消息的,queue是可以持久化消息。

采用Return机制来监听消息是否从exchange送到了指定的queue中

消息传递可靠性
1587801910926

开启Return机制,并在发送消息时,指定mandatory为true

// 开启return机制
channel.addReturnListener(new ReturnListener() {
    @Override
    public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
        // 当消息没有送达到queue时,才会执行。
        System.out.println(new String(body,"UTF-8") + "没有送达到Queue中!!");
    }
});

// 在发送消息时,指定mandatory参数为true,当队列没有接收到消息时,执行returnListener回调
channel.basicPublish("","HelloWorld",true,null,msg.getBytes());
7.2 SpringBoot实现
7.2.1 编写配置文件
spring:
  rabbitmq:
    publisher-confirm-type: simple  #新版本用
    publisher-confirms: true  #老版本用,确认机制开启,消息是否到达交换机
    publisher-returns: true  #return机制开启,消息是否到达队列
7.2.2 开启Confirm和Return
@Component
public class PublisherConfirmAndReturnConfig implements RabbitTemplate.ConfirmCallback ,RabbitTemplate.ReturnCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;//模板

    @PostConstruct  // init-method这个注解相当于是初始化方法
    public void initMethod(){
        rabbitTemplate.setConfirmCallback(this);//当前对象,设置回调方法
        rabbitTemplate.setReturnCallback(this);
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if(ack){
            System.out.println("消息已经送达到交换机Exchange");
        }else{
            System.out.println("消息没有送达到交换机Exchange");
        }
    }

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        System.out.println("消息没有送达到Queue");
    }
}
7.3 避免消息重复消费

重复消费消息,会对非幂等性操作造成问题,幂等性:查询;非幂等性:增删改,每个操作都会对数据有改动

重复消费消息的原因是,消费者没有给RabbitMQ一个ack

重复消费
1587820070938

为了解决消息重复消费的问题,可以采用Redis,在消费者消费消息之前,先将消息的id放到Redis中,

id-0(正在执行业务)id是消息id

id-1(执行业务成功)

如果ack失败,在RabbitMQ将消息交给其他的消费者时,先执行setnx,如果key已经存在,获取他的值,如果是0,当前消费者就什么都不做,如果是1,直接ack。

setnx:key value ;key存在就啥事都不做,不存在就添加

极端情况:第一个消费者在执行业务时,出现了死锁,在setnx的基础上,再给key设置一个生存时间。

生产者,发送消息时,指定messageId

//传递消息所携带的数据properties
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
    .deliveryMode(1)     //指定消息是否需要持久化 1 - 需要持久化  2 - 不需要持久化
    .messageId(UUID.randomUUID().toString())//消息ID
    .build();
String msg = "Hello-World!";
channel.basicPublish("","HelloWorld",true,properties,msg.getBytes());

消费者,在消费消息时,根据具体业务逻辑去操作redis

DefaultConsumer consume = new DefaultConsumer(channel){
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        Jedis jedis = new Jedis("192.168.199.109",6379);
        String messageId = properties.getMessageId();
        //1. setnx到Redis中,默认指定value-0, 添加messageId到redis中 key:message  value:0
        //如果结果是OK证明redis没有这个数据,则添加成功
        //如果有的话,则判断value值,若是0,不做任何事,若是1,则手动ack
        String result = jedis.set(messageId, "0", "NX", "EX", 10);//0正在执行业务,NX是否存在,EX生存时间
        if(result != null && result.equalsIgnoreCase("OK")) {
            System.out.println("接收到消息:" + new String(body, "UTF-8"));
            //2. 消费成功,set messageId 1
            jedis.set(messageId,"1");
            channel.basicAck(envelope.getDeliveryTag(),false);
        }else {
            //3. 如果setnx失败,获取key对应的value,如果是0,return,如果是1,则回复ack
            String s = jedis.get(messageId);
            if("1".equalsIgnoreCase(s)){
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        }
    }
};
7.4 SpringBoot如何实现
7.4.1 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
7.4.2 编写配置文件
spring:
  redis:
    host: 192.168.199.109
    port: 6379
7.4.3 修改生产者
在SpringbootRabbitmqApplicationTests类修改

@Test
void contextLoads() throws IOException {
    //携带数据
    CorrelationData messageId = new CorrelationData(UUID.randomUUID().toString());
    rabbitTemplate.convertAndSend("boot-topic-exchange","slow.red.dog","红色大狼狗!!",messageId);
    System.in.read();
}
7.4.4 修改消费者
//解决消息重复消费问题
@Autowired
private StringRedisTemplate redisTemplate;


@RabbitListener(queues = "boot-queue")
public void getMessage(String msg, Channel channel, Message message) throws IOException {
    //0. 获取MessageId
    String messageId = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");
    //1. 设置key到Redis,redisTemplate模板对象,opsForValue相当于keyString,setIfAbsent相当于setnx
    if(redisTemplate.opsForValue().setIfAbsent(messageId,"0",10, TimeUnit.SECONDS)) {
        //2. 消费消息
        System.out.println("接收到消息:" + msg);

        //3. 设置key的value为1
        redisTemplate.opsForValue().set(messageId,"1",10,TimeUnit.SECONDS);
        //4.  手动ack
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }else {
        //5. 获取Redis中的value即可 如果是1,手动ack
        if("1".equalsIgnoreCase(redisTemplate.opsForValue().get(messageId))){
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        }
    }
}

八、RabbitMQ应用


8.1 客户模块
8.1.1 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
8.1.2 编写配置文件
spring:
  rabbitmq:
    host: 192.168.199.109
    port: 5672
    username: test
    password: test
    virtual-host: /test
8.1.3 编写配置类
@Configuration
public class RabbitMQConfig {

    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange("openapi-customer-exchange",true,false);
    }

    @Bean
    public Queue queue(){
        return new Queue("openapi-customer-queue");
    }

    @Bean
    public Binding binding(Queue queue,TopicExchange topicExchange){
        return BindingBuilder.bind(queue).to(topicExchange).with("openapi.customer.*");
    }


}
8.1.4 修改Service的添加操作
//3. 发送消息
rabbitTemplate.convertAndSend("openapi-customer-exchange","openapi.customer.add",JSON.toJSON(customer));


/*//3. 调用搜索模块,添加数据到ES
        //1. 准备请求参数和请求头信息
        String json = JSON.toJSON(customer);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType("application/json;charset=utf-8"));
        HttpEntity<String> entity = new HttpEntity<>(json,headers);
        //2. 使用RestTemplate调用搜索模块
        restTemplate.postForObject("http://localhost:8080/search/customer/add", entity, String.class);*/
8.1.5 修改Service的删除操作
    /*修改*/
    @Override
    public void updateCustomer(Customer customer) {
        int i = customerMapper.updateCustomer(customer);
        if (i != 1) {
            log.error("【修改客户信息到数据库失败!】customer={}", customer);
            throw new RuntimeException("【修改客户信息到数据库失败!】");
        }
        //发送消息
        rabbitTemplate.convertAndSend("openapi-customer-exchange","openapi.customer.update",JSON.toJson(customer));
//        String json = JSON.toJson(customer);
//        HttpHeaders httpHeaders = new HttpHeaders();
//        httpHeaders.setContentType(MediaType.parseMediaType("application/json;charset=utf-8"));
//        HttpEntity httpEntity = new HttpEntity(json, httpHeaders);
//        restTemplate.postForObject("http://localhost:8080/search/customer/update", httpEntity, String.class);
    }
8.1.6 修改Service的删除操作
 /*批量删除*/
    @Override
    public void delCustomer(Integer[] ids) {
        int i = customerMapper.delCustomer(ids);
        System.out.println(i);
        if (i < 1) {
            for (int j = 0; j < ids.length; j++) {
                log.error("【删除客户信息到数据库失败!】ids={}", ids[j]);
            }
//            log.error("【删除客户信息到数据库失败!】ids={}", ids);
//            throw new RuntimeException("【删除客户信息到数据库失败!】");
        }
        //发送消息
        rabbitTemplate.convertAndSend("openapi-customer-exchange","openapi.customer.del",JSON.toJson(ids));
//        String json = JSON.toJson(ids);
//        HttpHeaders httpHeaders = new HttpHeaders();
//        httpHeaders.setContentType(MediaType.parseMediaType("application/json;charset=utf-8"));
//        HttpEntity httpEntity = new HttpEntity(json, httpHeaders);
//        restTemplate.postForObject("http://localhost:8080/search/customer/del", httpEntity, String.class);

    }
8.2 搜索模块
8.2.1 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
8.2.2 编写配置文件
spring:
  rabbitmq:
    host: 192.168.199.109
    port: 5672
    username: test
    password: test
    virtual-host: /test
    listener:
      simple:
        acknowledge-mode: manual
8.2.3 编写配置类
@Configuration
public class RabbitMQConfig {

    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange("openapi-customer-exchange",true,false);
    }

    @Bean
    public Queue queue(){
        return new Queue("openapi-customer-queue");
    }

    @Bean
    public Binding binding(Queue queue, TopicExchange topicExchange){
        return BindingBuilder.bind(queue).to(topicExchange).with("openapi.customer.*");
    }
}
8.2.4 编写消费者-监听状态
@Component
public class SearchListener {

    @Autowired
    private SearchService searchService;

    @RabbitListener(queues = "openapi-customer-queue")
    public void consume(String json, Channel channel, Message message) throws IOException {
        //1. 获取RoutingKey
        String receivedRoutingKey = message.getMessageProperties().getReceivedRoutingKey();
        //2. 使用switch
        switch (receivedRoutingKey){
            case "openapi.customer.add":
                //3. add操作调用Service完成添加
                searchService.addCustomer(JSON.parseJSON(json, Customer.class));
                //4. 手动ack
                channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        }
    }
}

工具类

public class JSON {
    /*对象转成JSON数据*/
    public static String toJson(Object obj) {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return "";
        }
    }
    /*JSON数据转成对象*/
    public static <T> T parseJson(String data, Class<T> clazz) {

        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.readValue(data, clazz);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 将data转化为1维整型数组, data形如:"1,3,4,5,6"
     */
    public static Integer[] toIntegerArray(String data) {
        //[21,22]
        data = data.substring(1, data.length() - 1);//将前端传过来的JSON数据的中括号切掉
        String[] datas = data.split(",");//将字符串转成一个字符串数组
        Integer[] tmp = new Integer[datas.length];//创建包装类整型数组
        //字符串数组转成包装类整型数组
        for (int i = 0; i < datas.length; i++) {
            tmp[i] = Integer.parseInt(datas[i]);
        }
        return tmp;
    }
}
8.2.5 删除

编写消费者-监听

@Component
public class SearchListener {

    @Autowired
    private SearchService searchService;

    @RabbitListener(queues = "openapi-customer-queue")
    public void msg(String msg, Channel channel, Message message) throws IOException, TimeoutException {
        //得到routingKey
        String routingKey = message.getMessageProperties().getReceivedRoutingKey();
        switch (routingKey){          
            case "openapi.customer.del":
                //删除客户操作 "1","2"
                searchService.delCustomer(JSON.toIntegerArray(msg.toString()));
                //手动ack回复
                channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
                break;
        }
        channel.close();
    }
}

controller

    @RequestMapping(value ="/del", produces = "application/json;charset=utf-8")
    public ResultVO updateCustomer(Integer[] ids){
        try {
            customerService.delCustomer(ids);
            return new ResultVO(true,"删除客户信息成功");
        } catch (Exception e) {
            e.printStackTrace();
            return new ResultVO(false,"删除客户信息失败!");
        }
    }
8.3 整个项目代码(客户和搜索)
8.3.1客户模块
前端页面html
<!DOCTYPE html>
<html >
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>开放平台管理系统-客户模块</title>
    <link rel="stylesheet" href="layui/css/layui.css">
    <link rel="stylesheet" href="easyui/default/easyui.css">
    <script src="layui/jquery-1.10.2.min.js"
            type="text/javascript"></script>
    <script src="easyui/jquery.easyui.min.js"
            type="text/javascript"></script>
</head>
<body class="layui-layout-body">
<div class="layui-layout layui-layout-admin">

    <div id="main" style="padding-top:20px;padding-left:50px;">
        <!-- 内容主体区域 -->
		<form action="#" class="layui-form">
			<div class="layui-form-item">
				<div class="layui-inline">
					<label>名称:</label>
					<div class="layui-inline">
						<input type="text" name="username" autocomplete="off" placeholder="请输入名称"
							   class="layui-input">
					</div>
					状态:
					<div class="layui-inline">
						<select name="state">
							<option value="">请选择</option>
							<option value="0">无效</option>
							<option value="1">有效</option>
						</select>
					</div>
					<button class="layui-btn" lay-submit lay-filter="customer-table">搜索</button>
				</div>
			</div>
		</form>
		<script type="text/html" id="customer-head-bar">
			<div class="layui-btn-container">
				<button class="layui-btn layui-btn-sm" lay-event="goEdit"><i class="layui-icon">&#xe654;</i>添加</button>
				<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="delete"><i class="layui-icon">&#xe640;</i>删除
				</button>
			</div>
		</script>
		<script type="text/html" id="customer-customer-bar">
			<a class="layui-btn layui-btn-xs" lay-event="edit-customer">编辑</a>
		</script>
		<table class="layui-table" lay-filter="customer-table" id="customer-table">
		</table>
    </div>

    <div class="layui-footer" style="left: 0;">
        <!-- 底部固定区域 -->
        © xxxjava.cn -程序员
    </div>
</div>
<script src="layui/layui.js"></script>


<script type="text/javascript" >
    layui.use(['table', 'layer', 'form'], function () {
        var table = layui.table;
        var layer = layui.layer;
        var form = layui.form;
        form.render();
        table.render({
            id: "customer-table",
            elem: '#customer-table'
            , toolbar: '#customer-head-bar'
            , cellMinWidth: 80
            , url: 'sys/customer/table' //数据接口
            , page: true //开启分页
            , cols: [[
                {checkbox: true},
                {field: 'id', title: 'ID', sort: true}
                , {field: 'username', title: '账号'}
                , {field: 'nickname', title: '公司名称'}
                , {field: 'money', title: '账户金额'}
                , {field: 'address', title: '公司地址'}
                , {
                    field: 'state', title: '状态', templet: function (data) {
                        return (data.state == 1) ? '<span class="layui-badge layui-bg-green">有效</span>' : '<span class="layui-badge layui-bg-red" >无效</span>'
                    }
                }
                , {fixed: 'right', title: '操作', toolbar: '#customer-customer-bar', width: 180}
            ]]
        });
        //头工具栏事件
        table.on('toolbar(customer-table)', function (obj) {
            switch (obj.event) {
                case 'goEdit':
                    openEditWindow(null);
                    break;
                case 'delete':
                    var data = table.checkStatus('customer-table').data;
                    if (data.length > 0) {
                        layer.confirm('真的删除行么', function (index) {
                            var param = ""
                            $.each(data, function (i, obj) {
                                param += "&ids=" + obj.id
                            })
                            $.ajax({
                                url: 'sys/customer/del',
                                data: param,
                                method: 'post',
                                success: function (result) {
                                    if (result.status) {
                                        table.reload('customer-table', {});
                                    } else {
                                        alert(result.message)
                                    }
                                    layer.close(index);
                                }
                            })
                        });
                    }
                    break;
            }
            ;
        ;
    });
    //监听行工具事件
    table.on('tool(customer-table)', function (obj) {
        var data = obj.data;
        switch (obj.event) {
            case 'edit-customer': {
                openEditWindow(data);
                break;
            }

        }
    });

    function openEditWindow(data) {
        layer.open({
            type: 1,
            content: data == null ? $('#customer-add-layer').html():$('#customer-edit-layer').html(),
            title: data == null ? '添加客户':'编辑客户', 
            area: ['500px', '450px'],
            btn: ['提交', '取消'] //可以无限个按钮
            , yes: function (index, layero) {
                layer.load()
                $.ajax({
                    url: "sys/customer/" + (data == null ? 'add' : 'update'),
                  //  url: '/sys/customer/update',
                    data: data == null ?$("#customer-add-form").serialize():$("#customer-edit-form").serialize(),
                    method: 'post',
                    success: function (result) {
                        if (result.status) {
                            table.reload('customer-table', {});
                            layer.close(index);
                        } else {
                            alert(result.message)
                        }
                        layer.closeAll('loading');
                    }
                })
            }, success: function (layero, index) {
                form.render()
                if (data != null) {
                    form.val("customer-edit-form", data);
                    form.val("customer-edit-form", {
                        "state": data.state + ""
                    });
                }

            }
        });
    }

    form.on('submit(customer-table)', function (data) {
        table.reload('customer-table', {
            page: {
                curr: 1 //重新从第 1 页开始
            },
            where: data.field
        });
        return false; //阻止表单跳转。如果需要表单跳转,去掉这段即可。
    });

    })
    ;

</script>

<script type="text/html" id="customer-edit-layer">
    <form id="customer-edit-form" style="width: 80%" class="layui-form" lay-filter="customer-edit-form">
        <input type="hidden" name="id">
        <div class="layui-form-item">
            <label class="layui-form-label">用户名</label>
            <div class="layui-input-block">
                <input type="text" name="username" required lay-verify="required" placeholder="请输入用户名"
                       autocomplete="off"
                       class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">公司名称</label>
            <div class="layui-input-block">
                <input type="text" name="nickname" required lay-verify="required" placeholder="请输入公司名称"
                       autocomplete="off"
                       class="layui-input">
            </div>
        </div>

        <div class="layui-form-item">
            <label class="layui-form-label">密码</label>
            <div class="layui-input-block">
                <input type="password" name="password" required lay-verify="required" placeholder="请输入密码"
                       autocomplete="off"
                       class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">地址</label>
            <div class="layui-input-block">
                <input type="text" name="address" required lay-verify="required" placeholder="请输入地址"
                       autocomplete="off"
                       class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">账户金额()</label>
            <div class="layui-input-block">
                <input type="number" name="money" required lay-verify="required" placeholder="请输入账号金额"
                       autocomplete="off"
                       class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">状态</label>
            <div class="layui-input-block">
                <input type="radio" name="state" title="有效" value="1" checked/>
                <input type="radio" name="state" title="无效" value="0"/>
            </div>
        </div>
    </form>
</script>



<script type="text/html" id="customer-add-layer">
    <form id="customer-add-form" style="width:80%;padding-top:10px;" class="layui-form" lay-filter="customer-edit-form">
        <input type="hidden" name="id">
        <div class="layui-form-item">
            <label class="layui-form-label">用户名</label>
            <div class="layui-input-block">
                <input type="text" name="username" required lay-verify="required" placeholder="请输入用户名"
                       autocomplete="off"
                       class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">名称</label>
            <div class="layui-input-block">
                <input type="text" name="nickname" required lay-verify="required" placeholder="请输入公司名称"
                       autocomplete="off"
                       class="layui-input">
            </div>
        </div>

        <div class="layui-form-item">
            <label class="layui-form-label">密码</label>
            <div class="layui-input-block">
                <input type="password" name="password" required lay-verify="required" placeholder="请输入密码"
                       autocomplete="off"
                       class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">地址</label>
            <div class="layui-input-block">
                <input type="text" name="address" required lay-verify="required" placeholder="请输入地址"
                       autocomplete="off"
                       class="layui-input">
            </div>
        </div>

        <div class="layui-form-item">
            <label class="layui-form-label">账户金额()</label>
            <div class="layui-input-block">
                <input type="number" name="money" required lay-verify="required" placeholder="请输入账号金额"
                       autocomplete="off"
                       class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">状态</label>
            <div class="layui-input-block">
                <input type="radio" name="state" title="有效" value="1" checked/>
                <input type="radio" name="state" title="无效" value="0"/>
            </div>
        </div>
    </form>
</script>
</body>
</html>

依赖
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

    </dependencies>
yml配置
#tomcat配置信息
server:
  port: 80
#连接数据库的配置

spring:
  #数据源
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///openapi_2005?characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
  #rabbitmq配置
  rabbitmq:
    host: 192.168.246.129
    port: 5672
    username: mallow
    password: 123
    virtual-host: /mallow #主机


#mybatis的配置
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.mallow.springboot02.entity
  configuration:
    map-underscore-to-camel-case: true

配置类config

RestTemplate的配置

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate getTemplate(){
        return new RestTemplate();
    }

}

RabbitMQ的配置

@Configuration
public class RabbitMQConfig {

    /*交换机*/
    @Bean
    public TopicExchange topicExchange(){
        // return new TopicExchange(交换名称,是否持久化,是否自动删除);
        return new TopicExchange("openapi-customer-exchange",true,false);
    }
    /*队列*/
    @Bean
    public Queue queue(){
        // return new Queue(队列名称,是否持久化,是否排外,是否自动删除);
        return new Queue("openapi-customer-queue",true,false,false);
    }
    /*将队列绑定到交换机*/
    @Bean
    public Binding binding(TopicExchange topicExchange,Queue queue){
        // return BindingBuilder.bind(队列).to(交换机).with(路由key);
        return BindingBuilder.bind(queue).to(topicExchange).with("openapi.customer.*");
    }

}
实体类
@Data
public class Customer implements Serializable {
    private static final long serialVersionUID = 1586034423739L;
    // 主键
    private Integer id;
    // 公司名
    private String username;
    // 密码
    private String password;
    // 昵称
    private String nickname;
    // 金钱
    private Long money;
	// 地址
    private String address;
    // 状态  
    private Integer state;
}

mapper

public interface CustomerMapper {
    int addCustomer(Customer customer);
    int updateCustomer(Customer customer);
    int delCustomer( Integer[] ids);
}
mapper的xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mallow.springboot02.mapper.CustomerMapper">
    <!--主键回填,keyProperty回填的属性,useGeneratedKeys回填的值是否要放到keyProperty中-->
    <insert id="addCustomer" keyProperty="id" useGeneratedKeys="true">
        insert into t_customer values (null,#{username},#{password},#{nickname},#{address},#{money},#{state})
    </insert>
    <update id="updateCustomer">
        update t_customer
        <set>
            <if test="username!=null and username!='' ">
                username=#{username},
            </if>
            <if test="password!=null and password!='' ">
                password=#{password},
            </if>
            <if test="nickname!=null and nickname!='' ">
                nickname=#{nickname},
            </if>
            <if test="address!=null and address!='' ">
                address=#{address},
            </if>
            <if test="money!=null and money!='' ">
                money=#{money},
            </if>
            <if test="state!=null ">
                state=#{state},
            </if>
        </set>
        where id=#{id}
    </update>
    <delete id="delCustomer">
        update t_customer set state=0 where id in
        <foreach collection="array" open="(" close=")" item="id" separator=",">
            #{id}
        </foreach>
    </delete>

</mapper>
service
public interface CustomerService {
    String queryByCondition(Map<String,Object> map);
    void addCustomer(Customer customer);
    void updateCustomer(Customer customer);
    void delCustomer(Integer[] ids);
}
service的实现类
@Service
@Slf4j
public class CustomerServiceImpl2 implements CustomerService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private CustomerMapper customerMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /*查询*/
    @Override
    public String queryByCondition(Map<String, Object> map) {
        //把请求的参数转成json
        String json = JSON.toJson(map);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.parseMediaType("application/json;charset=utf-8"));//指定类型
        //设置请求的json的数据和数据类型
        HttpEntity httpEntity = new HttpEntity(json, httpHeaders);
        //通过restTemplate调用search模块,发送请求去请求searchController的接口,返回json数据
        String result = restTemplate.postForObject("http://localhost:8080/search/customer/query", httpEntity, String.class);//String.class是返回类型
        return result;
    }
    /*添加*/
    @Override
    public void addCustomer(Customer customer) {
        //往数据库中添加数据
        int i = customerMapper.addCustomer(customer);
        if (i != 1) {
            log.error("【添加客户信息到数据库失败!】customer={}", customer);
            throw new RuntimeException("【添加客户信息到数据库失败!】");
        }
        //发送消息
        rabbitTemplate.convertAndSend("openapi-customer-exchange","openapi.customer.add",JSON.toJson(customer));
       /* //往es中添加数据
        //把请求的参数转成json
        String json = JSON.toJson(customer);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.parseMediaType("application/json;charset=utf-8"));
        //设置请求的json的数据和数据类型
        HttpEntity httpEntity = new HttpEntity(json, httpHeaders);
        //通过restTemplate调用search模块,发送请求去请求searchController的接口,返回json数据
        restTemplate.postForObject("http://localhost:8080/search/customer/add", httpEntity, String.class);
        */
    }
    /*修改*/
    @Override
    public void updateCustomer(Customer customer) {
        int i = customerMapper.updateCustomer(customer);
        if (i != 1) {
            log.error("【修改客户信息到数据库失败!】customer={}", customer);
            throw new RuntimeException("【修改客户信息到数据库失败!】");
        }
        //发送消息
        rabbitTemplate.convertAndSend("openapi-customer-exchange","openapi.customer.update",JSON.toJson(customer));
//        String json = JSON.toJson(customer);
//        HttpHeaders httpHeaders = new HttpHeaders();
//        httpHeaders.setContentType(MediaType.parseMediaType("application/json;charset=utf-8"));
//        HttpEntity httpEntity = new HttpEntity(json, httpHeaders);
//        restTemplate.postForObject("http://localhost:8080/search/customer/update", httpEntity, String.class);
    }
    /*批量删除*/
    @Override
    public void delCustomer(Integer[] ids) {
        int i = customerMapper.delCustomer(ids);
        System.out.println(i);
        if (i < 1) {
            for (int j = 0; j < ids.length; j++) {
                log.error("【删除客户信息到数据库失败!】ids={}", ids[j]);
            }
//            log.error("【删除客户信息到数据库失败!】ids={}", ids);
//            throw new RuntimeException("【删除客户信息到数据库失败!】");
        }
        //发送消息
        rabbitTemplate.convertAndSend("openapi-customer-exchange","openapi.customer.del",JSON.toJson(ids));
//        String json = JSON.toJson(ids);
//        HttpHeaders httpHeaders = new HttpHeaders();
//        httpHeaders.setContentType(MediaType.parseMediaType("application/json;charset=utf-8"));
//        HttpEntity httpEntity = new HttpEntity(json, httpHeaders);
//        restTemplate.postForObject("http://localhost:8080/search/customer/del", httpEntity, String.class);
    }
}
controller
@RestController
@RequestMapping("/sys/customer")
public class CustomerController {

    @Autowired
    private CustomerService customerService;

    @GetMapping(value = "/table", produces = "application/json;charset=utf-8")
    public String table(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "5") Integer limit) {
        Map<String, Object> map = new HashMap<>();
        map.put("page", page);
        map.put("limit", limit);
        String json = customerService.queryByCondition(map);
        return json;
    }

    @RequestMapping(value ="/add", produces = "application/json;charset=utf-8")
    public ResultVO addCustomer(Customer customer){
        try {
            customerService.addCustomer(customer);
            return new ResultVO(true,"添加客户信息成功");
        } catch (Exception e) {
            e.printStackTrace();
            return new ResultVO(false,"添加客户信息失败!");
        }
    }

    @PostMapping(value ="/update", produces = "application/json;charset=utf-8")
    public ResultVO updateCustomer( Customer customer){
        try {
            customerService.updateCustomer(customer);
            return new ResultVO(true,"修改客户信息成功");
        } catch (Exception e) {
            e.printStackTrace();
            return new ResultVO(false,"修改客户信息失败!");
        }
    }
    @RequestMapping(value ="/del", produces = "application/json;charset=utf-8")
    public ResultVO updateCustomer(Integer[] ids){
        try {
            customerService.delCustomer(ids);
            return new ResultVO(true,"删除客户信息成功");
        } catch (Exception e) {
            e.printStackTrace();
            return new ResultVO(false,"删除客户信息失败!");
        }
    }
}

工具类

ResultVO

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResultVO {

    private boolean status;
    private String message;
}

JSON

public class JSON {

    public static String toJson(Object obj) {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return "";
        }
    }

}
8.3.2 搜索模块
依赖
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>6.5.4</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>6.5.4</version>
        </dependency>

        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>
yml配置
elasticsearch:
  host: 192.168.246.129
  port: 9200
#rabbitmq配置
spring:
  rabbitmq:
    host: 192.168.246.129
    port: 5672
    username: mallow
    password: 123
    virtual-host: /mallow #主机
配置类

ElasticSearch

@Configuration
public class ElasticSearch {

    @Value("${elasticsearch.host}")
    private String host;
    @Value("${elasticsearch.port}")
    private Integer port;

    @Bean
    public RestHighLevelClient client() {

        //创建
        HttpHost httpHost = new HttpHost(host, port);
        //创建RestClientBuilder
        RestClientBuilder clientBuilder = RestClient.builder(httpHost);
        //创建RestHighLevelClient
        RestHighLevelClient client = new RestHighLevelClient(clientBuilder);

        return client;
    }
}

RabbitMQConfig

@Configuration
public class RabbitMQConfig {

    /*交换机*/
    @Bean
    public TopicExchange topicExchange(){
        // return new TopicExchange(交换名称,是否持久化,是否自动删除);
        return new TopicExchange("openapi-customer-exchange",true,false);
    }
    /*队列*/
    @Bean
    public Queue queue(){
        // return new Queue(队列名称,是否持久化,是否排外,是否自动删除);
        return new Queue("openapi-customer-queue",true,false,false);
    }
    /*将队列绑定到交换机*/
    @Bean
    public Binding binding(TopicExchange topicExchange, Queue queue){
        // return BindingBuilder.bind(队列).to(交换机).with(路由key);
        return BindingBuilder.bind(queue).to(topicExchange).with("openapi.customer.*");
    }

}
实体类
@Data
public class Customer implements Serializable {
    private static final long serialVersionUID = 1586034423739L;
    // 主键
    private Integer id;
    // 公司名
    private String username;
    // 密码
    private String password;
    // 昵称
    private String nickname;
    // 金钱
    private Long money;
	// 地址
    private String address;
    // 状态  
    private Integer state;
}
service
public interface SearchService {

    //返回的是json数据,可以使用String
    String searchByCondition(Map<String, Object> map) throws IOException, InvocationTargetException, IllegalAccessException;

    void addCustomer(Customer customer) throws IOException;
    void updateCustomer(Customer customer) throws IOException;
    void delCustomer(Integer[] ids) throws IOException;
}
service的实现类
@Service
@Slf4j
public class SearchServiceImpl implements SearchService {

    @Autowired
    private RestHighLevelClient client;
    String index = "openapi_customer";
    String type = "customer";

    /*查询*/
    @Override
    public String searchByCondition(Map<String, Object> map) throws IOException, InvocationTargetException, IllegalAccessException {
        //创建request对象
        SearchRequest request = new SearchRequest(index);
        request.types(type);

        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.sort("id", SortOrder.ASC);//升序
        //取值,前端传过来的数据
        Object name = map.get("name");
        Object state = map.get("state");
        if (!StringUtils.isEmpty(name)) {
            builder.query(QueryBuilders.termsQuery("username", name));//es对应的属性名username
        }
        if (state != null) {
            builder.query(QueryBuilders.termsQuery("state", state));
        }
        //页数
        Integer page = Integer.parseInt(map.get("page").toString());
        Integer limit = Integer.parseInt(map.get("limit").toString());
        //查询分页的数据
        builder.from((page - 1) * limit);//起始页下标
        builder.size(limit);//每页显示的条数
        request.source(builder);

        //操作es
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        TableDataVo<Customer> dataVo = new TableDataVo<>();
        //设置总条数
        dataVo.setCount(response.getHits().getTotalHits());
        //存储数据
        List<Customer> customerList = new ArrayList<>();
        for (SearchHit hit : response.getHits().getHits()) {
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            //每遍历一条数据,则创建一个对象
            Customer customer = new Customer();
            //会把map中key对应的value值,赋值给对象的属性,前提条件是key的名称必须和对象的属性一致
            BeanUtils.populate(customer, sourceAsMap);
            //循环的添加客户对象
            customerList.add(customer);
        }
        //设置存储数据
        dataVo.setData(customerList);
        //把对象转成对象,返回
        return JSON.toJson(dataVo);
    }

    /*添加*/
    @Override
    public void addCustomer(Customer customer) throws IOException {
        //创建request对象
        IndexRequest request = new IndexRequest(index, type, customer.getId() + "");
        //设置数据,把对象转成json数据
        String customerJson = JSON.toJson(customer);
        request.source(customerJson, XContentType.JSON);
        //client执行添加操作
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
        //返回结果
        if (!"CREATED".equalsIgnoreCase(response.getResult().toString())) {
            log.error("【添加文档失败!!】index={},type={},customerId={}" + index, type, customer.getId());

        }
    }

    /*修改*/
    @Override
    public void updateCustomer(Customer customer) throws IOException {
        UpdateRequest request = new UpdateRequest(index, type, customer.getId() + "");
        String customerJson = JSON.toJson(customer);
        request.doc(customerJson, XContentType.JSON);
        UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
        if (!"UPDATED".equalsIgnoreCase(response.getResult().toString())) {
            log.error("【修改文档失败!!】index={},type={},customerId={}" , index, type, customer.getId());
        }
    }

    /*删除*/
    @Override
    public void delCustomer(Integer[] ids) throws IOException {
        //批量删除
        BulkRequest request=new BulkRequest();
        for (int i = 0; i < ids.length; i++) {
            request.add(new DeleteRequest(index,type,ids[i]+""));
        }
        String idsJson = JSON.toJson(ids);
        BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
        System.out.println(response.toString());
//        if(!"DELETED".equalsIgnoreCase(response)){
//
//        }
    }

}

controller
@RestController
@RequestMapping("/search/customer")
public class SearchController {

    @Autowired
    private SearchService searchService;

    @PostMapping(value = "/query", produces = "application/json;charset=utf-8")
    public String query(@RequestBody Map<String, Object> map) throws IllegalAccessException, IOException, InvocationTargetException {

        return searchService.searchByCondition(map);
    }

    @RequestMapping(value = "/add", produces = "application/json;charset=utf-8")
    public void addCustomer(@RequestBody Customer customer) throws IOException {//@RequestBody获取前端参数
        searchService.addCustomer(customer);
    }

    @RequestMapping(value = "/update", produces = "application/json;charset=utf-8")
    public void updateCustomer(@RequestBody Customer customer) throws IOException {
        searchService.updateCustomer(customer);
    }

    @RequestMapping(value = "/del", produces = "application/json;charset=utf-8")
    public void delCustomer(@RequestBody Integer[] ids) throws IOException {
        searchService.delCustomer(ids);
    }
    @PostMapping(value = "/search", produces = "application/json;charset=utf-8")
    public String search(@RequestBody Map<String, Object> map) throws IllegalAccessException, IOException, InvocationTargetException {

        return searchService.searchByCondition(map);
    }
}

工具类

JSON

public class JSON {

    /*对象转成JSON数据*/
    public static String toJson(Object obj) {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return "";
        }
    }

    /*JSON数据转成对象*/
    public static <T> T parseJson(String data, Class<T> clazz) {

        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.readValue(data, clazz);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 将data转化为1维整型数组, data形如:"1,3,4,5,6"
     */
    public static Integer[] toIntegerArray(String data) {
        //[21,22]
        data = data.substring(1, data.length() - 1);//将前端传过来的JSON数据的中括号切掉
        String[] datas = data.split(",");//将字符串转成一个字符串数组
        Integer[] tmp = new Integer[datas.length];//创建包装类整型数组
        //字符串数组转成包装类整型数组
        for (int i = 0; i < datas.length; i++) {
            tmp[i] = Integer.parseInt(datas[i]);
        }
        return tmp;
    }


}

TableDataVO

@Data
public class TableDataVo<T> {

    private Integer code=0;
    private String msg="";
    private Long count;
    private List<T> data;

}
ES的创建索引和添加数据
@RunWith(SpringRunner.class)
@SpringBootTest
public class ElasticInitTests {

    @Autowired
    private RestHighLevelClient client;


    String index = "openapi_customer";
    String type = "customer";


    @Test
    public void createIndex() throws IOException {
        //1. 准备关于索引的settings
        Settings.Builder settings = Settings.builder()
                .put("number_of_shards", 5)
                .put("number_of_replicas", 1);

        //2. 准备关于索引的结构mappings
        XContentBuilder mappings = JsonXContent.contentBuilder()
                .startObject()
                .startObject("properties")
                .startObject("id")
                .field("type","integer")
                .endObject()
                .startObject("username")
                .field("type","keyword")
                .endObject()
                .startObject("password")
                .field("type","keyword")
                .endObject()
                .startObject("nickname")
                .field("type","text")
                .endObject()
                .startObject("money")
                .field("type","long")
                .endObject()
                .startObject("address")
                .field("type","text")
                .endObject()
                .startObject("state")
                .field("type","integer")
                .endObject()
                .endObject()
                .endObject();


        //3. 将settings和mappings封装到一个Request对象
        CreateIndexRequest request = new CreateIndexRequest(index)
                .settings(settings)
                .mapping(type,mappings);

        //4. 通过client对象去连接ES并执行创建索引
        CreateIndexResponse resp = client.indices().create(request, RequestOptions.DEFAULT);

        //5. 输出
        System.out.println("resp:" + resp.toString());

    }
    @Test
    public void bulkCreateDoc() throws IOException {
        //1. 准备多个json数据
        Customer c1 = new Customer();
        c1.setId(1);
        c1.setUsername("haier");
        c1.setPassword("111111");
        c1.setNickname("海尔集团");
        c1.setMoney(2000000L);
        c1.setAddress("青岛");
        c1.setState(1);

        Customer c2 = new Customer();
        c2.setId(2);
        c2.setUsername("lianxiang");
        c2.setPassword("111111");
        c2.setNickname("联想");
        c2.setMoney(1000000L);
        c2.setAddress("联想");
        c2.setState(1);

        Customer c3 = new Customer();
        c3.setId(3);
        c3.setUsername("google");
        c3.setPassword("111111");
        c3.setNickname("谷歌");
        c3.setMoney(1092L);
        c3.setAddress("没过");
        c3.setState(1);

        ObjectMapper mapper = new ObjectMapper();

        String json1 = mapper.writeValueAsString(c1);
        String json2 = mapper.writeValueAsString(c2);
        String json3 = mapper.writeValueAsString(c3);

        //2. 创建Request,将准备好的数据封装进去
        BulkRequest request = new BulkRequest();
        request.add(new IndexRequest(index,type,c1.getId().toString()).source(json1, XContentType.JSON));
        request.add(new IndexRequest(index,type,c2.getId().toString()).source(json2,XContentType.JSON));
        request.add(new IndexRequest(index,type,c3.getId().toString()).source(json3,XContentType.JSON));

        //3. 用client执行
        BulkResponse resp = client.bulk(request, RequestOptions.DEFAULT);

        //4. 输出结果
        System.out.println(resp.toString());
    }

}

个人笔记,思路,仅供参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值