Springboot项目整合RabbitMQ+Redis实现可靠的阿里云短信异步收发功能(手把手实操详细教程)

1、项目介绍

1.1、项目描述

以下是一个完整的 Spring Boot 项目案例,整合 RabbitMQ 实现阿里云短信异步收发,并将发送情况存入数据库,同时使用 Redis 缓存验证码;

这个项目旨在实现一个可靠的短信发送系统,结合了多种技术来确保短信的高效发送和管理。以下是对各个部分的详细描述:

1.2、项目结构

项目结构如下:

├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── tigerhhzz
│   │   │           ├── SpringbootMqAliyunAmsApplication.java
│   │   │           ├── config
│   │   │           │   └── RabbitMQConfig.java
│   │   │           ├── model
│   │   │           │   └── SmsLog.java
│   │   │           ├── service
│   │   │           │   ├── SmsConsumer.java
│   │   │           │   ├── SmsSenderService.java
│   │   │           │   ├── SmsService.java
│   │   │           │   └── VerificationCodeService.java
│   │   │           └── task
│   │   │               └── SmsResendTask.java
│   │   ├── resources
│   │   │   ├── application.yml
│   │   │   └── schema.sql
│   └── test
│       └── java
└── pom.xml

2、创建项目(idea)

2.1、依赖引入

  • 通过pom.xml文件引入了必要的依赖,包括 Spring Boot 的 AMQP 模块用于与 RabbitMQ 集成、JDBC 模块用于数据库操作、MySQL 数据库驱动、阿里云短信服务 SDK 和 Redis 模块用于缓存验证码。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.tigerhhzz</groupId>
    <artifactId>springboot-rabbitmq-aliyun-sms-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.0.6</version> <!-- 注:如提示报错,先升级基础包版,无法解决可联系技术支持 -->
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
            <version>2.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>tea-openapi</artifactId>
            <version>0.2.2</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>tea-util</artifactId>
            <version>0.2.20</version>
        </dependency>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.2、 配置文件

  • application.yml文件中配置了 RabbitMQ、阿里云短信服务、数据库和 Redis 的连接信息。这些配置使得项目能够连接到相应的服务和资源。
server:
  port: 8080
spring:
  application:
    name: springboot-rabbitmq-demo
    # 添加数据源配置
  datasource:
    url: jdbc:mysql://localhost:3306/springbootrabbitmq_db_msg?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
  #rabbitmq
  rabbitmq:
    host: xxxxxxxxxxx
    port: 5672
    virtual-host: /
    username: guest
    password: guest
    # 开启confirms回调 P -> Exchange
    publisher-confirms: true
    # 开启returnedMessage回调 Exchange -> Queue
    publisher-confrms-type: correlation
    publisher-returns: true
    # 设置手动确认(ack) Queue -> C
    listener:
      simple:
        acknowledge-mode: manual
      prefetch: 100
  #redis
  redis:
    host: 8.217.205.108
    port: 6379
    password: xxxxxxxx
    timeout: 50000
#阿里短信配置
aliyun:
  sms:
    accessKey: xxxxxxxx
    accessKeySecret: xxxxxxxxx
    signName: 珑湾崖头
    templateCode: SMS_474285221
    domain: dysmsapi.aliyuncs.com
    regionId: cn-beijing

# 配置mybatis的xml配置文件扫描目录
mybatis:
  mapper-locations:
    - classpath:mapper/*.xml
  configuration:
    # 打印SQL语句,需要注射掉这个mybatis属性配置,否则启动报错
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2.3、 数据库表

  • schema.sql文件中创建了sms_log表,用于记录短信的发送状态和时间。这个表有助于跟踪短信的发送历史,并可以用于后续的分析和故障排除。
CREATE TABLE sms_log (
    id INT AUTO_INCREMENT PRIMARY KEY,
    unique_id VARCHAR(36), -- 存储 UUID
    phone_number VARCHAR(20),
    message_content VARCHAR(255),
    send_status TINYINT, -- 0 表示未发送成功,1 表示发送成功
    send_time TIMESTAMP
);

2.4、 实体类

  • SmsLog类是一个简单的 Java 对象,用于表示数据库中的短信记录。它包含了短信的相关信息,如手机号码、发送状态和发送时间。
package com.tigerhhzz.model;

import lombok.Data;

import java.util.Date;

/**
 * @Author tigerhhzz
 * @Date 2024 10 05 11 37
 **/
@Data
public class SmsLog {
    
    
    private int id;
    private String uniqueId;
    private String phoneNumber;
    private String messageContent;
    private int sendStatus;
    private Date sendTime;
}

2.5、 配置类

  • RabbitMQConfig类配置了 RabbitMQ 的队列、交换器和绑定关系。通过定义这些元素,项目可以使用 RabbitMQ 进行异步消息传递。
package com.tigerhhzz.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author tigerhhzz
 * @Date 2024 10 05 11 38
 **/
@Configuration
public class RabbitMQConfig {
    
    

    public static final String SMS_QUEUE = "smsQueue";
    public static final String SMS_EXCHANGE = "smsExchange";
    public static final String SMS_ROUTING_KEY = "smsRoutingKey";

    @Bean
    public Queue smsQueue() {
    
    
        return new Queue(SMS_QUEUE);
    }

    @Bean
    public TopicExchange smsExchange() {
    
    
        return new TopicExchange(SMS_EXCHANGE);
    }

    @Bean
    public Binding binding(Queue smsQueue, TopicExchange smsExchange) {
    
    
        return BindingBuilder.bind(smsQueue).to(smsExchange).with(SMS_ROUTING_KEY);
    }
}

2.6、 验证码服务类

  • VerificationCodeService类负责生成和验证验证码。它使用 Redis 来缓存生成的验证码,并设置了过期时间。生成验证码时,它会生成一个随机的 6 位数字验证码,并将其存储在 Redis 中,同时返回给调用者。验证验证码时,它会从 Redis 中获取存储的验证码,并与用户输入的验证码进行比较。
package com.tigerhhzz.service;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Random;
import java.util.concurrent.TimeUnit;

@Service
public class VerificationCodeService {
    
    

    private final RedisTemplate<String, String> redisTemplate;

    public VerificationCodeService(RedisTemplate<String, String> redisTemplate) {
    
    
        this.redisTemplate = redisTemplate;
    }

    public String generateVerificationCode(String key) {
    
    
        // 生成随机验证码
        String verificationCode = generateRandomCode();
        // 将验证码存入 Redis,并设置过期时间,比如 5 分钟
        redisTemplate.opsForValue().set(key, verificationCode, 5, TimeUnit.MINUTES);
        return verificationCode;
    }

    public boolean verifyVerificationCode(String key, String code) {
    
    
        String storedCode = redisTemplate.opsForValue().get(key);
        return storedCode!= null && storedCode.equals(code);
    }

    private String generateRandomCode() {
    
    
        // 生成 6 位随机数字验证码
        Random random = new Random();
        int code = random.nextInt(900000) + 100000;
        return String.valueOf(code);
    }
}

2.7、 短信发送服务类

  • SmsService类负责实际的短信发送操作。它使用阿里云短信服务的 SDK 来发送短信,并在发送成功或失败时更新数据库中的发送状态。此外,它还与验证码服务类交互,生成并包含验证码在短信内容中。
package com.tigerhhzz.service;

import com.aliyun.teaopenapi.Client;
import com.aliyun.teaopenapi.models.Config;

import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.http.MethodType;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import com.aliyuncs.profile.DefaultProfile;

/**
 * @Author tigerhhzz
 * @Date 2024 10 05 11 40
 **/
@Service
public class SmsService {
    
    

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

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

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

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

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

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

    private final JdbcTemplate jdbcTemplate;
    private final VerificationCodeService verificationCodeService;

    public SmsService(JdbcTemplate jdbcTemplate, VerificationCodeService verificationCodeService) {
    
    
        this.jdbcTemplate = jdbcTemplate;
        this.verificationCodeService = verificationCodeService;
    }

    public String sendSms(String phoneNumber, String uniqueId) {
    
    
        try {
    
    
            // 生成验证码
            String verificationCode = verificationCodeService.generateVerificationCode(phoneNumber);


            DefaultProfile profile = DefaultProfile.getProfile(regionId,accessKeyId, accessKeySecret);
            IAcsClient client = new DefaultAcsClient(profile);

            CommonRequest request = new CommonRequest();
            request.setMethod(MethodType.POST);
            request.setDomain(domain);
            request.setVersion("2017-05-25");
            request.setAction("SendSms");
            request.putQueryParameter("RegionId", regionId);
            request.putQueryParameter("PhoneNumbers", phoneNumber); //目标手机号
            request.putQueryParameter("SignName", signName); //签名名称
            request.putQueryParameter("TemplateCode", templateCode); //短信模板code
            request.putQueryParameter("TemplateParam", "{\"code\":\"" + verificationCode + "\"}");//模板中变量替换

            CommonResponse response = client.getCommonResponse(request);
            String data = response.getData();
            System.out.println("发送短信后的响应结果:"+data);
            if (StringUtils.contains(data, "\"Message\":\"OK\"")) {
    
    
                // 发送成功,更新数据库状态
                jdbcTemplate.update("UPDATE sms_log SET send_status = 1, send_time = NOW() WHERE unique_id =?", uniqueId);
                return "发送成功,验证码:"+verificationCode;
            } else {
    
    
                // 发送失败,也可以记录失败原因等
                jdbcTemplate.update("UPDATE sms_log SET send_status = 0 WHERE unique_id =?", uniqueId);
                return "发送失败,验证码:"+verificationCode;
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
            // 发送异常,更新数据库状态为失败
            jdbcTemplate.update("UPDATE sms_log SET send_status = 0 WHERE unique_id =?", uniqueId);
            System.out.println("发送短信验证码失败~ phoneNumber = " + phoneNumber + e);
        }
        return null;
    }
}


2.8、 消费者类

  • SmsConsumer类是 RabbitMQ 的消费者,它监听特定的队列,并在接收到消息时调用短信发送服务类的方法来发送短信。
package com.tigerhhzz.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class SmsConsumer {
    
    

    private static final Logger LOGGER = LoggerFactory.getLogger(SmsConsumer.class);
    @Autowired
    private SmsService smsService;

//    @RabbitListener(queues = "smsQueue")
    @RabbitListener(queues = {
    
    "smsQueue"})
    public void receiveSmsTask( Map<String,String> map) {
    
    
        String phoneNumber = map.get("phoneNumber");
        String uniqueId = map.get("uniqueId");
        // 解析消息,获取手机号码和 UUID
        smsService.sendSms(phoneNumber, uniqueId);
    }
}

2.9、发送服务类

  • SmsSenderService类提供了一个方法,用于将短信发送任务异步发送到 RabbitMQ。在发送任务之前,它会将任务记录到数据库中,初始状态为未发送。
package com.tigerhhzz.service;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.util.UUID;

@Service
public class SmsSenderService {
    
    

    @Autowired
    private AmqpTemplate amqpTemplate;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void sendSmsAsync(String phoneNumber) {
    
    
        // 生成 UUID 作为唯一 ID
        String uniqueId = UUID.randomUUID().toString();
        // 先记录发送任务到数据库,初始状态为未发送
        jdbcTemplate.update("INSERT INTO sms_log (phone_number, message_content, send_status, unique_id) VALUES (?,?, 0,?)", phoneNumber, null, uniqueId);
        String message = phoneNumber + "," + uniqueId;
        amqpTemplate.convertAndSend("smsExchange", "smsRoutingKey", message);
    }
}

2.10、定时任务类

  • SmsResendTask类定义了一个定时任务,用于检查数据库中未发送成功的短信,并重新发送它们。这个定时任务每分钟执行一次,确保未发送成功的短信能够尽快得到重新发送。
package com.tigerhhzz.task;

import com.tigerhhzz.service.SmsSenderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Scheduled;

import org.springframework.scheduling.config.ScheduledTask;
import org.springframework.stereotype.Component;

/**
 * 定时任务
 * @Author tigerhhzz
 * @Date 2024 10 05 13 15
 **/
@Component
public class SmsResendTask {
    
    

    private static final Logger LOGGER = LoggerFactory.getLogger(SmsResendTask.class);

    private final SmsSenderService smsSenderService;
    private final JdbcTemplate jdbcTemplate;

    public SmsResendTask(SmsSenderService smsSenderService, JdbcTemplate jdbcTemplate) {
    
    
        this.smsSenderService = smsSenderService;
        this.jdbcTemplate = jdbcTemplate;
    }

    //@Scheduled(fixedRate = 60000) // 每分钟检查一次
    @Scheduled(cron = "0/30 * * * * ?")
    public void checkAndResendSms() {
    
    
        LOGGER.info("开始执行重新发送失败的消息!");
        jdbcTemplate.query("SELECT phone_number, unique_id FROM sms_log WHERE send_status = 0", (rs, rowNum) -> {
    
    
            String phoneNumber = rs.getString("phone_number");
            String uniqueId = rs.getString("unique_id");
            smsSenderService.sendSmsAsync(phoneNumber);
            return null;
        });
    }
}


2.11、启动类

  • Application类是项目的启动类,它使用 Spring Boot 的自动配置功能来启动应用程序。通过@SpringBootApplication注解和@ComponentScan注解,确保项目中的所有组件都能够被正确扫描和加载。
package com.tigerhhzz;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

/**
 * @Author tigerhhzz
 * @Date 2024 10 05 10 10
 **/
@SpringBootApplication
@ComponentScan(basePackages = "com.tigerhhzz")
public class SpringbootMqAliyunAmsApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(SpringbootMqAliyunAmsApplication.class, args);
        System.out.println("SpringbootMqAliyunAmsApplication启动成功!!!");
    }
}

2.12、测试控制器

package com.tigerhhzz.controller;

import com.tigerhhzz.service.SmsSenderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @Author tigerhhzz
 * @Date 2024 10 05 13 35
 **/
@RestController
@RequestMapping("/sms")
public class SmsController {
    
    

    @Autowired
    private SmsSenderService smsSenderService;

    /**
     * 通过mq发送短信验证码
     * @param phoneNumber
     */
    @RequestMapping(value="/sendsmsbymq/{phoneNumber}",method= RequestMethod.POST)
    public String sendCode(@PathVariable String phoneNumber ){
    
    
        smsSenderService.sendSmsAsync(phoneNumber);
        System.out.println("手机号:" +phoneNumber+";发送短信验证码成功");
        return "短信发送任务已提交,将异步发送。";
    }
}

3、效果测试

启动主启动类:

post请求:http://localhost:8080/sms/sendsmsbymq/150xxxx0598

使用postman进行测试:在这里插入图片描述

idea后台打印结果:
在这里插入图片描述
rabbitmq监控消息:
在这里插入图片描述
查看数据库:
在这里插入图片描述
结果:消息成功发送!!!

4、总结

通过以上的设计和实现,这个项目可以实现可靠的短信发送功能,并使用 Redis 缓存验证码来提高系统的安全性和用户体验。同时,通过异步发送和定时任务重发机制,可以确保短信的高可用性和可靠性。在实际应用中,可以根据具体需求进一步扩展和优化这个项目。

5、附件-整个源码仓库

https://gitee.com/spring2020/springboot-mq-aliyun-ams-application
感谢关注点赞!!!

在这里插入图片描述


人生从来没有真正的绝境。只要一个人的心中还怀着一粒信念的种子,那么总有一天,他就能走出困境,让生命重新开花结果。


猜你喜欢

转载自blog.csdn.net/weixin_43025151/article/details/142712485