一、前言
在现代 Web 应用开发中,短信验证码是保障用户账号安全、完成身份验证的核心功能之一。阿里云短信服务(SMS)凭借其高可用性、高到达率的特点,成为开发者的首选。本文将基于实际项目代码,手把手教你如何在 SpringBoot 项目中集成阿里云短信服务,实现验证码的生成、发送、缓存及记录功能。
二、开发前准备
2.1 阿里云短信服务配置
开通短信服务 :登录阿里云控制台,开通短信服务并完成企业 / 个人认证。
创建短信签名 :在短信控制台申请符合业务场景的短信签名(如 "运维数据抓取工具"),需审核通过。
创建短信模板 :创建验证码类短信模板,模板内容示例:您的验证码为${code},有效期15分钟,请尽快完成验证。,模板 CODE 以 SMS_开头(如 SMS_187240136)。
获取 AccessKey:在阿里云 AccessKey 管理页面创建 AccessKey ID 和 AccessKey Secret,注意妥善保管,避免泄露。
2.2 项目依赖引入
bash
<!--阿里云短信sdk核心包-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.1.0</version>
</dependency>
<!--阿里云短信sdk专用包-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>2.0.0</version>
</dependency>
三、核心代码实现
封装阿里云短信发送的核心逻辑,包括验证码生成、短信发送:
bash
package com.itl.utils;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import org.springframework.stereotype.Service;
/**
* 阿里云短信工具类 - 验证码发送
*/
@Service
public class AliyunSmsUtils {
// 产品名称和域名(无需修改)
static final String product = "Dysmsapi";
static final String domain = "dysmsapi.aliyuncs.com";
// 替换为自己的AccessKey ID和Secret
static final String accessKeyId = "12345678";
static final String accessKeySecret = "98672348";
// 验证码存储变量
private static int newcode;
/**
* 生成4位随机验证码
*/
public static void setNewcode() {
// 生成1000-1999之间的随机数(保证4位数)
newcode = (int) Math.round((Math.random() + 1) * 1000);
}
public static int getNewcode() {
return newcode;
}
/**
* 发送短信验证码
* @param telephone 接收手机号
* @param code 验证码
* @return SendSmsResponse 阿里云返回结果
* @throws ClientException 客户端异常
*/
public static SendSmsResponse sendSms(String telephone, String code) throws ClientException {
// 设置超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
// 初始化AcsClient
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
// 组装发送请求
SendSmsRequest request = new SendSmsRequest();
request.setPhoneNumbers(telephone); // 接收手机号
request.setSignName("运维数据抓取工具"); // 短信签名(需审核通过)
request.setTemplateCode("SMS_187240136"); // 短信模板CODE
request.setTemplateParam("{\"code\":\"" + code + "\"}"); // 模板变量替换
// 发送短信并返回结果
try {
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
if (sendSmsResponse.getCode() != null && sendSmsResponse.getCode().equals("OK")) {
System.out.println("短信发送成功!");
} else {
System.out.println("短信发送失败!");
}
return sendSmsResponse;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
3.2 数据库表设计(短信记录)
创建短信发送记录表,用于留存发送日志,方便问题排查:
bash
CREATE TABLE `epc_sms` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`phoneNumber` varchar(20) DEFAULT NULL COMMENT '接收手机号',
`code` varchar(20) DEFAULT NULL COMMENT '发送的验证码',
`message` varchar(255) DEFAULT NULL COMMENT '阿里云返回消息',
`requestId` varchar(50) DEFAULT NULL COMMENT '阿里云请求ID',
`result` varchar(50) DEFAULT NULL COMMENT '发送结果(OK/失败码)',
`createTime` varchar(50) DEFAULT NULL COMMENT '发送时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='项目短信表';
3.3 核心业务层(Service)
3.3.1 Service 接口
bash
package com.itl.service;
import com.itl.entity.po.Sms;
public interface SmsService {
/**
* 插入短信发送记录
* @param sms 短信实体
* @return 插入行数
*/
int insertSms(Sms sms);
/**
* 根据手机号查询短信记录
* @param phoneNumber 手机号
* @return 短信实体
*/
Sms selectSms(String phoneNumber);
}
3.3.2 Service 实现类
bash
package com.itl.service.impl;
import com.itl.entity.po.Sms;
import com.itl.mapper.SmsMapper;
import com.itl.service.SmsService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class SmsServiceImpl implements SmsService {
@Resource
private SmsMapper smsMapper;
@Override
public int insertSms(Sms sms) {
return smsMapper.insertSms(sms);
}
@Override
public Sms selectSms(String phoneNumber) {
return smsMapper.selectSms(phoneNumber);
}
}
3.4 控制器(Controller)
bash
package com.itl.controller;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.itl.core.constant.Constants;
import com.itl.core.entity.AjaxResult;
import com.itl.core.exception.ServiceException;
import com.itl.core.redis.RedisCache;
import com.itl.core.utils.StringUtils;
import com.itl.core.web.controller.BaseController;
import com.itl.entity.po.Sms;
import com.itl.project.system.domain.SysUser;
import com.itl.project.system.service.ISysConfigService;
import com.itl.project.system.service.ISysUserService;
import com.itl.service.SmsService;
import com.itl.utils.AliyunSmsUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* 短信发送控制器
* @author itl
* @date 2024-06-25
*/
@RestController
@RequestMapping("/project/sms")
public class SmsController extends BaseController {
@Resource
private RedisCache redisCache;
@Resource
private AliyunSmsUtils aliyunSmsUtils;
@Resource
private ISysUserService userService;
@Resource
private ISysConfigService configService;
@Resource
private SmsService smsService;
private static final Logger logger = LoggerFactory.getLogger(SmsController.class);
/**
* 发送验证码短信
* @param phoneNumber 接收手机号
* @return AjaxResult 响应结果
*/
@GetMapping(value = "/sendSms")
public AjaxResult sendSms(String phoneNumber){
// 1. 防重复发送:Redis中存在该手机号的验证码,拒绝重复发送
Boolean hasSmsKey = redisCache.hasKey(Constants.SMS_KEY + phoneNumber);
if (hasSmsKey){
return AjaxResult.error("请勿重复发送验证码");
}
// 2. 校验用户是否存在(根据业务需求可选)
SysUser user = new SysUser();
user.setPhoneNumber(phoneNumber);
Boolean userExistFlag = userService.checkUserExist(user);
if(!userExistFlag){
throw new ServiceException("无人员信息,请先注册用户");
}
String code = "";
String message ="";
String result ="";
String requestId ="";
try {
// 3. 生成4位验证码
AliyunSmsUtils.setNewcode();
code = Integer.toString(AliyunSmsUtils.getNewcode());
logger.info("发送的手机号为:" + phoneNumber);
logger.info("发送的验证码为:" + code);
// 4. 调用阿里云工具类发送短信
SendSmsResponse response = aliyunSmsUtils.sendSms(phoneNumber, code);
result = response.getCode();
message = response.getMessage();
requestId = response.getRequestId();
// 5. 校验发送结果
if(!"OK".equals(response.getCode())){
return AjaxResult.error(response.getMessage());
}
// 6. 缓存验证码到Redis(有效期从配置读取,默认15分钟)
String smsCodeExpiration = configService.selectConfigByKey("sys:sms:code");
Integer codeExpiration = StringUtils.isNotBlank(smsCodeExpiration) ? Integer.parseInt(smsCodeExpiration) : 15;
redisCache.setCacheObject(Constants.SMS_KEY+phoneNumber, code, codeExpiration, TimeUnit.MINUTES);
return AjaxResult.success();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7. 记录短信发送日志到数据库(无论成功失败都记录)
Sms sms= new Sms();
sms.setCode(code);
sms.setMessage(message);
sms.setResult(result);
sms.setPhoneNumber(phoneNumber);
sms.setRequestId(requestId);
sms.setCreateTime(new Date().toString()); // 补充创建时间
smsService.insertSms(sms);
}
return AjaxResult.error();
}
}
四、关键注意事项
- AccessKey 安全:不要将 AccessKey ID/Secret 硬编码到代码中,建议通过配置文件 / 配置中心管理,生产环境开启 RAM 权限控制。
- 防刷机制 :
通过 Redis 实现 "同一手机号 N 分钟内只能发送 1 次" 的限制;
可增加图形验证码前置校验,防止恶意刷短信。 - 验证码有效期:验证码缓存到 Redis 时务必设置过期时间,避免永久存储。
- 异常处理:短信发送失败时需记录完整的错误信息(如阿里云返回的 message、requestId),方便排查问题。
- 签名 / 模板审核:阿里云短信签名和模板需提前审核,测试阶段可使用测试模板和测试手机号。
- 超时设置:合理设置短信发送的超时时间(示例中为 10 秒),避免接口长时间阻塞。
五、功能测试
- 启动 SpringBoot 项目,调用接口:GET /project/sms/sendSms?phoneNumber=138xxxxxxxx;
- 查看 Redis:确认SMS_KEY+手机号的 key 已生成,且有正确的过期时间;
- 查看数据库:epc_sms表中新增一条短信记录;
- 查看手机:接收验证码短信,验证内容正确性。
六、总结
本文基于实际项目代码,完整实现了 SpringBoot 集成阿里云短信服务的核心流程:从前期的阿里云配置、依赖引入,到工具类封装、业务逻辑实现,再到防刷、日志记录等工程化细节。该方案具备高可用性和可扩展性,可直接应用于用户注册、登录验证、密码找回等常见业务场景。
核心知识点回顾
阿里云短信服务的核心是通过IAcsClient构建请求,传入手机号、签名、模板 CODE 和变量参数完成发送;
生产环境需做好防刷(Redis)、日志记录(数据库)、密钥安全 三大核心保障;
验证码发送结果无论成功失败,都需记录完整日志,便于问题排查和运维分析。
最后,觉得有用的话,点赞+收藏,避免下次遇到找不到解决方案!