如何设计?
最初设计将生成验证码redis中存入 key 为手机号,value为验证码的键值对作为一个接口实现,但有问题是验证码不唯一,比如该手机号登录的验证码可以用于该手机号找回密码,这样肯定是不行的,后来尝试改用redis的hash结构,但需要前端配合传递 type 类型存入redis中,并且接口不能并发,最后采用生成不同类型验证码的多个接口实现,比如登录获取验证码为登录获取验证码接口,修改密码获取验证码为获取验证码接口,(采用 类型+手机号 的组合作为redis的key存入redis)
逻辑
接口一:生成验证码,存入redis,发送短信
接口二:前端输入的验证码和redis中的验证码是否匹配
- /**
- * 生成(6位)验证码工具类
- */
- public class VerifyCodeUtils {
- public static String createCode(){
- return String.valueOf(ThreadLocalRandom.current().nextInt(100000, 999999));
- }
- }
需要获得短信签名,模板签名(或者appcode)
- /**
- *发送短信工具类
- */
- public class SmsUtils {
-
- /**
- * 仅仅完成将存入redis的验证码发送到目标手机上的短信功能
- * @param mob 手机号
- * @param code 后端生成的验证码
- */
- public static void sendMsg(String mob,String code){
-
- String host = "https://miitangs09.market.alicloudapi.com";
- String path = "/v1/tools/sms/code/sender";
- String method = "POST";
- String appcode = "填入购买服务时的appcode"; //购买服务的appcode,在历史订单中查看
- Map
headers = new HashMap(); - //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
- headers.put("Authorization", "APPCODE " + appcode);
- //根据API的要求,定义相对应的Content-Type
- headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
- //需要给X-Ca-Nonce的值生成随机字符串,每次请求不能相同
- headers.put("X-Ca-Nonce", UUID.randomUUID().toString());
- Map
querys = new HashMap(); - Map
bodys = new HashMap(); - bodys.put("filterVirtual", "false");
- bodys.put("phoneNumber", mob);
- bodys.put("reqNo", "miitangtest01");
- bodys.put("smsSignId", "0000"); //签名id
- bodys.put("smsTemplateNo", "0001"); //短信模板id
- bodys.put("verifyCode", code); //验证码
-
-
- try {
- /**
- * 重要提示如下:
- * HttpUtils请从
- * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
- * 下载
- *
- * 相应的依赖请参照
- * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
- */
- HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
- //获取response的body
- System.out.println(EntityUtils.toString(response.getEntity()));
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- }
- }
- /**
- * 手机登录验证码接口
- *
- * @param phone 手机号.
- */
- @PublishMethod
- String LoginCode(String phone);
- /**
- * 手机登录获取验证码的实现
- * 返回值为 redis 中的key (相同用户 不同验证码接口对于的 key 是不同的)
- * @param phone 手机号.
- * @return
- */
- @Override
- public String LoginCode(String phone) {
- AssertUtils.ThrowArgOutRangeException(phone.length(),"手机号码的长度必须为11",11,11);
- String code = VerifyCodeUtils.createCode(); //后台生成6位验证码
- System.out.println("手机登录后台生成的验证码:"+code);
- String key = RedisSmsEnum.Login.getKey() + phone; //拼接redis中唯一的key
- redisTemplate.opsForValue().set(key,code, 300, TimeUnit.SECONDS); //生成验证码放入redis中,设置5分钟过期时间
- System.out.println("存入redis的验证码"+redisTemplate.opsForValue().get(key));
- SmsUtils.sendMsg(phone,code); //发送(生成的验证码)到用户手机,暂时不开通,节约流量
- return key;
- }
- /**
- * 用户手机登录的实现 .
- *
- * @param phone 手机号.
- * @param verifyCode 验证码.
- 6c8512064f11420f83d6f7c3f10816ba
- */
- @Override
- public void loginBySms(String phone, String verifyCode) {
- AssertUtils.ThrowArgNullException("手机号不能为空",phone,true);
- AssertUtils.ThrowArgNullException("验证码不能为空",verifyCode,true);
- AssertUtils.ThrowArgOutRangeException(phone.length(),"手机号码的长度必须为11",11,11);
- AssertUtils.ThrowArgOutRangeException(verifyCode.length(),"验证码长度必须为6",6,6);
-
- String newPhone = RedisSmsEnum.Login.getKey() + phone; //匹配唯一的redis中key(key为类型+电话号码的组合,保证key的唯一性)
-
- if (redisTemplate.opsForValue().get(newPhone) == null){
- throw new PsCoreRuntimeException("验证码过期");
- }
- if (!verifyCode.equals(redisTemplate.opsForValue().get(newPhone))){
- throw new PsCoreRuntimeException("验证码输入错误");
- }
- if (verifyCode.equals(redisTemplate.opsForValue().get(newPhone))){
- UserPOQueryPara para = new UserPOQueryPara();
- para.setParamByphone(phone);
- if (userPODao.queryList(para, 0, -1).size() == 0){
- throw new PsCoreRuntimeException("该用户不存在");
- }
- System.out.println("用户根据手机验证码登录成功");
- System.out.println("登录之后用户信息"+userPODao.queryList(para, 0, -1));
- redisTemplate.delete(newPhone); //登录成功之后 删除验证码
- }
- }