package com.tcm.training.admin.app.auth.service;
|
|
import com.tcm.training.admin.app.auth.config.AppSmsAuthProperties;
|
import com.tcm.training.admin.app.auth.vo.AppSmsCodeSendResponse;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.stereotype.Service;
|
|
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.TimeUnit;
|
|
/**
|
* APP 登录短信验证码服务,负责验证码的生成、缓存和校验。
|
*/
|
@Service
|
public class AppSmsCodeService {
|
|
private static final String LOGIN_CODE_CACHE_PREFIX = "app:sms:login:";
|
private static final String REGISTER_CODE_CACHE_PREFIX = "app:sms:register:";
|
private static final String PASSWORD_RESET_CODE_CACHE_PREFIX = "app:sms:password-reset:";
|
|
private final StringRedisTemplate stringRedisTemplate;
|
private final AppSmsSender appSmsSender;
|
private final AppSmsAuthProperties appSmsAuthProperties;
|
|
/**
|
* 注入验证码缓存、发送器与调试配置。
|
*/
|
public AppSmsCodeService(StringRedisTemplate stringRedisTemplate,
|
AppSmsSender appSmsSender,
|
AppSmsAuthProperties appSmsAuthProperties) {
|
this.stringRedisTemplate = stringRedisTemplate;
|
this.appSmsSender = appSmsSender;
|
this.appSmsAuthProperties = appSmsAuthProperties;
|
}
|
|
/**
|
* 生成并发送登录验证码,同时写入缓存。
|
*/
|
public AppSmsCodeSendResponse createLoginCode(String phone) {
|
return createCode(phone, LOGIN_CODE_CACHE_PREFIX, "login");
|
}
|
|
/**
|
* 生成并发送注册验证码,同时写入缓存。
|
*/
|
public AppSmsCodeSendResponse createRegisterCode(String phone) {
|
return createCode(phone, REGISTER_CODE_CACHE_PREFIX, "register");
|
}
|
|
/**
|
* 生成并发送忘记密码验证码,同时写入缓存。
|
*/
|
public AppSmsCodeSendResponse createPasswordResetCode(String phone) {
|
return createCode(phone, PASSWORD_RESET_CODE_CACHE_PREFIX, "password-reset");
|
}
|
|
/**
|
* 校验登录验证码,成功后销毁缓存中的一次性验证码。
|
*/
|
public void validateLoginCode(String phone, String code) {
|
validateCode(phone, code, LOGIN_CODE_CACHE_PREFIX);
|
}
|
|
/**
|
* 校验注册验证码,成功后销毁缓存中的一次性验证码。
|
*/
|
public void validateRegisterCode(String phone, String code) {
|
validateCode(phone, code, REGISTER_CODE_CACHE_PREFIX);
|
}
|
|
/**
|
* 校验忘记密码验证码,成功后销毁缓存中的一次性验证码。
|
*/
|
public void validatePasswordResetCode(String phone, String code) {
|
validateCode(phone, code, PASSWORD_RESET_CODE_CACHE_PREFIX);
|
}
|
|
/**
|
* 统一生成指定场景的验证码并写入缓存。
|
*/
|
private AppSmsCodeSendResponse createCode(String phone, String cachePrefix, String scene) {
|
String code = generateCode();
|
stringRedisTemplate.opsForValue().set(
|
buildCacheKey(cachePrefix, phone),
|
code,
|
appSmsAuthProperties.getExpireSeconds(),
|
TimeUnit.SECONDS
|
);
|
if ("login".equals(scene)) {
|
appSmsSender.sendLoginCode(phone, code);
|
} else if ("register".equals(scene)) {
|
appSmsSender.sendRegisterCode(phone, code);
|
} else {
|
appSmsSender.sendPasswordResetCode(phone, code);
|
}
|
return new AppSmsCodeSendResponse(appSmsAuthProperties.getExpireSeconds());
|
}
|
|
/**
|
* 统一校验指定场景的验证码并执行一次性销毁。
|
*/
|
private void validateCode(String phone, String code, String cachePrefix) {
|
if (matchesDebugCode(code)) {
|
// 开发环境允许固定验证码直通,便于前后端联调。
|
return;
|
}
|
String cacheKey = buildCacheKey(cachePrefix, phone);
|
String cachedCode = stringRedisTemplate.opsForValue().get(cacheKey);
|
if (cachedCode == null || cachedCode.trim().isEmpty()) {
|
throw new IllegalArgumentException("验证码已失效,请重新获取");
|
}
|
if (!cachedCode.equals(code)) {
|
throw new IllegalArgumentException("验证码错误");
|
}
|
// 验证码登录成功后立即删除,避免重复使用。
|
stringRedisTemplate.delete(cacheKey);
|
}
|
|
/**
|
* 生成 6 位数字验证码。
|
*/
|
private String generateCode() {
|
int code = ThreadLocalRandom.current().nextInt(100000, 1000000);
|
return String.valueOf(code);
|
}
|
|
/**
|
* 判断是否命中本地调试验证码。
|
*/
|
private boolean matchesDebugCode(String code) {
|
if (!appSmsAuthProperties.isDebugEnabled()) {
|
return false;
|
}
|
String debugCode = appSmsAuthProperties.getDebugCode();
|
return debugCode != null && !debugCode.trim().isEmpty() && debugCode.equals(code);
|
}
|
|
/**
|
* 构造手机号维度的验证码缓存 key。
|
*/
|
private String buildCacheKey(String cachePrefix, String phone) {
|
return cachePrefix + phone;
|
}
|
}
|