SpringBoot接入腾讯云实现前后端验证码实现
实现效果
实现两个按钮,一个获取验证码按钮的接口,一个就是登录的接口
效果里面的验证码我设置了10秒过期
基础方法的实现(时间相减来设置过期时间)
讲讲原理:
这是我是根据发送短信的接口生成了验证码s,将s这个变量设置为该Controller层的全局变量,用于后面登录接口的比较。
然后为了保证手机和发送短信时的间对应,我有设置了map的全局变量,构成了手机为key与发送短信的时间为value值的映射。
在登录接口:根据手机号key,得到短信创建时间value值,比对登录的时的当前时间,判断是否过期。然后再比较验证码。
问题
效果是实现了,但是当多个用户同时访问的时候,这个全局验证码的值会不会也会跟着变化啊?比较是不是会不匹配啊?
解决方法
还是根据map来吧,创建Map<String, <类> map,根据key值为电话号码,value值为一个类实体类,实体类里面有创建时间和验证码。
1.添加依赖
<!-- 腾讯云短信依赖-->
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>3.1.270</version><!-- 注:这里只是示例版本号(可直接使用),可获取并替换为 最新的版本号,注意不要使用4.0.x版本(非最新版本) -->
</dependency>
2.直接写Controller层就好
@RestController
@CrossOrigin
public class smsController {
@Autowired
UserService userService;
//随机生成验证码
private String s = "";
//得到当前调用时间
private Long createTime = null;
//存电话号码和时间
Map<String, Long> map = new HashMap<>();
//这里有个Result类,可能没有,你可以改为你自己的或者,直接将返回值改为String也行
@GetMapping("send")
public Result sendMessage(String phone) {
try {
// 实例化一个认证对象,入参需要传入腾讯云账户secretId,secretKey,此处还需注意密钥对的保密
// 密钥可前往https://console.cloud.tencent.com/cam/capi网站进行获取
//拿自己的密钥
Credential cred = new Credential("****************", "***************");
// 实例化一个http选项,可选的,没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("sms.tencentcloudapi.com");
// 实例化一个client选项,可选的,没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
SmsClient client = new SmsClient(cred, "ap-guangzhou", clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
SendSmsRequest req = new SendSmsRequest();
//电话号码,可继续添加
String[] phoneNumberSet1 = {phone};
List<String> list = new ArrayList<>();
for (String s1 : phoneNumberSet1) {
list.add(s1);
}
req.setPhoneNumberSet(phoneNumberSet1);
//系统应用ID 不可少
req.setSmsSdkAppId("1400556240");
//签名内容 不可少 对应的是最开始头
req.setSignName("专注于java学习");
//模板id
req.setTemplateId("1069287");
s = CodeUtiles.generateOne();
createTime = System.currentTimeMillis();
map.put(list.get(0),createTime);
//验证码 参数{1},如果还有参数就继续往后添加即可
String[] templateParamSet1 = {s};
System.out.println("s:"+s);
req.setTemplateParamSet(templateParamSet1);
// 返回的resp是一个SendSmsResponse的实例,与请求对象对应
// SendSmsResponse resp = client.SendSms(req);
// 输出json格式的字符串回包
// System.out.println(SendSmsResponse.toJsonString(resp));
return Result.ok("发送成功,验证码为"+s);
} catch (Exception e) {
System.out.println(e.toString());
}
return Result.error("发送失败");
}
@PostMapping("phoneLogin")
public R login(@RequestParam(value = "code",required = false ) String code,
@RequestParam(value = "phone",required = false ) String phone){
//登录的时间
Long nowTime = System.currentTimeMillis();
//开始申请的时间
Long createdTime = map.get(phone);
//判断该电话号码是否申请了验证码
if (createdTime==null){
return R.error().message("验证码不存在");
}
//超过30秒,验证码过期,方便测试
if (nowTime - createdTime < 1000*30) {
if (code.equals(s)) {
// System.out.println("s::::"+s);
// UserVo userVo = userService.findUserPhone(phone);
// System.out.println("result:"+userVo);
// if (userVo != null){
// String token = JwtUtilstwo.getJwtToken(userVo.getUserName());
// System.out.println("token:"+token);
// return R.ok().data("token",token);
// }
return R.ok().data("验证码为", s);
}
}
return R.error().message("验证码已过期,请重新申请");
}
}
3.验证码生成
public class CodeUtiles {
private static final Logger log = Logger.getLogger("CodeUtiles.class");
/**
* 预约码码表
*/
// private static final char[] CODE_TABLE= {'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R',
// 'S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9'};
private static final char[] CODE_TABLE = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
/**
* 随机码默认位数-- 6位
*/
private static final int DEFAULT_DIGIT = 6;
/**
* 默认随机码个数-- 2个
*/
private static final int DEFAULT_NUMBER = 2;
private CodeUtiles() {
}
/**
* 默认生成单个6位预约码
*/
// @Scheduled(cron = "30 * * * * ? *")
public static String generateOne() {
StringBuilder strBuild = new StringBuilder();
for (int i = 0; i < DEFAULT_DIGIT; i++) {
// strBuild.append(CODE_TABLE[new Random().nextInt(36)]);
strBuild.append(CODE_TABLE[new Random().nextInt(10)]);
}
return strBuild.toString();
}
/**
* 指定位数生成随机码,位数需大于0,否则返回默认6位随机码
*
* @param digit 随机码的位数
*/
public static String generateOne(int digit) {
if (digit > 0) {
StringBuilder strBuild = new StringBuilder();
for (int i = 0; i <= digit; i++) {
// strBuild.append(CODE_TABLE[new Random().nextInt(36)]);
strBuild.append(CODE_TABLE[new Random().nextInt(10)]);
}
return strBuild.toString();
}
return generateOne();
}
/**
* 默认生成 2个 6位随机码
*/
public static List<String> generateList() {
List<String> randomCodes = new ArrayList<>(DEFAULT_NUMBER);
for (int i = 0; i < DEFAULT_NUMBER; i++) {
randomCodes.add(generateOne());
}
return randomCodes;
}
/**
* 指定生成随机码个数、位数生成随机码,位数需大于0,否则返回默认6位随机码,个数需大于1,否则默认2个
*
* @param number 随机码生成个数
* @param digit 随机码位数
*/
public static List<String> generateList(int number, int digit) {
if (number > 1) {
List<String> randomCodes = new ArrayList<>(number);
for (int i = 0; i < number; i++) {
randomCodes.add(generateOne(digit));
}
return randomCodes;
}
return generateList();
}
/**
* 测试入口
*/
public static void main(String[] args) {
log.info("循环开始" + System.currentTimeMillis());
for (int i = 0; i < 10; i++) {
System.out.println(generateOne());
}
log.info("循环结束" + System.currentTimeMillis());
}
}
Redis自动设置过去时间
原理
其实这个原理没啥好讲的,都被redis给封装好了,直接调用redis模板给你提供的方法就可以了。
感兴趣的还可以去看看源码啥的。
这里主要是还是用到了对map的存取过程。
1.添加依赖
<!-- Spring data redis 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.添加配置文件
Redis:
# 超时时间
timeout: 10000ms
# 服务器地址
host: 127.0.0.1
# 服务器端口
port: 6379
# 数据库
database: 0
#密码
password:
lettuce:
pool:
# 最大连接数,默认8
max-active: 1024
# 最大连接阻塞等待时间 ,默认-1
max-wait: 10000ms
#最大空闲连接
max-idle: 200
#最小空闲连接
min-idle: 5
3.Controller层直接注入
@Autowired
private RedisTemplate redisTemplate;
4.Controller层逻辑代码
//参数1:是key 键
//参数2:是值 是存入redis里的值
//参数3:时间,类型为long
//参数4:时间类型,
//如:TimeUnit.MILLISECONDS 代表毫秒
//TimeUnit.SECONDS 代表秒,还有天,周,月,年自己测试 过期时间为120秒
//这里时间要设置好,太快了像那种几百毫秒就不要了,反应不过来,会报错
redisTemplate.opsForValue().set(list.get(0), s, 100, TimeUnit.SECONDS);
//根据key值得到value值 --这里只是进行同一个方法的测试,下面那个才是真的测试
String phoneCode = redisTemplate.opsForValue().get(phone).toString();
5.Controller层的测试接口
@PostMapping("redisPhoneLogin")
public R redisPhoneLogin(@RequestParam(value = "code", required = false) String code,
@RequestParam(value = "phone", required = false) String phone) {
String phoneCode = redisTemplate.opsForValue().get(phone).toString();
if (phoneCode == null) {
return R.error().message("验证码不存在");
}
if (code.equals(phoneCode)) {
return R.ok().data("验证码为", s);
}
return R.error().message("验证码已过期,请重新申请");
}