diff --git a/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java b/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java index 2586addab..09d2f90ac 100644 --- a/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java +++ b/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java @@ -22,6 +22,7 @@ import org.dromara.common.ratelimiter.enums.LimitType; import org.dromara.common.redis.utils.RedisUtils; import org.dromara.common.web.core.WaveAndCircleCaptcha; import org.dromara.common.web.config.properties.CaptchaProperties; +import org.dromara.common.sms.config.properties.SmsProperties; import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.api.entity.SmsResponse; import org.dromara.sms4j.core.factory.SmsFactory; @@ -51,15 +52,28 @@ public class CaptchaController { private final CaptchaProperties captchaProperties; private final MailProperties mailProperties; + private final SmsProperties smsProperties; /** * 短信验证码 * * @param phonenumber 用户手机号 */ - @RateLimiter(key = "#phonenumber", time = 60, count = 1) @GetMapping("/resource/sms/code") public R smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) { + if (!smsProperties.getEnabled()) { + return R.fail("当前系统没有开启短信功能!"); + } + SpringUtils.getAopProxy(this).smsCodeImpl(phonenumber); + return R.ok(); + } + + /** + * 短信验证码 + * 独立方法避免短信功能关闭之后仍然走限流 + */ + @RateLimiter(key = "#phonenumber", time = 60, count = 1) + public void smsCodeImpl(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) { String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber; String code = RandomUtil.randomNumbers(4); RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION)); @@ -71,9 +85,8 @@ public class CaptchaController { SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map); if (!smsResponse.isSuccess()) { log.error("验证码短信发送异常 => {}", smsResponse); - return R.fail(smsResponse.getData().toString()); + throw new ServiceException(smsResponse.getData().toString()); } - return R.ok(); } /** diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 1b52fab96..747c88617 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -156,6 +156,8 @@ mail: --- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商 # https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用 sms: + # 是否开启短信功能(默认关,与 mail 保持一致) + enabled: false # 配置源类型用于标定配置来源(interface,yaml) config-type: yaml # 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制 diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml index d77ddf57c..9cc14637e 100644 --- a/ruoyi-admin/src/main/resources/application-prod.yml +++ b/ruoyi-admin/src/main/resources/application-prod.yml @@ -159,6 +159,8 @@ mail: --- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商 # https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用 sms: + # 是否开启短信功能(默认关,与 mail 保持一致) + enabled: false # 配置源类型用于标定配置来源(interface,yaml) config-type: yaml # 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制 diff --git a/ruoyi-admin/src/test/java/org/dromara/test/CaptchaSmsCodeTest.java b/ruoyi-admin/src/test/java/org/dromara/test/CaptchaSmsCodeTest.java new file mode 100644 index 000000000..fd76cc3ae --- /dev/null +++ b/ruoyi-admin/src/test/java/org/dromara/test/CaptchaSmsCodeTest.java @@ -0,0 +1,35 @@ +package org.dromara.test; + +import org.dromara.common.core.domain.R; +import org.dromara.common.mail.config.properties.MailProperties; +import org.dromara.common.sms.config.properties.SmsProperties; +import org.dromara.common.web.config.properties.CaptchaProperties; +import org.dromara.web.controller.CaptchaController; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * CaptchaController.smsCode 关闭路径单元测试 + *

直接构造 Controller 调用,不启动 Spring 上下文(无需 MySQL/Redis) + */ +@DisplayName("短信验证码开关单元测试") +public class CaptchaSmsCodeTest { + + @Tag("dev") + @DisplayName("sms.enabled=false 时 smsCode 返回未开启提示且不发信") + @Test + public void shouldReturnFailWhenSmsDisabled() { + SmsProperties smsProperties = new SmsProperties(); + smsProperties.setEnabled(false); + // 字段声明顺序决定构造参数顺序:captchaProperties, mailProperties, smsProperties + CaptchaController controller = new CaptchaController( + new CaptchaProperties(), new MailProperties(), smsProperties); + + R result = controller.smsCode("13800000000"); + + Assertions.assertEquals(R.FAIL, result.getCode()); + Assertions.assertEquals("当前系统没有开启短信功能!", result.getMsg()); + } +} diff --git a/ruoyi-admin/src/test/java/org/dromara/test/SmsPropertiesTest.java b/ruoyi-admin/src/test/java/org/dromara/test/SmsPropertiesTest.java new file mode 100644 index 000000000..feb3a03e4 --- /dev/null +++ b/ruoyi-admin/src/test/java/org/dromara/test/SmsPropertiesTest.java @@ -0,0 +1,72 @@ +package org.dromara.test; + +import org.dromara.common.sms.config.SmsAutoConfiguration; +import org.dromara.common.sms.config.properties.SmsProperties; +import org.dromara.common.sms.handler.SmsExceptionHandler; +import org.dromara.sms4j.api.dao.SmsDao; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * SmsProperties 绑定 + SmsAutoConfiguration 条件化测试 + *

使用 ApplicationContextRunner,无需启动完整上下文(不需要 MySQL/Redis) + */ +@DisplayName("SMS 开关配置测试") +public class SmsPropertiesTest { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SmsAutoConfiguration.class)); + + @Tag("dev") + @DisplayName("sms.enabled=false 时绑定到 SmsProperties.enabled=false") + @Test + public void shouldBindEnabledFalse() { + contextRunner + .withPropertyValues("sms.enabled=false") + .run(context -> { + assertThat(context).hasSingleBean(SmsProperties.class); + assertThat(context.getBean(SmsProperties.class).getEnabled()).isEqualTo(false); + }); + } + + @Tag("dev") + @DisplayName("sms.enabled=true 时绑定到 SmsProperties.enabled=true") + @Test + public void shouldBindEnabledTrue() { + contextRunner + .withPropertyValues("sms.enabled=true") + .run(context -> { + assertThat(context).hasSingleBean(SmsProperties.class); + assertThat(context.getBean(SmsProperties.class).getEnabled()).isEqualTo(true); + }); + } + + @Tag("dev") + @DisplayName("sms.enabled=false 时仍注册 SmsDao,但不创建 SmsExceptionHandler") + @Test + public void shouldAlwaysRegisterSmsDaoButOmitHandlerWhenDisabled() { + contextRunner + .withPropertyValues("sms.enabled=false") + .run(context -> { + assertThat(context).hasSingleBean(SmsDao.class); + assertThat(context).doesNotHaveBean(SmsExceptionHandler.class); + }); + } + + @Tag("dev") + @DisplayName("sms.enabled=true 时创建 PlusSmsDao / SmsExceptionHandler") + @Test + public void shouldCreateSmsBeansWhenEnabled() { + contextRunner + .withPropertyValues("sms.enabled=true") + .run(context -> { + assertThat(context).hasSingleBean(SmsDao.class); + assertThat(context).hasSingleBean(SmsExceptionHandler.class); + }); + } +} diff --git a/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java b/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java index 3a39cc216..fd83b471a 100644 --- a/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java +++ b/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java @@ -1,10 +1,13 @@ package org.dromara.common.sms.config; +import org.dromara.common.sms.config.properties.SmsProperties; import org.dromara.common.sms.core.dao.PlusSmsDao; import org.dromara.common.sms.handler.SmsExceptionHandler; import org.dromara.sms4j.api.dao.SmsDao; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @@ -14,6 +17,7 @@ import org.springframework.context.annotation.Primary; * @author Feng */ @AutoConfiguration(after = {RedisAutoConfiguration.class}) +@EnableConfigurationProperties(SmsProperties.class) public class SmsAutoConfiguration { @Primary @@ -26,6 +30,7 @@ public class SmsAutoConfiguration { * 异常处理器 */ @Bean + @ConditionalOnProperty(prefix = "sms", name = "enabled", havingValue = "true") public SmsExceptionHandler smsExceptionHandler() { return new SmsExceptionHandler(); } diff --git a/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/properties/SmsProperties.java b/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/properties/SmsProperties.java new file mode 100644 index 000000000..d6398f08b --- /dev/null +++ b/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/properties/SmsProperties.java @@ -0,0 +1,19 @@ +package org.dromara.common.sms.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 短信配置属性 + * + * @author ruoyi + */ +@Data +@ConfigurationProperties(prefix = "sms") +public class SmsProperties { + + /** + * 是否开启短信功能(默认关闭,与 mail.enabled 保持一致) + */ + private Boolean enabled; +}