Java实现短信验证码的示例代码

目录
  • 项目需求
  • 需求来由
  • 代码实现
    • 发送验证码方法
    • 注册方法
    • 忘记密码
    • 前端代码
  • 编码中遇到的问题
  • 如何改进

短信验证码相信大家都不陌生吗,但是短信验证码怎么生成的你真的了解吗,本文揭示本人项目中对短信验证码的。

项目需求

用户注册/忘记密码添加短信验证码

需求来由

登录注册页面需要确保用户同一个手机号只关联一个账号确保非人为操作,避免系统用户信息紊乱增加系统安全性

代码实现

同事提供了WebService接口,很好,之前没调过,又增加了困难。

这边用的阿里云的短信服务,废话少说上图,呸,上代码—

发送验证码方法

public AjaxResult sendVerificationCode(LoginBody loginBody) {
    //拼装redis的key
    String redisCodeKey = Constants.RECRUIT_CODE_KEY + loginBody.getUserName();
    //通过判断过期时间检验是否发送过验证码如果发送直接return
    if (redisCache.getExpire(redisCodeKey) >= 0) {
        return AjaxResult.error(TipsConstants.YZM_SEND_ALREADY);
    }
    //生成随机6位验证码
    String redisCodeValue = VerifyCodeUtils.generateSmsCode();
    //验证码类型这是根据同事给的webservice的文档单独封装的目前先这么写了;判断其是注册还是忘记密码
    VerificationCodeType verificationCodeType = VerificationCodeType.getByCode(loginBody.getVerificationCodeType());
    String templateCode = null;
    switch (verificationCodeType) {
        case REGISTER:
            templateCode = VerificationCodeType.REGISTER.getCode();
            break;
        case FORGET_PASSWORD:
            templateCode = VerificationCodeType.FORGET_PASSWORD.getCode();
            break;
        default:
            break;
    }
    //webservice接口需要json格式的参数
    JSONObject jsonObject = new JSONObject();
    jsonObject.put(WebServiceConstants.CODE, redisCodeValue);
    Map<String, String> resultMap = SMSUtils.sendMessage(loginBody.getUserName(),templateCode,jsonObject);
    //判断webservice接口返回的结果
    if (!resultMap.get(WebServiceConstants.SEND_SMS_RESULT).equals(Constants.SUCCESS)) {
        logger.info(resultMap.get(WebServiceConstants.OUT_MSG));
        logger.info(resultMap.get(WebServiceConstants.BIZ_ID));
        return AjaxResult.error(TipsConstants.MSG_SERVER_ERROR);
    }
    //存储到redis设置过期时间,这里设置了60s,根据需求来
    redisCache.setCacheObject(redisCodeKey, redisCodeValue, 60, TimeUnit.SECONDS);
    return AjaxResult.success();
}

注册方法

public AjaxResult register(LoginBody loginBody) {
    //拼装redis key
    String redisCodeKey = Constants.RECRUIT_CODE_KEY + loginBody.getUserName();
    //redisCache封装了redis的方法;
    //获取验证码判断验证码是否为空;输入的验证码与短信验证码是否一致
    String redisCodeValue = redisCache.getCacheObject(redisCodeKey);
    if (StringUtils.isEmpty(redisCodeValue) || !loginBody.getVerificationCode().equals(redisCodeValue)) {
        return AjaxResult.error(TipsConstants.YZM_ERROR);
    }
    //查表校验用户是否注册
    SysUser existUser = sysUserMapper.checkPhoneUnique(loginBody.getUserName());
    if (!ObjectUtil.isEmpty(existUser)) {
        return AjaxResult.error(TipsConstants.EXIST_USER_ERROR);
    }
    //对象copy,创建SysUser对象
    SysUser sysUser = BeanUtil.copyProperties(loginBody, SysUser.class, UserConstants.PASSWORD);
    sysUser.setPassword(SecurityUtils.encryptPassword(loginBody.getPassword()));
    //插入用户信息
    sysUserMapper.insertUser(sysUser);
    return AjaxResult.success(TipsConstants.REGISTER_SUCCESS);
}

忘记密码

public AjaxResult forgetPwd(LoginBody loginBody) {
    //拼装redis的key
    String redisCodeKey = Constants.RECRUIT_CODE_KEY + loginBody.getUserName();
    //获取验证码
    String redisCodeValue = redisCache.getCacheObject(redisCodeKey);
    if (!loginBody.getVerificationCode().equals(redisCodeValue)) {
        return AjaxResult.error(TipsConstants.YZM_ERROR);
    }
    //查表查询用户是否存在
    SysUser sysUser = sysUserMapper.checkPhoneUnique(loginBody.getUserName());
    if (ObjectUtil.isEmpty(sysUser)) {
        return AjaxResult.error(TipsConstants.NO_USER);
    }
    //密码加密
    loginBody.setPassword(SecurityUtils.encryptPassword(loginBody.getPassword()));
    //重置密码
    sysUserMapper.resetUserPwd(loginBody.getUserName(), loginBody.getPassword());
    return AjaxResult.success();
}

前端代码

这里只粘贴了发送验证码改变按钮的方法

sendCode(type) {
  this.$refs.registerForm.validateField('phone',(phoneError)=> {
    if(!phoneError){
      this.registerForm.verificationCodeType = type
      //短信验证码最大请求次数校验
      getSmsCode(this.registerForm).then(response => {
        if (response.code !== 200) {
          this.requestMax = true
        } else {
          this.msgSuccess('发送成功,请注意查收短信')
          this.requestMax = false
        }
        //发送验证码按钮修改
        if (!this.requestMax) {
          let time = 60
          this.buttonText = '已发送'
          this.isDisabled = true
          if (this.flag) {
            this.flag = false
            let timer = setInterval(() => {
              time--
              this.buttonText = time + ' 秒'
              if (time === 0) {
                clearInterval(timer)
                this.buttonText = '重新获取'
                this.isDisabled = false
                this.flag = true
              }
            }, 1000)
          }
        }
      })
    }
  })
},

编码中遇到的问题

1.webservice如何调用?

一开始导了很多关于webservice的相关依赖,结果掉不通没办法只能用Hutool了,send返回的是一个xml,再用documet将其解析就ok了。

SoapClient soapClient = SoapClient.create(WebServiceConfig.getMsgUrl())
        .setMethod(WebServiceMethod.SendSms.getCode(), WebServiceConfig.getNamespaceUri())
        .setParams(map, false);
String result = soapClient.send()

2.不能让用户无限制的请求发送验证码

据说短信平台有验证逻辑,为了安全还是给系统封了一层;这里通过注解,aop配合redis计数器进行最大请求次数验证。

代码如下

注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckRequestTimes {
    /**
     * 最大请求次数
     */
    String maxTimes() default "10";
    /**
     * 整个系统最大请求次数
     */
    String maxSystermTimes() default "1000";
    /**
     * 请求类型
     */
    RequestEnums reqType() default RequestEnums.COMMON;
    /**
     * 请求次数上限错误信息提示
     */
    String errorMsg() default TipsConstants.REQUEST_TIMES_MAX
}

Aspect

这部分代码我个人认为设计比较巧妙,可供读者思考,多利用设计模式思想去开发代码,让代码更优雅、更健壮、更可用,crud也有编出自己的骨气!!!(本实例涵盖了单例,模板方法)

@Aspect
@Component
@Order(2)
public class CheckRequestAspect {

    @Autowired
    RedisService redisService;
    @Autowired
    TokenService tokenService;

    private static Logger logger = LoggerFactory.getLogger(CheckRequestAspect.class);
    //防止并发,添加关键字实现共享
    private volatile ConcurrentHashMap<RequestEnums, RequestTimesAbstract> reqTimesProcessMap;
    
    @PostConstruct
    public void initExcelProcessorFactory() {
        //dcl 双重检查锁,也可进行懒散加载。因为现在基于spring容器单例,此锁可适当调整
        if (MapUtil.isNotEmpty(reqTimesProcessMap)) {
            return;
        }
        //眼熟不这叫懒汉式单例
        synchronized (this) {
            if (ObjectUtil.isNull(reqTimesProcessMap)) {
                reqTimesProcessMap = new ConcurrentHashMap(8);
            }
            //这里其实可以采用工厂方法去改造,由于业务没有太多类型所以就不设计工厂了
            reqTimesProcessMap.put(RequestEnums.COMMON, new UserCommReqTimes());
            reqTimesProcessMap.put(RequestEnums.SMS, new SMSCodeReqTimes());
        }
    }
    /**
     * 切入点
     */
    @Pointcut("@annotation(com.fuwai.hr.common.annotation.CheckRequestTimes)")
    public void checkPoint() {

    }
    /**
     * 环绕获取请求参数
     *
     * @param proceedingJoinPoint
     * @return
     */
    @Around("checkPoint()")
    public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) {
        //获取方法上的注解
        CheckRequestTimes checkRequestTimes = getAnnotation(proceedingJoinPoint);
        Object[] args = proceedingJoinPoint.getArgs();
        //判断是否到达最大请求次数,这里为了应对不同请求类型的处理方式写了一个抽象类,
        //便于扩展维护,沿用了了模板方法设计模式的思想
        if(!reqTimesProcessMap.get(checkRequestTimes.reqType()).judgeMaxTimes(args, checkRequestTimes, redisService)){
            return AjaxResult.error(HttpStatus.REQUEST_MAX, checkRequestTimes.errorMsg());
        }
        //执行请求方法
        Object proceed = null;
        try {
            proceed = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            logger.error(throwable.getMessage(), throwable);
        }
        return proceed;
    }
    /**
     * 获取方法上的注解以便拿到对应的值
     *
     * @param proceedingJoinPoint
     * @return
     */
    private CheckRequestTimes getAnnotation(ProceedingJoinPoint proceedingJoinPoint) {
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = signature.getMethod();
        if (method != null){
            return method.getAnnotation(CheckRequestTimes.class);
        }
        return null;
    }
}

抽象模板类

public abstract class RequestTimesAbstract {
    /**
     * 判断是否到达请求最大次数
     * @param object 参数
     * @param checkRequestTimes  注解
     * @param redisService   redis服务
     * @return
     */
    public abstract boolean judgeMaxTimes(Object object, CheckRequestTimes checkRequestTimes, RedisService redisService);
}

短信模板子类

public class SMSCodeReqTimes extends RequestTimesAbstract {
    @Override
    public boolean judgeMaxTimes(Object object, CheckRequestTimes checkRequestTimes, RedisService redisService) {
        Object[] objects= (Object[])object;
        LoginBody loginBody = JSONObject.parseObject(JSONObject.toJSONString(objects[0]), LoginBody.class);
        String phone = Constants.RECRUIT_CODE_TIMES_KEY + loginBody.getUserName() + Constants.NUM;
        //本地只有一个服务器,拼接一个ip的key;如果是分布式这种方式就不太可取了根据需求来吧
        StringBuilder ip = new StringBuilder();
        ip.append(Constants.RECRUIT_CODE_TIMES_KEY).append(LocalHostUtil.getLocalIp()).append(Constants.DELIVERY).append(Constants.NUM);
        //判断本地系统的最大请求方式和用户的请求次数
        if (StringUtils.isNotEmpty(ip) && StringUtils.isNotEmpty(phone)) {
            return redisService.judgeMaxRequestTimes(ip.toString(), checkRequestTimes.maxSystermTimes()) && redisService.judgeMaxRequestTimes(phone, checkRequestTimes.maxTimes());
        }
        return false;
    }
}

RedisService判断请求方法

这里实现了一简单redis计数器自己随手写的也不知道对不对;rediscache封装的redis一些操作

/**
 * 判断最大请求次数
 *
 * @param key 缓存对象key键
 * @param max 最大请求次数
 * @return
 */
@Override
public Boolean judgeMaxRequestTimes(String key, String max) {
    //获取key值,值为null插入值
    //不为null进行,判断是否到最大值,更新数值
    String value = redisCache.getCacheObject(key);
    if (StringUtils.isEmpty(value)) {
        //key存在的话不对齐进行操作,存在的话就他设置值
        redisCache.setIfAbsent(key, RecruitNumberConstants.NUMBER_1.toString(), RecruitNumberConstants.NUMBER_24, TimeUnit.HOURS);
        return true;
    }
    //最大次数 <= 当前访问次数
    if (Integer.valueOf(max).compareTo(Integer.valueOf(value)) <= RecruitNumberConstants.NUMBER_0) {
        return false;
    }
    //这里获取的是当前key的过期时间
    //(因为这边更新值的话,更新要不得设置过期时间要不不设置更新那ttl就变成了永久的了
    //两种方案都不合理那就只能获取他当前的剩余时间去更新了)
    Long expire = redisCache.getExpire(key);
    //key存在的话对其进行更新,不存在不对其进行操作
    return redisCache.setIfPresent(key, String.valueOf(Integer.parseInt(value) + RecruitNumberConstants.NUMBER_1), expire, TimeUnit.SECONDS);
}

如何改进

个人感觉这应该是不支持并发的,关于计数的操作可以用原子类去操作;我感觉我写的这玩意分布式估计也支持不了,有时间自己搭个环境再验证吧,懒得搞了。

到此这篇关于Java实现短信验证码的示例代码的文章就介绍到这了,更多相关Java 短信验证码内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java实现短信验证码5分钟有效时间

    本文实例为大家分享了java实现短信验证码5分钟有效时间,供大家参考,具体内容如下 实现一个发送短信验证码的请求,要求5分钟之内重复请求,返回同一个验证码. 网上可找到几种方案: 如,存储数据库或缓存中.实现起来比较麻烦,舍弃: 另一种方式即本例,使用session存储.其他方式,暂未进一步了解. 实现步骤: (springmvc) 1.controller中,获取session对象,取code,取不到新生成,并存储session中: 2.单手机号发送量,判断并 +1 记入数据库: 3.Time

  • JAVA实现利用第三方平台发送短信验证码

    前段时间自己做的一个小项目中,涉及到用短信验证码登录.注册的问题,之前没涉及过这一块,看了别人的博客其实也是似懂非懂的,现在就将自己做的利用第三方短信平台来发送验证码这个功能记下来. 本文以注册为例,在SpringMVC+Spring+Mybatis框架的基础上完成该短信验证码功能. 发送短信验证码的原理是:随机生成一个6位数字,将该6位数字保存到session当中,客户端通过sessionid判断对应的session,用户输入的验证码再与session记录的验证码进行比较. 为了防止有广告嫌疑

  • Java随机生成手机短信验证码的方法

    本文实例讲述了Java随机生成手机短信验证码的方法.分享给大家供大家参考,具体如下: /** * 创建指定数量的随机字符串 * @param numberFlag 是否是数字 * @param length * @return */ public static String createRandom(boolean numberFlag, int length){ String retStr = ""; String strTable = numberFlag ? "1234

  • 基于Java随机生成手机短信验证码的实例代码

    简单版 /** * 产生4位随机数(0000-9999) * * @return 4位随机数 */ public static String getFourRandom() { return StringUtils.leftPad(new Random().nextInt(10000) + "", 4, "0"); } 复杂版 /** * 创建指定数量的随机字符串 * @param numberFlag 是否是数字 * @param length * @return

  • Java实现短信发送验证码功能

    1:新建maven工程 2:开通腾讯云的短信服务功能,添加应用. 3:配置短信签名.短信正文模板,并审核通过(为了方便我就用微信公众号) 4:添加发送短信引用的腾讯云jar包 <dependency> <groupId>com.github.qcloudsms</groupId> <artifactId>qcloudsms</artifactId> <version>1.0.2</version> </depende

  • Java实现发送短信验证码功能

    一个发送短信验证码的功能,使用的是信易通的短信平台接口,然后在Java中使用HttpClient模拟POST请求或者GET请求(看短信平台要求,一般的情况下都是POST请求),调用短信平台提供的接口(遵循短信平台的接口规范即可).具体看代码: 使用HttpClient的时候需要在项目中引入: commons-httpclient-3.1.jar 这个jar包, 项目结构: 1.创建一个Http的模拟请求工具类,然后写一个POST方法或者GET方法 /** * 文件说明 * @Descriptio

  • java实现发送短信验证码

    最近用学习了一下调用第三方接口发送短信验证码的程序,希望能够帮助到大家. 1.首先下图为项目的目录结构,需要带入三个包: commons-httpclient-3.1.jar commons-logging-1.0.4.jar codec-1.3.jar 2.其次要创建模拟POST.GET请求的工具类: package com.demo.util; import java.io.IOException; import java.util.Map; import org.apache.common

  • Java实现短信验证码和国际短信群发功能的示例

    最近由于公司的业务拓展,需要给国外用户发送国际短信,像西班牙.葡萄牙.意大利这些国家都要发,还有中国的香港.澳门.台湾(港澳台)这些地区也要发,不过现在已经有许多公司提供国际短信的业务了,之前使用过云片的验证码业务,顺便看到他们也有国际短信的业务,并且更重要的是,不需要修改任何代码,只要添加下国际短信模板,就可以直接使用之前的代码继续发送国际短信,简直太方便了. 废话不多说,直接上代码. /** * Created by bingone on 15/12/16. */ import org.ap

  • java短信验证码获取次数限制实例

    现在不管什么项目,用到短信验证功能,都会在程序上设计一个短信验证码的获取次数限制,这样主要是避免短信验证码接口被刷. 前一段正好做一个项目的用户短信验证码登录功能,就研究了以下,下面贴出来分享一下. 这里涉及到的短信接口,用的第三方短信接口-动力思维思维乐信的(http://www.lx598.com/),如果想了解短信接口接入,可以到他们官网,查看下短信接口API文档说明,参考下面的代码应该就能弄明白. 用户注册部分,主要代码如下: //主要js方法: //获取手机验证码: function

  • Java实现短信验证码的示例代码

    目录 项目需求 需求来由 代码实现 发送验证码方法 注册方法 忘记密码 前端代码 编码中遇到的问题 如何改进 短信验证码相信大家都不陌生吗,但是短信验证码怎么生成的你真的了解吗,本文揭示本人项目中对短信验证码的. 项目需求 用户注册/忘记密码添加短信验证码 需求来由 登录注册页面需要确保用户同一个手机号只关联一个账号确保非人为操作,避免系统用户信息紊乱增加系统安全性 代码实现 同事提供了WebService接口,很好,之前没调过,又增加了困难. 这边用的阿里云的短信服务,废话少说上图,呸,上代码

  • 基于JAVA的短信验证码api调用代码实例

    本文实例为大家分享了JAVA的短信验证码api调用代码,供大家参考,具体内容如下 import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import jav

  • Java调取创蓝253短信验证码的实现代码

    基于创蓝253短信服务平台的Java调用短信接口API package com.bcloud.msg.http; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.URLDecoder; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; i

  • C#代码实现短信验证码接口示例

    本文实例为大家分享了C#实现短信验证码接口示例,供大家参考,具体内容如下 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Net; using System.IO; using System

  • SpringBoot实现阿里云短信发送的示例代码

    阿里云accessID和secret请自行进入阿里云申请 sms.template.code 请进入阿里云,进行短信服务进行魔板添加 开源代码地址在文章末尾 话不多说,直接上代码: application.properties: server.port=8002 #server.servlet.context-path=/ spring.datasource.url=jdbc:mysql://localhost:3306/ssm_message?useUnicode=true&character

  • Spring Security Oauth2.0 实现短信验证码登录示例

    本文介绍了Spring Security Oauth2.0 实现短信验证码登录示例,分享给大家,具体如下: 定义手机号登录令牌 /** * @author lengleng * @date 2018/1/9 * 手机号登录令牌 */ public class MobileAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = SpringSecur

  • mpvue网易云短信接口实现小程序短信登录的示例代码

    上一篇简单介绍了mpvue实现快递单号查询,慢慢发现mpvue真的和vue很像,但它有几乎十分的吻合小程序的语法规范,刚开始用起来会觉得特点的爽,但涉及到细节却是有很多采坑的地方.今天利用网上的网易云接口,再结合mpvue简单写一写小程序短信验证登录. 简单封装的一个网络请求文件,网易云接口网上大佬们GitHub上还是比较的多而且开源 const baseURL = "https://*****:1717"; //基路径 exports.http = function({url,met

随机推荐