Spring Boot实现短信服务完全指南

文章目录

    • 1. 短信服务基础知识
      • 1.1 什么是短信服务
      • 1.2 短信服务工作原理
      • 1.3 常用的短信服务商
      • 1.4 短信服务的成本与计费模式
      • 1.5 短信模板与签名
    • 2. 在Spring Boot中集成短信服务
      • 2.1 项目准备
        • 2.1.1 创建Spring Boot项目
        • 2.1.2 项目结构
      • 2.2 集成阿里云短信服务
        • 2.2.1 准备工作
        • 2.2.2 添加阿里云短信SDK依赖
        • 2.2.3 创建配置类
        • 2.2.4 创建模型类
        • 2.2.5 创建短信服务接口
        • 2.2.6 实现阿里云短信服务
        • 2.2.7 创建短信控制器
        • 2.2.8 使用示例
      • 2.3 集成腾讯云短信服务
        • 2.3.1 准备工作
        • 2.3.2 添加腾讯云短信SDK依赖
        • 2.3.3 修改配置文件
        • 2.3.4 实现腾讯云短信服务
      • 2.4 集成华为云短信服务
        • 2.4.1 准备工作
        • 2.4.2 添加华为云短信SDK依赖
        • 2.4.3 修改配置文件
        • 2.4.4 实现华为云短信服务
    • 3. 短信服务高级实现
      • 3.1 使用策略模式实现多短信服务商切换
        • 3.1.1 创建短信服务工厂
        • 3.1.2 调整SmsController使用工厂
      • 3.2 短信发送失败重试机制
        • 3.2.1 创建重试工具类
        • 3.2.2 在SmsService实现中添加重试逻辑
      • 3.3 短信发送频率限制
        • 3.3.1 配置限流参数
        • 3.3.2 创建频率限制拦截器
        • 3.3.3 注册拦截器
    • 4. 最佳实践和常见问题
      • 4.1 短信验证码最佳实践
      • 4.2 常见问题和解决方案
      • 4.3 短信服务监控
    • 5. 总结与扩展
      • 5.1 系统扩展建议
      • 5.2 最终架构图
      • 5.3 未来发展趋势
      • 5.4 总结

1. 短信服务基础知识

1.1 什么是短信服务

短信服务(SMS,Short Message Service)是一种允许应用程序通过API发送短信的云服务。在企业应用中,短信服务常用于:

  • 身份验证:发送验证码进行身份验证
  • 通知提醒:订单状态、物流更新、服务提醒等
  • 营销活动:促销信息、优惠券发放
  • 系统告警:系统异常、关键指标监控等

1.2 短信服务工作原理

短信服务的基本工作流程如下:

  1. 应用程序调用短信服务商提供的API
  2. 短信服务商接收请求并进行处理(验证、计费等)
  3. 短信服务商将消息发送至运营商网关
  4. 运营商网关将短信发送到用户手机
  5. 服务商返回发送结果(成功/失败)给应用程序

1.3 常用的短信服务商

在中国市场,主流的短信服务商包括:

服务商 优点 缺点 适用场景
阿里云 稳定性高,集成简单,有完整SDK 价格相对较高 大中型企业应用
腾讯云 与微信生态结合好,有免费体验 某些地区到达率不稳定 与腾讯生态结合的应用
华为云 安全性高,企业信誉好 API相对复杂 政企客户
容联云 价格实惠,到达率尚可 SDK更新不够及时 创业公司,成本敏感场景
云片网 接口简单,支持国际短信 大客户支持不足 小型应用,需要国际短信

1.4 短信服务的成本与计费模式

短信服务通常按照以下方式计费:

  • 按条计费:每发送一条短信计费一次(最常见)
  • 套餐包:预先购买一定数量的短信,价格更优惠
  • 不同内容不同价格:验证码短信通常比营销短信便宜
  • 不同地区不同价格:国内短信和国际短信价格差异大

1.5 短信模板与签名

在中国,由于监管要求,发送短信需要预先审核的短信模板短信签名

短信签名:显示在短信内容前面的标识,一般是企业名称或产品名称,如【淘宝网】。

短信模板:预设的短信内容格式,包含变量占位符,如:

【XX公司】您的验证码是${code},5分钟内有效,请勿泄露给他人。

2. 在Spring Boot中集成短信服务

2.1 项目准备

2.1.1 创建Spring Boot项目

首先,我们需要创建一个Spring Boot项目。可以通过Spring Initializr或IDE工具创建。

基本依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <optional>trueoptional>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-configuration-processorartifactId>
        <optional>trueoptional>
    dependency>
dependencies>
2.1.2 项目结构

建议采用以下项目结构:

src/main/java/com/example/sms
│
├── config
│   └── SmsConfig.java
│
├── service
│   ├── SmsService.java (接口)
│   └── impl
│       ├── AliyunSmsServiceImpl.java
│       ├── TencentSmsServiceImpl.java
│       └── ...
│
├── model
│   ├── SmsRequest.java
│   └── SmsResponse.java
│
├── controller
│   └── SmsController.java
│
├── util
│   └── SmsUtil.java
│
└── SmsApplication.java

2.2 集成阿里云短信服务

阿里云短信是国内使用较广泛的短信服务,下面详细介绍如何集成。

2.2.1 准备工作
  1. 注册阿里云账号并开通短信服务
  2. 在阿里云控制台创建AccessKey和AccessSecret
  3. 申请短信签名
  4. 创建短信模板
2.2.2 添加阿里云短信SDK依赖

pom.xml中添加:

<dependency>
    <groupId>com.aliyungroupId>
    <artifactId>aliyun-java-sdk-coreartifactId>
    <version>4.6.0version>
dependency>
<dependency>
    <groupId>com.aliyungroupId>
    <artifactId>dysmsapi20170525artifactId>
    <version>2.0.23version>
dependency>
2.2.3 创建配置类

创建application.yml配置文件:

sms:
  aliyun:
    access-key-id: 您的AccessKeyID
    access-key-secret: 您的AccessKeySecret
    sign-name: 您的短信签名
    templates:
      verification-code: SMS_1234567890  # 验证码模板ID
      order-notification: SMS_0987654321 # 订单通知模板ID
  
  # 短信发送的一些通用配置
  common:
    max-retry-count: 3  # 最大重试次数
    retry-interval: 1000 # 重试间隔(毫秒)
    code-expire: 300     # 验证码有效期(秒)

创建配置类读取这些配置:

package com.example.sms.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import lombok.Data;
import java.util.Map;

@Data
@Configuration
@ConfigurationProperties(prefix = "sms")
public class SmsConfig {
   
    
    private AliyunConfig aliyun;
    private CommonConfig common;
    
    @Data
    public static class AliyunConfig {
   
        private String accessKeyId;
        private String accessKeySecret;
        private String signName;
        private Map<String, String> templates;
    }
    
    @Data
    public static class CommonConfig {
   
        private int maxRetryCount;
        private long retryInterval;
        private int codeExpire;
    }
}
2.2.4 创建模型类

SmsRequest.java:

package com.example.sms.model;

import lombok.Builder;
import lombok.Data;
import java.util.Map;

@Data
@Builder
public class SmsRequest {
   
    private String templateId;  // 短信模板ID
    private String phoneNumber; // 手机号码
    private Map<String, String> templateParams; // 模板参数
}

SmsResponse.java:

package com.example.sms.model;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class SmsResponse {
   
    private boolean success;    // 是否发送成功
    private String code;        // 状态码
    private String message;     // 消息
    private String requestId;   // 请求ID
    private String bizId;       // 业务ID
}
2.2.5 创建短信服务接口
package com.example.sms.service;

import com.example.sms.model.SmsRequest;
import com.example.sms.model.SmsResponse;

public interface SmsService {
   
    
    /**
     * 发送短信
     *
     * @param request 短信请求
     * @return 短信响应
     */
    SmsResponse sendSms(SmsRequest request);
    
    /**
     * 发送验证码
     *
     * @param phoneNumber 手机号码
     * @return 验证码
     */
    String sendVerificationCode(String phoneNumber);
    
    /**
     * 验证验证码
     *
     * @param phoneNumber 手机号码
     * @param code 验证码
     * @return 是否验证通过
     */
    boolean verifyCode(String phoneNumber, String code);
}
2.2.6 实现阿里云短信服务
package com.example.sms.service.impl;

import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.teaopenapi.models.Config;
import com.example.sms.config.SmsConfig;
import com.example.sms.model.SmsRequest;
import com.example.sms.model.SmsResponse;
import com.example.sms.service.SmsService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class AliyunSmsServiceImpl implements SmsService {
   

    private static final String VERIFICATION_CODE_PREFIX = "sms:vcode:";
    private static final String VERIFICATION_CODE_TEMPLATE_KEY = "verification-code";
    
    @Autowired
    private SmsConfig smsConfig;
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    private Client client;
    
    @PostConstruct
    public void init() throws Exception {
   
        Config config = new Config()
                .setAccessKeyId(smsConfig.getAliyun().getAccessKeyId())
                .setAccessKeySecret(smsConfig.getAliyun().getAccessKeySecret())
                .setEndpoint("dysmsapi.aliyuncs.com");
        client = new Client(config);
    }
    
    @Override
    public SmsResponse sendSms(SmsRequest request) {
   
        try {
   
            // 构建请求对象
            SendSmsRequest sendSmsRequest = new SendSmsRequest()
                    .setPhoneNumbers(request.getPhoneNumber())
                    .setSignName(smsConfig.getAliyun().getSignName())
                    .setTemplateCode(request.getTemplateId())
                    .setTemplateParam(objectMapper.writeValueAsString(request.getTemplateParams()));
            
            log.info("发送短信请求: {}", sendSmsRequest);
            
            // 发送短信
            SendSmsResponse response = client.sendSms(sendSmsRequest);
            log.info("短信发送结果: {}", response);
            
            // 处理响应
            boolean success = "OK".equals(response.getBody().getCode());
            return SmsResponse.builder()
                    .success(success)
                    .code(response.getBody().getCode())
                    .message(response.getBody().getMessage())
                    .requestId(response.getBody().getRequestId())
                    .bizId(response.getBody().getBizId())
                    .build();
        } catch (Exception e) {
   
            log.error("短信发送异常", e);
            return SmsResponse.builder()
                    .success(false)
                    .code("UNKNOWN_ERROR")
                    .message(e.getMessage())
                    .build();
        }
    }
    
    @Override
    public String sendVerificationCode(String phoneNumber) {
   
        // 生成6位随机验证码
        String code = generateVerificationCode(6);
        
        // 构建短信请求
        Map<String, String> templateParams = new HashMap<>();
        templateParams.put("code", code);
        
        SmsRequest request = SmsRequest.builder()
                .phoneNumber(phoneNumber)
                .templateId(smsConfig.getAliyun().getTemplates().get(VERIFICATION_CODE_TEMPLATE_KEY))
                .templateParams(templateParams)
                .build();
        
        // 发送短信
        SmsResponse response = sendSms(request);
        
        if (response.isSuccess()) {
   
            // 将验证码存入Redis,设置过期时间
            String key = VERIFICATION_CODE_PREFIX + phoneNumber;
            redisTemplate.opsForValue().set(key, code, smsConfig.getCommon().getCodeExpire(), TimeUnit.SECONDS);
            return code;
        } else {
   
            log.error("验证码发送失败: {}", response);
            throw new RuntimeException("验证码发送失败: " + response.getMessage());
        }
    }
    
    @Override
    public boolean verifyCode(String phoneNumber, String code) {
   
        String key = VERIFICATION_CODE_PREFIX + phoneNumber;
        String savedCode = redisTemplate.opsForValue().get(key);
        
        if (StringUtils.hasText(savedCode) && savedCode.equals(code)) {
   
            // 验证成功后删除验证码
            redisTemplate.delete(key);
            return true;
        }
        return false;
    }
    
    /**
     * 生成随机验证码
     */
    private String generateVerificationCode(int length) {
   
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
   
            sb.append(random.nextInt(10));
        }
        return sb.toString();
    }
}
2.2.7 创建短信控制器
package com.example.sms.controller

你可能感兴趣的:(spring,boot,阿里云)