Bootstrap

若依微服务集成手机短信验证码登陆

为了响应公司项目的特定需求,增强用户体验与安全性,集成手机短信验证码登录功能至基于若依微服务框架开发的应用中,故创作此篇为未来类似项目提供了可借鉴的实施范例。

1.设计思路

大致可分为这几个阶段:生成验证码并存储至redis缓存中,发送验证码,用户登陆验证

2.发送手机验证码接口

/**
     * 发送手机验证码
     * @param phoneNumber
     * @return
     */
    @GetMapping("/code/{phoneNumber}")
    public AjaxResult sendCode(@PathVariable String phoneNumber) {

        // 从Redis中获取最后一次发送验证码的时间戳
        String lastSentTimeStr = redisService.getCacheObject(phoneNumber + "lastSendTime");
        if (lastSentTimeStr != null) {
            long lastSentTime = Long.parseLong(lastSentTimeStr);
            // 计算当前时间与上次发送时间的时间差
            long timeDiff = Duration.between(Instant.ofEpochMilli(lastSentTime), Instant.now()).toMillis();
            // 如果时间差小于60秒,则返回错误
            if (timeDiff < 60_000) {
                return AjaxResult.error("您的短信发送过于频繁,请60s后再试!");
            }
        }
        //生成6位随机验证码
        Random randObj = new Random();
        String smsCode = Integer.toString(100000 + randObj.nextInt(900000));
        //发送短信
        boolean isSend = sysSmsApiService.send(phoneNumber,smsCode);
        if(!isSend){
            return AjaxResult.error("短信发送失败!");
        }
        // 更新Redis中该手机号最后发送验证码的时间戳
        redisService.setCacheObject(phoneNumber + "lastSendTime", String.valueOf(Instant.now().toEpochMilli()), 60L, TimeUnit.SECONDS);
        //将验证码保存至redis缓存中,设置有限期为5分钟
        redisService.setCacheObject(phoneNumber,smsCode,5L,TimeUnit.MINUTES);
        return AjaxResult.success();
    }

3.发送手机验证码接口



    @Value("${tax.url}")
    private String url;
    @Value("${tax.key}")
    private String key;
    @Value("${tax.vipara}")
    private String vipara;
    
    /**
     * 发送短信
     * @param phoneNumber
     * @param smsCode
     * @return
     */
    @Override
    public boolean send(String phoneNumber, String smsCode) {

        try {
            SendtoVo sendtoVo = new SendtoVo();
            sendtoVo.setSjhm(phoneNumber);
            String content = "短信模板";   //自己根据业务需求编写
            sendtoVo.setContent(content);
            HashMap<Object, Object> map = new HashMap<>();
            map.put("*******", "********");

            map.put("data", JSON.toJSONString(sendtoVo));
            Map result = doPostUtils.doPost(url, map, key, vipara);    //此处传入小程序配置参数
//            System.out.println("短信发送返回结果======================:" + result);
            if (result != null) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

4.post请求工具类

    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param httpUrl
     *            发送请求的 URL
     * @param map
     *  请求参数是json
     *  @param key
     * key
     * @return 所代表远程资源的响应结果
     */
    public static Map doPost(String httpUrl, Map map,String key,String vipara) {
        log.info(JSON.toJSONString(map));
        map.put("data",Base64Utils.JsonToBase64((String) map.get("data")));
        String jsonString = JSON.toJSONString(map);
        String Aesdata = AesUtils.encrypt(jsonString, key,vipara);
        HttpURLConnection connection = null;
        InputStream is = null;
        OutputStream os = null;
        BufferedReader br = null;
        String result = null;
        try {
            URL url = new URL(httpUrl);
            // 通过远程url连接对象打开连接
            connection = (HttpURLConnection) url.openConnection();
            // 设置连接请求方式
            connection.setRequestMethod("POST");
            // 设置连接主机服务器超时时间:15000毫秒
            connection.setConnectTimeout(15000);
            // 设置读取主机服务器返回数据超时时间:60000毫秒
            connection.setReadTimeout(60000);

            // 默认值为:false,当向远程服务器传送数据/写数据时,需要设置为true
            connection.setDoOutput(true);
            // 默认值为:true,当前向远程服务读取数据时,设置为true,该参数可有可无
            connection.setDoInput(true);
            // 设置传入参数的格式:请求参数应该是 name1=value1&name2=value2 的形式。
            connection.setRequestProperty("Content-Type", "application/json");
            // 设置鉴权信息:Authorization: Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0
            // connection.setRequestProperty("Authorization", "Bearer
            // da3efcbf-0845-4fe3-8aba-ee040be542c0");
            // 通过连接对象获取一个输出流
            os = connection.getOutputStream();
            // 通过输出流对象将参数写出去/传输出去,它是通过字节数组写出的
            os.write(Aesdata.getBytes());
            // 通过连接对象获取一个输入流,向远程读取
            if (connection.getResponseCode() == 200) {

                is = connection.getInputStream();
                // 对输入流对象进行包装:charset根据工作项目组的要求来设置
                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));

                StringBuffer sbf = new StringBuffer();
                String temp = null;
                // 循环遍历一行一行读取数据
                while ((temp = br.readLine()) != null) {
                    sbf.append(temp);
                    sbf.append("\r\n");
                }
                result = sbf.toString();
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (null != br) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != os) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            // 断开与远程地址url的连接
            connection.disconnect();
        }
      String result1 = AesUtils.decrypt(result, key,vipara);
      Map map1 = JSON.parseObject(result1, Map.class);
      String data2 = (String) map1.get("data");
      if ("0".equals(map1.get("code").toString())){
        String s = Base64Utils.Base64ToJson(data2);
        map1.put("data",s);
        log.info(map1.toString());
        return map1;
      }
     return null;
    }

5.登录校验接口

	/**
     * 手机短信登录
     * @param phoneNumber
     * @param smsCode
     * @return
     */
    public LoginUser smsLogin(String phoneNumber, String smsCode) {
        // 手机号或验证码为空 错误
        if (StringUtils.isAnyEmpty(phoneNumber, smsCode)) {
            recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, "手机号/验证码必须填写");
            throw new ServiceException("手机号/验证码必须填写");
        }
        // 手机号输入错误
        if (phoneNumber.length() != UserConstants.PHONE_NUMBER_LENGTH){
            recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, "手机号输入错误");
            throw new ServiceException("手机号输入错误,请检查");
        }
        // 验证码输入错误
        if (smsCode.length() != UserConstants.CODE_LENGTH){
            recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, "验证码输入错误");
            throw new ServiceException("验证码输入错误,请检查");
        }
        //通过手机号查询用户信息
        R<LoginUser> userResult = remoteUserService.getUserInfoByPhoneNumber(phoneNumber, SecurityConstants.INNER);
        if (R.FAIL == userResult.getCode()) {
            throw new ServiceException(userResult.getMsg());
        }
        if (StringUtils.isNull(userResult.getData())) {
            recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, "登录手机号不存在");
            throw new ServiceException("登录手机号:" + phoneNumber + " 不存在");
        }
        LoginUser userInfo = userResult.getData();

        //在redis中获取验证码
        String cacheCode = redisService.getCacheObject(phoneNumber);
        if(cacheCode == null || ObjectUtils.isEmpty(cacheCode)) {
            recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, "验证码不存在或已过期");
            throw new ServiceException("验证码不存在或已过期");
        }
        if(cacheCode.equals(smsCode)){
            //验证码正确,可删除验证码
            redisService.deleteObject(phoneNumber);
            recordLogininfor(phoneNumber, Constants.LOGIN_SUCCESS, "登录成功");
        } else {
            recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, "验证码输入有误");
            throw new ServiceException("验证码输入有误");
        }
        return userInfo;
    }

6.nacos配置请求白名单

在这里插入图片描述

7.nacos配置短信服务密钥

在这里插入图片描述

8.获取手机验证码测试

在这里插入图片描述
9.用户登录测试
在这里插入图片描述

;