完善短信封装
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
package org.jiayunet.sms;
|
||||
|
||||
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
|
||||
import com.aliyun.teautil.models.RuntimeOptions;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -7,10 +10,9 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.jiayunet.tool.server.RedisServerTool;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author zk
|
||||
@@ -18,75 +20,59 @@ import java.util.concurrent.TimeUnit;
|
||||
@Component("smsAbility")
|
||||
@ConditionalOnProperty(name = "app.sms.service_provider", havingValue = "aliyun")
|
||||
@Slf4j
|
||||
public class AliYunSmsAbility implements ISmsAbility {
|
||||
public class AliYunSmsAbility {
|
||||
|
||||
@Autowired
|
||||
private com.aliyun.dysmsapi20170525.Client aliyunSmsClient;
|
||||
|
||||
@Autowired
|
||||
private RedisServerTool redisServerTool;
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public void sendVerificationCode(String phone, VerifyCodeAttribute attribute) {
|
||||
String redisKey = attribute.getRedisKeyPre()+phone;
|
||||
|
||||
// 验证是否发送
|
||||
boolean existKey = redisServerTool.hasKey(redisKey);
|
||||
|
||||
if (!attribute.getCover()){
|
||||
Assert.isTrue(!existKey,"验证码已发送");
|
||||
}
|
||||
|
||||
// 生成验证码
|
||||
int randomNumber = new Random().nextInt(1000000);
|
||||
String number = String.format("%06d", randomNumber);
|
||||
|
||||
// 发送验证码
|
||||
Assert.isTrue(sendVerificationCode(phone,number,attribute.getSignName(),attribute.getTemplateCode()),"短信发送失败,请稍后重试");
|
||||
|
||||
// 跟新redisK
|
||||
redisServerTool.set(redisKey,number,attribute.getEffectiveTime(), TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVerificationCode(String phone, VerifyCodeAttribute attribute) {
|
||||
return redisServerTool.get(attribute.getRedisKeyPre()+phone,String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delVerificationCode(String phone, VerifyCodeAttribute attribute) {
|
||||
redisServerTool.delete(attribute.getRedisKeyPre()+phone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendNotification(String phone, String message) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* ali 发送验证码
|
||||
* 发送验证短信
|
||||
*
|
||||
* @param phone 手机号
|
||||
* @param validCode 验证码
|
||||
* @param signName 签名
|
||||
* @param templateCode 模板
|
||||
* @param variableMap 短信变量参数 key;变量名 value: 替换值
|
||||
* @return 发送结果
|
||||
*/
|
||||
private boolean sendVerificationCode(String phone, String validCode, String signName, String templateCode) {
|
||||
com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest =
|
||||
new com.aliyun.dysmsapi20170525.models.SendSmsRequest().setSignName(signName).setTemplateCode(templateCode)
|
||||
.setPhoneNumbers(phone).setTemplateParam("{\"code\":\"" + validCode + "\"}");
|
||||
public boolean sendSms(String phone, String signName, String templateCode, Map<String, String> variableMap) {
|
||||
|
||||
com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
|
||||
try {
|
||||
// 复制代码运行请自行打印 API 的返回值
|
||||
Assert.hasText(phone, "手机号码空");
|
||||
Assert.hasText(signName, "签名为空");
|
||||
Assert.hasText(templateCode, "短信模板code为空");
|
||||
|
||||
// 确保非空
|
||||
variableMap = variableMap == null ? new HashMap<>() : variableMap;
|
||||
String templateParam = objectMapper.writeValueAsString(variableMap);
|
||||
|
||||
SendSmsRequest sendSmsRequest =
|
||||
new SendSmsRequest().setSignName(signName).setTemplateCode(templateCode).setPhoneNumbers(phone).setTemplateParam(templateParam);
|
||||
|
||||
// 运行时配置对象
|
||||
RuntimeOptions runtime = new RuntimeOptions()
|
||||
.setReadTimeout(5000) // 读取超时 5 秒
|
||||
.setConnectTimeout(3000); // 连接超时 3 秒
|
||||
|
||||
// 发送短信
|
||||
SendSmsResponse response = aliyunSmsClient.sendSmsWithOptions(sendSmsRequest, runtime);
|
||||
if (!response.getStatusCode().equals(200)||!response.getBody().getCode().equals("OK")){
|
||||
log.error("短信发送失败: 失败原因:{}",response.getBody());
|
||||
|
||||
if (response.getStatusCode() != 200 || !response.getBody().getCode().equals("OK")) {
|
||||
log.error("短信发送失败: phone={}, templateCode={}, 失败原因:{}", phone, templateCode, response.getBody());
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("短信发送成功: phone={}, templateCode={}", phone, templateCode);
|
||||
return true;
|
||||
}catch (Throwable e){
|
||||
log.error("短信发送异常: 异常:{}",e.getMessage());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("短信发送异常: phone={}, templateCode={}", phone, templateCode, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package org.jiayunet.sms;
|
||||
|
||||
/**
|
||||
* 抽象短信发送接口
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
public interface ISmsAbility {
|
||||
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
* @param phone 手机号
|
||||
* @param attribute 配置
|
||||
*/
|
||||
void sendVerificationCode(String phone, VerifyCodeAttribute attribute);
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
* @param phone 手机号
|
||||
* @param attribute 配置
|
||||
*/
|
||||
String getVerificationCode(String phone, VerifyCodeAttribute attribute);
|
||||
|
||||
/**
|
||||
* 删除验证码
|
||||
* @param phone 手机号
|
||||
* @param attribute 配置
|
||||
*/
|
||||
void delVerificationCode(String phone, VerifyCodeAttribute attribute);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 发送通知短信
|
||||
*
|
||||
* @param phone 手机号
|
||||
* @param message 消息
|
||||
*/
|
||||
void sendNotification(String phone, String message);
|
||||
|
||||
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package org.jiayunet.sms;
|
||||
import lombok.Getter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* 短信发送配配置
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Getter
|
||||
public class VerificationConfig implements VerifyCodeAttribute {
|
||||
/**
|
||||
* 模板签名
|
||||
*/
|
||||
private final String signName;
|
||||
/**
|
||||
* 模板code
|
||||
*/
|
||||
private final String templateCode;
|
||||
/**
|
||||
* redis 名
|
||||
*/
|
||||
private final String redisKeyPre;
|
||||
/**
|
||||
* 有效时间 单位分钟
|
||||
*/
|
||||
private final Integer effectiveTime;
|
||||
/**
|
||||
* 覆盖已存在的redis值
|
||||
*/
|
||||
private final Boolean cover;
|
||||
|
||||
/**
|
||||
* 构造器
|
||||
*/
|
||||
public static class Builder {
|
||||
private String signName;
|
||||
private String templateCode;
|
||||
private String redisKeyPre;
|
||||
private Integer effectiveTime;
|
||||
private Boolean cover;
|
||||
|
||||
public VerificationConfig.Builder config(VerifyCodeAttribute attribute){
|
||||
this.signName = attribute.getSignName();
|
||||
this.templateCode = attribute.getTemplateCode();
|
||||
this.redisKeyPre = attribute.getRedisKeyPre();
|
||||
this.effectiveTime = attribute.getEffectiveTime();
|
||||
this.cover = attribute.getCover();
|
||||
return this;
|
||||
}
|
||||
public VerificationConfig.Builder config(String signName, String templateCode,String redisKeyPre) {
|
||||
this.signName = signName;
|
||||
this.templateCode = templateCode;
|
||||
this.redisKeyPre = redisKeyPre;
|
||||
this.effectiveTime = 5;
|
||||
this.cover = true;
|
||||
return this;
|
||||
}
|
||||
public VerificationConfig.Builder config(String signName, String templateCode,String redisKeyPre,Integer effectiveTime) {
|
||||
this.signName = signName;
|
||||
this.templateCode = templateCode;
|
||||
this.redisKeyPre = redisKeyPre;
|
||||
this.effectiveTime = effectiveTime;
|
||||
this.cover = true;
|
||||
return this;
|
||||
}
|
||||
public VerificationConfig.Builder config(String signName, String templateCode,String redisKeyPre,Integer effectiveTime,Boolean cover) {
|
||||
this.signName = signName;
|
||||
this.templateCode = templateCode;
|
||||
this.redisKeyPre = redisKeyPre;
|
||||
this.effectiveTime = effectiveTime;
|
||||
this.cover = cover;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public VerificationConfig.Builder redisKeyPre(String redisKeyPre) {
|
||||
this.redisKeyPre = redisKeyPre;
|
||||
return this;
|
||||
}
|
||||
|
||||
public VerificationConfig.Builder templateCode(String templateCode) {
|
||||
this.templateCode = templateCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public VerificationConfig.Builder signName(String signName) {
|
||||
this.signName = signName;
|
||||
return this;
|
||||
}
|
||||
public VerificationConfig.Builder effectiveTime(Integer effectiveTime) {
|
||||
this.effectiveTime = effectiveTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public VerificationConfig.Builder cover(Boolean cover) {
|
||||
this.cover = cover;
|
||||
return this;
|
||||
}
|
||||
public VerificationConfig build() {
|
||||
Assert.hasText(signName,"signName不能为空");
|
||||
Assert.hasText(templateCode,"templateCode不能为空");
|
||||
Assert.hasText(redisKeyPre,"redisKey不能为空");
|
||||
Assert.notNull(effectiveTime,"effectiveTime不能为空");
|
||||
Assert.notNull(cover,"cover不能为空");
|
||||
return new VerificationConfig(signName, templateCode, redisKeyPre,effectiveTime,cover);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 私有化构造
|
||||
* @param signName 签名
|
||||
* @param templateCode 短信模板
|
||||
* @param redisKeyPre redis键名
|
||||
* @param effectiveTime 有效时间
|
||||
* @param cover 是否覆盖发送
|
||||
*/
|
||||
private VerificationConfig(String signName, String templateCode, String redisKeyPre, Integer effectiveTime, Boolean cover) {
|
||||
this.signName = signName;
|
||||
this.templateCode = templateCode;
|
||||
this.redisKeyPre = redisKeyPre;
|
||||
this.effectiveTime = effectiveTime;
|
||||
this.cover = cover;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package org.jiayunet.sms;
|
||||
|
||||
/**
|
||||
* @author zk
|
||||
*/
|
||||
public interface VerifyCodeAttribute {
|
||||
/**
|
||||
* 签名
|
||||
*
|
||||
*/
|
||||
String getSignName();
|
||||
|
||||
/**
|
||||
* 模板code
|
||||
*/
|
||||
String getTemplateCode();
|
||||
|
||||
/**
|
||||
* redisKey 名字
|
||||
*/
|
||||
String getRedisKeyPre();
|
||||
|
||||
/**
|
||||
* 有效时间
|
||||
*/
|
||||
Integer getEffectiveTime();
|
||||
|
||||
/**
|
||||
* 是否覆盖发送
|
||||
*/
|
||||
Boolean getCover();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.jiayunet.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.jiayunet.constant.sms.SmsVariableAllows;
|
||||
import org.jiayunet.constant.sms.UniversalSmsVariable;
|
||||
|
||||
/**
|
||||
* @author zk
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum SmsTemplateEnum {
|
||||
|
||||
/**
|
||||
* 通用验证码
|
||||
*/
|
||||
UNIVERSAL("加鱼宠物", "SMS_480245067", 5, UniversalSmsVariable.class);
|
||||
|
||||
/**
|
||||
* 模板签名
|
||||
*/
|
||||
private final String signName;
|
||||
|
||||
/**
|
||||
* 模板code
|
||||
*/
|
||||
private final String templateCode;
|
||||
|
||||
/**
|
||||
* 仅在验证码短信时配置, 发送短信是将默认使用短信模板+手机号未key, code 的值为value, 且禁止重复发送
|
||||
* NULL:不激活短信有效禁止重发限制
|
||||
* 有效时间 单位分钟
|
||||
*/
|
||||
private final Integer effectiveTime;
|
||||
|
||||
/**
|
||||
* 短信参数对象类
|
||||
*/
|
||||
private final Class<? extends SmsVariableAllows> variableClass;
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package org.jiayunet.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.jiayunet.sms.VerifyCodeAttribute;
|
||||
|
||||
/**
|
||||
* @author zk
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum SmsVerifyCodeEnum implements VerifyCodeAttribute {
|
||||
/**
|
||||
* 通用验证码
|
||||
*/
|
||||
UNIVERSAL("加鱼宠物", "SMS_480245067", "universal:code:sms:", 5, false);
|
||||
|
||||
|
||||
/**
|
||||
* 模板签名
|
||||
*/
|
||||
private final String signName;
|
||||
/**
|
||||
* 模板code
|
||||
*/
|
||||
private final String templateCode;
|
||||
/**
|
||||
* redis 名
|
||||
*/
|
||||
private final String redisKeyPre;
|
||||
/**
|
||||
* 有效时间 单位分钟
|
||||
*/
|
||||
private final Integer effectiveTime;
|
||||
/**
|
||||
* 覆盖已存在的redis值
|
||||
*/
|
||||
private final Boolean cover;
|
||||
|
||||
|
||||
public String getRedisKey(String phone) {
|
||||
Assert.hasText(phone, "手机号码不能为空");
|
||||
return this.redisKeyPre + phone;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.jiayunet.constant.sms;
|
||||
|
||||
/**
|
||||
* 短信模板参数抽象类
|
||||
*/
|
||||
public abstract class SmsVariableAllows {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.jiayunet.constant.sms;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 通用验证码
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class UniversalSmsVariable extends SmsVariableAllows{
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package org.jiayunet.server;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jiayunet.constant.SmsTemplateEnum;
|
||||
import org.jiayunet.constant.sms.SmsVariableAllows;
|
||||
import org.jiayunet.constant.sms.UniversalSmsVariable;
|
||||
import org.jiayunet.sms.AliYunSmsAbility;
|
||||
import org.jiayunet.tool.server.RedisServerTool;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 短信服务
|
||||
* @author zk
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class SmsServer {
|
||||
|
||||
private static final String SMS_KEY_PREFIX = "sms:";
|
||||
|
||||
@Autowired
|
||||
private AliYunSmsAbility aliYunSmsAbility;
|
||||
|
||||
@Autowired
|
||||
private RedisServerTool redisServerTool;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 发送短信(普通短信或验证码短信)。
|
||||
* 根据 {@link SmsTemplateEnum} 中配置的有效期以及 {@code variable}
|
||||
* 是否包含非空的 {@code code} 字段来决定走验证码路径还是普通短信路径。
|
||||
*
|
||||
* @param phone 目标手机号,不能为空
|
||||
* @param template 短信模板枚举,必须与 {@code variable} 的类型匹配
|
||||
* @param variable 短信参数对象,可能是 {@link UniversalSmsVariable}(验证码)或其他实现
|
||||
* @return {@code true} 表示短信发送成功,{@code false} 表示发送失败或前置参数校验未通过
|
||||
*/
|
||||
public boolean send(String phone, SmsTemplateEnum template, SmsVariableAllows variable) {
|
||||
try {
|
||||
Assert.hasText(phone, "手机号不能为空");
|
||||
Assert.notNull(template, "短信模板不能为空");
|
||||
Assert.notNull(variable, "短信参数不能为空");
|
||||
|
||||
// 校验参数类型是否匹配
|
||||
if (!template.getVariableClass().isInstance(variable)) {
|
||||
log.error("短信参数类型不匹配: template={}, expectedClass={}, actualClass={}",
|
||||
template.name(), template.getVariableClass().getName(), variable.getClass().getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 判断是否激活验证码模式
|
||||
boolean isVerifyMode = isVerifyCodeMode(template, variable);
|
||||
|
||||
if (isVerifyMode) {
|
||||
return sendVerifyCode(phone, template, (UniversalSmsVariable) variable);
|
||||
} else {
|
||||
return sendNormalSms(phone, template, variable);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("发送短信异常: phone={}, template={}", phone, template.name(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码是否正确。
|
||||
*
|
||||
* 根据手机号与短信模板生成 Redis 键,从缓存中读取
|
||||
* 先前发送的验证码并与用户提供的 {@code code} 进行比较。
|
||||
* 若缓存不存在或已过期则返回 {@code false},若匹配则返回 {@code true}。
|
||||
*
|
||||
* @param phone 目标手机号,不能为空
|
||||
* @param template 短信模板枚举,对应的验证码在 Redis 中的键会使用其模板码
|
||||
* @param code 待校验的验证码,不能为空
|
||||
* @return {@code true} 表示验证码匹配且未过期,{@code false} 表示不匹配或已失效
|
||||
*/
|
||||
public boolean verify(String phone, SmsTemplateEnum template, String code) {
|
||||
try {
|
||||
Assert.hasText(phone, "手机号不能为空");
|
||||
Assert.notNull(template, "短信模板不能为空");
|
||||
Assert.hasText(code, "验证码不能为空");
|
||||
|
||||
String key = buildVerifyCodeKey(template, phone);
|
||||
String storedCode = redisServerTool.get(key, String.class);
|
||||
|
||||
if (storedCode == null) {
|
||||
log.warn("验证码不存在或已过期: phone={}, template={}", phone, template.name());
|
||||
return false;
|
||||
}
|
||||
|
||||
return code.equals(storedCode);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("校验验证码异常: phone={}, template={}", phone, template.name(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除已发送的验证码缓存。
|
||||
*
|
||||
* 根据手机号和短信模板生成对应的 Redis 键并删除,以防止后续的验证码校验仍能通过。
|
||||
* 该方法在验证码已使用或需要手动失效时调用。
|
||||
*
|
||||
* @param phone 目标手机号,不能为空
|
||||
* @param template 短信模板枚举,用于确定 Redis 键的前缀
|
||||
*/
|
||||
public void clear(String phone, SmsTemplateEnum template) {
|
||||
try {
|
||||
String key = buildVerifyCodeKey(template, phone);
|
||||
redisServerTool.delete(key);
|
||||
log.info("清除验证码成功: phone={}, template={}", phone, template.name());
|
||||
} catch (Exception e) {
|
||||
log.error("清除验证码异常: phone={}, template={}", phone, template.name(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前请求是否属于验证码模式。
|
||||
*
|
||||
* 判定依据:
|
||||
* 1. {@link SmsTemplateEnum} 必须配置有效时间(effectiveTime),表示该模板用于验证码场景。
|
||||
* 2. 变量对象 {@code variable} 必须拥有 `getCode()` 方法,并且返回值在转换为字符串后非空。
|
||||
*
|
||||
* 通过反射检查 `getCode()` 方法,以避免对具体实现类(如 {@link UniversalSmsVariable})的硬性依赖,
|
||||
* 使得任何实现了同名方法的变量对象都可被识别为验证码变量。
|
||||
*
|
||||
* @param template 短信模板枚举,用于判断是否配置了验证码有效期
|
||||
* @param variable 短信参数对象,需具备可获取验证码的 `getCode()` 方法
|
||||
* @return {@code true} 表示应走验证码发送流程,{@code false} 表示走普通短信流程
|
||||
*/
|
||||
private boolean isVerifyCodeMode(SmsTemplateEnum template, SmsVariableAllows variable) {
|
||||
// 若模板没有配置有效时间,则不属于验证码模式
|
||||
if (template.getEffectiveTime() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 通过反射检查变量对象是否具有 getCode() 方法,以及返回的值是否非空
|
||||
try {
|
||||
|
||||
java.lang.reflect.Method getCodeMethod = variable.getClass().getMethod("getCode");
|
||||
Object codeObj = getCodeMethod.invoke(variable);
|
||||
|
||||
// 只要 getCode 方法返回的值不为 null 且转换为字符串后非空,即视为验证码模式
|
||||
if (codeObj != null) {
|
||||
String codeStr = String.valueOf(codeObj);
|
||||
return !codeStr.isEmpty();
|
||||
}
|
||||
|
||||
} catch (NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) {
|
||||
// 没有 getCode 方法或调用失败,说明不是验证码变量
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送验证码短信。
|
||||
*
|
||||
* 在发送前先检查 Redis 中是否已有未过期的验证码,防止重复发送。
|
||||
* 发送成功后将验证码存入 Redis,键使用模板码与手机号的组合,并设置模板配置的有效期。
|
||||
*
|
||||
* @param phone 目标手机号,不能为空
|
||||
* @param template 短信模板枚举,必须包含验证码相关配置(如有效时间)
|
||||
* @param variable {@link UniversalSmsVariable},其中必须包含非空的 {@code code}
|
||||
* @return {@code true} 表示短信发送及缓存成功,{@code false} 表示发送被拦截或失败
|
||||
*/
|
||||
private boolean sendVerifyCode(String phone, SmsTemplateEnum template, UniversalSmsVariable variable) {
|
||||
String key = buildVerifyCodeKey(template, phone);
|
||||
|
||||
// 检查是否重复发送
|
||||
if (redisServerTool.hasKey(key)) {
|
||||
log.warn("验证码尚未过期,请勿重复发送: phone={}, template={}", phone, template.name());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 转换参数对象为 Map<String, String>
|
||||
Map<String, String> variableMap = objectMapper.convertValue(variable, new com.fasterxml.jackson.core.type.TypeReference<Map<String, String>>() {});
|
||||
|
||||
// 发送短信
|
||||
boolean success = aliYunSmsAbility.sendSms(
|
||||
phone,
|
||||
template.getSignName(),
|
||||
template.getTemplateCode(),
|
||||
variableMap
|
||||
);
|
||||
|
||||
// 发送成功后存储验证码
|
||||
if (success) {
|
||||
redisServerTool.set(key, variable.getCode(), template.getEffectiveTime(), TimeUnit.MINUTES);
|
||||
log.info("验证码已存储: phone={}, template={}, effectiveTime={}分钟",
|
||||
phone, template.name(), template.getEffectiveTime());
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送普通短信
|
||||
*/
|
||||
private boolean sendNormalSms(String phone, SmsTemplateEnum template, SmsVariableAllows variable) {
|
||||
// 转换参数对象为 Map<String, String>
|
||||
Map<String, String> variableMap = objectMapper.convertValue(variable, new com.fasterxml.jackson.core.type.TypeReference<Map<String, String>>() {});
|
||||
|
||||
// 直接发送短信
|
||||
return aliYunSmsAbility.sendSms(
|
||||
phone,
|
||||
template.getSignName(),
|
||||
template.getTemplateCode(),
|
||||
variableMap
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建验证码 Redis Key
|
||||
*/
|
||||
private String buildVerifyCodeKey(SmsTemplateEnum template, String phone) {
|
||||
return SMS_KEY_PREFIX + template.getTemplateCode() + ":" + phone;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user