前言
最近华为云云耀云服务器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.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邮箱和阿里云短信验证码服务,采用主题模式进行验证码的发送;