与dev重组登录
This commit is contained in:
+6
-4
@@ -1,5 +1,7 @@
|
||||
package cn.novalon.gym.manage.auth.service;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 短信服务接口
|
||||
*
|
||||
@@ -14,7 +16,7 @@ public interface SmsService {
|
||||
* @param phone 手机号
|
||||
* @return 发送结果
|
||||
*/
|
||||
boolean sendVerificationCode(String phone);
|
||||
Mono<Boolean> sendVerificationCode(String phone);
|
||||
|
||||
/**
|
||||
* 验证短信验证码
|
||||
@@ -23,7 +25,7 @@ public interface SmsService {
|
||||
* @param code 验证码
|
||||
* @return 验证结果
|
||||
*/
|
||||
boolean verifyCode(String phone, String code);
|
||||
Mono<Boolean> verifyCode(String phone, String code);
|
||||
|
||||
/**
|
||||
* 获取验证码(用于测试或特殊场景)
|
||||
@@ -31,5 +33,5 @@ public interface SmsService {
|
||||
* @param phone 手机号
|
||||
* @return 验证码
|
||||
*/
|
||||
String getVerificationCode(String phone);
|
||||
}
|
||||
Mono<String> getVerificationCode(String phone);
|
||||
}
|
||||
+2
-2
@@ -116,14 +116,14 @@ public class PhoneAuthServiceImpl implements PhoneAuthService {
|
||||
@Override
|
||||
public Mono<Boolean> sendSmsCode(String phone) {
|
||||
log.info("发送短信验证码, phone: {}", phone);
|
||||
return Mono.fromCallable(() -> smsService.sendVerificationCode(phone));
|
||||
return smsService.sendVerificationCode(phone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<PhoneLoginVO> codeLogin(PhoneCodeLoginDto request) {
|
||||
log.info("手机号验证码登录, phone: {}", request.getPhone());
|
||||
|
||||
return Mono.fromCallable(() -> smsService.verifyCode(request.getPhone(), request.getCode()))
|
||||
return smsService.verifyCode(request.getPhone(), request.getCode())
|
||||
.flatMap(verified -> {
|
||||
if (!verified) {
|
||||
log.warn("验证码验证失败, phone: {}", request.getPhone());
|
||||
|
||||
+151
-101
@@ -1,148 +1,198 @@
|
||||
package cn.novalon.gym.manage.auth.service.impl;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.novalon.gym.manage.auth.config.SmsProperties;
|
||||
import cn.novalon.gym.manage.auth.service.SmsService;
|
||||
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import com.aliyun.dysmsapi20170525.Client;
|
||||
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
|
||||
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
|
||||
import com.aliyun.teaopenapi.models.Config;
|
||||
import com.aliyuncs.CommonRequest;
|
||||
import com.aliyuncs.CommonResponse;
|
||||
import com.aliyuncs.DefaultAcsClient;
|
||||
import com.aliyuncs.IAcsClient;
|
||||
import com.aliyuncs.exceptions.ClientException;
|
||||
import com.aliyuncs.http.MethodType;
|
||||
import com.aliyuncs.profile.DefaultProfile;
|
||||
import com.aliyuncs.profile.IClientProfile;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
/**
|
||||
* 短信服务实现类
|
||||
*
|
||||
* @author auto-generated
|
||||
* @date 2026-06-20
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SmsServiceImpl implements SmsService {
|
||||
|
||||
private static final long SEND_INTERVAL_SECONDS = 60;
|
||||
private static final long CODE_EXPIRE_SECONDS = 300;
|
||||
|
||||
private final SmsProperties smsProperties;
|
||||
private final RedisUtil redisUtil;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public boolean sendVerificationCode(String phone) {
|
||||
public Mono<Boolean> sendVerificationCode(String phone) {
|
||||
log.info("发送短信验证码, phone: {}", phone);
|
||||
|
||||
try {
|
||||
// 生成验证码
|
||||
String code = generateCode();
|
||||
String rateLimitKey = RedisKeyConstants.SMS_CODE + phone + ":rate_limit";
|
||||
|
||||
// 发送短信
|
||||
boolean sent = sendSms(phone, code);
|
||||
return redisUtil.get(rateLimitKey, Long.class)
|
||||
.defaultIfEmpty(0L)
|
||||
.flatMap(lastSendTime -> {
|
||||
long currentTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
|
||||
|
||||
if (sent) {
|
||||
// 将验证码存入Redis,5分钟过期
|
||||
String cacheKey = RedisKeyConstants.SMS_CODE + phone;
|
||||
redisUtil.setWithExpire(cacheKey, code, smsProperties.getCodeExpireSeconds())
|
||||
.doOnSuccess(result -> log.info("验证码已缓存, phone: {}, code: {}, expire: {}s",
|
||||
phone, code, smsProperties.getCodeExpireSeconds()))
|
||||
.block();
|
||||
if (currentTime - lastSendTime < SEND_INTERVAL_SECONDS) {
|
||||
long remainingSeconds = SEND_INTERVAL_SECONDS - (currentTime - lastSendTime);
|
||||
log.warn("发送频率限制, phone: {}, 剩余时间: {}秒", phone, remainingSeconds);
|
||||
return Mono.just(false);
|
||||
}
|
||||
|
||||
log.info("短信验证码发送成功, phone: {}", phone);
|
||||
return true;
|
||||
}
|
||||
return Mono.fromCallable(() -> {
|
||||
try {
|
||||
IAcsClient client = createClient();
|
||||
|
||||
log.warn("短信验证码发送失败, phone: {}", phone);
|
||||
return false;
|
||||
CommonRequest request = new CommonRequest();
|
||||
request.setSysMethod(MethodType.POST);
|
||||
request.setSysDomain("dypnsapi.aliyuncs.com");
|
||||
request.setSysVersion("2017-05-25");
|
||||
request.setSysAction("SendSmsVerifyCode");
|
||||
request.putQueryParameter("PhoneNumber", phone);
|
||||
request.putQueryParameter("SignName", smsProperties.getSignName());
|
||||
request.putQueryParameter("TemplateCode", smsProperties.getTemplateCode());
|
||||
request.putQueryParameter("TemplateParam", "{\"code\":\"##code##\",\"min\":\"5\"}");
|
||||
request.putQueryParameter("ReturnVerifyCode", "true");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("发送短信验证码异常, phone: {}", phone, e);
|
||||
return false;
|
||||
}
|
||||
log.info("阿里云号码认证请求参数 - signName: {}, templateCode: {}, templateParam: {}",
|
||||
smsProperties.getSignName(),
|
||||
smsProperties.getTemplateCode(),
|
||||
"{\"code\":\"##code##\",\"min\":\"5\"}");
|
||||
|
||||
CommonResponse response = client.getCommonResponse(request);
|
||||
String responseData = response.getData();
|
||||
log.info("阿里云号码认证原始响应: {}", responseData);
|
||||
|
||||
JsonNode jsonNode = objectMapper.readTree(responseData);
|
||||
|
||||
JsonNode codeNode = jsonNode.get("Code");
|
||||
if (codeNode == null) {
|
||||
log.error("阿里云响应中找不到Code字段, 原始响应: {}", responseData);
|
||||
return false;
|
||||
}
|
||||
|
||||
String code = codeNode.asText();
|
||||
|
||||
if ("OK".equals(code)) {
|
||||
JsonNode requestIdNode = jsonNode.get("RequestId");
|
||||
String requestId = requestIdNode != null ? requestIdNode.asText() : "unknown";
|
||||
log.info("短信验证码发送成功, phone: {}, requestId: {}", phone, requestId);
|
||||
|
||||
// 提取验证码并存入Redis
|
||||
JsonNode modelNode = jsonNode.get("Model");
|
||||
if (modelNode != null) {
|
||||
JsonNode verifyCodeNode = modelNode.get("VerifyCode");
|
||||
if (verifyCodeNode != null) {
|
||||
String verifyCode = verifyCodeNode.asText();
|
||||
String smsCodeKey = RedisKeyConstants.SMS_CODE + phone;
|
||||
redisUtil.setWithExpire(smsCodeKey, verifyCode, CODE_EXPIRE_SECONDS).subscribe();
|
||||
log.info("验证码已存入Redis, phone: {}, key: {}, expire: {}秒", phone, smsCodeKey, CODE_EXPIRE_SECONDS);
|
||||
} else {
|
||||
log.warn("响应中未找到Model.VerifyCode字段, 原始响应: {}", responseData);
|
||||
}
|
||||
} else {
|
||||
log.warn("响应中未找到Model字段, 原始响应: {}", responseData);
|
||||
}
|
||||
|
||||
redisUtil.setWithExpire(rateLimitKey, currentTime, SEND_INTERVAL_SECONDS).subscribe();
|
||||
return true;
|
||||
}
|
||||
|
||||
JsonNode messageNode = jsonNode.get("Message");
|
||||
String message = messageNode != null ? messageNode.asText() : "unknown";
|
||||
log.error("短信验证码发送失败, phone: {}, code: {}, message: {}", phone, code, message);
|
||||
return false;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("发送短信验证码异常, phone: {}, 异常信息: {}", phone, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyCode(String phone, String code) {
|
||||
public Mono<Boolean> verifyCode(String phone, String code) {
|
||||
log.info("验证短信验证码, phone: {}", phone);
|
||||
|
||||
try {
|
||||
String cacheKey = RedisKeyConstants.SMS_CODE + phone;
|
||||
String cachedCode = redisUtil.get(cacheKey, String.class).block();
|
||||
return Mono.fromCallable(() -> {
|
||||
try {
|
||||
IAcsClient client = createClient();
|
||||
|
||||
if (cachedCode == null) {
|
||||
log.warn("验证码已过期或不存在, phone: {}", phone);
|
||||
CommonRequest request = new CommonRequest();
|
||||
request.setSysMethod(MethodType.POST);
|
||||
request.setSysDomain("dypnsapi.aliyuncs.com");
|
||||
request.setSysVersion("2017-05-25");
|
||||
request.setSysAction("CheckSmsVerifyCode");
|
||||
request.putQueryParameter("PhoneNumber", phone);
|
||||
request.putQueryParameter("VerifyCode", code);
|
||||
|
||||
log.info("阿里云号码认证核验参数 - phone: {}, code: {}", phone, code);
|
||||
|
||||
CommonResponse response = client.getCommonResponse(request);
|
||||
String responseData = response.getData();
|
||||
log.info("阿里云号码认证核验原始响应: {}", responseData);
|
||||
|
||||
JsonNode jsonNode = objectMapper.readTree(responseData);
|
||||
|
||||
JsonNode codeNode = jsonNode.get("Code");
|
||||
if (codeNode == null) {
|
||||
log.error("阿里云核验响应中找不到Code字段, 原始响应: {}", responseData);
|
||||
return false;
|
||||
}
|
||||
|
||||
String responseCode = codeNode.asText();
|
||||
JsonNode modelNode = jsonNode.get("Model");
|
||||
JsonNode verifyResultNode = modelNode != null ? modelNode.get("VerifyResult") : null;
|
||||
boolean verifyResult = verifyResultNode != null && "PASS".equals(verifyResultNode.asText());
|
||||
|
||||
if ("OK".equals(responseCode) && verifyResult) {
|
||||
log.info("验证码验证成功, phone: {}", phone);
|
||||
return true;
|
||||
}
|
||||
|
||||
JsonNode messageNode = jsonNode.get("Message");
|
||||
String message = messageNode != null ? messageNode.asText() : "unknown";
|
||||
log.warn("验证码验证失败, phone: {}, code: {}, message: {}, result: {}",
|
||||
phone, responseCode, message, verifyResult);
|
||||
return false;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("验证短信验证码异常, phone: {}, 异常信息: {}", phone, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cachedCode.equals(code)) {
|
||||
// 验证成功后删除验证码
|
||||
redisUtil.delete(cacheKey).block();
|
||||
log.info("验证码验证成功, phone: {}", phone);
|
||||
return true;
|
||||
}
|
||||
|
||||
log.warn("验证码不匹配, phone: {}, input: {}, cached: {}", phone, code, cachedCode);
|
||||
return false;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("验证短信验证码异常, phone: {}", phone, e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVerificationCode(String phone) {
|
||||
public Mono<String> getVerificationCode(String phone) {
|
||||
try {
|
||||
String cacheKey = RedisKeyConstants.SMS_CODE + phone;
|
||||
return redisUtil.get(cacheKey, String.class).block();
|
||||
return redisUtil.get(cacheKey, String.class);
|
||||
} catch (Exception e) {
|
||||
log.error("获取验证码异常, phone: {}", phone, e);
|
||||
return null;
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*/
|
||||
private String generateCode() {
|
||||
return RandomUtil.randomNumbers(smsProperties.getCodeLength());
|
||||
private IAcsClient createClient() throws ClientException {
|
||||
IClientProfile profile = DefaultProfile.getProfile(
|
||||
"cn-hangzhou",
|
||||
smsProperties.getAccessKeyId(),
|
||||
smsProperties.getAccessKeySecret());
|
||||
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", "Dypnsapi", "dypnsapi.aliyuncs.com");
|
||||
return new DefaultAcsClient(profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信
|
||||
*/
|
||||
private boolean sendSms(String phone, String code) throws Exception {
|
||||
log.info("调用阿里云短信API发送短信, phone: {}, code: {}", phone, code);
|
||||
|
||||
// 创建阿里云短信客户端
|
||||
Config config = new Config()
|
||||
.setAccessKeyId(smsProperties.getAccessKeyId())
|
||||
.setAccessKeySecret(smsProperties.getAccessKeySecret())
|
||||
.setEndpoint("dysmsapi.aliyuncs.com");
|
||||
|
||||
Client client = new Client(config);
|
||||
|
||||
// 构建发送短信请求
|
||||
SendSmsRequest request = new SendSmsRequest()
|
||||
.setPhoneNumbers(phone)
|
||||
.setSignName(smsProperties.getSignName())
|
||||
.setTemplateCode(smsProperties.getTemplateCode())
|
||||
.setTemplateParam("{\"code\":\"" + code + "\"}");
|
||||
|
||||
// 发送短信
|
||||
SendSmsResponse response = client.sendSms(request);
|
||||
|
||||
log.info("阿里云短信API响应, phone: {}, code: {}, message: {}",
|
||||
phone, response.getBody().getCode(), response.getBody().getMessage());
|
||||
|
||||
// 判断发送结果
|
||||
if ("OK".equals(response.getBody().getCode())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
log.error("阿里云短信发送失败, phone: {}, code: {}, message: {}",
|
||||
phone, response.getBody().getCode(), response.getBody().getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user