个人博客 毕业设计5-RabbitMQ

一、简介

1、是什么

在这之前,我们先看下机构图
在这里插入图片描述
一般就是发送消息,然后给exchange,如果发生阻塞了,先丢给队列,继续把下一个消息发给exchange。然后队列就一个一个地给对应的消费者接受消息。这样就是异步处理,加快了流程,提高效率。而RabbitMQ就是干这个的。

消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量 削锋等问题实现高性能,高可用,可伸缩和最终一致性

2、AMQP

RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。

AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放 标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不 受产品、开发语言等条件的限制。

3、特性

  1. 可靠性
  2. 灵活的路由
  3. 消息集群
  4. 高可用
  5. 多种协议
  6. 多语言客户端
  7. 管理界面
  8. 跟踪机制
  9. 插件机制

二、RabbitMQ初识

1、docker下载与安装

下载
docker pull rabbitmq:management
安装
docker run -di --name=tensquare_rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p 15672:15672 -p 25672:25672 rabbitmq:management

6个端口,都要给它暴露,但我们用也只是用15672

访问http://192.168.12.128:15672/,账号密码都是guest
在这里插入图片描述
在这里插入图片描述

2、直接模式(Direct)

在这里插入图片描述

将消息直接发送给唯一一个节点使用,直接到queue。其中,其经过一个rabbitMQ自带的exchange:"",该exchange的名字为空字符串,一般称为default exchange。

  • 这种情况不需要将exchange 进行任何绑定操作
  • 传送消息只需要一个RouteKey,就是发送到的队列名字
  • 如果vhost中部存在RouteKey中指定的队列名,则该消息会被抛弃

1)创建队列

在这里插入图片描述

在这里插入图片描述

2)写代码,创建生产者

生产者,就是产生一个消息,给队列,等有接收者出现了,队列就把消息给接收者

主要就是这个maven

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

yml

spring:
  rabbitmq:
    host: 192.168.12.128

编写生产者测试类

@SpringBootTest
class TensquareRabbitmqDemoApplicationTests {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    void contextLoads() {
    
    
        rabbitTemplate.convertAndSend("MyQueue","生产者发送的一条消息");
    }

}

  • 其中convertAndSend()是一个发送消息的函数,其参数如下,直接模式采用convertAndSend(String routingKey, Object object)就是(队列名,消息对象)。但是学到后面routingKey是一个规则而不是队列名了,这里为什么可以这样用我也不是很懂,但直接模式把routingKey当作队列名就很好理解了
    在这里插入图片描述
    运行,查看queue
    在这里插入图片描述
    有了一条消息,这时候就要有一个消费者对其进行接受

编写消费者

@Component
//接受哪个队列的消息
@RabbitListener(queues = "MyQueue")
public class Customer1 {
    
    

    @RabbitHandler
    public void showMsg(String msg){
    
    
        System.out.println("消费者接受到消息:"+msg);
    }
}
  • @RabbitListener(queues = "MyQueue")接受哪个队列的消息
  • @RabbitHandler方法前加就对了,然后其发送的是什么类型,这个参数就要写什么类型

在这里插入图片描述

3、分列模式(Fanout)

在这里插入图片描述
将一个消息发给一个或者几个交换器,然后交换器和某些队列绑定。绑定后,该交换器收到消息,将该消息都发给队列,然后对应的消费者和队列绑定接收到消息。这样,可以实现群发的效果

  • 这种模式不需要RouteKey
  • 这种模式需要提前将Exchange与Queue进行绑定,一个Exchange可以绑定多个 Queue,一个Queue可以同多个Exchange进行绑定。
  • 如果接受到消息的Exchange没有与任何Queue绑定,则消息会被抛弃

1)交换器与队列绑定

多创建几个队列

在这里插入图片描述

创建交换器

在这里插入图片描述

绑定

在这里插入图片描述
在这里插入图片描述
to queue写绑定的队列,这样就完成交换器和队列的绑定
在这里插入图片描述

2)消息生产者

测试类

    @Test
    void test2(){
    
    
        rabbitTemplate.convertAndSend("ex_fanout","","分列消息");
    }
  • 使用convertAndSend(String exchange, String routingKey, Object object)函数,第一个是交换器名字,第二个是规则(主题模式会说到的),第三个就是发送消息的对象

在这里插入图片描述

3)消息消费者

分别编写3个类,每个类接受不同的队列

@Component
//接受哪个队列的消息
@RabbitListener(queues = "MyQueue")
public class Customer1 {
    
    

    @RabbitHandler
    public void showMsg(String msg){
    
    
        System.out.println("队列1接受到消息:"+msg);
    }
}
@Component
//接受哪个队列的消息
@RabbitListener(queues = "MyQueue2")
public class Customer2 {
    
    

    @RabbitHandler
    public void showMsg(String msg){
    
    
        System.out.println("队列2接收到消息:"+msg);
    }
}
@Component
//接受哪个队列的消息
@RabbitListener(queues = "MyQueue3")
public class Customer3 {
    
    

    @RabbitHandler
    public void showMsg(String msg){
    
    
        System.out.println("队列3接收到消息:"+msg);
    }
}

启动application

队列2接收到消息:分列消息
队列3接收到消息:分列消息
队列1接受到消息:分列消息

4、主题模式(Topic)

主题模式说白就是分列模式的高级版。分列模式发消息的convertAndSend(String exchange, String routingKey, Object object)中的routingKey是空,而主题模式就是对这个进行编写

1、概述

routingKey,翻译过来就是路由键,有点类是一个下标之类的东西,有了routingkey。在GUI界面中exchange和queue绑定对应的routingkey,在Java中创建消息的时候,添加不同的routingkey,可以对应地接受到消息

之前也学过一个,根据发送不同的消息,交换器发送给不同的队列。比如这个
在这里插入图片描述
一个生产者,一个交换器,3个队列。
其中,队列A和交换器绑定但是routingkey为good.#(#代表是匹配多个),其他同理。这个意思就是说,如果发送的消息时写的routingkey是good.abc、good.asdfasdf,队列A都能接受,而BC不能接受。如果发送消息时的routingkey是asd.log、zxcvz.log,B能接受而AB不能接受。如果是good.log,则ABC都能接受。

看能接受什么,还是得看设置的routingkey,然后消息和routingkey进来,匹配,发送消息

  • 这种模式需要RouteKey,也许要提前绑定Exchange与Queue
  • “#”表示0个或若干个关键字,“”表示一个关键字。如“log.”能与“log.warn”匹配,无法 与“log.warn.timeout”匹配;但是“log.#”能与上述两者匹配。
  • 同样,如果Exchange没有发现能够与RouteKey匹配的Queue,则会抛弃此消息
  • 在进行绑定时,要提供一个该队列关心的主题,如“#.log.#”表示该队列关心所有涉及 log的消息(一个RouteKey为”MQ.log.error”的消息会被转发到该队列)。

2、创建交换器与绑定

创建交换器

创建交换器
在这里插入图片描述
绑定
在这里插入图片描述
绑定完成
在这里插入图片描述

3、消费者

good.ass:

    @Test
    void test3(){
    
    
        rabbitTemplate.convertAndSend("ex_topic","good.ass","主题模式1");
    }

队列1接受到消息:主题模式1

ass.log:

    @Test
    void test4(){
    
    
        rabbitTemplate.convertAndSend("ex_topic","ass.log","主题模式2");
    }

队列2接收到消息:主题模式2

good.log:

    @Test
    void test5(){
    
    
        rabbitTemplate.convertAndSend("ex_topic","good.log","主题模式3");
    }

队列1接受到消息:主题模式3
队列2接收到消息:主题模式3
队列3接收到消息:主题模式3

5、代码

链接

三、用户微服务

1、准备工作

代码生成,端口9008
导包

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
	  <dependency>
		  <groupId>org.springframework.boot</groupId>
		  <artifactId>spring-boot-starter-data-redis</artifactId>
		  <version>2.3.4.RELEASE</version>
	  </dependency>
	  <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
	  <dependency>
		  <groupId>org.springframework.boot</groupId>
		  <artifactId>spring-boot-starter-amqp</artifactId>
		  <version>2.3.4.RELEASE</version>
	  </dependency>

yml:

spring:
  redis:
    host: 192.168.12.128
  rabbitmq:
    host: 192.168.12.128

2、发送短信

发送短信,生成随机数,给缓冲一份,给用户(用RabbitMQ发送)一份,然后现在测试给控制台显示一份
在这里插入图片描述
在此之前先导入一个随机数的包,这个包还有很多功能

<dependency>
	  <groupId>org.apache.commons</groupId>
	  <artifactId>commons-lang3</artifactId>
	  <version>3.4</version>
 </dependency>

controller

	@PostMapping(value = "sendsms/{mobile}")
	public Result sendSms(@PathVariable String mobile){
    
    
		userService.sendSms(mobile);
		return new Result(true,StatusCode.OK,"短信发送成功");
	}

在rabbitmq创建一个sms名的queue
在这里插入图片描述

service

@Service
public class UserService {
    
    

	@Autowired
	private UserDao userDao;
	
	@Autowired
	private RedisTemplate redisTemplate;
	
	@Autowired
	private RabbitTemplate rabbitTemplate;
	
	public void sendSms(String mobile) {
    
    
		//生成6位数字验证码
		String checkcode = RandomStringUtils.randomNumeric(6);
		//向缓存保留一份,在缓存保留2消失
		redisTemplate.opsForValue().set("checkcode_"+mobile,checkcode,2, TimeUnit.HOURS);
		//给用户发一份(rabbitmq)
		//队列为sms,发送Map类型
		Map<String,Object> map = new HashMap<>();
		map.put("mobile",mobile);
		map.put("checkcode",checkcode);
		rabbitTemplate.convertAndSend("sms",map);
		//控制台输出
		System.out.println("验证码为"+checkcode);
	}
}

在这里插入图片描述
在这里插入图片描述

验证码为250061

3、用户注册

在这里插入图片描述
controller

	@PostMapping("register/{code}")
	public Result register(@PathVariable String code,@RequestBody User user){
    
    
		userService.register(user,code);
		return new Result(true,StatusCode.OK,"注册成功");
	}
	public void register(User user, String code) {
    
    
		String checkcodeRedis = (String) redisTemplate.opsForValue().get("checkcode_" + user.getMobile());
		if (checkcodeRedis==null){
    
    
			throw new RuntimeException("请先获取手机验证码");
		}
		if (!checkcodeRedis.equals(code)){
    
    
			throw new RuntimeException("验证码错误");
		}

		user.setId(idWorker.nextId()+"");
		user.setRegdate(new Date());
		user.setUpdatedate(new Date());
		user.setLastdate(new Date());
		user.setOnline(0l);
		user.setFanscount(0);
		user.setFollowcount(0);

		userDao.save(user);
	}
  • 这个注册没用到rabbitmq

在这里插入图片描述
在这里插入图片描述

四、短信微服务

用来消费rabbitmq,上面用户微服务是生成一个消息,这里消费一个消息,并且把消息发送给用户,然后利用上面的用户注册完成注册

创建模块,导入amqp包

	<dependency>
  		<groupId>org.springframework.boot</groupId>
     	<artifactId>spring-boot-starter-amqp</artifactId>
        <version>2.3.4.RELEASE</version>
    </dependency>

yml

server:
  port: 9009
spring:
  application:
    name: tensquare-sms
  rabbitmq:
    host: 192.168.12.128

1、监听类

@Component
@RabbitListener(queues = "sms")
public class SmsListener {
    
    

    @RabbitHandler
    public void sendSms(Map<String,Object> msg){
    
    
        System.out.println("手机号:"+msg.get("mobile"));
        System.out.println("验证码:"+msg.get("checkcode"));
    }
}

手机号:18200000000
验证码:250061

2、发送短信(阿里云通信)

4分5一条短信,就是说1块可以发22次。

进入阿里云,登陆注册,产品选择短信服务

1)申请签名与模板

  • 申请签名
  • 申请模板

在这里插入图片描述
在这里插入图片描述
这里写好点,不然不给过的。

2)创建AccessKey

在这里插入图片描述
创建一个AccessKey,然后里面有账号和密码的
在这里插入图片描述
这个很重要的啊,不要给别人看了

3)充钱

就充1.2块OK了

4)写代码

导包

        <!-- https://mvnrepository.com/artifact/com.aliyun/aliyun-java-sdk-dysmsapi -->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
            <version>1.1.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.aliyun/aliyun-java-sdk-core -->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.5.16</version>
        </dependency>

点进API查看,有这么个东西
在这里插入图片描述
但是,大佬已经帮我们打包好成一个类了,我们就感谢地拿来用吧(其实就是把官方的代码,弄成一个类封装好)

①工具类

package com.tensquare.sms.utils;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * 短信工具类
 * @author Administrator
 *
 */
@Component
public class SmsUtil {
    
    

    //产品名称:云通信短信API产品,开发者无需替换
    static final String product = "Dysmsapi";
    //产品域名,开发者无需替换
    static final String domain = "dysmsapi.aliyuncs.com";

    @Autowired
    private Environment env;

    // TODO 此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)

    /**
     * 发送短信
     * @param mobile 手机号
     * @param template_code 模板号
     * @param sign_name 签名
     * @param param 参数
     * @return
     * @throws ClientException
     */
    public SendSmsResponse sendSms(String mobile,String template_code,String sign_name,String param) throws ClientException {
    
    
        String accessKeyId =env.getProperty("aliyun.sms.accessKeyId");
        String accessKeySecret = env.getProperty("aliyun.sms.accessKeySecret");
        //可自助调整超时时间
        System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
        System.setProperty("sun.net.client.defaultReadTimeout", "10000");
        //初始化acsClient,暂不支持region化
        IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
        DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
        IAcsClient acsClient = new DefaultAcsClient(profile);
        //组装请求对象-具体描述见控制台-文档部分内容
        SendSmsRequest request = new SendSmsRequest();
        //必填:待发送手机号
        request.setPhoneNumbers(mobile);
        //必填:短信签名-可在短信控制台中找到
        request.setSignName(sign_name);
        //必填:短信模板-可在短信控制台中找到
        request.setTemplateCode(template_code);
        //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
        request.setTemplateParam(param);
        //选填-上行短信扩展码(无特殊需求用户请忽略此字段)
        //request.setSmsUpExtendCode("90997");
        //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
        request.setOutId("yourOutId");
        //hint 此处可能会抛出异常,注意catch
        SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
        return sendSmsResponse;
    }

    public  QuerySendDetailsResponse querySendDetails(String mobile,String bizId) throws ClientException {
    
    
        String accessKeyId =env.getProperty("accessKeyId");
        String accessKeySecret = env.getProperty("accessKeySecret");
        //可自助调整超时时间
        System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
        System.setProperty("sun.net.client.defaultReadTimeout", "10000");
        //初始化acsClient,暂不支持region化
        IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
        DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
        IAcsClient acsClient = new DefaultAcsClient(profile);
        //组装请求对象
        QuerySendDetailsRequest request = new QuerySendDetailsRequest();
        //必填-号码
        request.setPhoneNumber(mobile);
        //可选-流水号
        request.setBizId(bizId);
        //必填-发送日期 支持30天内记录查询,格式yyyyMMdd
        SimpleDateFormat ft = new SimpleDateFormat("yyyyMMdd");
        request.setSendDate(ft.format(new Date()));
        //必填-页大小
        request.setPageSize(10L);
        //必填-当前页码从1开始计数
        request.setCurrentPage(1L);
        //hint 此处可能会抛出异常,注意catch
        QuerySendDetailsResponse querySendDetailsResponse = acsClient.getAcsResponse(request);
        return querySendDetailsResponse;
    }
}
  • private Environment env;:自动拿到yml中的配置
  • public SendSmsResponse sendSms(String mobile,String template_code,String sign_name,String param)翻译为人话:sendSms(电话号码,签名,模板,一个字符串的key=>value东西),其中,key为:
    在这里插入图片描述

yml添加

aliyun:
  sms:
    accessKeyId: 你猜猜
    accessKeySecret: 你猜猜
    sign_name: 十次方1024博客
    template_code: SMS_205826458
  • accessKeyId和accessKeySecret就是申请的AccessKey 里面的东西
  • sign_name
    在这里插入图片描述
  • template_code
    在这里插入图片描述

②修改SmsListener

@Component
@RabbitListener(queues = "sms")
public class SmsListener {
    
    

    //导入工具类
    @Autowired
    private SmsUtil smsUtil;

    //从yml自动拿去数据
    @Value("${aliyun.sms.sign_name}")
    private String sign_name;//签名

    @Value(("${aliyun.sms.template_code}"))
    private String template_code;//模板编号


    @RabbitHandler
    public void sendSms(Map<String,Object> map){
    
    
        String mobile = (String) map.get("mobile");
        String checkcode = (String) map.get("checkcode");

        try {
    
    
        //{"code":"验证码"}
            smsUtil.sendSms(mobile,template_code,sign_name,"{\"code\":\""+checkcode+"\"}");
        } catch (ClientException e) {
    
    
            e.printStackTrace();
        }

        System.out.println("手机号:"+mobile);
        System.out.println("验证码:"+checkcode);
    }
}

5)测试

启动UserApplication和SmsApplication

发短信
在这里插入图片描述
sms类控制台:

手机号:18200778967
验证码:858345

user控制台

验证码为858345

手机短信
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
哦我的天啊,感动哭了

user和sms的代码

猜你喜欢

转载自blog.csdn.net/yi742891270/article/details/110086219