SpringBoot整合手机短信验证码

手机短信验证码技术

1.流程图

  1. 前端点击发送手机验证码
    后端判断恶意请求拦截【手机号码限制次数 - redis设置过期时间,自增 - 大于10次直接抛异常 - 没有做】
  2. 验证图形验证码是否正确,不正确直接抛业务异常
    前端用户输入的图形验证码和之前在redis保存的图形验证码对比【5分钟有效】,本地存储中 key
  3. 如果正确,判断当前手机验证码是否有效
    直接判断redis有没有:有就是没有过期,没有就是过期了或第一次发送
    4.1. 在redis中没有找到手机验证码:第一次发送或手机验证码过期
    a.重新生成新的手机验证码
    4.2. 在redis中找到手机验证码:验证码还没过期,点击重新获取
    a.判断重新获取验证码是否过了重发时间:防止违规操作 - 刷新页面又可以重新获取
    为什么会有重发时间:由于网络问题,需要重发时间 和 防止重复点击重复请求
    a1.如果没有过重发时间,又在重新点击重新验证码,调用接口 - 违规操作 - 抛业务异常
    a2.如果过了重发时间,可以点击重新获取
    b?.没有过期的验证码还在 <=> 重新获取要生成新的验证码【新的验证码如何选择:重新生成?还是用以前的没有过期的那一个】
    5.记录验证码信息 = 保存到redis,设置过期时间
    6.调用短信接口发送短信:手机code,手机号码

2.手机验证码发送细节

  1. redis记录/保存验证码
    key:业务键:手机号 - 防止业务不兼容,如果同时手机号注册或登录,相同的key会覆盖
    value:验证码:时间戳 - 可以获取时间戳判断重发时间
    例如:register:188866666666 - 9527:14688777777711
    register:188866666666 - 业务键:电话号码 - 因为以后其他业务还需要发送验证码,例如登录
    9527:14688777777711 - 验证码:时间戳 - 时间戳用于计算是否重发时间
  2. 第一次发或验证码过期了:直接走主流程
  3. 后续再发:
    要验证是否过期 - redis中获取是否为null
    要判断是否过重发时间 - 时间戳
    要使用redis存储验证码
    要重置过期时间

3.短信发送接口

依赖

<!-- https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient -->

<dependency>

<groupId>commons-httpclient</groupId>

<artifactId>commons-httpclient</artifactId>

<version>3.1</version>

</dependency>

接口,具体看对接的发送平台,查看官网API,如下列中国网建

package com.lzc.basic.utils;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;

import java.io.IOException;

public class SendMsgUtils {

    public static void sendMsg(String phone,String code) {

        try {
            HttpClient client = new HttpClient();
            PostMethod post = new PostMethod("https://utf8api.smschinese.cn/");
            post.addRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=utf-8");//在头文件中设置转码
            NameValuePair[] data ={ new NameValuePair("Uid", "lzcxx"),new NameValuePair("Key", "7702FE8575F11493D0EA14149F57E457"),new NameValuePair("smsMob",phone),new NameValuePair("smsText",code)};
            post.setRequestBody(data);

            client.executeMethod(post);
            Header[] headers = post.getResponseHeaders();
            int statusCode = post.getStatusCode();
            System.out.println("statusCode:"+statusCode); //HTTP状态码
            for(Header h : headers){
                System.out.println(h.toString());
            }
            String result = new String(post.getResponseBodyAsString().getBytes("utf-8"));
            System.out.println(result);  //打印返回消息状态

            post.releaseConnection();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

}

4.发送短信验证码前端,前端页面按钮设置倒计时60s

  1. 手机注册的div上添加id属性值,并且数据双向绑定

<input type="tel" name="" v-model="phoneUserForm.phone" id="phone" placeholder="请输入手机号">

  1. 获取的a标签换成button
    <button type="button" @click="sendMobileCode();">获取

<script type="text/javascript">

new Vue({

//挂载点:当前Vue实例中的所有数据【模型数据data,方法methods】只能在id="app"的元素中使用

el: "#app",

//模型数据:可以有多个,所以也是json对象

data: {

phoneUserForm: {

imageCode: '', //用户输入的图形验证码

phone: '18706553265' //注册手机号

},

base64ImageCode: ''

},

//方法:可以有多个,所以也是json对象

methods: {

//获取手机验证码

sendMobileCode() {

//1.判断手机号不为空

if (!this.phoneUserForm.phone) {

alert("手机号不能为空");

return;

}

//2.判断图片验证码不为空

if (!this.phoneUserForm.imageCode) {

alert("图片验证码不能为空");

return;

}

//3.获取按钮,禁用按钮 发送时灰化不能使用,发送成功倒计时60才能使用,如果发送失败立即可以发送

var sendBtn = $(event.target);//获取到按钮

sendBtn.attr("disabled", true);//设置属性:disabled="true"

//组装请求参数

var param = {

phone: this.phoneUserForm.phone, //手机号码

imageCode: this.phoneUserForm.imageCode, //图形验证码

imageCodeKey: localStorage.getItem("phoneCodeKey") //获取redis的图形验证码的key

};

//4.发送ajax请求

this.$http.post("/verifyCode/smsCode", param).then(res => {

var ajaxResult = res.data;

if (ajaxResult.success) {

alert("手机验证码已经发送到您的手机,请在3分钟内使用");

//4.1.发送成:倒计时

var time = 60;

//周期性定时器

var interval = window.setInterval(function () {

//每一条倒计时减一

time = time - 1;

//把倒计时时间搞到按钮上

sendBtn.html(time + "s");

//4.2.倒计时完成恢复按钮

if (time <= 0) {

sendBtn.html("重新发送");

sendBtn.attr("disabled", false); //解除禁用

//清除定时器

window.clearInterval(interval);

}

}, 1000);

} else {

//4.3.发送失败:提示,恢复按钮

sendBtn.attr("disabled", false);

alert(ajaxResult.message);

}

})

}

},

//页面一加载就会执行

mounted() {

this.getImageCode();

}

})

</script>

5.总结

获取对接官网的key授权码,导入依赖,前端发送请求携带手机号,后端Dto接收参数,调用接口1.参数校验(JSR303);2.业务校验;3.业务实现

@Override
    public void sendPhoneCode(PhoneCodeDto phoneCodeDto) {
        //1.参数校验
        //2.业务校验
        //2.1校验图形验证码是否存在
        String uuid = phoneCodeDto.getUuid();
        Object objImageCode = redisTemplate.opsForValue().get(uuid);
        if (objImageCode == null) {
            throw new GlobalException("1021", "验证码不存在");
        }
        //2.2校验图形验证码是否正确
        String imageCode = phoneCodeDto.getImageCode();
        if (!StrUtil.equalsAnyIgnoreCase(objImageCode.toString(), imageCode)) {
            throw new GlobalException("1022", "验证码不正确!");
        }
        //2.3手机号是否已被注册
        String phone = phoneCodeDto.getPhone();
        User user = userMapper.findOneByPhone(phone);
        if (user != null) {
            throw new GlobalException("1023", "手机号已注册!");
        }
        //字符串的format方法会把%s替换了的值返回给你
        String key = String.format(VerifyCodeConstant.SEND_PHONE_CODE_KEY, phone);
        //业务实现
        Long expire = redisTemplate.getExpire(key, TimeUnit.MILLISECONDS);
        String code = "";
        // 判断expire返回值是否为空-2或者超过60秒过期了
        if (expire == null || expire < 240000) {
            code = RandomUtil.randomString(6);
        } else {
            throw new GlobalException("1024", "验证码已过期!");
        }
        //将新的code存到redis中
        redisTemplate.opsForValue().set(key, code, 5, TimeUnit.MINUTES);
        //发送短信
//        SendMsgUtils.sendMsg(phone,code);
        System.out.println(code);
    }

猜你喜欢

转载自blog.csdn.net/lzc19991201/article/details/131018649