在开发 Web 应用(特别是 H5 移动端)时,短信验证码登录和注册是必不可少的功能。本文将详细介绍如何在 Spring Boot 项目中集成阿里云的 DypnsAPI(号码认证服务),实现短信验证码的发送与校验
💡 核心提示 :本文使用的是 dypnsapi(号码认证服务)而非传统的 dysmsapi(短信服务)DypnsAPI 提供了更便捷的"一键登录"和验证码校验能力,特别适合移动端 H5 场景
1 准备工作与配置
1.1 第一步注册 AccessKey
注册并登录阿里云短信服务


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

1.2配置发送验证码(获取 AccessKey ID 和 AccessKey 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, "错误验证码应校验失败");
}
}
使用方式 :
- 先填写 TEST_PHONE, 并执行第一个 @Test 方法
- 接受到验证码后 , 填写 TEST_CODE , 再执行第二个@Test 方法
- 运行成功即发送和校验成功

总结
通过上述步骤,我们成功在 Spring Boot 项目中集成了阿里云 DypnsAPI。
- 优势 :使用
DypnsAPI可以绕过传统短信服务繁琐的签名和模板审核(在测试阶段),非常适合快速开发和验证 H5 登录功能。 - 注意 :生产环境请务必妥善保管
AccessKey,建议使用 RAM 子账号的 AK 并限制权限,避免泄露风险。