欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java實現(xiàn)短信驗證碼的示例代碼

 更新時間:2022年03月09日 09:37:07   作者:鴻鵠ing  
本文主要介紹了Java實現(xiàn)短信驗證碼的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下

短信驗證碼相信大家都不陌生嗎,但是短信驗證碼怎么生成的你真的了解嗎,本文揭示本人項目中對短信驗證碼的。

項目需求

用戶注冊/忘記密碼添加短信驗證碼

需求來由

登錄注冊頁面需要確保用戶同一個手機號只關(guān)聯(lián)一個賬號確保非人為操作,避免系統(tǒng)用戶信息紊亂增加系統(tǒng)安全性

代碼實現(xiàn)

同事提供了WebService接口,很好,之前沒調(diào)過,又增加了困難。

這邊用的阿里云的短信服務(wù),廢話少說上圖,呸,上代碼—

發(fā)送驗證碼方法

public AjaxResult sendVerificationCode(LoginBody loginBody) {
    //拼裝redis的key
    String redisCodeKey = Constants.RECRUIT_CODE_KEY + loginBody.getUserName();
    //通過判斷過期時間檢驗是否發(fā)送過驗證碼如果發(fā)送直接return
    if (redisCache.getExpire(redisCodeKey) >= 0) {
        return AjaxResult.error(TipsConstants.YZM_SEND_ALREADY);
    }
    //生成隨機6位驗證碼
    String redisCodeValue = VerifyCodeUtils.generateSmsCode();
    //驗證碼類型這是根據(jù)同事給的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格式的參數(shù)
    JSONObject jsonObject = new JSONObject();
    jsonObject.put(WebServiceConstants.CODE, redisCodeValue);
    Map<String, String> resultMap = SMSUtils.sendMessage(loginBody.getUserName(),templateCode,jsonObject);
    //判斷webservice接口返回的結(jié)果
    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設(shè)置過期時間,這里設(shè)置了60s,根據(jù)需求來
    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,創(chuàng)建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();
}

前端代碼

這里只粘貼了發(fā)送驗證碼改變按鈕的方法

sendCode(type) {
  this.$refs.registerForm.validateField('phone',(phoneError)=> {
    if(!phoneError){
      this.registerForm.verificationCodeType = type
      //短信驗證碼最大請求次數(shù)校驗
      getSmsCode(this.registerForm).then(response => {
        if (response.code !== 200) {
          this.requestMax = true
        } else {
          this.msgSuccess('發(fā)送成功,請注意查收短信')
          this.requestMax = false
        }
        //發(fā)送驗證碼按鈕修改
        if (!this.requestMax) {
          let time = 60
          this.buttonText = '已發(fā)送'
          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如何調(diào)用?

一開始導(dǎo)了很多關(guān)于webservice的相關(guān)依賴,結(jié)果掉不通沒辦法只能用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.不能讓用戶無限制的請求發(fā)送驗證碼

據(jù)說短信平臺有驗證邏輯,為了安全還是給系統(tǒng)封了一層;這里通過注解,aop配合redis計數(shù)器進(jìn)行最大請求次數(shù)驗證。

代碼如下

注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckRequestTimes {
    /**
     * 最大請求次數(shù)
     */
    String maxTimes() default "10";
    /**
     * 整個系統(tǒng)最大請求次數(shù)
     */
    String maxSystermTimes() default "1000";
    /**
     * 請求類型
     */
    RequestEnums reqType() default RequestEnums.COMMON;
    /**
     * 請求次數(shù)上限錯誤信息提示
     */
    String errorMsg() default TipsConstants.REQUEST_TIMES_MAX
}

Aspect

這部分代碼我個人認(rèn)為設(shè)計比較巧妙,可供讀者思考,多利用設(shè)計模式思想去開發(fā)代碼,讓代碼更優(yōu)雅、更健壯、更可用,crud也有編出自己的骨氣?。。?本實例涵蓋了單例,模板方法)

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

? ? @Autowired
? ? RedisService redisService;
? ? @Autowired
? ? TokenService tokenService;

? ? private static Logger logger = LoggerFactory.getLogger(CheckRequestAspect.class);
? ? //防止并發(fā),添加關(guān)鍵字實現(xiàn)共享
? ? private volatile ConcurrentHashMap<RequestEnums, RequestTimesAbstract> reqTimesProcessMap;
? ??
? ? @PostConstruct
? ? public void initExcelProcessorFactory() {
? ? ? ? //dcl 雙重檢查鎖,也可進(jìn)行懶散加載。因為現(xiàn)在基于spring容器單例,此鎖可適當(dāng)調(diào)整
? ? ? ? if (MapUtil.isNotEmpty(reqTimesProcessMap)) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? //眼熟不這叫懶漢式單例
? ? ? ? synchronized (this) {
? ? ? ? ? ? if (ObjectUtil.isNull(reqTimesProcessMap)) {
? ? ? ? ? ? ? ? reqTimesProcessMap = new ConcurrentHashMap(8);
? ? ? ? ? ? }
? ? ? ? ? ? //這里其實可以采用工廠方法去改造,由于業(yè)務(wù)沒有太多類型所以就不設(shè)計工廠了
? ? ? ? ? ? reqTimesProcessMap.put(RequestEnums.COMMON, new UserCommReqTimes());
? ? ? ? ? ? reqTimesProcessMap.put(RequestEnums.SMS, new SMSCodeReqTimes());
? ? ? ? }
? ? }
? ? /**
? ? ?* 切入點
? ? ?*/
? ? @Pointcut("@annotation(com.fuwai.hr.common.annotation.CheckRequestTimes)")
? ? public void checkPoint() {

? ? }
? ? /**
? ? ?* 環(huán)繞獲取請求參數(shù)
? ? ?*
? ? ?* @param proceedingJoinPoint
? ? ?* @return
? ? ?*/
? ? @Around("checkPoint()")
? ? public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) {
? ? ? ? //獲取方法上的注解
? ? ? ? CheckRequestTimes checkRequestTimes = getAnnotation(proceedingJoinPoint);
? ? ? ? Object[] args = proceedingJoinPoint.getArgs();
? ? ? ? //判斷是否到達(dá)最大請求次數(shù),這里為了應(yīng)對不同請求類型的處理方式寫了一個抽象類,
? ? ? ? //便于擴展維護(hù),沿用了了模板方法設(shè)計模式的思想
? ? ? ? if(!reqTimesProcessMap.get(checkRequestTimes.reqType()).judgeMaxTimes(args, checkRequestTimes, redisService)){
? ? ? ? ? ? return AjaxResult.error(HttpStatus.REQUEST_MAX, checkRequestTimes.errorMsg());
? ? ? ? }
? ? ? ? //執(zhí)行請求方法
? ? ? ? Object proceed = null;
? ? ? ? try {
? ? ? ? ? ? proceed = proceedingJoinPoint.proceed();
? ? ? ? } catch (Throwable throwable) {
? ? ? ? ? ? logger.error(throwable.getMessage(), throwable);
? ? ? ? }
? ? ? ? return proceed;
? ? }
? ? /**
? ? ?* 獲取方法上的注解以便拿到對應(yīng)的值
? ? ?*
? ? ?* @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 {
    /**
     * 判斷是否到達(dá)請求最大次數(shù)
     * @param object 參數(shù)
     * @param checkRequestTimes  注解
     * @param redisService   redis服務(wù)
     * @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;
        //本地只有一個服務(wù)器,拼接一個ip的key;如果是分布式這種方式就不太可取了根據(jù)需求來吧
        StringBuilder ip = new StringBuilder();
        ip.append(Constants.RECRUIT_CODE_TIMES_KEY).append(LocalHostUtil.getLocalIp()).append(Constants.DELIVERY).append(Constants.NUM);
        //判斷本地系統(tǒng)的最大請求方式和用戶的請求次數(shù)
        if (StringUtils.isNotEmpty(ip) && StringUtils.isNotEmpty(phone)) {
            return redisService.judgeMaxRequestTimes(ip.toString(), checkRequestTimes.maxSystermTimes()) && redisService.judgeMaxRequestTimes(phone, checkRequestTimes.maxTimes());
        }
        return false;
    }
}

RedisService判斷請求方法

這里實現(xiàn)了一簡單redis計數(shù)器自己隨手寫的也不知道對不對;rediscache封裝的redis一些操作

/**
 * 判斷最大請求次數(shù)
 *
 * @param key 緩存對象key鍵
 * @param max 最大請求次數(shù)
 * @return
 */
@Override
public Boolean judgeMaxRequestTimes(String key, String max) {
    //獲取key值,值為null插入值
    //不為null進(jìn)行,判斷是否到最大值,更新數(shù)值
    String value = redisCache.getCacheObject(key);
    if (StringUtils.isEmpty(value)) {
        //key存在的話不對齊進(jìn)行操作,存在的話就他設(shè)置值
        redisCache.setIfAbsent(key, RecruitNumberConstants.NUMBER_1.toString(), RecruitNumberConstants.NUMBER_24, TimeUnit.HOURS);
        return true;
    }
    //最大次數(shù) <= 當(dāng)前訪問次數(shù)
    if (Integer.valueOf(max).compareTo(Integer.valueOf(value)) <= RecruitNumberConstants.NUMBER_0) {
        return false;
    }
    //這里獲取的是當(dāng)前key的過期時間
    //(因為這邊更新值的話,更新要不得設(shè)置過期時間要不不設(shè)置更新那ttl就變成了永久的了
    //兩種方案都不合理那就只能獲取他當(dāng)前的剩余時間去更新了)
    Long expire = redisCache.getExpire(key);
    //key存在的話對其進(jìn)行更新,不存在不對其進(jìn)行操作
    return redisCache.setIfPresent(key, String.valueOf(Integer.parseInt(value) + RecruitNumberConstants.NUMBER_1), expire, TimeUnit.SECONDS);
}

如何改進(jìn)

個人感覺這應(yīng)該是不支持并發(fā)的,關(guān)于計數(shù)的操作可以用原子類去操作;我感覺我寫的這玩意分布式估計也支持不了,有時間自己搭個環(huán)境再驗證吧,懶得搞了。

到此這篇關(guān)于Java實現(xiàn)短信驗證碼的示例代碼的文章就介紹到這了,更多相關(guān)Java 短信驗證碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論