大家好,我是孙嵓,短信验证码相信大家都不陌生吗,但是短信验证码怎么生成的你真的了解吗,本文揭示本人项目中对短信验证码的。
项目需求
用户注册/忘记密码添加短信验证码
需求来由
登录注册页面需要确保用户同一个手机号只关联一个账号确保非人为操作,避免系统用户信息紊乱增加系统安全性
代码实现
同事提供了WebService接口,很好,之前没调过,又增加了困难。
这边用的阿里云的短信服务,废话少说上图,呸,上代码—
发送验证码方法
public AjaxResult sendVerificationCode(LoginBody loginBody) {
String redisCodeKey = Constants.RECRUIT_CODE_KEY + loginBody.getUserName();
if (redisCache.getExpire(redisCodeKey) >= 0) {
return AjaxResult.error(TipsConstants.YZM_SEND_ALREADY);
}
String redisCodeValue = VerifyCodeUtils.generateSmsCode();
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;
}
JSONObject jsonObject = new JSONObject();
jsonObject.put(WebServiceConstants.CODE, redisCodeValue);
Map<String, String> resultMap = SMSUtils.sendMessage(loginBody.getUserName(),templateCode,jsonObject);
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);
}
redisCache.setCacheObject(redisCodeKey, redisCodeValue, 60, TimeUnit.SECONDS);
return AjaxResult.success();
}
注册方法
public AjaxResult register(LoginBody loginBody) {
String redisCodeKey = Constants.RECRUIT_CODE_KEY + loginBody.getUserName();
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);
}
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) {
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() {
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() {
}
@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;
}
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 {
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;
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一些操作
@Override
public Boolean judgeMaxRequestTimes(String key, String max) {
String value = redisCache.getCacheObject(key);
if (StringUtils.isEmpty(value)) {
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;
}
Long expire = redisCache.getExpire(key);
return redisCache.setIfPresent(key, String.valueOf(Integer.parseInt(value) + RecruitNumberConstants.NUMBER_1), expire, TimeUnit.SECONDS);
}
如何改进
个人感觉这应该是不支持并发的,关于计数的操作可以用原子类去操作;我感觉我写的这玩意分布式估计也支持不了,有时间自己搭个环境再验证吧,懒得搞了。
以上就是本文的全部内容了,能力有限,理性对待
如果感觉还不错的话,欢迎点赞和关注🦋
分享经验,贴近项目,crud永不为奴!!!
|