加锁防止短信重复发送
背景
短信验证码登录场景,限制验证码只能在 1min
内获取一次,防止重复点击导致验证码重复发送
依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
</dependency>
配置Bean
@ConditionalOnBean(SmsProperties.class)
@Bean
public Client smsClient(SmsProperties smsProperties) {
Config config = new Config()
.setAccessKeyId(smsProperties.getAccessKeyId())
.setAccessKeySecret(smsProperties.getAccessKeySecret());
return new Client(config);
}
@ConditionalOnBean(RedissonProperties.class)
@Bean
public RedissonClient redissonClient(RedissonProperties redissonProperties) {
Config config = new Config();
if (redissonProperties.isSingle()) {
config.useSingleServer()
.setDatabase(redissonProperties.getDatabase())
.setPassword(redissonProperties.getPassword())
.setAddress(redissonProperties.getAddress());
} else { / ... }
return Redisson.create(config);
}
发送短信方法
这里加锁防止其他客户端发送同种业务的短信,可能包括发送不成功的短信
@Override
public void sendSms(SmsRecord smsRecord) {
SendSmsRequest sendSmsRequest = new SendSmsRequest();
BeanUtils.copyProperties(smsRecord, sendSmsRequest);
RLock lock = redissonClient.getLock(String.format(SEND_LOCK_KEY, smsRecord.getBiz(), smsRecord.getPhoneNumbers()));
try {
lock.lock();
SendSmsResponse sendSmsResponse = smsClient.sendSms(sendSmsRequest);
SendSmsResponseBody body;
if (null == (body = sendSmsResponse.body)) { return; }
// BizId RequestId
smsRecord.setStatus(body.code);
} catch (Exception e) {
smsRecord.setStatus(CommonConstants.FAIL);
log.error("发送失败:{}", e.getMessage(), e);
} finally {
lock.unlock();
}
}
Demo
1、发送短信验证码
先判断是否在限制的时间内发送过验证码,再异步发送短信,发送失败可以设置定时器扫表重发(有效时间内)或者忽略。
@Override
public void sendVerifyCode(SmsBo smsBo) {
// 判断是否已发送
String limitKey = String.format(LIMIT_KEY, smsBo.getBiz(), smsBo.getPhoneNumbers());
if (Boolean.TRUE.equals(redisTemplate.hasKey(limitKey))) {
log.error("限制时间内已发送过验证码,{}, {}", smsBo.getBiz(), smsBo.getPhoneNumbers());
throw new CustomException(ErrorCode.CANNOT_SEND_SMS_REPEATED);
}
// 发送验证码
// 这里加锁防止重复点击
RLock lock = redissonClient.getLock(String.format(LIMIT_KEY, smsBo.getBiz(), limitKey));
try {
lock.lock();
// 判断是否已发送
if (Boolean.TRUE.equals(redisTemplate.hasKey(limitKey))) {
log.error("限制时间内已发送过验证码,{}, {}", smsBo.getBiz(), smsBo.getPhoneNumbers());
throw new CustomException(ErrorCode.CANNOT_SEND_SMS_REPEATED);
}
// 记录已发送 (假设限制 60s 内只能发送一次)
redisTemplate.opsForValue().set(limitKey, true, 60, TimeUnit.SECONDS);
} finally {
lock.unlock();
}
// 异步去发送
commonExecutorService.execute(() -> {
// 生成验证码
String code = RandomUtil.randomString(RANDOM_STR, RANDOM_LEN);
// 记录验证码
redisTemplate.opsForValue().set(String.format(VERIFY_CODE_KEY, smsBo.getBiz(), smsBo.getPhoneNumbers()), code, EXPIRED_SECOND, TimeUnit.SECONDS);
SmsRecord smsRecord = new SmsRecord();
BeanUtils.copyProperties(smsBo, smsRecord);
// 发送验证码
sendSms(smsRecord);
// 保存发送记录
smsRecordService.insert(smsRecord);
});
}
2、验证
@Override
public void verifyCode(SmsBo smsBo, String code) {
String key = String.format(VERIFY_CODE_KEY, smsBo.getBiz(), smsBo.getPhoneNumbers());
if (!Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
throw new RuntimeException("验证码无效");
}
if (!Objects.equals(code, redisTemplate.opsForValue().get(key))) {
throw new RuntimeException("验证码错误");
}
redisTemplate.delete(key);
}
原文地址:https://blog.csdn.net/kuun993/article/details/140576514
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!