微服务项目第5天-消息中间件RabbitMQ

微服务项目第5天-消息中间件RabbitMQ,其中部分资料来源于十次方课程讲义。

1、消息中间件 RabbitMQ介绍

消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题实现高性能,高可用,可伸缩和终一致性[架构] 使用较多的消息队列有 ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketM

2、架构图

在这里插入图片描述

3、主要概念

RabbitMQ Server: 也叫broker server,它是一种传输服务。 他的角色就是维护一条 从Producer到Consumer的路线,保证数据能够按照指定的方式进行传输。
Producer: 消息生产者,如图A、B、C,数据的发送方。消息生产者连接RabbitMQ服 务器然后将消息投递到Exchange。
Consumer: 消息消费者,如图1、2、3,数据的接收方。消息消费者订阅队列, RabbitMQ将Queue中的消息发送到消息消费者。
Exchange: 生产者将消息发送到Exchange(交换器),由Exchange将消息路由到一个 或多个Queue中(或者丢弃)。Exchange并不存储消息。RabbitMQ中的Exchange有 direct、fanout、topic、headers四种类型,每种类型对应不同的路由规则。
Queue:(队列)是RabbitMQ的内部对象,用于存储消息。消息消费者就是通过订阅 队列来获取消息的,RabbitMQ中的消息都只能存储在Queue中,生产者生产消息并终 投递到Queue中,消费者可以从Queue中获取消息并消费。多个消费者可以订阅同一个 Queue,这时Queue中的消息会被平均分摊给多个消费者进行处理,而不是每个消费者 都收到所有的消息并处理。
RoutingKey: 生产者在将消息发送给Exchange的时候,一般会指定一个routing key, 来指定这个消息的路由规则,而这个routing key需要与Exchange Type及binding key联 合使用才能终生效。在Exchange Type与binding key固定的情况下(在正常使用时一 般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过 指定routing key来决定消息流向哪里。RabbitMQ为routing key设定的长度限制为255 bytes。
Connection: (连接): Producer和Consumer都是通过TCP连接到RabbitMQ Server 的。以后我们可以看到,程序的起始处就是建立这个TCP连接。
Channels: (信道): 它建立在上述的TCP连接中。数据流动都是在Channel中进行 的。也就是说,一般情况是程序起始建立TCP连接,第二步就是建立这个Channel。
VirtualHost: 权限控制的基本单位,一个VirtualHost里面有若干Exchange和 MessageQueue,以及指定被哪些user使用

4、安装注意事项

  • Eralng这个是rabbitMQ 的开发语言(相当于环境)
  • 配套软件中提供rabbitmq-server-3.7.4.exe的安装路径不能有中文,不能有空格

5、三种模式

三种模式:直接模式、分裂模式、主题模式

5.1 直接模式

在这里插入图片描述
每个端口的监听器会依次拿到消息

5.2 分裂模式

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

5.3 主题模式

在这里插入图片描述
图示解释:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
消费者每次只能拿到一个消息,如果已经拿到消息,就不会接受下边的生产者发送的消息。

  • exchange:交换机,用于(或者根据规则)绑定队列,直接指向队列
  • routingKey:队列名称(其实就是查找交换对应的一个key)
  • 直接模式的时候填写队列名
  • 分裂模式的时候可以不填写
  • 主题模式的时候填写规则

备注:

  • 交换器说到底是一个名称与队列绑定的列表。当消息发布到交换器时,实际上是由你所连接的信道,将消息路由键同交换器上绑定的列表进行比较,后路由消息。
  • 任何发送到Topic Exchange的消息都会被转发到所有关心RouteKey中指定话题的 Queue上

6、普通户用和后台用户

在这里插入图片描述

7、手机验证码整体流程

整体篇幅一共两个模块:
(1)用户注册模块
(2)发短信消息监听模块

  1. 用户注册模块中:使用工具创建随机生成的6位短信验证码
  2. 存入缓存(用于注册时候的消息比对)
  3. 存入消息队列(用于使用阿里云工具监听进行给用户发短信)
  4. 给用户发一份
/**
	 * 发送短信验证码
	 * @param mobile
	 */
	public void sendSms(String mobile){
		//生成随机6位数
		String checkcode = RandomStringUtils.randomNumeric(6);
		//向缓存中存一分
		redisTemplate.opsForValue().set("checkcode_" + mobile, checkcode,6, TimeUnit.HOURS);
		//给用户发一份
		// 把电话和验证码封装成一个map.(先往消息队列中放)
		Map map = new HashMap();
		map.put("mobile", mobile);
		map.put("checkcode", checkcode);
		//做用户加密登录的时候暂时先把消息队列注释掉,直接用控制台的验证码
		rabbitTemplate.convertAndSend("sms",map);
		//在控制台打印一份(方便测试)
		System.out.println("验证码为:" + checkcode);
	}

  1. 创建一个map,封装电话和验证码,
  2. 放入队列等待消费,即等待使用工具发送给用户
  3. 控制台打印一份(做测试)
  4. 创建单独一个模块–短信消息监听模块
  5. 消息队列的消息要消费掉
  6. 监听到map之后使用阿里云短信发送工具将验证码发送给用户
    短信监听模块导包:
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
            <version>2.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
         <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>3.2.5</version>
        </dependency>
    </dependencies>

配置文件:(application.yml)

server:
  port: 9009
spring:
  application:
    name: tensquare-sms #指定服务名
  rabbitmq:
    host: 192.168.197.129
aliyun:   #自己在阿里云官网上买
  sms:
    accessKeyId: xxx
    accessKeySecret: xxx
    template_code: xxx
    sign_name: xxx

监听类:

import com.aliyuncs.exceptions.ClientException;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import tensquare.utils.SmsUtil;

import java.util.Map;

@Component   //作为保证能被扫描到的组件
@RabbitListener(queues = "sms")
public class RabbitListenerSms {

    @Autowired
    private SmsUtil smsUtil;

    //这个测试的模板和签名写在配置文件中
    //spring的EL表达式 (也是拿配置文件中值的方法),这个测试只有一个,方便使用
    @Value("${aliyun.sms.template_code}")
    private String template_code;

    @Value("${aliyun.sms.sign_name}")
    private String sign_name;

    @RabbitHandler
    public void listen(Map<String ,String> map){
        String mobile = map.get("mobile");
        String checkcode = map.get("checkcode");
        System.out.println("手机号:" + map.get("mobile"));
        System.out.println("验证码:" + map.get("checkcode"));
        try {
            smsUtil.sendSms(mobile,template_code,sign_name,"{\"checkcode\":\""+checkcode+"\"}");
            //param中的 key 要对应网上申请写的模板中的参数;
            // 表示把从队列中拿到的checkcode封装放到模板的checkcode中然后发送短信
        } catch (ClientException e) {
            e.printStackTrace();
        }
    }
}

短信工具类:(可以在阿里云上找)

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;
    }
}
  1. 用户注册模块
  2. 从缓存中拿到短信验证码和用户输入的验证码比对
  3. 成功则进行用户信息入库(save)
/**
	 *  用户注册
	 * @param code 短信验证码
	 * @param user 用户信息
	 * @return
	 */
	@RequestMapping(value = "/register/{code}",method= RequestMethod.POST)
	public Result regist(@PathVariable String code,@RequestBody User user){
		//得到缓存中的验证码
		String checkcodeRedis = (String) redisTemplate.opsForValue().get("checkcode_" + user.getMobile());
		if (checkcodeRedis.isEmpty()){
			return new Result(true,StatusCode.OK,"请先获取手机验证码!");
		}
		if (!code.equals(checkcodeRedis)){
			return new Result(true,StatusCode.OK,"请输入正确的验证码!");
		}

		//下次使用消息队列中的验证码进行消费
		userService.add(user);   //对用户信息的设置写在service层,也可以写在controller层;
		return new Result(true,StatusCode.OK,"注册成功!");
	}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值