为了响应公司项目的特定需求,增强用户体验与安全性,集成手机短信验证码登录功能至基于若依微服务框架开发的应用中,故创作此篇为未来类似项目提供了可借鉴的实施范例。
文章目录
- 1.设计思路
- 2.发送手机验证码接口
- 3.发送手机验证码接口
- 4.post请求工具类
- 5.登录校验接口
- 6.nacos配置请求白名单
- 7.nacos配置短信服务密钥
- 8.获取手机验证码测试
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*/@Overridepublic 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,当向远程服务器传送数据/写数据时,需要设置为trueconnection.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.用户登录测试