短信发送校验以及图形码校验

一:摘要

  短信校验需要发送短信,但是只有三大运行商有发送短信的能力,要发送短信只有找三大运营商或者中间商,简单地说就是要找第三方的短信平台,常见的有阿里云、京东智联云、乐讯通、网建通等平台,我们需要拿到它们提供的UID和KEY,参考它们提供的API文档即可完成发送短信的功能,这个功能是需要付费的。

=======================短信发送======================

二:前端发送请求,一般来说,与图形验证码联合使用,在获取短信验证码之前需要校验用户输入的图形验证码是否正确,后台已经根据前台传递的key作为key值存入了Redis中,它在发送请求之前通过localStorage.setItem将此key值存入了浏览器本地,我们在向后台发送短信请求时就需要通过localStorage.getItem获取到这个key传入后台供Redis取值校验,所以传入后台的需要有:手机号、用户填写的图形验证码、uuid

//获取短信验证码
sendMobileCode(){
    //手机号不为空
    if(this.phoneUserForm.phone==null){
        alert("手机号不能为空");
        return;
    }
    //2.判断图片验证码不为空
    if(this.phoneUserForm.imageCode==null){
        alert("图片验证码不能为空");
        return;
    }
    //3.获取按钮,禁用按钮  发送时灰化不能使用,发送成功倒计时60才能使用,如果发送失败立即可以发送
    var sendBtn = $(event.target);
    sendBtn.attr("disabled",true);

    //传入后台的对象
    var param = {
        //手机号
        phone: this.phoneUserForm.phone,
        //用户填写的图形验证码
        imageCode: this.phoneUserForm.imageCode,
        //localStorage中的uuid
        key: localStorage.getItem("code")
    };

    //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);
                //4.2.倒计时完成恢复按钮
                if(time <= 0){
                    sendBtn.html("重新发送");
                    sendBtn.attr("disabled",false);
                    //清除定时器
                    window.clearInterval(interval);
                }
            },1000);
        }else{
            //4.3.发送失败:提示,恢复按钮
            sendBtn.attr("disabled",false);
            alert(ajaxResult.msg);
        }
    })
},

三:后台接口通过Map的形式参收传过来的参数,也可以通过对象接收

/**
     *接收前端获取短信按钮的请求,并用Redis校验图形验证码
     * @return
     */
    @PostMapping("/smsCode")
    public AjaxResult smsCode(@RequestBody Map<String,String> map){
        field field = new field();
        field.setPhone(map.get("phone"));
        try {
            verifyCodeService.smsCode(map);
            return AjaxResult.getResult();
        }
        catch (BusinessException e) {
            e.printStackTrace();
            return AjaxResult.getResult().setMsg(e.getMessage());
        }
        catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.getResult().setMsg("业务繁忙");
        }
    }

四:业务层主要对传过来的参数进行了判空、从Redis中将之前存的图形验证码取出来与用户输入的对比、判断是否过期、判断手机号是否注册,如果图形验证码没问题就处理短信业务,为了安全,存值格式为:key【手机号+业务】,value【验证码:时间戳】,先根据key获取,获取不到则说明验证码过期了或者根本没有,此时就需要调用StrUtils工具类生成4位验证码,然后将新的验证码按格式存入Redis,然后使用短信运营商的工具类发送短信,如果存在,则判断存入时间是否大于一分钟,如果小于则抛异常,否则不进行处理【此时用户有效的验证码依旧为之前的】直接发送短信

/**
     * 校验图形验证码,并发送短信
     * @param map
     */
    @Override
    public void smsCode(Map<String, String> map) {

        //==============1.校验图形验证码是否存在或者是否有效=============
        //判空
        if(StringUtils.isEmpty(map.get("phone")) ||
            StringUtils.isEmpty(map.get("imageCode")) ||
            StringUtils.isEmpty(map.get("key")) ){
            throw new BusinessException("参数不可为空");
        }

        //从Redis取出存入的key值【key==上个方法的key,都是从localStorage中取的值】
        Object value = redisTemplate.opsForValue().get(map.get("key"));
        if(value==null||"".equals(value)){
            throw new BusinessException("图形验证码过期,请重新获取");
        }
        //判断图形验证码是否正确  equalsIgnoreCase:不区分大小写
        if(!value.toString().equalsIgnoreCase(map.get("imageCode"))){
            throw new BusinessException("图形验证码输入错误");
        }
        //判断手机号是否注册
        if(userService.findByPhone(map.get("phone"))!=null){
            throw new BusinessException("此手机号已经注册,请直接登录");
        }

        //==============2.校验短信验证码是否存在或者是否有效=============
        //Redis存值格式:key:手机号+业务  value:code:时间戳
        //判断验证码是否过期 ,code为根据key查出来的验证码
        Object code = redisTemplate.opsForValue().get(map.get("phone") + "register");
        String newCode = null;
        if(code!=null){  //说明没有过期
            //将返回的对象短信部分【code】赋值给newCode  code:时间戳
            newCode = code.toString().split(":")[0];
            //将返回的对象转为String
            String s = code.toString();
            //分割字符串,获取到时间戳
            String time = s.split(":")[1];
            //获取当前time剩余的有效时间  当前时间戳-存入时的时间戳
            Long validTime = System.currentTimeMillis() - Long.valueOf(time);
            if(validTime<=1*60*1000){  //说明当前时间距离存入验证码的时间戳还没过去一分钟
                throw new BusinessException("请勿重复发送");
            }
        }else{  //说明短信验证码过期了,重新生成一个验证码
            newCode = StrUtils.getComplexRandomString(4);
            //将新生成的验证码添加到redis
            redisTemplate.opsForValue().set(map.get("phone")+"register",newCode+":"+System.currentTimeMillis(),3,TimeUnit.MINUTES);
        }
        //发送短信
        //SmsUtils.sendSms(map.get("phone"),"验证码为"+s1);
        System.out.println(map.get("phone")+"您的验证码为:"+newCode+",请在3分钟内使用");
    }

五:到此短信发送业务处理完毕

=======================短信校验======================

六:前端将用户输入的数据点击注册按钮发送至后台

//手机短信——注册按钮
registerPhone(){
    this.$http.post("/register/phone",this.phoneUserForm)
    }

 七:后台接口接收参数,调用业务层方法

/**
     *手机短信注册
     * @param map
     * @return AjaxResult
     */
@PostMapping("/phone")
public AjaxResult phoneRegister(@RequestBody Map<String,String> map){
    try {
        registerService.phone(map);
        return AjaxResult.getResult();
    }
    catch (BusinessException e) {
        e.printStackTrace();
        return AjaxResult.getResult().setMsg(e.getMessage());
    }
    catch (Exception e) {
        e.printStackTrace();
        return AjaxResult.getResult().setMsg("系统异常");
    }
}

八:业务层主要对参数进行了判空,判断密码是否一致,通过手机号+业务从Redis中取值判断是否过期,判断验证码是否与Redis中的一致,如果都没问题则直接将数据添加至logininfo表和user表【需要对密码进行加盐加密/加密加盐处理,为了安全】

/**
     * 手机短信注册
     * @param map
     */
@Override
public void phone(Map<String, String> map) {
    //判空
    if(StringUtils.isEmpty(map.get("imageCode"))){
        throw new BusinessException("图形验证码为空");
    }
    if(StringUtils.isEmpty(map.get("phone"))){
        throw new BusinessException("手机号为空");
    }
    if(StringUtils.isEmpty(map.get("password"))){
        throw new BusinessException("密码为空");
    }
    if(StringUtils.isEmpty(map.get("verifyCode"))){
        throw new BusinessException("短信验证码为空");
    }
    //判断两次密码是否一致
    if(!map.get("password").equals(map.get("passwordRepeat"))){
        throw new BusinessException("两次密码不一致,请检查你输入的数据");
    }
    //校验短信验证码是否过期
    if(redisTemplate.opsForValue().get(map.get("phone")+"register")==null){
        throw new BusinessException("验证码已经过期或手机号不一致");
    }
    //判断短信验证码是否正确
    if(!map.get("verifyCode").equals(redisTemplate.opsForValue().get(map.get("phone")+"register").toString().split(":")[0])){
        throw new BusinessException("验证码不一致");
    }
    //判断手机号是否注册
    User phone = userMapper.findByPhone(map.get("phone"));
    if(phone!=null){
        throw new BusinessException("手机号已经被注册");
    }
    //==========================添加logininfo表===========================
    LoginInfo loginInfo = new LoginInfo();
    //获取32位盐值
    String salt = StrUtils.getComplexRandomString(32);
    loginInfo.setSalt(salt);
    loginInfo.setPhone(map.get("phone"));
    loginInfo.setUsername(map.get("phone"));
    //加密加盐
    loginInfo.setPassword(Md5Utils.encrypByMd5(map.get("password")+salt));
    loginInfoMapper.add(loginInfo);
    //==========================添加user表===========================
    User user = new User();
    user.setLogininfo_id(loginInfo.getId());
    user.setDate(new Date());
    user.setState(1);
    //直接将loginInfo对象的共有字段拷贝到user对象
    BeanUtils.copyProperties(loginInfo,user);
    userMapper.add(user);
}

九:到此短信发送和验证以及图形验证码验证结束

posted @ 2022-07-28 19:49  yyybl  阅读(239)  评论(0)    收藏  举报