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 SCENE_LOGIN = "login";
|
private static final String SCENE_REGISTER = "register";
|
private static final String SCENE_PASSWORD_RESET = "password-reset";
|
private static final String SCENE_PHONE_CHANGE = "phone-change";
|
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 static final String PHONE_CHANGE_CODE_CACHE_PREFIX = "app:sms:phone-change:";
|
|
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 AppSmsCodeSendResponse createPhoneChangeCode(String phone) {
|
return createCode(phone, PHONE_CHANGE_CODE_CACHE_PREFIX, "phone-change");
|
}
|
|
/**
|
* 校验登录验证码,缓存有效期内允许重复提交。
|
*/
|
public void validateLoginCode(String phone, String code) {
|
validateCode(phone, code, LOGIN_CODE_CACHE_PREFIX, SCENE_LOGIN);
|
}
|
|
/**
|
* 校验注册验证码,缓存有效期内允许重复提交。
|
*/
|
public void validateRegisterCode(String phone, String code) {
|
validateCode(phone, code, REGISTER_CODE_CACHE_PREFIX, SCENE_REGISTER);
|
}
|
|
/**
|
* 校验忘记密码验证码,缓存有效期内允许重复提交。
|
*/
|
public void validatePasswordResetCode(String phone, String code) {
|
validateCode(phone, code, PASSWORD_RESET_CODE_CACHE_PREFIX, SCENE_PASSWORD_RESET);
|
}
|
|
/**
|
* 校验修改手机号验证码,缓存有效期内允许重复提交。
|
*/
|
public void validatePhoneChangeCode(String phone, String code) {
|
validateCode(phone, code, PHONE_CHANGE_CODE_CACHE_PREFIX, SCENE_PHONE_CHANGE);
|
}
|
|
/**
|
* 统一生成指定场景的验证码并写入缓存。
|
*/
|
private AppSmsCodeSendResponse createCode(String phone, String cachePrefix, String scene) {
|
String code = generateCode();
|
if (SCENE_LOGIN.equals(scene)) {
|
appSmsSender.sendLoginCode(phone, code);
|
} else if (SCENE_REGISTER.equals(scene)) {
|
appSmsSender.sendRegisterCode(phone, code);
|
} else if (SCENE_PASSWORD_RESET.equals(scene)) {
|
appSmsSender.sendPasswordResetCode(phone, code);
|
} else {
|
appSmsSender.sendPhoneChangeCode(phone, code);
|
}
|
stringRedisTemplate.opsForValue().set(
|
buildCacheKey(cachePrefix, phone),
|
code,
|
appSmsAuthProperties.getExpireSeconds(),
|
TimeUnit.SECONDS
|
);
|
return new AppSmsCodeSendResponse(appSmsAuthProperties.getExpireSeconds());
|
}
|
|
/**
|
* 统一校验指定场景的验证码,验证码仅按缓存 TTL 自然失效。
|
*/
|
private void validateCode(String phone, String code, String cachePrefix, String scene) {
|
if (matchesDebugCode(scene, 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("验证码错误");
|
}
|
}
|
|
/**
|
* 生成 6 位数字验证码。
|
*/
|
private String generateCode() {
|
int code = ThreadLocalRandom.current().nextInt(100000, 1000000);
|
return String.valueOf(code);
|
}
|
|
/**
|
* 判断当前场景是否命中调试验证码。
|
*/
|
private boolean matchesDebugCode(String scene, String code) {
|
AppSmsAuthProperties.DebugScene debugScene = resolveDebugScene(scene);
|
if (debugScene == null || !debugScene.isEnabled()) {
|
return false;
|
}
|
String debugCode = debugScene.getCode();
|
return debugCode != null && !debugCode.trim().isEmpty() && debugCode.equals(code);
|
}
|
|
/**
|
* 读取指定场景的调试验证码配置。
|
*/
|
private AppSmsAuthProperties.DebugScene resolveDebugScene(String scene) {
|
if (SCENE_LOGIN.equals(scene)) {
|
return appSmsAuthProperties.getLogin();
|
}
|
if (SCENE_REGISTER.equals(scene)) {
|
return appSmsAuthProperties.getRegister();
|
}
|
if (SCENE_PASSWORD_RESET.equals(scene)) {
|
return appSmsAuthProperties.getPasswordReset();
|
}
|
if (SCENE_PHONE_CHANGE.equals(scene)) {
|
return appSmsAuthProperties.getPhoneChange();
|
}
|
return null;
|
}
|
|
/**
|
* 构造手机号维度的验证码缓存 key。
|
*/
|
private String buildCacheKey(String cachePrefix, String phone) {
|
return cachePrefix + phone;
|
}
|
}
|