From 35d92c01487b56a2c9836cf28ece9fa33f6a2396 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Sat, 17 Oct 2026 23:39:48 +0800 Subject: [PATCH] =?UTF-8?q?fix(backend):=20=E5=90=8E=E5=8F=B0=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E9=99=90=E6=B5=81=E9=85=8D=E7=BD=AE=E4=BC=98=E5=85=88?= =?UTF-8?q?=E5=B9=B6=E4=BF=9D=E7=95=99=E6=B3=A8=E5=85=A5=E5=85=9C=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api-phase1.md | 5 ++++- .../src/main/resources/application.yml | 4 ++++ .../app/service/AppAuthServiceTest.java | 7 ++++--- .../fruitshorts/user/service/AppAuthService.java | 15 +++++++++++---- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/docs/api-phase1.md b/docs/api-phase1.md index d66f2ca..e3c9e2b 100644 --- a/docs/api-phase1.md +++ b/docs/api-phase1.md @@ -125,7 +125,10 @@ app.register.max-per-ip-per-day app.register.max-per-device-per-day ``` -键缺失或值不是整数时回退默认 5 / 3;值小于等于 0 表示对应维度不限制。 +运行时优先读取 `sys_config`。键缺失或值不是整数时,回退 `application.yml` +中的 `fruitshorts.auth.register-max-per-ip-per-day` / +`fruitshorts.auth.register-max-per-device-per-day`,缺省为 5 / 3; +值小于等于 0 表示对应维度不限制。 开发种子配置为 0 / 0,生产建议恢复为 5 / 3。 **残留风险**:IP 取自 `X-Forwarded-For`、设备号由客户端上报,都能伪造,只能挡脚本批量; diff --git a/fruitshorts-app-api/src/main/resources/application.yml b/fruitshorts-app-api/src/main/resources/application.yml index c2d1f4c..ae8eefe 100644 --- a/fruitshorts-app-api/src/main/resources/application.yml +++ b/fruitshorts-app-api/src/main/resources/application.yml @@ -43,6 +43,10 @@ springdoc: path: /v3/api-docs fruitshorts: + auth: + # 仅作 sys_config 缺失/非法时的兜底;运行时优先读取后台「系统配置」 + register-max-per-ip-per-day: ${REGISTER_MAX_PER_IP_PER_DAY:5} + register-max-per-device-per-day: ${REGISTER_MAX_PER_DEVICE_PER_DAY:3} interaction: # MACHINE_FIRST:机审通过即发布,命中 REVIEW 级敏感词才转人工 # PRE_REVIEW :全部先审后发(生产建议) diff --git a/fruitshorts-app-api/src/test/java/com/fruitshorts/app/service/AppAuthServiceTest.java b/fruitshorts-app-api/src/test/java/com/fruitshorts/app/service/AppAuthServiceTest.java index 4424ada..f3f3e4d 100644 --- a/fruitshorts-app-api/src/test/java/com/fruitshorts/app/service/AppAuthServiceTest.java +++ b/fruitshorts-app-api/src/test/java/com/fruitshorts/app/service/AppAuthServiceTest.java @@ -26,6 +26,7 @@ import com.fruitshorts.user.vo.TokenRespVO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.util.ReflectionTestUtils; import java.time.Duration; import java.time.LocalDateTime; @@ -193,9 +194,9 @@ class AppAuthServiceTest { @Test void should_fallback_to_default_limit_when_config_is_not_numeric() { - setRegisterLimits(5, 3); - given(configService.get("app.register.max-per-ip-per-day", "5")).willReturn("invalid"); - given(userMapper.selectCount(any())).willReturn(5L); + ReflectionTestUtils.setField(authService, "maxRegisterPerIpPerDay", 2); + given(configService.get("app.register.max-per-ip-per-day", "2")).willReturn("invalid"); + given(userMapper.selectCount(any())).willReturn(2L); assertThatThrownBy(() -> authService.register(emailRegister("abcd1234", null), "203.0.113.8")) .isInstanceOf(ServiceException.class) diff --git a/fruitshorts-module-user/src/main/java/com/fruitshorts/user/service/AppAuthService.java b/fruitshorts-module-user/src/main/java/com/fruitshorts/user/service/AppAuthService.java index 8ed03b1..e0f8d56 100644 --- a/fruitshorts-module-user/src/main/java/com/fruitshorts/user/service/AppAuthService.java +++ b/fruitshorts-module-user/src/main/java/com/fruitshorts/user/service/AppAuthService.java @@ -26,6 +26,7 @@ import com.fruitshorts.user.vo.LogoutReqVO; import com.fruitshorts.user.vo.RegisterReqVO; import com.fruitshorts.user.vo.TokenRespVO; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.DuplicateKeyException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -61,6 +62,14 @@ public class AppAuthService { private final PasswordEncoder passwordEncoder; private final ConfigService configService; + /** 后台 sys_config 缺失或非法时使用;小于等于 0 表示不限制。 */ + @Value("${fruitshorts.auth.register-max-per-ip-per-day:5}") + private int maxRegisterPerIpPerDay = 5; + + /** 后台 sys_config 缺失或非法时使用;小于等于 0 表示不限制。 */ + @Value("${fruitshorts.auth.register-max-per-device-per-day:3}") + private int maxRegisterPerDevicePerDay = 3; + @Transactional(rollbackFor = Exception.class) public TokenRespVO register(RegisterReqVO req, String clientIp) { String targetType = AppIdentifier.normalizeType(req.getTargetType()); @@ -305,8 +314,6 @@ public class AppAuthService { private static final String REGISTER_IP_LIMIT_KEY = "app.register.max-per-ip-per-day"; private static final String REGISTER_DEVICE_LIMIT_KEY = "app.register.max-per-device-per-day"; - private static final int DEFAULT_REGISTER_IP_LIMIT = 5; - private static final int DEFAULT_REGISTER_DEVICE_LIMIT = 3; private static final int PASSWORD_MIN_LENGTH = 8; private static final int PASSWORD_MAX_LENGTH = 64; @@ -339,8 +346,8 @@ public class AppAuthService { */ private void guardRegisterRisk(String clientIp, String deviceId) { LocalDateTime since = LocalDateTime.now().minusDays(1); - int ipLimit = registerLimit(REGISTER_IP_LIMIT_KEY, DEFAULT_REGISTER_IP_LIMIT); - int deviceLimit = registerLimit(REGISTER_DEVICE_LIMIT_KEY, DEFAULT_REGISTER_DEVICE_LIMIT); + int ipLimit = registerLimit(REGISTER_IP_LIMIT_KEY, maxRegisterPerIpPerDay); + int deviceLimit = registerLimit(REGISTER_DEVICE_LIMIT_KEY, maxRegisterPerDevicePerDay); String ip = AppIdentifier.nullSafe(clientIp); if (ipLimit > 0 && StringUtils.hasText(ip)) { Long byIp = appUserMapper.selectCount(Wrappers.lambdaQuery()