package com.xjhs.findmemerchant.security.sms; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.time.Duration; import java.util.Random; @Slf4j @Service @RequiredArgsConstructor public class SmsCodeService { /** Redis key 前缀:sms:code:{scene}:{phone} */ private static final String SMS_CODE_KEY_PREFIX = "sms:code:"; /** 验证码有效期:5 分钟 */ private static final Duration SMS_CODE_TTL = Duration.ofMinutes(5); private final StringRedisTemplate redisTemplate; private final Random random = new Random(); private Object smsSender; // 你自己的短信通道接口 private String buildKey(String phone, String scene) { return SMS_CODE_KEY_PREFIX + scene + ":" + phone; } /** * 生成 6 位数字验证码 */ private String generateCode() { return String.format("%06d", random.nextInt(1_000_000)); } /** * 发送短信验证码:生成 -> 存 Redis -> 调短信通道发出去 */ public void sendVerificationCode(String phone, String scene) { var code = generateCode(); var key = buildKey(phone, scene); // 存到 Redis,设置 TTL redisTemplate.opsForValue().set(key, code, SMS_CODE_TTL); // TODO:调用你自己的短信通道(阿里云、腾讯云等) // smsSender.sendSmsCode(phone, scene, code); // 开发阶段也可以打印一下方便调试 log.debug("send sms code, phone={},scene={},code={}", phone,scene, code); } /** * 校验验证码: * - key 不存在 => 过期 / 未发送 => 抛 SmsCodeExpiredException * - code 不匹配 => 抛 SmsCodeInvalidException * - 匹配 => 删除 key(一次性) */ public void verifyCode(String phone, String scene, String inputCode) throws Exception { String key = buildKey(phone, scene); String realCode = redisTemplate.opsForValue().get(key); if (realCode == null) { // 对齐 Go 里的 ErrSMSCodeExpired throw new Exception("验证码已过期或未发送"); } if (!realCode.equals(inputCode)) { // 不正确,但不删除,让用户继续尝试(错误次数由 AuthService 控制) throw new Exception("验证码错误"); } // 验证通过,删除验证码(一次性) redisTemplate.delete(key); } }