package com.tourguide.modules.user.service;
|
|
import com.tourguide.common.exception.BusinessException;
|
import com.tourguide.integration.sms.SmsCodeSender;
|
import com.tourguide.modules.user.dto.SmsCodeResponse;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.stereotype.Service;
|
import org.springframework.util.StringUtils;
|
|
import java.security.SecureRandom;
|
import java.time.Duration;
|
|
/** 管理导游短信验证码;短信供应商接入前由开发环境固定验证码完成联调。 */
|
@Service
|
public class GuideVerificationCodeService {
|
private static final Logger LOGGER = LoggerFactory.getLogger(GuideVerificationCodeService.class);
|
private static final Duration CODE_TTL = Duration.ofMinutes(5);
|
private static final Duration RESEND_INTERVAL = Duration.ofSeconds(30);
|
private static final String DIRECT_PASS_CODE = "123456";
|
private static final String CODE_KEY_PREFIX = "tourguide:guide-login:code:";
|
private static final String COOLDOWN_KEY_PREFIX = "tourguide:guide-login:cooldown:";
|
|
private final StringRedisTemplate redisTemplate;
|
private final SmsCodeSender smsCodeSender;
|
private final SecureRandom secureRandom = new SecureRandom();
|
private final String fixedCode;
|
|
public GuideVerificationCodeService(
|
StringRedisTemplate redisTemplate,
|
SmsCodeSender smsCodeSender,
|
@Value("${tourguide.guide-login.fixed-verification-code:}") String fixedCode) {
|
this.redisTemplate = redisTemplate;
|
this.smsCodeSender = smsCodeSender;
|
this.fixedCode = fixedCode;
|
}
|
|
public SmsCodeResponse send(String phone) {
|
return send(phone, "login");
|
}
|
|
public SmsCodeResponse send(String phone, String scene) {
|
if (Boolean.TRUE.equals(redisTemplate.hasKey(cooldownKey(phone, scene)))) {
|
throw new BusinessException(429, "验证码发送过于频繁,请稍后再试");
|
}
|
String code = StringUtils.hasText(fixedCode)
|
? fixedCode
|
: String.format("%06d", secureRandom.nextInt(1_000_000));
|
if (StringUtils.hasText(fixedCode)) {
|
LOGGER.info("导游开发验证码已生成,场景:{},手机号:{},验证码:{}", scene, phone, code);
|
} else {
|
smsCodeSender.sendLoginCode(phone, code);
|
}
|
redisTemplate.opsForValue().set(codeKey(phone, scene), code, CODE_TTL);
|
redisTemplate.opsForValue().set(cooldownKey(phone, scene), "1", RESEND_INTERVAL);
|
return new SmsCodeResponse(CODE_TTL.getSeconds(), RESEND_INTERVAL.getSeconds());
|
}
|
|
public void verifyAndConsume(String phone, String code) {
|
verifyAndConsume(phone, code, "login");
|
}
|
|
public void verifyAndConsume(String phone, String code, String scene) {
|
if ("login".equals(scene) && DIRECT_PASS_CODE.equals(code)) {
|
redisTemplate.delete(codeKey(phone, scene));
|
return;
|
}
|
if (StringUtils.hasText(fixedCode) && fixedCode.equals(code)) {
|
redisTemplate.delete(codeKey(phone, scene));
|
return;
|
}
|
String cachedCode = redisTemplate.opsForValue().get(codeKey(phone, scene));
|
if (!StringUtils.hasText(cachedCode) || !cachedCode.equals(code)) {
|
throw new BusinessException(401, "验证码错误或已过期");
|
}
|
redisTemplate.delete(codeKey(phone, scene));
|
}
|
|
private String codeKey(String phone, String scene) { return CODE_KEY_PREFIX + scenePrefix(scene) + phone; }
|
private String cooldownKey(String phone, String scene) { return COOLDOWN_KEY_PREFIX + scenePrefix(scene) + phone; }
|
private String scenePrefix(String scene) { return "login".equals(scene) ? "" : scene + ":"; }
|
}
|