短信发送校验以及图形码校验
一:摘要
短信校验需要发送短信,但是只有三大运行商有发送短信的能力,要发送短信只有找三大运营商或者中间商,简单地说就是要找第三方的短信平台,常见的有阿里云、京东智联云、乐讯通、网建通等平台,我们需要拿到它们提供的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);
}
九:到此短信发送和验证以及图形验证码验证结束