对齐 mail.enabled 的软开关方案:新增 SmsProperties、条件化 SmsAutoConfiguration、 CaptchaController.smsCode 调用层判断。详见 docs/superpowers/specs/2026-06-30-sms-toggle-design.md Co-Authored-By: Claude <noreply@anthropic.com>
7.4 KiB
设计文档:为 SMS 增加 enabled 开关(对齐 mail)
- 日期:2026-06-30
- 分支:5.X
- 状态:已批准(待实现)
1. 背景与目标
RuoYi-Vue-Plus 中,部分服务在配置文件里带开关,例如 mail.enabled 可以关闭邮件功能;但 sms 没有开关,短信功能始终启用。
目标:为 sms 增加一个 enabled 开关,使其与 mail.enabled 在语义与代码结构上完全对齐(软开关)。
2. 现状
2.1 Mail(有开关)——两层防护
- 配置层:
application-dev.yml中mail.enabled: false。 - Bean 层:
MailConfig(ruoyi-common-mail/.../config/MailConfig.java)用@ConditionalOnProperty(value="mail.enabled", havingValue="true")创建MailAccount;关掉时不创建该 Bean。 - 调用层:
CaptchaController.emailCode()显式判断mailProperties.getEnabled(),关掉时返回R.fail("当前系统没有开启邮箱功能!"),并拆出独立的emailCodeImpl()承载@RateLimiter,避免功能关闭后仍消耗限流配额。
2.2 SMS(无开关)
- 配置层:
application-dev.yml中sms:只有config-type/restricted/minute-max/account-max/blends,无enabled。 - Bean 层:
SmsAutoConfiguration(ruoyi-common-sms/.../config/SmsAutoConfiguration.java)无条件注册PlusSmsDao(@Primary SmsDao,Redis 实现)与SmsExceptionHandler;模块内没有 properties 类、也没有SmsUtils。 - 第三方层:
sms4j-spring-boot-starter自身的自动配置读取sms.blends.*,始终初始化SmsFactory/SmsBlend。 - 调用层:直接调用
SmsFactory.getSmsBlend("config1")。- 活跃调用方:
CaptchaController.smsCode()(真实场景,短信验证码)、SmsController(demo)。 FlwCommonServiceImpl中对SmsFactory的引用是注释代码,不计入范围。
- 活跃调用方:
2.3 关键差异
MailAccount 是项目自有 Bean,可用 @ConditionalOnProperty 精确掐断;SmsFactory 是第三方 sms4j 自动初始化的,无法用同一注解直接掐掉。本设计采用软开关:真正"关掉发送"由调用层判断完成;Bean 层只做与 MailConfig 对齐的清理(关掉时不创建项目自有的 SMS Bean)。
3. 决策记录
- 开关语义:软开关(已确认)。
sms.enabled=false时:调用层返回友好"未开启"提示;项目自有的PlusSmsDao/SmsExceptionHandler不创建;sms4j starter 仍会在后台初始化(其默认 in-memorySmsDao兜底,不影响应用启动,且不会真实发信)。 - 默认值:
false(与 mail 一致)。 - 命名:
sms.enabled(与mail.enabled完全一致)。 - 范围:仅覆盖真实调用方
CaptchaController.smsCode();demoSmsController不动。
4. 详细设计
4.1 配置文件
文件:ruoyi-admin/src/main/resources/application-dev.yml 与 application-prod.yml。
在 sms: 节点顶部新增 enabled:
--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
sms:
# 是否开启短信功能(默认关,与 mail 保持一致)
enabled: false
# 配置源类型用于标定配置来源(interface,yaml)
config-type: yaml
# ...其余原配置不变
4.2 新增 SmsProperties
文件:ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/properties/SmsProperties.java
package org.dromara.common.sms.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 短信配置属性
*/
@Data
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
/**
* 是否开启短信功能
*/
private Boolean enabled;
}
sms前缀与 sms4j 的blends等配置共存。Spring Boot 允许同一前缀绑定多个@ConfigurationProperties类,只要字段不冲突;enabled不属于 sms4j 使用的字段。该假设须在实现阶段通过启动验证。
4.3 SmsAutoConfiguration 条件化
文件:ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java
- 类上新增
@EnableConfigurationProperties(SmsProperties.class)。 smsDao()与smsExceptionHandler()两个@Bean方法各加@ConditionalOnProperty(prefix = "sms", name = "enabled", havingValue = "true")。
效果:sms.enabled=false 时不创建 PlusSmsDao、SmsExceptionHandler(对齐 MailConfig 关掉后不创建 MailAccount)。
4.4 CaptchaController.smsCode 调用层判断
文件:ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java
照搬 emailCode / emailCodeImpl 的拆分手法:
- 类字段注入
SmsProperties(与已有的MailProperties并列)。 smsCode(String phonenumber):- 保留
@GetMapping("/resource/sms/code")。 - 移除方法上的
@RateLimiter。 - 开头判断
if (!smsProperties.getEnabled()) return R.fail("当前系统没有开启短信功能!");。 - 否则调用
SpringUtils.getAopProxy(this).smsCodeImpl(phonenumber);并return R.ok();。
- 保留
- 新增
smsCodeImpl(String phonenumber):- 标注
@RateLimiter(key = "#phonenumber", time = 60, count = 1)。 - 承载原
smsCode内部的验证码生成、Redis 缓存、SmsFactory.getSmsBlend("config1").sendMessage(...)逻辑。
- 标注
这样功能关闭时不再消耗限流配额,与
emailCode的实现一致。
5. 不在范围内(YAGNI)
- 不新建
SmsUtils包装类:现有调用方直接使用SmsFactory,开关不需要它。 - 不修改 demo
SmsController:仅测试用途;如需一致可后续补充。 - 不改动 sms4j 自身的自动配置:属于"硬开关"范畴,本次明确排除。
6. 风险与验证(TDD)
| 风险 | 验证方式 |
|---|---|
sms 前缀与 sms4j 共存绑定时启动报错 |
启动 Spring 上下文,断言 SmsProperties.enabled 能被正确读取 |
关掉 PlusSmsDao 后 sms4j 找不到 SmsDao 而崩溃 |
sms.enabled=false 下上下文正常启动,sms4j 使用默认 in-memory SmsDao 兜底 |
PlusSmsDao条件化的回退决策:实现阶段先验证 sms4j 是否自带默认SmsDao。
- 若自带默认
SmsDao:按设计对PlusSmsDao加条件化(关掉时用默认兜底)。- 若不自带且启动强依赖
SmsDaoBean:则PlusSmsDao保持无条件注册(始终提供 Redis 实现),仅对SmsExceptionHandler加条件化,真正"关掉发送"完全由 4.4 的调用层判断承担。届时回退方案不影响本设计的开关语义(仍是软开关)。 | 开关行为不符合预期 |enabled=false:无PlusSmsDaoBean + 调用/resource/sms/code返回"未开启"提示;enabled=true:逻辑与现状一致 |
测试要点:
sms.enabled=false:(1) 不存在PlusSmsDaoBean;(2) 调用smsCode返回失败提示且不进入smsCodeImpl;(3) 上下文正常启动。sms.enabled=true:PlusSmsDao存在且为@Primary;smsCode正常进入发送流程(与改动前行为一致)。
7. 影响面
- 配置:
application-dev.yml、application-prod.yml(sms:节点新增enabled)。 - 代码:
- 新增
SmsProperties.java。 - 修改
SmsAutoConfiguration.java。 - 修改
CaptchaController.java(smsCode拆分 + 注入SmsProperties)。
- 新增
- 注册:
SmsAutoConfiguration已在ruoyi-common-sms的AutoConfiguration.imports中,无需改动;新增的SmsProperties通过@EnableConfigurationProperties注册,无需改动 imports。 - 兼容性:默认
false与 mail 一致;现有未配置sms.enabled的环境需显式设置才能开启短信功能。