华为云云耀云服务器L实例评测|RabbitMQ的Docker版本安装 + 延迟插件安装 & QQ邮箱和阿里云短信验证码的主题模式发送

在这里插入图片描述

前言

最近华为云云耀云服务器L实例上新,也搞了一台来玩,期间遇到各种问题,在解决问题的过程中学到不少和运维相关的知识。

本篇博客介绍RabbitMQ的Docker版本安装和配置,延迟插件的安装;结合QQ邮箱和阿里云短信验证码服务,采用主题模式进行验证码的发送。

关于邮箱验证码和手机短信验证码可以参考以下博客

SpringBoot项目(验证码整合)——springboot整合email & springboot整合阿里云短信服务

在这里插入图片描述

其他相关的华为云云耀云服务器L实例评测文章列表如下:

在这里插入图片描述

在这里插入图片描述

引出


1.RabbitMQ的Docker版本安装和配置,延迟插件的安装;
2.结合QQ邮箱和阿里云短信验证码服务,采用主题模式进行验证码的发送;

RabbitMQ的Docker版本安装

1.拉取镜像创建容器

docker pull rabbitmq

在这里插入图片描述

docker run -itd --name=rabbitmq_pet \
-e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123 \
-p 15672:15672 -p 5672:5672 rabbitmq

在这里插入图片描述

查看rabbitmq的版本,3.9.11

在这里插入图片描述

docker ps查看当前运行的容器

在这里插入图片描述

2.开放端口和访问

华为云控制台开放端口

在这里插入图片描述

前端访问报错

在这里插入图片描述

无法显示页面

在这里插入图片描述

打开管理页面

前端访问,需要打开管理页面

在这里插入图片描述

 docker exec -it rabbitmq_pet bash
rabbitmq-plugins enable rabbitmq_management

输入用户名密码,进入rabbitmq管理页面

在这里插入图片描述

允许查看channels

在这里插入图片描述

进入rabbitmq容器进行修改

在这里插入图片描述

 cd /etc/rabbitmq/conf.d/
echo management_agent.disable_metrics_collector=false > management_agent.disable_metrics_collector.conf

在这里插入图片描述

重启后可以进入channels页面

在这里插入图片描述

3.安装延迟插件

下载支持3.9.x的插件

https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases?after=rabbitmq_v3_6_12

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

一开始没有延迟插件,需要安装一下延迟插件

上传延迟插件到文件夹

在这里插入图片描述

docker cp ./rabbitmq_delayed_message_exchange-3.9.0.ez rabbitmq_pet:/plugins

进入容器,允许延迟插件

在这里插入图片描述

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

刷新前端页面,延迟插件安装成功

在这里插入图片描述

安装成功

在这里插入图片描述

使用rabbitmq进行验证码的发送

在这里插入图片描述

1.依赖导入

        <!--        rabbitmq queue的包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

在这里插入图片描述

spring:
  rabbitmq:
    host: 124.70.138.34
    port: 5672
    username: admin
    password: 123
    # 确认收到
    publisher-confirm-type: correlated
    publisher-returns: true

2.配置文件

在这里插入图片描述

package com.tianju.fresh.config;


import com.tianju.fresh.util.RabbitMQConstance;
import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;


/**
 * rabbitmq的配置类
 */
@Configuration
public class RabbitMQConfig {

    /**
     * 创建邮箱的队列
     * @return 邮箱的rabbitmq队列
     */
    @Bean
    public Queue emailQueue(){
        return new Queue(
                RabbitMQConstance.MQ_EMAIL_QUEUE,
                RabbitMQConstance.durable,
                RabbitMQConstance.exclusive,
                RabbitMQConstance.autoDelete
        );
    }

    /**
     * 电话队列
     * @return 电话的队列
     */
    @Bean
    public Queue phoneQueue(){
        return new Queue(
                RabbitMQConstance.MQ_PHONE_QUEUE,
                RabbitMQConstance.durable,
                RabbitMQConstance.exclusive,
                RabbitMQConstance.autoDelete
        );
    }

    /**
     * 队列的交换机fanout
     * @return 队列的交换机fanout
     */
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange(
                RabbitMQConstance.MQ_FANOUT_EXCHANGE,
                RabbitMQConstance.durable,
                RabbitMQConstance.autoDelete
        );
    }

    /**
     * 主题模式topic的交换机
     * @return 主题模式的topic交换机
     */
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange(
                RabbitMQConstance.MQ_TOPIC_EXCHANGE,
                RabbitMQConstance.durable,
                RabbitMQConstance.autoDelete
        );
    }


    /**
     * ######################建立队列和交换机的绑定关系 ###################
     */

    /**
     * 建立邮箱队列和交换机的绑定关系
     * @return 绑定的关系
     */
    @Bean
    public Binding emailBlinding(){
        return BindingBuilder.bind(emailQueue())
                .to(topicExchange())
                .with("topic.email");
    }


    /**
     * 建立电话队列和交换机的绑定关系
     * @return
     */
    @Bean
    public Binding phoneBlinding(){
        return BindingBuilder.bind(phoneQueue())
                .to(topicExchange())
                .with("topic.phone");
    }

    /**
     * ###################### 对象转换成json字符串进行发送 ##############
     */

    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(messageConverter());// 修改转换器
        return rabbitTemplate;
    }

}

在这里插入图片描述

package com.tianju.fresh.util;

/**
 * 项目中用的常量
 */
public interface RabbitMQConstance {

    /**
     * rabbitmq相关的常量
     */
    String MQ_EMAIL_QUEUE="mq_email_queue";
    String MQ_PHONE_QUEUE="mq_phone_queue";
    String MQ_FANOUT_EXCHANGE="mq_fanout_exchange";
    String MQ_TOPIC_EXCHANGE="mq_topic_exchange";

    // 参数 String name, boolean durable, boolean exclusive, boolean autoDelete
    boolean durable = true; // 表示队列是否持久化
    boolean exclusive = false; // 是否排它式
    boolean autoDelete = false;

}

3.发送的业务service代码

在这里插入图片描述

    /**
     * ########################## 用户注册需要的东西 ###################
     */
    HttpResp getRegisterSMSCode(String input);

    /**
     * 发送消息给主题模式的交换机,
     * 交换机将消息转发给邮箱队列
     * @param smsDto
     */
    void sendToEmailQueue(SMSDto smsDto);

    /**
     * 发送消息给主题模式的交换机,
     * 交换机把消息发送给电话队列
     * @param smsDto
     */
    void sendToPhoneQueue(SMSDto smsDto);

service接口的实现

    @Override
    public HttpResp getRegisterSMSCode(String input) {
        // 1.输入邮箱或者手机号码是否合法
        // 2.redis里面是否有
        // 3.根据邮箱 或 手机 发送不同的消息给交换机
        // 4.返回给前端验证码

        if(!SMSUtil.isEmailOrPhone(input)){
            return HttpResp.failed("输入的邮箱 或 手机号码不合法");
        }

        if (redisUtil.isKeyInRedis(input)){
            return HttpResp.failed("验证码已发送,请检查,稍后重试");
        }

        String code = SMSUtil.getCode();

        if (SMSUtil.isEmail(input)){ // 发送邮箱验证码
            sendToEmailQueue(
                    new SMSDto(input,"邮箱验证码",code)
            );
            Map map = new HashMap();
            map.put(input, code);
            return HttpResp.success(map);
        }

        sendToPhoneQueue(
                new SMSDto(input, null, code)
        );
        return HttpResp.success(code);
    }

    @Override
    public void sendToEmailQueue(SMSDto smsDto) {
        // 发送消息给邮箱,邮箱验证码
        rabbitTemplate.convertAndSend(
                RabbitMQConstance.MQ_TOPIC_EXCHANGE,
                "topic.email",
                smsDto
        );
        log.debug("{} [ 邮箱验证码 生产者向 主题模式交换机: ] 发送一条消息 {}",new Date(), smsDto);
    }

    @Override
    public void sendToPhoneQueue(SMSDto smsDto) {
        // 生产者:发送消息给主题交换机,主题交换机把消息给电话队列
        // 消费者:监听电话队列,如果电话队列有消息,就进行消费,调用发送电话验证码的方法,发送手机验证码
        rabbitTemplate.convertAndSend(
                RabbitMQConstance.MQ_TOPIC_EXCHANGE,
                "topic.phone",
                smsDto
        );
        log.debug("{} [ 手机验证码 生产者向 主题模式交换机: ] 发送一条消息 {}",new Date(), smsDto);
    }

smsDto实体类,进行验证码对象传输

在这里插入图片描述

package com.tianju.fresh.entity.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SMSDto {
    private String target;// 目标邮箱或者手机号码
    private String msg; // 补充的消息
    private String code; // 验证码
}

4.监听队列消息发送验证码

在这里插入图片描述

package com.tianju.fresh.rabbitMQ;

import com.tianju.fresh.entity.dto.SMSDto;

/**
 * 监听手机验证码 邮箱 的队列
 * 如果有消息,就进行消费 发送验证码
 */
public interface ConsumerService {

    /**
     * 调用给邮箱发送验证码
     * @param smsDto 数据传输层对象,包含目标邮箱,消息,验证码
     */
    void callSendToEmail(SMSDto smsDto);

    /**
     * 调用给手机发送短信验证码
     * @param smsDto 数据传输层对象,包含目标手机号码,验证码
     */
    void callSendToPhone(SMSDto smsDto);
}

接口代码的实现类

package com.tianju.fresh.rabbitMQ.impl;

import com.tianju.fresh.entity.dto.SMSDto;
import com.tianju.fresh.rabbitMQ.ConsumerService;
import com.tianju.fresh.util.RabbitMQConstance;
import com.tianju.fresh.util.RedisUtil;
import com.tianju.fresh.util.SMSUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
@Slf4j
public class ConsumerServiceImpl implements ConsumerService {

    @Autowired
    private SMSUtil smsUtil;

    @Autowired
    private RedisUtil redisUtil;

    @RabbitListener(queues = RabbitMQConstance.MQ_EMAIL_QUEUE)
    @Override
    public void callSendToEmail(SMSDto smsDto) {
        log.debug("[ 邮箱队列 消费者模块:] 在{} 获得一条消息{},即将发送邮箱验证码",new Date(),smsDto);
        smsUtil.sendEmailCode(smsDto.getTarget(),smsDto.getMsg(),smsDto.getCode());
        // 存到redis里面,有效时间是5分钟
        redisUtil.saveStringValue(smsDto.getTarget(), smsDto.getCode(), 60*5);
        log.debug("邮箱验证码存到redis中,有效期为 5分钟");
    }

    @RabbitListener(queues = RabbitMQConstance.MQ_PHONE_QUEUE)
    @Override
    public void callSendToPhone(SMSDto smsDto) {
        log.debug("[ 电话队列 消费者模块:] 在{} 获得一条消息{},即将发送手机验证码",new Date(),smsDto);
        smsUtil.sendPhoneCode(smsDto.getTarget(), smsDto.getCode());
        // 验证码存到redis中
        redisUtil.saveStringValue(smsDto.getTarget(), smsDto.getCode(), 60*5);
        log.debug("手机验证码存到redis中,有效期为 5分钟");
    }
}

5.用到的工具类

在这里插入图片描述

package com.tianju.fresh.util;

import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
@PropertySource("classpath:config/ali.properties")
@Slf4j
public class SMSUtil {

    // 阿里云短信服务的配置
    @Value("${ali.msg.AccessIdKey}")
    private String AccessIdKey;
    @Value("${ali.msg.AccessKeySecret}")
    private String AccessKeySecret;

    // QQ邮箱的配置
    @Value("${spring.mail.username}")
    private String from;
    @Resource
    private JavaMailSender javaMailSender;

    /**
     * 发送手机验证码
     * @param tel 接收验证码的手机号码
     * @param code 验证码
     */
    public void sendPhoneCode(String tel,String code) {
        DefaultProfile profile = DefaultProfile.getProfile(
                "cn-hangzhou",
                AccessIdKey, //AccessIdKey
                AccessKeySecret); //AccessKey Secret
        IAcsClient client = new DefaultAcsClient(profile);
        CommonRequest request = new CommonRequest();
        request.setSysMethod(MethodType.POST);
        //下面这3个不要改动
        request.setSysDomain("dysmsapi.aliyuncs.com");
        request.setSysVersion("2017-05-25");
        request.setSysAction("SendSms");
        //接收短信的手机号码
        request.putQueryParameter("PhoneNumbers",tel);//此处写电话号码
        //短信签名名称
        request.putQueryParameter("SignName","阿里云短信测试");
        //短信模板ID
        request.putQueryParameter("TemplateCode","SMS_154950909");
        //短信模板变量对应的实际值 ${code} 中的值
        Map<String,String> param = new HashMap<>(2);
        param.put("code", String.valueOf(code)); //写入的短信内容,验证码
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));
        try {
            CommonResponse response = client.getCommonResponse(request);
            System.out.println(response.getData());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
            Date date = new Date();
            String formattedDate = sdf.format(date);
            log.debug("在 {} 时,发送一条短信验证码[ {} ]给手机 {}",formattedDate,code,tel);
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (ClientException e) {
            e.printStackTrace();
        }
    }



    /**
     * 发送qq邮箱的代码
     * @param email 接收邮件信息的邮箱地址
     * @param subject 邮件的主题:标题
     * @param content 邮件的内容:你的验证码是3927
     */
    public void sendEmailCode(String email, String subject, String content) {
        SimpleMailMessage mailMessage = new SimpleMailMessage();
        mailMessage.setSubject(subject);
        mailMessage.setTo(email);
        mailMessage.setText(content);
        mailMessage.setSentDate(new Date());
        mailMessage.setFrom(from);
        javaMailSender.send(mailMessage);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        String formattedDate = sdf.format(mailMessage.getSentDate());
        log.debug("在 {} 发送一条邮件[ {} ]给 {}",formattedDate,mailMessage.getText(),mailMessage.getTo());
    }

    /**
     * 判断是不是合法的手机号码
     * @param input 待判断的手机号码
     * @return
     */
    public static boolean isPhoneNumber(String input) {
        String pattern = "^1[3456789]\\d{9}$";
        Pattern regex = Pattern.compile(pattern);
        Matcher matcher = regex.matcher(input);
        return matcher.matches();
    }

    /**
     * 判断是不是合法的邮箱
     * @param input 待判断的邮箱
     * @return
     */
    public static boolean isEmail(String input) {
        String pattern = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
        Pattern regex = Pattern.compile(pattern);
        Matcher matcher = regex.matcher(input);
        return matcher.matches();
    }

    /**
     * 判断是不是合法的手机号码,或者邮箱
     * @param input
     * @return
     */
    public static boolean isEmailOrPhone(String input){
        if (isPhoneNumber(input)){
            return true;
        }else return isEmail(input);
    }

    /**
     * 获取随机生成的4位密码
     * @return 随机生成的4位密码
     */
    public static String getCode(){
        Random random = new Random();
        return random.nextInt(900000) + 100000 +"";
    }

    public static void main(String[] args) {
        System.out.println(getCode());
    }
}

在这里插入图片描述

package com.tianju.fresh.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Set;
import java.util.concurrent.TimeUnit;


@Component
public class RedisUtil {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public void  saveObjectToRedis(String key,Object json){
        redisTemplate.opsForValue().set(key,json);
    }

    /**
     * 从redis里面获取json对象,如果没有,返回null
     * @param key
     * @return
     */
    public Object getJsonFromRedis(String key){
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 删除redis里面的值,键
     * @param key 删除的键
     * @return
     */
    public Boolean deleteKey(String key){
        return redisTemplate.delete(key);
    }

    /**
     * 存到Redis里面的 set 中
     * @param key 存的键
     * @param value 存到set的值
     */
    public void saveSetToRedis(String key,String... value){
        for (String s:value){
            if (s!=null && !s.equals("")){
                stringRedisTemplate.opsForSet().add(key, s);
            }
        }
    }

    /**
     * 判断是否在redis的set中
     * @param key
     * @param val
     * @return
     */
    public Boolean isInSet(String key,String val){
        return stringRedisTemplate.opsForSet().isMember(key, val);
    }

    /**
     * 获得set中的值
     * @param key 键
     * @return
     */
    public Set<String> getSet(String key){
        return stringRedisTemplate.opsForSet().members(key);
    }

    /**
     * 从redis里面的set删除数据
     * @param key
     * @param val
     */
    public void removeFromSet(String key,String val){
        stringRedisTemplate.opsForSet().remove(key, val);
    }



    /**
     * 获得存到redis里面的键对应的string类型的值
     * @param key 键
     * @return
     */
    public String getStringValue(String key){
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 保存到redis里面string
     * @param key
     * @param timeout 过期时间,传的是多少s之后过期
     * @param val
     */
    public void saveStringValue(String key,String val,Integer... timeout){
        if (timeout==null){
            stringRedisTemplate.opsForValue().set(key,val);
        }else {
            stringRedisTemplate.opsForValue().set(key,val,timeout[0], TimeUnit.SECONDS);
        }
    }

    /**
     * 看某个键是否存在于Redis中
     * @param key 待检验的键
     * @return
     */
    public Boolean isKeyInRedis(String key){
        return stringRedisTemplate.hasKey(key);
    }
}

6.controller层代码

在这里插入图片描述

package com.tianju.fresh.controller;

import com.tianju.fresh.entity.vo.LoginVo;
import com.tianju.fresh.resp.HttpResp;
import com.tianju.fresh.service.CustomerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/user")
@Slf4j
public class CustomerController {

    @Autowired
    private CustomerService customerService;

    @PostMapping("/login")
    public HttpResp login(@RequestBody LoginVo loginVo){
        log.debug("登陆前端传的参数>>>"+loginVo);
        return customerService.login(loginVo);
    }

    @GetMapping("/login/getSMSCode/{str}")
    public  HttpResp getSMSCode(@PathVariable("str") String str){
        log.debug("即将给邮箱/手机 "+str+"发送验证码");
        return customerService.getLoginSMSCode(str);
    }

    @GetMapping("/register/getSMSCode/{str}")
    public  HttpResp getRegisterSMSCode(@PathVariable("str") String str){
        log.debug("即将给邮箱/手机 "+str+"发送验证码");
        return customerService.getRegisterSMSCode(str);
    }
}

效果展示

项目启动后,主题模式的交换机和两个队列初始化成功

在这里插入图片描述

邮箱队列和电话队列

在这里插入图片描述

交换机和队列之间的绑定关系

在这里插入图片描述

手机验证码

调用controller层代码后,后台打印日志

在这里插入图片描述

手机验证码存储到Redis里面,有效期5分钟

在这里插入图片描述

获得阿里云发送的短信

在这里插入图片描述

邮箱验证码

调用controller层接口发送邮箱验证码

在这里插入图片描述

邮箱验证码存到Redis里面

在这里插入图片描述

收到邮箱验证码

在这里插入图片描述


总结

1.RabbitMQ的Docker版本安装和配置,延迟插件的安装;
2.结合QQ邮箱和阿里云短信验证码服务,采用主题模式进行验证码的发送;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Arya's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值