From ae48a1264f6cd15771f48d16a95a398b8c6aafe5 Mon Sep 17 00:00:00 2001 From: zk Date: Wed, 11 Mar 2026 11:48:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=9F=AD=E4=BF=A1=E5=B0=81?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jiayunet/sms/AliYunSmsAbility.java | 96 ++++---- .../java/org/jiayunet/sms/ISmsAbility.java | 43 ---- .../org/jiayunet/sms/VerificationConfig.java | 125 ---------- .../org/jiayunet/sms/VerifyCodeAttribute.java | 33 --- .../jiayunet/constant/SmsTemplateEnum.java | 42 ++++ .../jiayunet/constant/SmsVerifyCodeEnum.java | 48 ---- .../constant/sms/SmsVariableAllows.java | 8 + .../constant/sms/UniversalSmsVariable.java | 19 ++ .../java/org/jiayunet/server/SmsServer.java | 231 ++++++++++++++++++ 9 files changed, 341 insertions(+), 304 deletions(-) delete mode 100644 common/src/main/java/org/jiayunet/sms/ISmsAbility.java delete mode 100644 common/src/main/java/org/jiayunet/sms/VerificationConfig.java delete mode 100644 common/src/main/java/org/jiayunet/sms/VerifyCodeAttribute.java create mode 100644 manager/src/main/java/org/jiayunet/constant/SmsTemplateEnum.java delete mode 100644 manager/src/main/java/org/jiayunet/constant/SmsVerifyCodeEnum.java create mode 100644 manager/src/main/java/org/jiayunet/constant/sms/SmsVariableAllows.java create mode 100644 manager/src/main/java/org/jiayunet/constant/sms/UniversalSmsVariable.java create mode 100644 manager/src/main/java/org/jiayunet/server/SmsServer.java diff --git a/common/src/main/java/org/jiayunet/sms/AliYunSmsAbility.java b/common/src/main/java/org/jiayunet/sms/AliYunSmsAbility.java index 8997ab6..d9f5f27 100644 --- a/common/src/main/java/org/jiayunet/sms/AliYunSmsAbility.java +++ b/common/src/main/java/org/jiayunet/sms/AliYunSmsAbility.java @@ -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 phone 手机号 + * @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 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; } } + } diff --git a/common/src/main/java/org/jiayunet/sms/ISmsAbility.java b/common/src/main/java/org/jiayunet/sms/ISmsAbility.java deleted file mode 100644 index 38736a1..0000000 --- a/common/src/main/java/org/jiayunet/sms/ISmsAbility.java +++ /dev/null @@ -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); - - -} diff --git a/common/src/main/java/org/jiayunet/sms/VerificationConfig.java b/common/src/main/java/org/jiayunet/sms/VerificationConfig.java deleted file mode 100644 index dd986b1..0000000 --- a/common/src/main/java/org/jiayunet/sms/VerificationConfig.java +++ /dev/null @@ -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; - } -} diff --git a/common/src/main/java/org/jiayunet/sms/VerifyCodeAttribute.java b/common/src/main/java/org/jiayunet/sms/VerifyCodeAttribute.java deleted file mode 100644 index bc5d714..0000000 --- a/common/src/main/java/org/jiayunet/sms/VerifyCodeAttribute.java +++ /dev/null @@ -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(); - -} diff --git a/manager/src/main/java/org/jiayunet/constant/SmsTemplateEnum.java b/manager/src/main/java/org/jiayunet/constant/SmsTemplateEnum.java new file mode 100644 index 0000000..c935bf4 --- /dev/null +++ b/manager/src/main/java/org/jiayunet/constant/SmsTemplateEnum.java @@ -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 variableClass; + +} \ No newline at end of file diff --git a/manager/src/main/java/org/jiayunet/constant/SmsVerifyCodeEnum.java b/manager/src/main/java/org/jiayunet/constant/SmsVerifyCodeEnum.java deleted file mode 100644 index 2c478ad..0000000 --- a/manager/src/main/java/org/jiayunet/constant/SmsVerifyCodeEnum.java +++ /dev/null @@ -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; - } - - -} diff --git a/manager/src/main/java/org/jiayunet/constant/sms/SmsVariableAllows.java b/manager/src/main/java/org/jiayunet/constant/sms/SmsVariableAllows.java new file mode 100644 index 0000000..deb3d17 --- /dev/null +++ b/manager/src/main/java/org/jiayunet/constant/sms/SmsVariableAllows.java @@ -0,0 +1,8 @@ +package org.jiayunet.constant.sms; + +/** + * 短信模板参数抽象类 + */ +public abstract class SmsVariableAllows { + +} diff --git a/manager/src/main/java/org/jiayunet/constant/sms/UniversalSmsVariable.java b/manager/src/main/java/org/jiayunet/constant/sms/UniversalSmsVariable.java new file mode 100644 index 0000000..2054ea8 --- /dev/null +++ b/manager/src/main/java/org/jiayunet/constant/sms/UniversalSmsVariable.java @@ -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; + + +} diff --git a/manager/src/main/java/org/jiayunet/server/SmsServer.java b/manager/src/main/java/org/jiayunet/server/SmsServer.java new file mode 100644 index 0000000..931a0e6 --- /dev/null +++ b/manager/src/main/java/org/jiayunet/server/SmsServer.java @@ -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 + Map variableMap = objectMapper.convertValue(variable, new com.fasterxml.jackson.core.type.TypeReference>() {}); + + // 发送短信 + 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 + Map variableMap = objectMapper.convertValue(variable, new com.fasterxml.jackson.core.type.TypeReference>() {}); + + // 直接发送短信 + 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; + } + +}