【实战教程】SpringBoot 集成阿里云短信服务实现验证码发送

一、前言

在现代 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();
    }
}

四、关键注意事项

  1. AccessKey 安全:不要将 AccessKey ID/Secret 硬编码到代码中,建议通过配置文件 / 配置中心管理,生产环境开启 RAM 权限控制。
  2. 防刷机制
    通过 Redis 实现 "同一手机号 N 分钟内只能发送 1 次" 的限制;
    可增加图形验证码前置校验,防止恶意刷短信。
  3. 验证码有效期:验证码缓存到 Redis 时务必设置过期时间,避免永久存储。
  4. 异常处理:短信发送失败时需记录完整的错误信息(如阿里云返回的 message、requestId),方便排查问题。
  5. 签名 / 模板审核:阿里云短信签名和模板需提前审核,测试阶段可使用测试模板和测试手机号。
  6. 超时设置:合理设置短信发送的超时时间(示例中为 10 秒),避免接口长时间阻塞。

五、功能测试

  1. 启动 SpringBoot 项目,调用接口:GET /project/sms/sendSms?phoneNumber=138xxxxxxxx;
  2. 查看 Redis:确认SMS_KEY+手机号的 key 已生成,且有正确的过期时间;
  3. 查看数据库:epc_sms表中新增一条短信记录;
  4. 查看手机:接收验证码短信,验证内容正确性。

六、总结

本文基于实际项目代码,完整实现了 SpringBoot 集成阿里云短信服务的核心流程:从前期的阿里云配置、依赖引入,到工具类封装、业务逻辑实现,再到防刷、日志记录等工程化细节。该方案具备高可用性和可扩展性,可直接应用于用户注册、登录验证、密码找回等常见业务场景。

核心知识点回顾

阿里云短信服务的核心是通过IAcsClient构建请求,传入手机号、签名、模板 CODE 和变量参数完成发送;

生产环境需做好防刷(Redis)、日志记录(数据库)、密钥安全 三大核心保障;

验证码发送结果无论成功失败,都需记录完整日志,便于问题排查和运维分析。

最后,觉得有用的话,点赞+收藏,避免下次遇到找不到解决方案!

相关推荐
RANCE_atttackkk2 小时前
[Java]实现使用邮箱找回密码的功能
java·开发语言·前端·spring boot·intellij-idea·idea
韩立学长3 小时前
【开题答辩实录分享】以《智能大学宿舍管理系统的设计与实现》为例进行选题答辩实录分享
数据库·spring boot·后端
编码者卢布6 小时前
【Azure Storage Account】Azure Table Storage 跨区批量迁移方案
后端·python·flask
大佐不会说日语~6 小时前
使用Docker Compose 部署时网络冲突问题排查与解决
运维·网络·spring boot·docker·容器
好好研究7 小时前
SpringBoot扩展SpringMVC
java·spring boot·spring·servlet·filter·listener
独自破碎E8 小时前
Spring Boot + LangChain4j 报错:Bean 类型不匹配的解决办法
spring boot·python·pycharm
tb_first8 小时前
SSM速通3
java·jvm·spring boot·mybatis
她说..8 小时前
策略模式+工厂模式实现审批流(面试问答版)
java·后端·spring·面试·springboot·策略模式·javaee
梦梦代码精9 小时前
开源、免费、可商用:BuildingAI一站式体验报告
开发语言·前端·数据结构·人工智能·后端·开源·知识图谱