hejianhao
2026-08-28 16a57a8fc6868cea9171a1119698572407a2fc03
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/auth/service/AppSmsCodeService.java
@@ -14,7 +14,14 @@
@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;
@@ -35,26 +42,90 @@
     * 生成并发送登录验证码,同时写入缓存。
     */
    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(phone),
                buildCacheKey(cachePrefix, phone),
                code,
                appSmsAuthProperties.getExpireSeconds(),
                TimeUnit.SECONDS
        );
        appSmsSender.sendLoginCode(phone, code);
        return new AppSmsCodeSendResponse(appSmsAuthProperties.getExpireSeconds());
    }
    /**
     * 校验登录验证码,成功后销毁缓存中的一次性验证码。
     * 统一校验指定场景的验证码,验证码仅按缓存 TTL 自然失效。
     */
    public void validateLoginCode(String phone, String code) {
        if (matchesDebugCode(code)) {
            // 开发环境允许固定验证码直通,便于前后端联调。
    private void validateCode(String phone, String code, String cachePrefix, String scene) {
        if (matchesDebugCode(scene, code)) {
            // 调试验证码按场景控制,生产环境仅放开短信登录场景。
            return;
        }
        String cacheKey = buildCacheKey(phone);
        String cacheKey = buildCacheKey(cachePrefix, phone);
        String cachedCode = stringRedisTemplate.opsForValue().get(cacheKey);
        if (cachedCode == null || cachedCode.trim().isEmpty()) {
            throw new IllegalArgumentException("验证码已失效,请重新获取");
@@ -62,8 +133,6 @@
        if (!cachedCode.equals(code)) {
            throw new IllegalArgumentException("验证码错误");
        }
        // 验证码登录成功后立即删除,避免重复使用。
        stringRedisTemplate.delete(cacheKey);
    }
    /**
@@ -75,20 +144,40 @@
    }
    /**
     * 判断是否命中本地调试验证码。
     * 判断当前场景是否命中调试验证码。
     */
    private boolean matchesDebugCode(String code) {
        if (!appSmsAuthProperties.isDebugEnabled()) {
    private boolean matchesDebugCode(String scene, String code) {
        AppSmsAuthProperties.DebugScene debugScene = resolveDebugScene(scene);
        if (debugScene == null || !debugScene.isEnabled()) {
            return false;
        }
        String debugCode = appSmsAuthProperties.getDebugCode();
        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 phone) {
        return LOGIN_CODE_CACHE_PREFIX + phone;
    private String buildCacheKey(String cachePrefix, String phone) {
        return cachePrefix + phone;
    }
}