前言
最近华为云云耀云服务器L实例上新,也搞了一台来玩,期间遇到各种问题,在解决问题的过程中学到不少和运维相关的知识。
本篇博客介绍RabbitMQ的Docker版本安装和配置,延迟插件的安装;结合QQ邮箱和阿里云短信验证码服务,采用主题模式进行验证码的发送。
关于邮箱验证码和手机短信验证码可以参考以下博客
SpringBoot项目(验证码整合)——springboot整合email & springboot整合阿里云短信服务
其他相关的华为云云耀云服务器L实例评测文章列表如下:
-
初始化配置SSH连接 & 安装MySQL的docker镜像 & 安装redis以及主从搭建 & 7.2版本redis.conf配置文件
-
安装Java8环境 & 配置环境变量 & spring项目部署 &【!】存在问题未解决
-
部署spring项目端口开放问题的解决 & 服务器项目环境搭建MySQL,Redis,Minio…指南
-
由于自己原因导致MySQL数据库被攻击 & MySQL的binlog日志文件的理解
-
认识redis未授权访问漏洞 & 漏洞的部分复现 & 设置连接密码 & redis其他命令学习
-
canal | 拉取创建canal镜像配置相关参数 & 搭建canal连接MySQL数据库 & spring项目应用canal初步
-
canal | 基于canal缓存自动更新流程 & SpringBoot项目应用案例和源码
-
Docker版的Minio安装 & Springboot项目中的使用 & 结合vue进行图片的存取
-
在Redis的Docker容器中安装BloomFilter & 在Spring中使用Redis插件版的布隆过滤器
-
Elasticsearch的Docker版本的安装和参数设置 & 端口开放和浏览器访问
-
Elasticsearch的可视化Kibana工具安装 & IK分词器的安装和使用
-
Elasticsearch的springboot整合 & Kibana进行全查询和模糊查询
引出
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.34port: 5672username: adminpassword: 123# 确认收到publisher-confirm-type: correlatedpublisher-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队列*/@Beanpublic Queue emailQueue(){return new Queue(RabbitMQConstance.MQ_EMAIL_QUEUE,RabbitMQConstance.durable,RabbitMQConstance.exclusive,RabbitMQConstance.autoDelete);}/*** 电话队列* @return 电话的队列*/@Beanpublic Queue phoneQueue(){return new Queue(RabbitMQConstance.MQ_PHONE_QUEUE,RabbitMQConstance.durable,RabbitMQConstance.exclusive,RabbitMQConstance.autoDelete);}/*** 队列的交换机fanout* @return 队列的交换机fanout*/@Beanpublic FanoutExchange fanoutExchange(){return new FanoutExchange(RabbitMQConstance.MQ_FANOUT_EXCHANGE,RabbitMQConstance.durable,RabbitMQConstance.autoDelete);}/*** 主题模式topic的交换机* @return 主题模式的topic交换机*/@Beanpublic TopicExchange topicExchange(){return new TopicExchange(RabbitMQConstance.MQ_TOPIC_EXCHANGE,RabbitMQConstance.durable,RabbitMQConstance.autoDelete);}/*** ######################建立队列和交换机的绑定关系 ###################*//*** 建立邮箱队列和交换机的绑定关系* @return 绑定的关系*/@Beanpublic Binding emailBlinding(){return BindingBuilder.bind(emailQueue()).to(topicExchange()).with("topic.email");}/*** 建立电话队列和交换机的绑定关系* @return*/@Beanpublic Binding phoneBlinding(){return BindingBuilder.bind(phoneQueue()).to(topicExchange()).with("topic.phone");}/*** ###################### 对象转换成json字符串进行发送 ##############*/@Beanpublic MessageConverter messageConverter(){return new Jackson2JsonMessageConverter();}@Beanpublic 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 autoDeleteboolean 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接口的实现
@Overridepublic 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);}@Overridepublic void sendToEmailQueue(SMSDto smsDto) {// 发送消息给邮箱,邮箱验证码rabbitTemplate.convertAndSend(RabbitMQConstance.MQ_TOPIC_EXCHANGE,"topic.email",smsDto);log.debug("{} [ 邮箱验证码 生产者向 主题模式交换机: ] 发送一条消息 {}",new Date(), smsDto);}@Overridepublic 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 {@Autowiredprivate SMSUtil smsUtil;@Autowiredprivate RedisUtil redisUtil;@RabbitListener(queues = RabbitMQConstance.MQ_EMAIL_QUEUE)@Overridepublic 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)@Overridepublic 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;@Resourceprivate JavaMailSender javaMailSender;/*** 发送手机验证码* @param tel 接收验证码的手机号码* @param code 验证码*/public void sendPhoneCode(String tel,String code) {DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou",AccessIdKey, //AccessIdKeyAccessKeySecret); //AccessKey SecretIAcsClient 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","阿里云短信测试");//短信模板IDrequest.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 {@Autowiredprivate RedisTemplate<String,Object> redisTemplate;@Autowiredprivate 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 {@Autowiredprivate 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邮箱和阿里云短信验证码服务,采用主题模式进行验证码的发送;