SpringBoot 集成阿里云短信认证服务(个人)

在开发 Web 应用(特别是 H5 移动端)时,短信验证码登录和注册是必不可少的功能。本文将详细介绍如何在 Spring Boot 项目中集成阿里云的 DypnsAPI(号码认证服务),实现短信验证码的发送与校验

💡 核心提示 :本文使用的是 dypnsapi(号码认证服务)而非传统的 dysmsapi(短信服务)DypnsAPI 提供了更便捷的"一键登录"和验证码校验能力,特别适合移动端 H5 场景

1 准备工作与配置

1.1 第一步注册 AccessKey

注册并登录阿里云短信服务

阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台

进入 AccessKey 管理页面,创建或获取你的 AccessKey ID****和 AccessKey Secret**。这是代码连接阿里云服务的凭证**

1.2配置发送验证码(获取 AccessKey IDAccessKey Secret)

由于个人开发者可能无法通过传统短信服务的严格资质验证,我们可以使用 阿里云短信认证服务 进行测试和开发

① 进入 阿里云短信认证服务控制台(不是短信服务平台)

阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台

② 绑定测试手机号:在控制台中添加你的手机号作为测试号码, 通过阿里云验证码验证

③API 调试:在控制台下方找到"API 调试"页面,选择"展示必填参数",填写相关信息

点击发起调用,手机收到验证码,即完成发送验证码测试

1.3配置校验验证码(获取 SchemeName)

获取 SchemeName :在 Web 服务配置(H5)中,记录下生成的 方案名称 (SchemeName),后续代码配置需要用到。

  • 注意:如果是云服务器部署,回调地址建议配置公网 IP

1.4在 idea 中进行配置

pom 文件

复制代码
<!--阿里云短信认证服务-->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>dypnsapi20170525</artifactId>
            <version>2.0.0</version>
        </dependency>

application.yaml 文件

复制代码
sms:
  access-key-id: your_access_key_id          # 替换为你的 ID
  access-key-secret: your_access_key_secret  # 替换为你的 Secret
  sign-name: 速通互联验证码                  	 # 你的签名名称
  template-code: 100001                      # 模板CODE,测试环境通常为 100001-100005
  scheme-name: abc                           # 控制台生成的方案名称

此处的配置信息在上面均有提到

2.核心代码实现

2.1 封装阿里云短信服务(DypnsAPI)(核心代码)

我们将短信服务封装为一个工具类 SMSUtil,利用 Spring 的依赖注入管理生命周期

代码结构说明
  • @PostConstruct init(): 项目启动时初始化阿里云 Client。
  • sendMessage(): 发送验证码,支持服务端自动生成或自定义验证码。
  • checkVerifyCode(): 调用阿里云接口校验用户输入的验证码是否正确

有部分 spring 注解需要自行引入 pom 文件

java 复制代码
package com.example.lotterysystem.common.utils;

import com.aliyun.dypnsapi20170525.Client;
import com.aliyun.dypnsapi20170525.models.CheckSmsVerifyCodeRequest;
import com.aliyun.dypnsapi20170525.models.CheckSmsVerifyCodeResponse;
import com.aliyun.dypnsapi20170525.models.SendSmsVerifyCodeRequest;
import com.aliyun.dypnsapi20170525.models.SendSmsVerifyCodeResponse;
import com.aliyun.tea.TeaException;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 阿里云短信认证工具类 (DypnsAPI 短信验证码)
 */
@Component
public class SMSUtil {

    private static final Logger logger = LoggerFactory.getLogger(SMSUtil.class);

    @Value("${sms.sign-name}")
    private String signName;

    @Value("${sms.access-key-id}")
    private String accessKeyId;

    @Value("${sms.access-key-secret}")
    private String accessKeySecret;

    @Value("${sms.template-code}")
    private String templateCode;

    @Value("${sms.scheme-name:}")
    private String schemeName;

    private Client client;

    private String lastError;

    public String getLastError() {
        return lastError;
    }

    @PostConstruct
    public void init() {
        try {
            this.client = createClient();
            logger.info("SMSUtil 初始化成功, signName={}, templateCode={}, schemeName={}, ak={}",
                    signName, templateCode, schemeName,
                    accessKeyId != null && accessKeyId.length() >= 4 ? accessKeyId.substring(0, 4) + "****" : "****");
        } catch (Exception e) {
            logger.error("SMSUtil 初始化失败: {}", e.getMessage());
        }
    }

    /**
     * 发送短信验证码
     * @param templateCode 模板编码
     * @param phoneNumbers 手机号
     * @return boolean     true=发送成功, false=发送失败
     */
    public boolean sendMessage(String templateCode, String phoneNumbers) {
        return sendMessage(templateCode, phoneNumbers, null);
    }

    /**
     * 发送短信验证码
     * @param templateCode 模板编码
     * @param phoneNumbers 手机号
     * @param verifyCode   验证码(如果为null则由服务端自动生成)
     * @return boolean     true=发送成功, false=发送失败
     */
    public boolean sendMessage(String templateCode, String phoneNumbers, String verifyCode) {
        lastError = null;
        if (client == null) {
            lastError = "SMSUtil 未初始化完成,无法发送";
            logger.error(lastError);
            return false;
        }
        try {
            String templateParam;
            if (verifyCode != null) {
                templateParam = "{\"code\":\"" + verifyCode + "\",\"min\":\"5\"}";
            } else {
                templateParam = "{\"code\":\"##code##\",\"min\":\"5\"}";
            }

            SendSmsVerifyCodeRequest request = new SendSmsVerifyCodeRequest()
                    .setPhoneNumber(phoneNumbers)
                    .setSignName(signName)
                    .setTemplateCode(templateCode)
                    .setSchemeName(schemeName)
                    .setTemplateParam(templateParam);

            RuntimeOptions runtime = new RuntimeOptions();
            SendSmsVerifyCodeResponse response = client.sendSmsVerifyCodeWithOptions(request, runtime);

            if (response == null || response.getBody() == null) {
                lastError = "短信发送返回为空";
                logger.warn("{}, phoneNumber: {}", lastError, phoneNumbers);
                return false;
            }

            String code = response.getBody().getCode();
            if ("OK".equals(code)) {
                logger.info("短信发送成功, phoneNumber: {}", phoneNumbers);
                return true;
            } else {
                lastError = "Code=" + code + ", Message=" + response.getBody().getMessage();
                logger.warn("短信发送失败, phoneNumber: {}, {}", phoneNumbers, lastError);
                return false;
            }
        } catch (TeaException error) {
            lastError = "TeaException, Code=" + (error.getCode() != null ? error.getCode() : "N/A")
                    + ", Message=" + error.getMessage()
                    + ", Recommend=" + (error.getData() != null ? error.getData().get("Recommend") : "N/A");
            logger.error("短信发送 API 异常, phoneNumber: {}, {}", phoneNumbers, lastError);
        } catch (Exception error) {
            TeaException teaError = new TeaException(error.getMessage(), error);
            lastError = "Exception, Message=" + error.getMessage();
            logger.error("短信发送系统异常, phoneNumber: {}, {}", phoneNumbers, lastError);
        }
        return false;
    }

    /**
     * 校验短信验证码
     * @param phoneNumber 手机号
     * @param verifyCode  验证码
     * @return boolean    true=校验通过, false=校验失败
     */
    public boolean checkVerifyCode(String phoneNumber, String verifyCode) {
        lastError = null;
        if (client == null) {
            lastError = "SMSUtil 未初始化完成,无法校验";
            logger.error(lastError);
            return false;
        }
        try {
            CheckSmsVerifyCodeRequest request = new CheckSmsVerifyCodeRequest()
                    .setPhoneNumber(phoneNumber)
                    .setVerifyCode(verifyCode)
                    .setSchemeName(schemeName);

            RuntimeOptions runtime = new RuntimeOptions();
            CheckSmsVerifyCodeResponse response = client.checkSmsVerifyCodeWithOptions(request, runtime);

            if (response == null || response.getBody() == null) {
                lastError = "验证码校验返回为空";
                logger.warn("{}, phoneNumber: {}", lastError, phoneNumber);
                return false;
            }

            String code = response.getBody().getCode();
            if ("OK".equals(code)) {
                logger.info("验证码校验成功, phoneNumber: {}", phoneNumber);
                return true;
            } else {
                lastError = "Code=" + code + ", Message=" + response.getBody().getMessage();
                logger.warn("验证码校验失败, phoneNumber: {}, {}", phoneNumber, lastError);
                return false;
            }
        } catch (TeaException error) {
            lastError = "TeaException, Code=" + (error.getCode() != null ? error.getCode() : "N/A")
                    + ", Message=" + error.getMessage()
                    + ", Recommend=" + (error.getData() != null ? error.getData().get("Recommend") : "N/A");
            logger.error("验证码校验 API 异常, phoneNumber: {}, {}", phoneNumber, lastError);
        } catch (Exception error) {
            lastError = "Exception, Message=" + error.getMessage();
            logger.error("验证码校验系统异常, phoneNumber: {}, {}", phoneNumber, lastError);
        }
        return false;
    }

    /**
     * 初始化 Client
     */
    private Client createClient() throws Exception {
        Config config = new Config()
                .setAccessKeyId(accessKeyId)
                .setAccessKeySecret(accessKeySecret);

        config.endpoint = "dypnsapi.aliyuncs.com";

        return new Client(config);
    }
}

2.2.编写单元测试代码

java 复制代码
package com.example.lotterysystem;

import com.example.lotterysystem.common.utils.SMSUtil;
import org.junit.jupiter.api.DisplayName;
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
public class SmSTest {

    @Autowired
    private SMSUtil smsUtil;

    private static final String TEST_PHONE = "11111111";		//	11位手机号
    private static final String TEST_CODE = "0000";         //  需要每次修改
    private static final String TEMPLATE_CODE = "100001";

    @Test
    @DisplayName("发送短信验证码 - 服务端生成验证码")
    void testSendMessage() {
        boolean result = smsUtil.sendMessage(TEMPLATE_CODE, TEST_PHONE);
        if (!result) {
            System.out.println("发送失败: " + smsUtil.getLastError());
        }
        assertTrue(result, "短信发送应成功");
    }



    @Test
    @DisplayName("校验正确的验证码")
    void testCheckVerifyCode() {
        boolean result = smsUtil.checkVerifyCode(TEST_PHONE, TEST_CODE);
        if (!result) {
            System.out.println("校验失败: " + smsUtil.getLastError());
        }
        assertTrue(result, "验证码" + TEST_CODE + "应校验通过");
    }

    @Test
    @DisplayName("校验错误的验证码")
    void testCheckVerifyCodeWrong() {
        boolean result = smsUtil.checkVerifyCode(TEST_PHONE, "0000");
        assertFalse(result, "错误验证码应校验失败");
    }



}

使用方式 :

  1. 先填写 TEST_PHONE, 并执行第一个 @Test 方法
  2. 接受到验证码后 , 填写 TEST_CODE , 再执行第二个@Test 方法
  3. 运行成功即发送和校验成功

总结

通过上述步骤,我们成功在 Spring Boot 项目中集成了阿里云 DypnsAPI。

  • 优势 :使用 DypnsAPI 可以绕过传统短信服务繁琐的签名和模板审核(在测试阶段),非常适合快速开发和验证 H5 登录功能。
  • 注意 :生产环境请务必妥善保管 AccessKey,建议使用 RAM 子账号的 AK 并限制权限,避免泄露风险。
相关推荐
John jj1 小时前
拆解 Telegram 群组频道收录市场:三类方案,一个可运行的评分模型,目前TG中文人工评分加模型评分机制——LetsTG收录“快速”、“无门槛”
大数据·后端·python·深度学习·搜索引擎·django·全文检索
Gopher_HBo1 小时前
Engine 核心(gin.go)
后端
用户125758524362 小时前
为什么队列长度归零,不代表后台异步任务真的跑完了
redis·后端·go
晚安code2 小时前
Java编程规范避坑指南:阿里开发手册15条强制规约实战解析
java·后端
聚搜云——JuSouClouD2 小时前
深圳阿里云代理商:K8s Pod OOM智能排查修复指南
阿里云·kubernetes·云计算
小蒜学长2 小时前
“守望自然”招募志愿者环保行动网站的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端
程序猿老杨3 小时前
MQTT协议深度解析:从ESP32设备端到云端Broker的工程化实践
后端·物联网·芯片
明月_清风3 小时前
显存即正义:不同显存容量能训多大的模型?一文说清硬件边界与训练策略
前端·后端·ai编程
weixin_BYSJ19873 小时前
【java项目分享】springboot阅读推荐平台10600
java·javascript·spring boot·python·django·flask·php