Appearance
无论是采用邮箱验证码还是短信验证码,都需要防止用户进行盗刷。手机验证码厂商会额外产生费用,而邮箱验证码虽然无需额外收费,但是被大量盗刷,带宽、连接等都被占用,仍会导致系统无法正常使用。
需求分析
验证码5分钟过期,一分钟内不准重复发送。
我们对Redis的Key和Value进行特殊处理来实现防刷设计,使用阿里云发送短信可查看使用阿里云短信服务发送验证码
- Key采用验证码类型加上邮箱地址/手机号码组成,例如register_code_1919301983@qq.com,
- register_code代表该验证码类型是注册验证码
- 1919301983@qq.com是邮箱地址,如果是短信验证码则改成手机号码
- 设置过期时间为5分钟,代表该验证码5分钟内有效
- Value由验证码加上当前系统时间的毫秒值组成,例如0025_1660530518000
- 系统根据key获取到value时,使用split根据"_"进行字符串截取得到一个数组,下标为0的就是验证码
- 下标为1的就是发送验证码的时间,我们使用当前系统时间的毫秒值减去发送验证码的毫秒值,如果在1分钟内,则提醒用户N秒后重新发送验证码
- 时间方面都可以根据自己的业务进行设计
发送验证码实战
java
public boolean sendRegisterCode(String mail) {
// 1.校验邮箱格式
boolean isEmail = CommonUtils.isEmail(mail);
if (!isEmail) throw new DefaultException("请输入正确的邮箱格式");
// 2.查看邮箱是否注册
Boolean isMember = redisTemplate.opsForSet().isMember(CacheKey.MALL_KEY, mail);
if (isMember) throw new DefaultException("该邮箱已注册");
// 3.获取验证码的Key
String cacheCodeKey = getCacheCode(CodeEnum.REGISTER, mail);
// 4.从Redis获取验证码,如果不为空判断是否大于一分钟,如果没有则生成验证码并存储到Redis
String cacheCodeValue = (String) redisTemplate.opsForValue().get(cacheCodeKey);
if (StringUtils.isNotBlank(cacheCodeValue)) {
// 4.1 获取时间戳,判断是否大于0
long ttl = Long.parseLong(cacheCodeValue.split("_")[1]);
if (System.currentTimeMillis() - ttl < 1000 * 60) {
log.info("重复发送验证码,时间间隔:{} 秒", (60 - ((System.currentTimeMillis() - ttl) / 1000)));
throw new DefaultException("请" + (60 - ((System.currentTimeMillis() - ttl) / 1000)) + "s后发送验证码");
}
}
// 5.生成验证码,验证码由:验证码_毫秒值
String code = RandomUtil.randomNumbers(6) + "_" + System.currentTimeMillis();
// 6.存储验证码
redisTemplate.opsForValue().set(cacheCodeKey, code, 5, TimeUnit.MINUTES);
// 7.发送验证码
MailUtil.send(mail, "星空航班-用户注册验证码", String.format(CacheKey.MALL_CODE_HTML, "注册", code.split("_")[0], code.split("_")[0]), true);
return true;
}
验证验证码是否正确
java
public boolean mailRegister(RegisterParam param) {
// 1.获取验证码的Key
String cacheCodeKey = getCacheCode(CodeEnum.REGISTER, param.getMail());
// 2.查询验证码
String cacheCodeValue = (String) redisTemplate.opsForValue().get(cacheCodeKey);
// 3.如果没有找到验证码,代表验证码以及过期了
if (StringUtils.isBlank(cacheCodeValue)) throw new DefaultException("验证码有误,请重新输入");
// 4.判断验证码是否正确
if (!param.getCode().equals(cacheCodeValue.split("_")[0])) throw new DefaultException("验证码有误,请重新输入");
// TODO 业务逻辑
return true;
}