1.短信验证码
1.1.概念模型图
1.2.供应商选择
方案1:对接三大运营商接口
如果量少,三大运营商不屌您。
方案2:三方服务
一些有短信服务商,它们去对接三大运行商,封装为自己的接口。我们对接它们就ok。它们赚取差价。
先使用三方服务,等运营后,量大了,再找三大运营商对接。
2)选择三方服务商
阿里大于
腾讯
华为
中国网建 http://www.smschinese.cn/ 5条是免费的
这里我使用中国网建提供的5条免费短信来做测试
1.3.代码演示
首先准备短信发送接口:
SmsClient
package com.penny.client;
import com.penny.util.AjaxResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "HRM-COMMON", fallbackFactory = SmsClientFallbackFactory.class )//服务提供
@RequestMapping("/sms")
public interface SmsClient {
@PostMapping
AjaxResult send(@RequestParam String params);
}
提供托底处理方法
SmsClientFallbackFactory
package com.penny.client;
import com.penny.util.AjaxResult;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
@Component
public class SmsClientFallbackFactory implements FallbackFactory<SmsClient> {
@Override
public SmsClient create(Throwable throwable) {
return new SmsClient() {
@Override
public AjaxResult send(String params) {
return null;
}
};
}
}
在中国网建注册账号后,他会给你提供一个短信密钥
此时我们准备一个SmsContants接口用来准备短信接口所需常量
package com.penny.constants;
/**
* 短信接口常量类
*/
public interface SmsContants {
//注册名
public static final String SMS_UID = "你在中国网建注册的用户名";
//密钥
public static final String SMS_KEY = "d41d8cd98f00b204e980";
}
创建一个service接口
ISmsService
package com.penny.service;
import com.penny.util.AjaxResult;
import java.util.Map;
public interface ISmsService {
/**
* 发送短信验证码
* @param params
* @return
*/
AjaxResult sendSmsCode(Map<String, String> params);
}
写一个实现类实现发送短信方法(此处可参考中国网建提供的短信pai接口文档)
中国网建短信pai接口文档
SmsServiceImpl
package com.penny.service.impl;
import com.penny.service.ISmsService;
import com.penny.util.AjaxResult;
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 org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
public class SmsServiceImpl implements ISmsService {
//参数可变
@Override
public AjaxResult sendSmsCode(Map<String, String> params) {
System.out.println(params);
PostMethod post = null;
try {
HttpClient client = new HttpClient();
post = new PostMethod("http://utf8.api.smschinese.cn");
post.addRequestHeader("Content-Type",
"application/x-www-form-urlencoded;charset=utf-8");//在头文件中设置转码
//动态参数 //map===>list====>array
List<NameValuePair> datas = new ArrayList<>();
for (String key : params.keySet()) {
datas.add( new NameValuePair(key, params.get(key) ));
}
NameValuePair[] data = datas.toArray(new NameValuePair[]{});
post.setRequestBody(data);
client.executeMethod(post);
Header[] headers = post.getResponseHeaders();
int statusCode = post.getStatusCode(); //400 404 200 500
String result = new String(post.getResponseBodyAsString().getBytes("utf-8"));
System.out.println("statusCode:"+statusCode);
System.out.println(result); //打印返回消息状态
if (statusCode==200){
//对各种错误码进行处理
return AjaxResult.me().setMessage(result);
}else{
return AjaxResult.me().setSuccess(false).setMessage(result);
}
}catch (Exception e){
e.printStackTrace();
}
finally {
if (post != null) {
post.releaseConnection();
}
}
return null;
}
}
然后再controller里面封装参数,再调用发送短信方法
SmsController
package com.penny.controller;
import com.alibaba.fastjson.JSONObject;
import com.penny.constants.SmsContants;
import com.penny.service.ISmsService;
import com.penny.util.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/sms")
public class SmsController {
@Autowired
private ISmsService smsService;
//1 以下两个放到常量类中
//Uid=本站用户名
//Key=接口安全秘钥
//2 以下两个是动态传入
// smsMob=手机号码&smsText=验证码:8888
@PostMapping
AjaxResult send(@RequestParam String params){
System.out.println(params);
Map<String,String> paramsTmp =
JSONObject.parseObject(params, Map.class);
//封装参数
paramsTmp.put("Uid", SmsContants.SMS_UID);
paramsTmp.put("Key", SmsContants.SMS_KEY);
return smsService.sendSmsCode(paramsTmp);
}
}
然后回到上一章节的VerifycodeServiceImpl实现类
完成剩下的发送短信验证码的验证及发送操作
//发送短信验证码
private AjaxResult sendSmsCode(String mobile) {
//定义短信的key(前缀为严格格式要求)
String smsKey = "SMS_CODE:"+mobile;
//生成一个随机验证码
String smsCode = StrUtils.getComplexRandomString(4);
//如果原来验证码未过期,有效则替换刚刚生成的
String smsCodeRedis = redisClient.get(smsKey);
System.out.println(smsCodeRedis);
if(StringUtils.hasLength(smsCodeRedis)){
//如果有效,判断是否已经过了重发时间,如果没有过同时且有人访问就报错
String[] split = smsCodeRedis.split(":");
String timeStr = split[1];
//定义时间戳判断时间
long time = System.currentTimeMillis()-Long.valueOf(timeStr);
//如果重发时间小于60s,则拒绝
if(time<1000*60){
return AjaxResult.me().setSuccess(false).setMessage("请勿重复发送短信验证码!");
}
//超过了60s则可以正常发送短信验证码
smsCode = split[0];
}
//储存到redis且设置5分钟后过期
redisClient.addForTime(smsKey,smsCode+":"+System.currentTimeMillis(),60*5);
System.out.println("[Penny]已经发送验证码"+smsCode+"到用户手机:"+mobile);
Map<String,String> maps = new HashMap<>();
maps.put("smsMob", mobile);
maps.put("smsText", "感谢您注册TimeCraft科技,验证码为:" + smsCode + ",请在5分钟之内使用;如有疑问,请联系你的爱人");
//将map转换为string
smsClient.send(JSONObject.toJSONString(maps));
return AjaxResult.me();
}
操作成功则会返回如下
同时短信会发送至你输入的指定手机号上
2.注册功能
首先准备接口
ISsoService
package com.penny.service;
import com.penny.domain.Sso;
import com.baomidou.mybatisplus.service.IService;
import com.penny.util.AjaxResult;
import java.util.Map;
/**
* <p>
* 会员登录账号 服务类
* </p>
*
* @author Penny
* @since 2020-02-28
*/
public interface ISsoService extends IService<Sso> {
/**
* 注册
* @param params
* @return
*/
AjaxResult register(Map<String,String> params);
/**
* 登陆
* @param sso
* @return
*/
AjaxResult login(Sso sso);
}
写一个实现类如实现register注册方法,并且在注册的同时做验证
SsoServiceImpl
@Override
public AjaxResult register(Map<String, String> params) {
String mobile = params.get("mobile");
String password = params.get("password");
String smsCode = params.get("smsCode");
//校验
AjaxResult result = validateParam(mobile,password,smsCode);
if(!result.isSuccess()){
return result;
}
//保存sso信息
Sso sso = new Sso();
sso.setPhone(mobile);
//获取随机验证字符串
String salt = StrUtils.getComplexRandomString(32);
//设置盐值
sso.setSalt(salt);
//使用随机验证给密码md5加密,并设置
//输入密码+以后做校验的时候先从数据库查询盐=md5,再和数据库查询出来进行比较
String Md5Password = MD5.getMD5(password + salt);
sso.setPassword(Md5Password);
sso.setNickName(mobile);
//设置安全等级
sso.setSecLevel(0);
sso.setBitState(1L);
sso.setCreateTime(System.currentTimeMillis());
ssoMapper.insert(sso);
//保存关联对象
VipBase vipBase = new VipBase();
vipBase.setCreateTime(System.currentTimeMillis());
vipBase.setSsoId(sso.getId());
vipBase.setRegChannel(1);
vipBase.setRegTime(System.currentTimeMillis());
vipBaseMapper.insert(vipBase);
return AjaxResult.me();
}
private AjaxResult validateParam(String mobile, String password, String smsCode) {
//手机号空验证
if(!StringUtils.hasLength(mobile) || !StringUtils.hasLength(password)){
return AjaxResult.me().setSuccess(false).setMessage("请输入正确的用户名或密码!");
}
//手机号已注册
//查询数据库
List<Sso> phone = ssoMapper.selectList(new EntityWrapper<Sso>().eq("phone", mobile));
if(phone != null && phone.size()>0){
return AjaxResult.me().setSuccess(false).setMessage("该手机号已注册");
}
// 短信验证码校验 通过key从redis获取
String smsCodeStr = redisClient.get("SMS_CODE:" + mobile);
String smsCodeRedis = smsCodeStr.split(":")[0];
if(!smsCodeRedis.equals(smsCode)){
return AjaxResult.me().setSuccess(false).setMessage("请输入正确的短信验证码");
}
return AjaxResult.me();
}
最后在controller里面调用service层的register注册方法即可
SsoController
//注册
@PostMapping("/register")
public AjaxResult register(@RequestBody Map<String,String> params)
{
return ssoService.register(params);
}