713 lines
19 KiB
Markdown
713 lines
19 KiB
Markdown
|
|
# 阿里云SMS短信服务集成文档
|
|||
|
|
|
|||
|
|
## 目录
|
|||
|
|
1. [概述](#概述)
|
|||
|
|
2. [Maven依赖](#maven依赖)
|
|||
|
|
3. [配置文件](#配置文件)
|
|||
|
|
4. [代码实现](#代码实现)
|
|||
|
|
5. [使用示例](#使用示例)
|
|||
|
|
6. [常见问题](#常见问题)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
本文档提供阿里云短信服务(SMS)的完整集成方案,包括验证码发送、验证码校验、发送频率限制等功能。
|
|||
|
|
|
|||
|
|
### 功能特性
|
|||
|
|
- ✅ 发送6位数字验证码
|
|||
|
|
- ✅ 验证码5分钟有效期
|
|||
|
|
- ✅ 每个手机号每天最多发送10次
|
|||
|
|
- ✅ 使用Redis存储验证码和发送次数
|
|||
|
|
- ✅ 完整的错误处理和日志记录
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Maven依赖
|
|||
|
|
|
|||
|
|
### pom.xml
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<dependencies>
|
|||
|
|
<!-- 阿里云短信SDK -->
|
|||
|
|
<dependency>
|
|||
|
|
<groupId>com.aliyun</groupId>
|
|||
|
|
<artifactId>dysmsapi20170525</artifactId>
|
|||
|
|
<version>2.0.24</version>
|
|||
|
|
</dependency>
|
|||
|
|
|
|||
|
|
<!-- 阿里云核心SDK -->
|
|||
|
|
<dependency>
|
|||
|
|
<groupId>com.aliyun</groupId>
|
|||
|
|
<artifactId>tea-openapi</artifactId>
|
|||
|
|
<version>0.2.8</version>
|
|||
|
|
</dependency>
|
|||
|
|
|
|||
|
|
<!-- 阿里云Tea工具 -->
|
|||
|
|
<dependency>
|
|||
|
|
<groupId>com.aliyun</groupId>
|
|||
|
|
<artifactId>tea-util</artifactId>
|
|||
|
|
<version>0.2.21</version>
|
|||
|
|
</dependency>
|
|||
|
|
|
|||
|
|
<!-- Spring Boot Redis -->
|
|||
|
|
<dependency>
|
|||
|
|
<groupId>org.springframework.boot</groupId>
|
|||
|
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
|||
|
|
</dependency>
|
|||
|
|
|
|||
|
|
<!-- Spring Boot Web -->
|
|||
|
|
<dependency>
|
|||
|
|
<groupId>org.springframework.boot</groupId>
|
|||
|
|
<artifactId>spring-boot-starter-web</artifactId>
|
|||
|
|
</dependency>
|
|||
|
|
</dependencies>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 配置文件
|
|||
|
|
|
|||
|
|
### application.yml
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
spring:
|
|||
|
|
data:
|
|||
|
|
redis:
|
|||
|
|
host: localhost
|
|||
|
|
port: 6379
|
|||
|
|
database: 0
|
|||
|
|
password: # 如果有密码则填写
|
|||
|
|
|
|||
|
|
# 阿里云短信服务配置
|
|||
|
|
aliyun:
|
|||
|
|
sms:
|
|||
|
|
# 短信签名(需要在阿里云控制台申请)
|
|||
|
|
sign-name: 你的短信签名
|
|||
|
|
# 短信模板CODE(需要在阿里云控制台申请)
|
|||
|
|
template-code: SMS_xxxxxxxx
|
|||
|
|
# 阿里云AccessKey ID
|
|||
|
|
access-key-id: ${ALIYUN_ACCESS_KEY_ID:your-access-key-id}
|
|||
|
|
# 阿里云AccessKey Secret
|
|||
|
|
access-key-secret: ${ALIYUN_ACCESS_KEY_SECRET:your-access-key-secret}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 环境变量配置(推荐)
|
|||
|
|
|
|||
|
|
**Linux/Mac**:
|
|||
|
|
```bash
|
|||
|
|
export ALIYUN_ACCESS_KEY_ID=your-access-key-id
|
|||
|
|
export ALIYUN_ACCESS_KEY_SECRET=your-access-key-secret
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Windows**:
|
|||
|
|
```cmd
|
|||
|
|
set ALIYUN_ACCESS_KEY_ID=your-access-key-id
|
|||
|
|
set ALIYUN_ACCESS_KEY_SECRET=your-access-key-secret
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 代码实现
|
|||
|
|
|
|||
|
|
### 1. Service接口
|
|||
|
|
|
|||
|
|
**SmsService.java**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package com.example.service;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 短信服务接口
|
|||
|
|
*/
|
|||
|
|
public interface SmsService {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送短信验证码
|
|||
|
|
*
|
|||
|
|
* @param phoneNumber 手机号码
|
|||
|
|
* @return 是否发送成功
|
|||
|
|
* @throws Exception 发送异常
|
|||
|
|
*/
|
|||
|
|
boolean sendVerificationCode(String phoneNumber) throws Exception;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证短信验证码
|
|||
|
|
*
|
|||
|
|
* @param phoneNumber 手机号码
|
|||
|
|
* @param code 验证码
|
|||
|
|
* @return 是否验证通过
|
|||
|
|
*/
|
|||
|
|
boolean verifyCode(String phoneNumber, String code);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. Service实现类
|
|||
|
|
|
|||
|
|
**SmsServiceImpl.java**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package com.example.service.impl;
|
|||
|
|
|
|||
|
|
import com.aliyun.dysmsapi20170525.Client;
|
|||
|
|
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
|
|||
|
|
import com.aliyun.tea.TeaException;
|
|||
|
|
import com.aliyun.teautil.models.RuntimeOptions;
|
|||
|
|
import com.example.service.SmsService;
|
|||
|
|
import org.slf4j.Logger;
|
|||
|
|
import org.slf4j.LoggerFactory;
|
|||
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|||
|
|
import org.springframework.beans.factory.annotation.Value;
|
|||
|
|
import org.springframework.data.redis.core.RedisTemplate;
|
|||
|
|
import org.springframework.stereotype.Service;
|
|||
|
|
|
|||
|
|
import java.time.Duration;
|
|||
|
|
import java.time.LocalDateTime;
|
|||
|
|
import java.time.format.DateTimeFormatter;
|
|||
|
|
import java.util.Random;
|
|||
|
|
import java.util.concurrent.TimeUnit;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 阿里云短信服务实现类
|
|||
|
|
*/
|
|||
|
|
@Service
|
|||
|
|
public class SmsServiceImpl implements SmsService {
|
|||
|
|
|
|||
|
|
private static final Logger logger = LoggerFactory.getLogger(SmsServiceImpl.class);
|
|||
|
|
|
|||
|
|
@Value("${aliyun.sms.sign-name}")
|
|||
|
|
private String signName;
|
|||
|
|
|
|||
|
|
@Value("${aliyun.sms.template-code}")
|
|||
|
|
private String templateCode;
|
|||
|
|
|
|||
|
|
@Value("${aliyun.sms.access-key-id}")
|
|||
|
|
private String accessKeyId;
|
|||
|
|
|
|||
|
|
@Value("${aliyun.sms.access-key-secret}")
|
|||
|
|
private String accessKeySecret;
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private RedisTemplate<String, Object> redisTemplate;
|
|||
|
|
|
|||
|
|
// 短信验证码Redis前缀
|
|||
|
|
private static final String SMS_CODE_PREFIX = "sms:code:";
|
|||
|
|
// 短信发送次数Redis前缀
|
|||
|
|
private static final String SMS_COUNT_PREFIX = "sms:count:";
|
|||
|
|
// 短信验证码有效期(分钟)
|
|||
|
|
private static final long SMS_CODE_EXPIRE = 5;
|
|||
|
|
// 每天最大发送次数
|
|||
|
|
private static final int MAX_SMS_COUNT_PER_DAY = 10;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建阿里云短信客户端
|
|||
|
|
*/
|
|||
|
|
private Client createSmsClient() throws Exception {
|
|||
|
|
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
|
|||
|
|
config.accessKeyId = accessKeyId;
|
|||
|
|
config.accessKeySecret = accessKeySecret;
|
|||
|
|
config.endpoint = "dysmsapi.aliyuncs.com";
|
|||
|
|
return new Client(config);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成6位随机数字验证码
|
|||
|
|
*/
|
|||
|
|
private String generateVerificationCode() {
|
|||
|
|
Random random = new Random();
|
|||
|
|
StringBuilder code = new StringBuilder();
|
|||
|
|
for (int i = 0; i < 6; i++) {
|
|||
|
|
code.append(random.nextInt(10));
|
|||
|
|
}
|
|||
|
|
return code.toString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取当天结束时间的剩余秒数
|
|||
|
|
*/
|
|||
|
|
private long getSecondsUntilEndOfDay() {
|
|||
|
|
LocalDateTime now = LocalDateTime.now();
|
|||
|
|
LocalDateTime endOfDay = now.toLocalDate().atTime(23, 59, 59);
|
|||
|
|
Duration duration = Duration.between(now, endOfDay);
|
|||
|
|
return duration.getSeconds();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public boolean sendVerificationCode(String phoneNumber) throws Exception {
|
|||
|
|
// 1. 检查当天发送次数是否达到上限
|
|||
|
|
String countKey = SMS_COUNT_PREFIX + phoneNumber + ":" +
|
|||
|
|
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
|||
|
|
Integer count = (Integer) redisTemplate.opsForValue().get(countKey);
|
|||
|
|
|
|||
|
|
if (count != null && count >= MAX_SMS_COUNT_PER_DAY) {
|
|||
|
|
logger.warn("手机号{}今日短信发送次数已达上限: {}", phoneNumber, MAX_SMS_COUNT_PER_DAY);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 生成6位随机验证码
|
|||
|
|
String verificationCode = generateVerificationCode();
|
|||
|
|
|
|||
|
|
// 3. 构建短信请求
|
|||
|
|
Client client = createSmsClient();
|
|||
|
|
SendSmsRequest sendSmsRequest = new SendSmsRequest()
|
|||
|
|
.setSignName(signName)
|
|||
|
|
.setTemplateCode(templateCode)
|
|||
|
|
.setPhoneNumbers(phoneNumber)
|
|||
|
|
.setTemplateParam("{\"code\":\"" + verificationCode + "\"}");
|
|||
|
|
|
|||
|
|
RuntimeOptions runtime = new RuntimeOptions();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 4. 发送短信
|
|||
|
|
client.sendSmsWithOptions(sendSmsRequest, runtime);
|
|||
|
|
logger.info("短信验证码发送成功,手机号: {}", phoneNumber);
|
|||
|
|
|
|||
|
|
// 5. 将验证码保存到Redis,设置过期时间
|
|||
|
|
String codeKey = SMS_CODE_PREFIX + phoneNumber;
|
|||
|
|
redisTemplate.opsForValue().set(codeKey, verificationCode, SMS_CODE_EXPIRE, TimeUnit.MINUTES);
|
|||
|
|
|
|||
|
|
// 6. 增加当天发送次数,并设置过期时间为当天结束
|
|||
|
|
if (count == null) {
|
|||
|
|
count = 0;
|
|||
|
|
}
|
|||
|
|
redisTemplate.opsForValue().set(countKey, count + 1, getSecondsUntilEndOfDay(), TimeUnit.SECONDS);
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
|
|||
|
|
} catch (TeaException error) {
|
|||
|
|
logger.error("短信发送失败, 手机号: {}, 错误信息: {}, 诊断信息: {}",
|
|||
|
|
phoneNumber, error.getMessage(), error.getData().get("Recommend"));
|
|||
|
|
return false;
|
|||
|
|
} catch (Exception error) {
|
|||
|
|
logger.error("短信发送异常, 手机号: {}", phoneNumber, error);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public boolean verifyCode(String phoneNumber, String code) {
|
|||
|
|
if (phoneNumber == null || code == null) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
String codeKey = SMS_CODE_PREFIX + phoneNumber;
|
|||
|
|
String savedCode = (String) redisTemplate.opsForValue().get(codeKey);
|
|||
|
|
|
|||
|
|
if (savedCode != null && savedCode.equals(code)) {
|
|||
|
|
// 验证成功后删除验证码
|
|||
|
|
redisTemplate.delete(codeKey);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. Controller控制器
|
|||
|
|
|
|||
|
|
**SmsController.java**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package com.example.controller;
|
|||
|
|
|
|||
|
|
import com.example.common.ApiResponse;
|
|||
|
|
import com.example.common.ResultUtils;
|
|||
|
|
import com.example.service.SmsService;
|
|||
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|||
|
|
import io.swagger.v3.oas.annotations.Parameter;
|
|||
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|||
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|||
|
|
import org.springframework.web.bind.annotation.*;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 短信控制器
|
|||
|
|
*/
|
|||
|
|
@RestController
|
|||
|
|
@RequestMapping("/sms")
|
|||
|
|
@Tag(name = "短信接口", description = "提供短信验证码相关功能")
|
|||
|
|
public class SmsController {
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private SmsService smsService;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送短信验证码
|
|||
|
|
*
|
|||
|
|
* @param phoneNumber 手机号
|
|||
|
|
* @return 发送结果
|
|||
|
|
*/
|
|||
|
|
@PostMapping("/sendCode")
|
|||
|
|
@Operation(summary = "发送短信验证码", description = "向指定手机号发送验证码,每个手机号每天最多发送10次")
|
|||
|
|
public ApiResponse<Boolean> sendVerificationCode(
|
|||
|
|
@Parameter(description = "手机号码", required = true)
|
|||
|
|
@RequestParam String phoneNumber) {
|
|||
|
|
try {
|
|||
|
|
boolean success = smsService.sendVerificationCode(phoneNumber);
|
|||
|
|
if (success) {
|
|||
|
|
return ResultUtils.success(true, "验证码发送成功");
|
|||
|
|
} else {
|
|||
|
|
return ResultUtils.error(40001, "发送验证码失败,请稍后重试");
|
|||
|
|
}
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
return ResultUtils.error(50000, "发送验证码异常:" + e.getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证短信验证码
|
|||
|
|
*
|
|||
|
|
* @param phoneNumber 手机号
|
|||
|
|
* @param code 验证码
|
|||
|
|
* @return 验证结果
|
|||
|
|
*/
|
|||
|
|
@PostMapping("/verifyCode")
|
|||
|
|
@Operation(summary = "验证短信验证码", description = "验证手机号和验证码是否匹配")
|
|||
|
|
public ApiResponse<Boolean> verifyCode(
|
|||
|
|
@Parameter(description = "手机号码", required = true)
|
|||
|
|
@RequestParam String phoneNumber,
|
|||
|
|
@Parameter(description = "验证码", required = true)
|
|||
|
|
@RequestParam String code) {
|
|||
|
|
boolean valid = smsService.verifyCode(phoneNumber, code);
|
|||
|
|
if (valid) {
|
|||
|
|
return ResultUtils.success(true, "验证成功");
|
|||
|
|
} else {
|
|||
|
|
return ResultUtils.error(40002, "验证码错误或已过期");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. 通用响应类(可选)
|
|||
|
|
|
|||
|
|
**ApiResponse.java**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package com.example.common;
|
|||
|
|
|
|||
|
|
import lombok.Data;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 通用API响应类
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
public class ApiResponse<T> {
|
|||
|
|
private int code;
|
|||
|
|
private String message;
|
|||
|
|
private T data;
|
|||
|
|
|
|||
|
|
public ApiResponse(int code, String message, T data) {
|
|||
|
|
this.code = code;
|
|||
|
|
this.message = message;
|
|||
|
|
this.data = data;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**ResultUtils.java**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package com.example.common;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 响应工具类
|
|||
|
|
*/
|
|||
|
|
public class ResultUtils {
|
|||
|
|
|
|||
|
|
public static <T> ApiResponse<T> success(T data) {
|
|||
|
|
return new ApiResponse<>(200, "success", data);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static <T> ApiResponse<T> success(T data, String message) {
|
|||
|
|
return new ApiResponse<>(200, message, data);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static <T> ApiResponse<T> error(int code, String message) {
|
|||
|
|
return new ApiResponse<>(code, message, null);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 使用示例
|
|||
|
|
|
|||
|
|
### 1. 发送验证码
|
|||
|
|
|
|||
|
|
**请求**:
|
|||
|
|
```bash
|
|||
|
|
curl -X POST http://localhost:8080/sms/sendCode \
|
|||
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|||
|
|
-d "phoneNumber=13800138000"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**响应**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"message": "验证码发送成功",
|
|||
|
|
"data": true
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 验证验证码
|
|||
|
|
|
|||
|
|
**请求**:
|
|||
|
|
```bash
|
|||
|
|
curl -X POST http://localhost:8080/sms/verifyCode \
|
|||
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|||
|
|
-d "phoneNumber=13800138000&code=123456"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**响应**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"message": "验证成功",
|
|||
|
|
"data": true
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 在业务代码中使用
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Service
|
|||
|
|
public class UserService {
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private SmsService smsService;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 用户注册
|
|||
|
|
*/
|
|||
|
|
public void register(String phoneNumber, String code, String password) {
|
|||
|
|
// 1. 验证验证码
|
|||
|
|
if (!smsService.verifyCode(phoneNumber, code)) {
|
|||
|
|
throw new BusinessException("验证码错误或已过期");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 执行注册逻辑
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 找回密码
|
|||
|
|
*/
|
|||
|
|
public void resetPassword(String phoneNumber, String code, String newPassword) {
|
|||
|
|
// 1. 验证验证码
|
|||
|
|
if (!smsService.verifyCode(phoneNumber, code)) {
|
|||
|
|
throw new BusinessException("验证码错误或已过期");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 重置密码
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 常见问题
|
|||
|
|
|
|||
|
|
### 1. 如何申请阿里云短信服务?
|
|||
|
|
|
|||
|
|
1. 登录[阿里云控制台](https://www.aliyun.com/)
|
|||
|
|
2. 进入"短信服务"产品页面
|
|||
|
|
3. 申请短信签名(需要企业资质或个人认证)
|
|||
|
|
4. 申请短信模板(需要审核,一般1-2个工作日)
|
|||
|
|
5. 获取AccessKey ID和AccessKey Secret
|
|||
|
|
|
|||
|
|
### 2. 短信模板格式
|
|||
|
|
|
|||
|
|
**模板示例**:
|
|||
|
|
```
|
|||
|
|
您的验证码是${code},请在5分钟内完成验证。
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**模板变量**:
|
|||
|
|
- 使用 `${变量名}` 格式
|
|||
|
|
- 代码中传递JSON格式:`{"code":"123456"}`
|
|||
|
|
|
|||
|
|
### 3. 常见错误码
|
|||
|
|
|
|||
|
|
| 错误码 | 说明 | 解决方案 |
|
|||
|
|
|--------|------|---------|
|
|||
|
|
| isv.BUSINESS_LIMIT_CONTROL | 触发业务流控 | 降低发送频率 |
|
|||
|
|
| isv.MOBILE_NUMBER_ILLEGAL | 手机号码格式错误 | 检查手机号格式 |
|
|||
|
|
| isv.TEMPLATE_MISSING_PARAMETERS | 模板参数缺失 | 检查模板参数 |
|
|||
|
|
| isv.INVALID_PARAMETERS | 参数异常 | 检查所有参数 |
|
|||
|
|
| isv.AMOUNT_NOT_ENOUGH | 账户余额不足 | 充值 |
|
|||
|
|
| isv.TEMPLATE_PARAMS_ILLEGAL | 模板变量值非法 | 检查变量值格式 |
|
|||
|
|
|
|||
|
|
### 4. 如何修改验证码位数?
|
|||
|
|
|
|||
|
|
修改 `generateVerificationCode()` 方法中的循环次数:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
private String generateVerificationCode() {
|
|||
|
|
Random random = new Random();
|
|||
|
|
StringBuilder code = new StringBuilder();
|
|||
|
|
// 修改这里的数字,比如改为4位验证码
|
|||
|
|
for (int i = 0; i < 4; i++) {
|
|||
|
|
code.append(random.nextInt(10));
|
|||
|
|
}
|
|||
|
|
return code.toString();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. 如何修改验证码有效期?
|
|||
|
|
|
|||
|
|
修改常量 `SMS_CODE_EXPIRE`:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 修改为10分钟
|
|||
|
|
private static final long SMS_CODE_EXPIRE = 10;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6. 如何修改每日发送次数限制?
|
|||
|
|
|
|||
|
|
修改常量 `MAX_SMS_COUNT_PER_DAY`:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 修改为5次
|
|||
|
|
private static final int MAX_SMS_COUNT_PER_DAY = 5;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7. Redis连接失败怎么办?
|
|||
|
|
|
|||
|
|
确保Redis服务已启动:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Linux/Mac
|
|||
|
|
redis-server
|
|||
|
|
|
|||
|
|
# 检查Redis是否运行
|
|||
|
|
redis-cli ping
|
|||
|
|
# 应该返回 PONG
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8. 如何测试短信发送?
|
|||
|
|
|
|||
|
|
阿里云提供测试环境,可以使用测试手机号:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 测试环境配置
|
|||
|
|
config.endpoint = "dysmsapi.aliyuncs.com"; // 正式环境
|
|||
|
|
// config.endpoint = "dysmsapi-test.aliyuncs.com"; // 测试环境(如果有)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9. 生产环境安全建议
|
|||
|
|
|
|||
|
|
1. **不要硬编码密钥**:使用环境变量或配置中心
|
|||
|
|
2. **启用HTTPS**:保护API通信安全
|
|||
|
|
3. **添加图形验证码**:防止恶意刷短信
|
|||
|
|
4. **IP限流**:防止单个IP频繁请求
|
|||
|
|
5. **手机号验证**:验证手机号格式和归属地
|
|||
|
|
6. **监控告警**:设置短信发送量告警
|
|||
|
|
|
|||
|
|
### 10. 性能优化建议
|
|||
|
|
|
|||
|
|
1. **Redis连接池**:配置合理的连接池参数
|
|||
|
|
2. **异步发送**:使用 `@Async` 异步发送短信
|
|||
|
|
3. **批量发送**:如果需要群发,使用批量接口
|
|||
|
|
4. **缓存优化**:合理设置Redis过期时间
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 完整项目结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
src/
|
|||
|
|
├── main/
|
|||
|
|
│ ├── java/
|
|||
|
|
│ │ └── com/
|
|||
|
|
│ │ └── example/
|
|||
|
|
│ │ ├── controller/
|
|||
|
|
│ │ │ └── SmsController.java
|
|||
|
|
│ │ ├── service/
|
|||
|
|
│ │ │ ├── SmsService.java
|
|||
|
|
│ │ │ └── impl/
|
|||
|
|
│ │ │ └── SmsServiceImpl.java
|
|||
|
|
│ │ ├── common/
|
|||
|
|
│ │ │ ├── ApiResponse.java
|
|||
|
|
│ │ │ └── ResultUtils.java
|
|||
|
|
│ │ └── Application.java
|
|||
|
|
│ └── resources/
|
|||
|
|
│ └── application.yml
|
|||
|
|
└── test/
|
|||
|
|
└── java/
|
|||
|
|
└── com/
|
|||
|
|
└── example/
|
|||
|
|
└── service/
|
|||
|
|
└── SmsServiceTest.java
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 单元测试示例
|
|||
|
|
|
|||
|
|
**SmsServiceTest.java**
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package com.example.service;
|
|||
|
|
|
|||
|
|
import org.junit.jupiter.api.Test;
|
|||
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|||
|
|
import org.springframework.boot.test.context.SpringBootTest;
|
|||
|
|
|
|||
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|||
|
|
|
|||
|
|
@SpringBootTest
|
|||
|
|
class SmsServiceTest {
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private SmsService smsService;
|
|||
|
|
|
|||
|
|
@Test
|
|||
|
|
void testSendVerificationCode() throws Exception {
|
|||
|
|
// 使用测试手机号
|
|||
|
|
String phoneNumber = "13800138000";
|
|||
|
|
boolean result = smsService.sendVerificationCode(phoneNumber);
|
|||
|
|
assertTrue(result, "验证码发送应该成功");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Test
|
|||
|
|
void testVerifyCode() throws Exception {
|
|||
|
|
String phoneNumber = "13800138000";
|
|||
|
|
|
|||
|
|
// 发送验证码
|
|||
|
|
smsService.sendVerificationCode(phoneNumber);
|
|||
|
|
|
|||
|
|
// 验证错误的验证码
|
|||
|
|
boolean result1 = smsService.verifyCode(phoneNumber, "000000");
|
|||
|
|
assertFalse(result1, "错误的验证码应该验证失败");
|
|||
|
|
|
|||
|
|
// 注意:无法测试正确的验证码,因为验证码是随机生成的
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 总结
|
|||
|
|
|
|||
|
|
本文档提供了阿里云SMS短信服务的完整集成方案,包括:
|
|||
|
|
|
|||
|
|
✅ Maven依赖配置
|
|||
|
|
✅ 完整的代码实现
|
|||
|
|
✅ 配置文件示例
|
|||
|
|
✅ 使用示例和测试方法
|
|||
|
|
✅ 常见问题解答
|
|||
|
|
✅ 安全和性能优化建议
|
|||
|
|
|
|||
|
|
将此文档保存到你的项目中,以后需要集成阿里云SMS时可以直接参考使用。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 相关链接
|
|||
|
|
|
|||
|
|
- [阿里云短信服务官网](https://www.aliyun.com/product/sms)
|
|||
|
|
- [阿里云短信服务API文档](https://help.aliyun.com/document_detail/101414.html)
|
|||
|
|
- [阿里云短信服务SDK](https://help.aliyun.com/document_detail/215759.html)
|
|||
|
|
- [阿里云短信服务控制台](https://dysms.console.aliyun.com/)
|