一、需求
1. 每条验证码发送间隔最小为 1分钟
2. 每个手机号每天最多发送三条验证码
3. 验证码 5 分钟内有效
4. 次日 00:00 恢复发送
二、实现思路
1. 发送前验证手机号是否符合要求
2. 生成短信验证码
3. 发送验证码到手机
三、代码实现
1. Controller
private final String MOBILE_REGEX = "^((13[0-9])|(14[0-9])|(15[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))\\d{8}$";
@PostMapping(value = "sms")
public GeneralResult code(@RequestParam("mobile") String mobile){
//region 格式验证
if(mobile.length() != 11){
return GeneralResult.ERROR("手机号格式错误");
} else {
Pattern pattern = Pattern.compile(MOBILE_REGEX);
Matcher matcher = pattern.matcher(mobile);
if(!matcher.matches()) {
return GeneralResult.ERROR("手机号格式错误");
}
}
//endregion
try{
// 发送验证码
messageService.sendAuthCode(mobile);
return GeneralResult.SUCCESS("发送成功");
} catch (Exception ex) {
ex.printStackTrace();
return GeneralResult.ERROR(ex.getMessage());
}
}
2. Service
/**
* 手机号验证
* @param mobile
* @return
*/
private Map<String, Object> validateMobile(final String mobile){
Map<String, Object> resultMap = new HashMap<>();
// 验证黑名单
if(inBlacklist(mobile)) {
resultMap.put("ok", false);
resultMap.put("msg", "手机号已被限制");
return resultMap;
}
// 是否首次
if(!redis.hasKey(mobile)) {
resultMap.put("ok", true);
resultMap.put("msg", "首次发送他验证码");
return resultMap;
}
// 验证发送次数
BoundHashOperations<String, Object, Object> hashObj = redis.boundHashOps(mobile + SMS_RECORD_SUFFIX_KEY);
Integer count = Integer.valueOf(hashObj.get("count").toString());
if(count >= MAX_COUNT) {
resultMap.put("ok", false);
resultMap.put("msg", "当日发送已达上限");
return resultMap;
}
// 验证发送时间
Long lastTime = Long.valueOf((String) hashObj.get("last_time"));
long seconds = (System.currentTimeMillis() - lastTime) / 1000;
if(seconds > SEND_INTERVAL) {
resultMap.put("ok", true);
} else {
resultMap.put("ok", false);
resultMap.put("msg", "不满足间隔时间");
}
return resultMap;
}
/**
* 生成六位验证码
* @return
*/
private String generateCode(){
Random random = new Random();
int x = random.nextInt(899999);
String code = String.valueOf(x + 100000);
return code;
}
/**
* 写入缓存
* @param mobile
* @param code
*/
private void writeCache(String mobile, String code){
BoundValueOperations<String, String> sms = redis.boundValueOps(SMS_PREFIX_KEY + mobile);
sms.set(code, 300, TimeUnit.SECONDS);
BoundHashOperations<String, Object, Object> hashObj = redis.boundHashOps(mobile + SMS_RECORD_SUFFIX_KEY);
hashObj.put("last_time", String.valueOf(System.currentTimeMillis()));
if(!hashObj.hasKey("count")) {
hashObj.put("count", BigDecimal.ZERO.toString());
}
hashObj.increment("count", 1);
if(hashObj.getExpire() == -1) {
hashObj.expireAt(getExpireTime());
}
}
/**
* 获取缓存过期时间
* @return
*/
private Date getExpireTime(){
LocalDateTime expireTime = LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
Instant instant = expireTime.atZone(ZoneId.systemDefault()).toInstant();
return Date.from(instant);
}
// 执行发送
@Override
public void sendAuthCode(String mobile) throws Exception {
//region 验证手机号
Map<String, Object> result = validateMobile(mobile);
if((Boolean) result.get("ok") == false) {
throw new RuntimeException((String) result.get("msg"));
}
//endregion
// 生成验证码
String code = generateCode();
// 调用发送接口
SMSUtil.SendCode(mobile, code);
System.out.println("短信发送成功");
// 写入缓存中
writeCache(mobile, code);
}
四、总结
主要用到了 Redis 中的过期清除的功能和两个数据类型分别为 Hash 和 String。String 类型用于存储发送的验证码并设置清除时间为 300s ,也就是说如果验证码在 300秒之内没有被验证,那么超过这个时间 验证码就不存在了,需要从新生成。
Hash 类型主要用来记录短信的发送情况,分别为: 上一次发送时间(last_time)、发送次数(count) 等。 同时类型本身也设定了超时时间为次日的 00:00, 这样就保障了短信隔天正常发送。
来源:CSDN
作者:kk380446
链接:https://blog.csdn.net/kk380446/article/details/93993098