0. 前言
在注册各种网站的时候,我们经常需要使用到短信验证码,可是个人开发的时候,在各大平台申请短信验证码的模板的时,往往需要已经上线的项目,这里找到一个容联云的网站,能够免费133天短信验证码测试。容联云,全球智能通讯云服务商 (yuntongxun.com)
1. api申请
注册号账号后,我们需要创建一个应用


点击创建好的应用中的应用管理,获取其中的appID

在控制台首页保存好开发者账号id和token,外泄会导致账号金额被盗取,由于没有进行认证,所以我只能使用默认的3个测试号码

2. 创建spring项目
创建一个普通的spring项目,点击最上方的开发文档SDK文档,参考其中的文档编写测试项目:Java SDK (yuntongxun.com)

2.1 导入依赖
根据上方内容可以得知,我们需要在pom.xml中导入这个项目提供的一些依赖包,这里列出生成验证码用的hutool各种使用到的依赖包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.cloopen</groupId> <artifactId>java-sms-sdk</artifactId> <version>1.0.4</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.19</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
|
2.2 添加api

根据官方提供的参考文档我们可以得知,我们需要定义其中的
accountSId ;accountToken ;appId 这三个变量,就是上述提到的三个值
2.3 application.yml
在这里配置全局常量,就是上述的token值,

2.4 SMSModel
由于每次使用到 CCPRestSmsSDK sdk = new CCPRestSmsSDK();这个对象,其中由需要设置id等等属性,我们将他封装成为一个对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;
@Component @Data @AllArgsConstructor @NoArgsConstructor @ConfigurationProperties("yuntongxun.sms") public class SMSModel { private String accountId; private String authToken; private String appId; private String serverIp; private String serverPort; }
|
2.5 JSON封装
统一传递方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;
import java.io.Serializable;
@AllArgsConstructor @NoArgsConstructor @Data public class ResponseModel<T> implements Serializable{ private Integer code; private String msg; private T data; }
|
2.6 SMSUtil
这里主要做的就是生成验证码和发送的请求了,由于并发量比较小,可以使用map存储也可以使用更加规范的redis存储,这里两种方式都会给出,redis需要自行配置环境
redis版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| package com.kd_13.ssmcode.util;
import cn.hutool.captcha.generator.RandomGenerator; import com.cloopen.rest.sdk.BodyType; import com.cloopen.rest.sdk.CCPRestSmsSDK; import com.kd_13.ssmcode.model.SMSModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component;
import java.util.HashMap; import java.util.concurrent.TimeUnit;
@Component public class SMSUtil {
@Autowired private SMSModel smsModel; @Autowired private RedisTemplate redisTemplate;
public String sendSMS(String phone) { String ServerIp = smsModel.getServerIp(); String serverPort = smsModel.getServerPort(); String accountSId = smsModel.getAccountId(); String accountToken = smsModel.getAuthToken(); String appId = smsModel.getAppId(); CCPRestSmsSDK sdk = new CCPRestSmsSDK(); sdk.init(ServerIp, serverPort); sdk.setAccount(accountSId, accountToken); sdk.setAppId(appId); sdk.setBodyType(BodyType.Type_JSON); String to = phone; String templateId = "1"; RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4); String randomNum = randomGenerator.generate(); Long expire = 2l; String[] datas = { randomNum, expire.toString() }; HashMap<String, Object> result = sdk.sendTemplateSMS(to, templateId, datas); if ("000000".equals(result.get("statusCode"))) { redisTemplate.opsForValue().set(phone,randomNum); redisTemplate.expire(phone,10L, TimeUnit.SECONDS); return randomNum; } else { return null; } }
public boolean checkSMS(String phone,String code){ Object obj = redisTemplate.opsForValue().get(phone); if(obj == null){ throw new RuntimeException("验证码过期"); } String redis_code = (String)obj; return redis_code.equals(code); } }
|
map版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| package com.kd_13.ssmcode.util;
import cn.hutool.captcha.generator.RandomGenerator; import com.cloopen.rest.sdk.BodyType; import com.cloopen.rest.sdk.CCPRestSmsSDK; import com.kd_13.ssmcode.model.SMSModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;
import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit;
@Component public class SMSUtil_map {
@Autowired private SMSModel smsModel;
private final Map<String, ExpiringCode> codeStore = new HashMap<>();
public String sendSMS(String phone) { String ServerIp = smsModel.getServerIp(); String serverPort = smsModel.getServerPort(); String accountSId = smsModel.getAccountId(); String accountToken = smsModel.getAuthToken(); String appId = smsModel.getAppId(); CCPRestSmsSDK sdk = new CCPRestSmsSDK(); sdk.init(ServerIp, serverPort); sdk.setAccount(accountSId, accountToken); sdk.setAppId(appId); sdk.setBodyType(BodyType.Type_JSON); String to = phone; String templateId = "1"; RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4); String randomNum = randomGenerator.generate();
HashMap<String, Object> result = sdk.sendTemplateSMS(to, templateId, new String[]{randomNum, "2"}); if ("000000".equals(result.get("statusCode"))) { codeStore.put(phone, new ExpiringCode(randomNum, System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5))); return randomNum; } else { return null; } }
public boolean checkSMS(String phone, String code) { ExpiringCode expiringCode = codeStore.get(phone); System.out.println("当前时间"+System.currentTimeMillis()); System.out.println("数据库时间"+expiringCode.expiryTime); if (expiringCode == null || System.currentTimeMillis() > expiringCode.expiryTime) { throw new RuntimeException("验证码过期"); } return expiringCode.code.equals(code); }
private static class ExpiringCode { private final String code; private final long expiryTime;
public ExpiringCode(String code, long expiryTime) { this.code = code; this.expiryTime = expiryTime; } } }
|
2.7 SMSController
控制层,和接口打交道的,不做过多赘述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| package com.kd_13.ssmcode.controller;
import com.kd_13.ssmcode.model.ResponseModel; import com.kd_13.ssmcode.util.SMSUtil; import com.kd_13.ssmcode.util.SMSUtil_map; import lombok.val; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.*;
import java.util.Map; import java.util.Objects;
@RestController public class SMSController {
@Autowired private SMSUtil_map smsUtil;
@PostMapping("/sendSMS") public ResponseModel sendSMS(@RequestBody Map<String, String> map) { String phone = map.get("phone"); String sms = smsUtil.sendSMS(phone); Integer code = 500; String msg = "error"; Object data = null; if (sms != null && !sms.isEmpty()) { code = 200; msg = "success"; data = sms; } return new ResponseModel(code, msg, data); }
@GetMapping("/checkSMS") public ResponseModel checkSMS(@RequestHeader String phone, @RequestHeader String codeSMS) { boolean resule = smsUtil.checkSMS(phone, codeSMS); Integer code = 500; String msg = "error"; Object data = null; if (resule) { code = 200; msg = "success"; } return new ResponseModel(code, msg, data); }
}
|
3. 接口测试
发送验证码
1
| http://localhost:8080/sendSMS
|
校验验证码
1
| http://localhost:8080/checkSMS?
|
4. 网页端测试
依旧是使用同一个端口号进行测试,避免跨域问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>验证码验证</title> <style> body { font-family: Arial, sans-serif; margin: 20px; padding: 20px; background-color: #f4f4f4; border-radius: 5px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); } input { padding: 10px; margin: 10px 0; width: calc(100% - 22px); border: 1px solid #ccc; border-radius: 4px; } button { padding: 10px; background-color: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; } button:hover { background-color: #218838; } .message { margin-top: 10px; color: red; } </style> </head> <body>
<h2>验证码验证</h2> <input type="text" id="phone" placeholder="请输入手机号" /> <button id="sendSMS">发送验证码</button> <div class="message" id="sendMessage"></div>
<input type="text" id="codeSMS" placeholder="请输入验证码" /> <button id="checkSMS">核对验证码</button> <div class="message" id="checkMessage"></div>
<script> document.getElementById('sendSMS').onclick = function() { const phone = document.getElementById('phone').value; fetch('sendSMS', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ phone: phone }) }) .then(response => response.json()) .then(data => { document.getElementById('sendMessage').innerText = data.msg; }) .catch(error => { document.getElementById('sendMessage').innerText = '发送失败,请重试!'; }); };
document.getElementById('checkSMS').onclick = function() { const phone = document.getElementById('phone').value; const codeSMS = document.getElementById('codeSMS').value; fetch('/checkSMS', { method: 'GET', headers: { 'Phone': phone, 'CodeSMS': codeSMS } }) .then(response => response.json()) .then(data => { document.getElementById('checkMessage').innerText = data.msg; }) .catch(error => { document.getElementById('checkMessage').innerText = '验证失败,请重试!'; }); }; </script>
</body> </html>
|
5. 目录结构
