diff --git a/pom.xml b/pom.xml
index 862fc39..67366cb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,12 +5,12 @@
org.springframework.boot
spring-boot-starter-parent
- 3.1.0
+ 3.1.1
xyz.playedu
playedu-api
- 1.0-beta.7
+ 1.1
playedu-api
playedu-api
@@ -132,11 +132,6 @@
hutool-core
5.8.16
-
- cn.hutool
- hutool-captcha
- 5.8.16
-
diff --git a/src/main/java/xyz/playedu/api/config/PlayEduConfig.java b/src/main/java/xyz/playedu/api/config/PlayEduConfig.java
index 35d5d46..c514dba 100644
--- a/src/main/java/xyz/playedu/api/config/PlayEduConfig.java
+++ b/src/main/java/xyz/playedu/api/config/PlayEduConfig.java
@@ -26,4 +26,10 @@ public class PlayEduConfig {
@Value("${spring.profiles.active}")
private String env;
+
+ @Value("${playedu.limiter.duration}")
+ private Long limiterDuration;
+
+ @Value("${playedu.limiter.limit}")
+ private Long limiterLimit;
}
diff --git a/src/main/java/xyz/playedu/api/config/RedisConfig.java b/src/main/java/xyz/playedu/api/config/RedisConfig.java
index 0ef34a7..cbcc1d6 100644
--- a/src/main/java/xyz/playedu/api/config/RedisConfig.java
+++ b/src/main/java/xyz/playedu/api/config/RedisConfig.java
@@ -17,10 +17,14 @@ package xyz.playedu.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.springframework.scripting.support.ResourceScriptSource;
@Configuration
public class RedisConfig {
@@ -43,4 +47,13 @@ public class RedisConfig {
return redisTemplate;
}
+
+ @Bean(name = "rateLimiterScript")
+ public RedisScript rateLimiterScript() {
+ DefaultRedisScript script = new DefaultRedisScript<>();
+ script.setScriptSource(
+ new ResourceScriptSource(new ClassPathResource("lua/RateLimiterScript.lua")));
+ script.setResultType(Long.class);
+ return script;
+ }
}
diff --git a/src/main/java/xyz/playedu/api/controller/backend/AdminRoleController.java b/src/main/java/xyz/playedu/api/controller/backend/AdminRoleController.java
index e64a4c7..b5abc18 100644
--- a/src/main/java/xyz/playedu/api/controller/backend/AdminRoleController.java
+++ b/src/main/java/xyz/playedu/api/controller/backend/AdminRoleController.java
@@ -38,11 +38,6 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
-/**
- * @Author 杭州白书科技有限公司
- *
- * @create 2023/2/21 15:56
- */
@RestController
@RequestMapping("/backend/v1/admin-role")
@Slf4j
diff --git a/src/main/java/xyz/playedu/api/controller/backend/CourseController.java b/src/main/java/xyz/playedu/api/controller/backend/CourseController.java
index d4b9130..b67efd3 100644
--- a/src/main/java/xyz/playedu/api/controller/backend/CourseController.java
+++ b/src/main/java/xyz/playedu/api/controller/backend/CourseController.java
@@ -40,11 +40,6 @@ import java.text.ParseException;
import java.util.*;
import java.util.stream.Collectors;
-/**
- * @Author 杭州白书科技有限公司
- *
- * @create 2023/2/24 14:16
- */
@RestController
@Slf4j
@RequestMapping("/backend/v1/course")
diff --git a/src/main/java/xyz/playedu/api/controller/backend/DepartmentController.java b/src/main/java/xyz/playedu/api/controller/backend/DepartmentController.java
index f3bb170..b19a623 100644
--- a/src/main/java/xyz/playedu/api/controller/backend/DepartmentController.java
+++ b/src/main/java/xyz/playedu/api/controller/backend/DepartmentController.java
@@ -44,11 +44,6 @@ import xyz.playedu.api.types.paginate.UserPaginateFilter;
import java.util.*;
import java.util.stream.Collectors;
-/**
- * @Author 杭州白书科技有限公司
- *
- * @create 2023/2/19 10:33
- */
@RestController
@Slf4j
@RequestMapping("/backend/v1/department")
diff --git a/src/main/java/xyz/playedu/api/controller/backend/LoginController.java b/src/main/java/xyz/playedu/api/controller/backend/LoginController.java
index e37fc7b..92499d7 100644
--- a/src/main/java/xyz/playedu/api/controller/backend/LoginController.java
+++ b/src/main/java/xyz/playedu/api/controller/backend/LoginController.java
@@ -26,14 +26,15 @@ import xyz.playedu.api.constant.BPermissionConstant;
import xyz.playedu.api.domain.AdminUser;
import xyz.playedu.api.event.AdminUserLoginEvent;
import xyz.playedu.api.middleware.BackendPermissionMiddleware;
-import xyz.playedu.api.middleware.ImageCaptchaCheckMiddleware;
import xyz.playedu.api.request.backend.LoginRequest;
import xyz.playedu.api.request.backend.PasswordChangeRequest;
import xyz.playedu.api.service.AdminUserService;
import xyz.playedu.api.service.BackendAuthService;
+import xyz.playedu.api.service.RateLimiterService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.util.HelperUtil;
import xyz.playedu.api.util.IpUtil;
+import xyz.playedu.api.util.RedisUtil;
import xyz.playedu.api.util.RequestUtil;
import java.util.HashMap;
@@ -50,20 +51,31 @@ public class LoginController {
@Autowired private ApplicationContext ctx;
+ @Autowired private RateLimiterService rateLimiterService;
+
@PostMapping("/login")
- @ImageCaptchaCheckMiddleware
public JsonResponse login(@RequestBody @Validated LoginRequest loginRequest) {
AdminUser adminUser = adminUserService.findByEmail(loginRequest.email);
if (adminUser == null) {
return JsonResponse.error("邮箱或密码错误");
}
+
+ String limitKey = "admin-login-limit:" + loginRequest.getEmail();
+ Long reqCount = rateLimiterService.current(limitKey, 3600L);
+ if (reqCount > 5) {
+ return JsonResponse.error("连续五次错误,请稍后再试");
+ }
+
String password =
HelperUtil.MD5(loginRequest.getPassword() + adminUser.getSalt()).toLowerCase();
if (!adminUser.getPassword().equals(password)) {
return JsonResponse.error("邮箱或密码错误");
}
+
+ RedisUtil.del(limitKey);
+
if (adminUser.getIsBanLogin().equals(1)) {
- return JsonResponse.error("当前用户已禁止登录");
+ return JsonResponse.error("当前管理员已禁止登录");
}
String token = authService.loginUsingId(adminUser.getId(), RequestUtil.url());
diff --git a/src/main/java/xyz/playedu/api/controller/backend/SystemController.java b/src/main/java/xyz/playedu/api/controller/backend/SystemController.java
index 6e0f18f..c197630 100644
--- a/src/main/java/xyz/playedu/api/controller/backend/SystemController.java
+++ b/src/main/java/xyz/playedu/api/controller/backend/SystemController.java
@@ -17,19 +17,15 @@ package xyz.playedu.api.controller.backend;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xyz.playedu.api.BCtx;
import xyz.playedu.api.constant.CConfig;
-import xyz.playedu.api.service.ImageCaptchaService;
-import xyz.playedu.api.types.ImageCaptchaResult;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.util.RequestUtil;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -40,19 +36,6 @@ import java.util.Map;
@Slf4j
public class SystemController {
- @Autowired private ImageCaptchaService imageCaptchaService;
-
- @GetMapping("/image-captcha")
- public JsonResponse imageCaptcha() throws IOException {
- ImageCaptchaResult imageCaptchaResult = imageCaptchaService.generate();
-
- HashMap data = new HashMap<>();
- data.put("key", imageCaptchaResult.getKey());
- data.put("image", imageCaptchaResult.getImage());
-
- return JsonResponse.data(data);
- }
-
@GetMapping("/config")
public JsonResponse config() {
Map configData = BCtx.getConfig();
diff --git a/src/main/java/xyz/playedu/api/controller/frontend/IndexController.java b/src/main/java/xyz/playedu/api/controller/frontend/IndexController.java
index 6de4065..af4a71f 100644
--- a/src/main/java/xyz/playedu/api/controller/frontend/IndexController.java
+++ b/src/main/java/xyz/playedu/api/controller/frontend/IndexController.java
@@ -18,17 +18,10 @@ package xyz.playedu.api.controller.frontend;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
-import xyz.playedu.api.types.JsonResponse;
-
-/**
- * @Author 杭州白书科技有限公司
- *
- * @create 2023/3/24 17:42
- */
@RestController
public class IndexController {
@GetMapping("/")
- public JsonResponse index() {
- return JsonResponse.success();
+ public String index() {
+ return "系统正在运行中...";
}
}
diff --git a/src/main/java/xyz/playedu/api/controller/frontend/LoginController.java b/src/main/java/xyz/playedu/api/controller/frontend/LoginController.java
index aaec818..92075b6 100644
--- a/src/main/java/xyz/playedu/api/controller/frontend/LoginController.java
+++ b/src/main/java/xyz/playedu/api/controller/frontend/LoginController.java
@@ -28,13 +28,14 @@ import xyz.playedu.api.domain.User;
import xyz.playedu.api.event.UserLoginEvent;
import xyz.playedu.api.event.UserLogoutEvent;
import xyz.playedu.api.exception.LimitException;
-import xyz.playedu.api.middleware.ImageCaptchaCheckMiddleware;
import xyz.playedu.api.request.frontend.LoginPasswordRequest;
import xyz.playedu.api.service.FrontendAuthService;
+import xyz.playedu.api.service.RateLimiterService;
import xyz.playedu.api.service.UserService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.util.HelperUtil;
import xyz.playedu.api.util.IpUtil;
+import xyz.playedu.api.util.RedisUtil;
import xyz.playedu.api.util.RequestUtil;
import java.util.HashMap;
@@ -49,8 +50,9 @@ public class LoginController {
@Autowired private ApplicationContext ctx;
+ @Autowired private RateLimiterService rateLimiterService;
+
@PostMapping("/password")
- @ImageCaptchaCheckMiddleware
public JsonResponse password(@RequestBody @Validated LoginPasswordRequest req)
throws LimitException {
String email = req.getEmail();
@@ -59,9 +61,19 @@ public class LoginController {
if (user == null) {
return JsonResponse.error("邮箱或密码错误");
}
+
+ String limitKey = "login-limit:" + req.getEmail();
+ Long reqCount = rateLimiterService.current(limitKey, 600L);
+ if (reqCount >= 10) {
+ return JsonResponse.error("登录失败次数过多,请稍候再试");
+ }
+
if (!HelperUtil.MD5(req.getPassword() + user.getSalt()).equals(user.getPassword())) {
return JsonResponse.error("邮箱或密码错误");
}
+
+ RedisUtil.del(limitKey);
+
if (user.getIsLock() == 1) {
return JsonResponse.error("当前学员已锁定无法登录");
}
diff --git a/src/main/java/xyz/playedu/api/controller/frontend/SystemController.java b/src/main/java/xyz/playedu/api/controller/frontend/SystemController.java
index f2d729e..49c5d36 100644
--- a/src/main/java/xyz/playedu/api/controller/frontend/SystemController.java
+++ b/src/main/java/xyz/playedu/api/controller/frontend/SystemController.java
@@ -22,27 +22,17 @@ import org.springframework.web.bind.annotation.RestController;
import xyz.playedu.api.constant.CConfig;
import xyz.playedu.api.service.AppConfigService;
-import xyz.playedu.api.service.ImageCaptchaService;
-import xyz.playedu.api.types.ImageCaptchaResult;
import xyz.playedu.api.types.JsonResponse;
-import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
-/**
- * @Author 杭州白书科技有限公司
- *
- * @create 2023/3/13 11:26
- */
@RestController
@RequestMapping("/api/v1/system")
public class SystemController {
@Autowired private AppConfigService appConfigService;
- @Autowired private ImageCaptchaService imageCaptchaService;
-
@GetMapping("/config")
public JsonResponse config() {
Map configs = appConfigService.keyValues();
@@ -65,15 +55,4 @@ public class SystemController {
return JsonResponse.data(data);
}
-
- @GetMapping("/image-captcha")
- public JsonResponse imageCaptcha() throws IOException {
- ImageCaptchaResult imageCaptchaResult = imageCaptchaService.generate();
-
- HashMap data = new HashMap<>();
- data.put("key", imageCaptchaResult.getKey());
- data.put("image", imageCaptchaResult.getImage());
-
- return JsonResponse.data(data);
- }
}
diff --git a/src/main/java/xyz/playedu/api/controller/frontend/UserController.java b/src/main/java/xyz/playedu/api/controller/frontend/UserController.java
index 97b7fab..8acf4f1 100644
--- a/src/main/java/xyz/playedu/api/controller/frontend/UserController.java
+++ b/src/main/java/xyz/playedu/api/controller/frontend/UserController.java
@@ -37,11 +37,6 @@ import xyz.playedu.api.util.PrivacyUtil;
import java.util.*;
import java.util.stream.Collectors;
-/**
- * @Author 杭州白书科技有限公司
- *
- * @create 2023/3/13 09:21
- */
@RestController
@RequestMapping("/api/v1/user")
@Slf4j
diff --git a/src/main/java/xyz/playedu/api/middleware/AdminMiddleware.java b/src/main/java/xyz/playedu/api/middleware/AdminMiddleware.java
index dc81cbb..359d365 100644
--- a/src/main/java/xyz/playedu/api/middleware/AdminMiddleware.java
+++ b/src/main/java/xyz/playedu/api/middleware/AdminMiddleware.java
@@ -25,14 +25,16 @@ import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import xyz.playedu.api.BCtx;
-import xyz.playedu.api.bus.AppBus;
import xyz.playedu.api.bus.BackendBus;
+import xyz.playedu.api.config.PlayEduConfig;
import xyz.playedu.api.domain.AdminUser;
import xyz.playedu.api.service.AdminUserService;
import xyz.playedu.api.service.AppConfigService;
import xyz.playedu.api.service.BackendAuthService;
+import xyz.playedu.api.service.RateLimiterService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.util.HelperUtil;
+import xyz.playedu.api.util.IpUtil;
import java.io.IOException;
import java.util.Map;
@@ -45,12 +47,14 @@ public class AdminMiddleware implements HandlerInterceptor {
@Autowired private AdminUserService adminUserService;
- @Autowired private AppBus appBus;
-
@Autowired private BackendBus backendBus;
@Autowired private AppConfigService configService;
+ @Autowired private RateLimiterService rateLimiterService;
+
+ @Autowired private PlayEduConfig playEduConfig;
+
@Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler)
@@ -59,6 +63,12 @@ public class AdminMiddleware implements HandlerInterceptor {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
+ String reqCountKey = "api-limiter:" + IpUtil.getIpAddress();
+ Long reqCount = rateLimiterService.current(reqCountKey, playEduConfig.getLimiterDuration());
+ if (reqCount > playEduConfig.getLimiterLimit()) {
+ return responseTransform(response, 429, "太多请求");
+ }
+
// 读取全局配置
Map systemConfig = configService.keyValues();
BCtx.setConfig(systemConfig);
diff --git a/src/main/java/xyz/playedu/api/middleware/BackendPermissionMiddleware.java b/src/main/java/xyz/playedu/api/middleware/BackendPermissionMiddleware.java
index c1a865c..92aea9a 100644
--- a/src/main/java/xyz/playedu/api/middleware/BackendPermissionMiddleware.java
+++ b/src/main/java/xyz/playedu/api/middleware/BackendPermissionMiddleware.java
@@ -17,11 +17,6 @@ package xyz.playedu.api.middleware;
import java.lang.annotation.*;
-/**
- * @Author 杭州白书科技有限公司
- *
- * @create 2023/2/21 16:40
- */
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
diff --git a/src/main/java/xyz/playedu/api/middleware/FrontMiddleware.java b/src/main/java/xyz/playedu/api/middleware/FrontMiddleware.java
index 1036770..dd52ba9 100644
--- a/src/main/java/xyz/playedu/api/middleware/FrontMiddleware.java
+++ b/src/main/java/xyz/playedu/api/middleware/FrontMiddleware.java
@@ -25,12 +25,15 @@ import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import xyz.playedu.api.FCtx;
+import xyz.playedu.api.config.PlayEduConfig;
import xyz.playedu.api.constant.FrontendConstant;
import xyz.playedu.api.domain.User;
import xyz.playedu.api.service.FrontendAuthService;
+import xyz.playedu.api.service.RateLimiterService;
import xyz.playedu.api.service.UserService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.util.HelperUtil;
+import xyz.playedu.api.util.IpUtil;
import java.io.IOException;
@@ -42,6 +45,10 @@ public class FrontMiddleware implements HandlerInterceptor {
@Autowired private UserService userService;
+ @Autowired private RateLimiterService rateLimiterService;
+
+ @Autowired private PlayEduConfig playEduConfig;
+
@Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler)
@@ -50,6 +57,12 @@ public class FrontMiddleware implements HandlerInterceptor {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
+ String reqCountKey = "api-limiter:" + IpUtil.getIpAddress();
+ Long reqCount = rateLimiterService.current(reqCountKey, playEduConfig.getLimiterDuration());
+ if (reqCount > playEduConfig.getLimiterLimit()) {
+ return responseTransform(response, 429, "太多请求");
+ }
+
if (FrontendConstant.UN_AUTH_URI_WHITELIST.contains(request.getRequestURI())) {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
diff --git a/src/main/java/xyz/playedu/api/middleware/ImageCaptchaCheckMiddleware.java b/src/main/java/xyz/playedu/api/middleware/ImageCaptchaCheckMiddleware.java
deleted file mode 100644
index 799834b..0000000
--- a/src/main/java/xyz/playedu/api/middleware/ImageCaptchaCheckMiddleware.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2023 杭州白书科技有限公司
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package xyz.playedu.api.middleware;
-
-import java.lang.annotation.*;
-
-@Documented
-@Target({ElementType.METHOD})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface ImageCaptchaCheckMiddleware {}
diff --git a/src/main/java/xyz/playedu/api/middleware/impl/BackendPermissionMiddlewareImpl.java b/src/main/java/xyz/playedu/api/middleware/impl/BackendPermissionMiddlewareImpl.java
index 1d14025..69ce647 100644
--- a/src/main/java/xyz/playedu/api/middleware/impl/BackendPermissionMiddlewareImpl.java
+++ b/src/main/java/xyz/playedu/api/middleware/impl/BackendPermissionMiddlewareImpl.java
@@ -32,11 +32,6 @@ import xyz.playedu.api.types.JsonResponse;
import java.util.HashMap;
-/**
- * @Author 杭州白书科技有限公司
- *
- * @create 2023/2/21 16:42
- */
@Aspect
@Component
@Slf4j
diff --git a/src/main/java/xyz/playedu/api/middleware/impl/ImageCaptchaCheckMiddlewareImpl.java b/src/main/java/xyz/playedu/api/middleware/impl/ImageCaptchaCheckMiddlewareImpl.java
deleted file mode 100644
index 50501ca..0000000
--- a/src/main/java/xyz/playedu/api/middleware/impl/ImageCaptchaCheckMiddlewareImpl.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 杭州白书科技有限公司
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package xyz.playedu.api.middleware.impl;
-
-import lombok.extern.slf4j.Slf4j;
-
-import org.aspectj.lang.ProceedingJoinPoint;
-import org.aspectj.lang.annotation.Around;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Pointcut;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import xyz.playedu.api.request.backend.types.ImageCaptchaRequestInterface;
-import xyz.playedu.api.service.ImageCaptchaService;
-import xyz.playedu.api.types.JsonResponse;
-
-@Aspect
-@Component
-@Slf4j
-public class ImageCaptchaCheckMiddlewareImpl {
- @Autowired private ImageCaptchaService imageCaptchaService;
-
- @Pointcut("@annotation(xyz.playedu.api.middleware.ImageCaptchaCheckMiddleware)")
- private void doPointcut() {}
-
- @Around("doPointcut()")
- public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
- ImageCaptchaRequestInterface request =
- (ImageCaptchaRequestInterface) joinPoint.getArgs()[0];
- if (!imageCaptchaService.verify(request.getCaptchaKey(), request.getCaptchaValue())) {
- return JsonResponse.error("图形验证码错误");
- }
- return joinPoint.proceed();
- }
-}
diff --git a/src/main/java/xyz/playedu/api/request/backend/LoginRequest.java b/src/main/java/xyz/playedu/api/request/backend/LoginRequest.java
index 51745fb..05799bd 100644
--- a/src/main/java/xyz/playedu/api/request/backend/LoginRequest.java
+++ b/src/main/java/xyz/playedu/api/request/backend/LoginRequest.java
@@ -15,19 +15,15 @@
*/
package xyz.playedu.api.request.backend;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import jakarta.validation.constraints.NotNull;
import lombok.Data;
-import xyz.playedu.api.request.backend.types.ImageCaptchaRequestInterface;
-
import java.io.Serial;
import java.io.Serializable;
@Data
-public class LoginRequest implements Serializable, ImageCaptchaRequestInterface {
+public class LoginRequest implements Serializable {
@Serial private static final long serialVersionUID = 1L;
@@ -36,12 +32,4 @@ public class LoginRequest implements Serializable, ImageCaptchaRequestInterface
@NotNull(message = "请输入密码")
public String password;
-
- @NotNull(message = "请输入图形验证码")
- @JsonProperty("captcha_value")
- public String captchaValue;
-
- @NotNull(message = "captchaKey参数为空")
- @JsonProperty("captcha_key")
- public String captchaKey;
}
diff --git a/src/main/java/xyz/playedu/api/request/frontend/LoginPasswordRequest.java b/src/main/java/xyz/playedu/api/request/frontend/LoginPasswordRequest.java
index 3d69777..9a31af1 100644
--- a/src/main/java/xyz/playedu/api/request/frontend/LoginPasswordRequest.java
+++ b/src/main/java/xyz/playedu/api/request/frontend/LoginPasswordRequest.java
@@ -15,33 +15,16 @@
*/
package xyz.playedu.api.request.frontend;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
-import xyz.playedu.api.request.backend.types.ImageCaptchaRequestInterface;
-
-/**
- * @Author 杭州白书科技有限公司
- *
- * @create 2023/3/10 13:13
- */
@Data
-public class LoginPasswordRequest implements ImageCaptchaRequestInterface {
+public class LoginPasswordRequest {
@NotBlank(message = "请输入邮箱")
private String email;
@NotBlank(message = "请输入密码")
private String password;
-
- @NotBlank(message = "请输入验证码")
- @JsonProperty("captcha_key")
- private String captchaKey;
-
- @NotBlank(message = "请输入验证码")
- @JsonProperty("captcha_val")
- private String captchaValue;
}
diff --git a/src/main/java/xyz/playedu/api/service/ImageCaptchaService.java b/src/main/java/xyz/playedu/api/service/ImageCaptchaService.java
deleted file mode 100644
index b1b2dd5..0000000
--- a/src/main/java/xyz/playedu/api/service/ImageCaptchaService.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 杭州白书科技有限公司
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package xyz.playedu.api.service;
-
-import xyz.playedu.api.types.ImageCaptchaResult;
-
-import java.io.IOException;
-
-public interface ImageCaptchaService {
-
- ImageCaptchaResult generate() throws IOException;
-
- boolean verify(String key, String code);
-}
diff --git a/src/main/java/xyz/playedu/api/request/backend/types/ImageCaptchaRequestInterface.java b/src/main/java/xyz/playedu/api/service/RateLimiterService.java
similarity index 80%
rename from src/main/java/xyz/playedu/api/request/backend/types/ImageCaptchaRequestInterface.java
rename to src/main/java/xyz/playedu/api/service/RateLimiterService.java
index e84f959..16d3097 100644
--- a/src/main/java/xyz/playedu/api/request/backend/types/ImageCaptchaRequestInterface.java
+++ b/src/main/java/xyz/playedu/api/service/RateLimiterService.java
@@ -13,11 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package xyz.playedu.api.request.backend.types;
+package xyz.playedu.api.service;
-public interface ImageCaptchaRequestInterface {
+public interface RateLimiterService {
- String getCaptchaValue();
-
- String getCaptchaKey();
+ public Long current(String key, Long seconds);
}
diff --git a/src/main/java/xyz/playedu/api/service/impl/ImageCaptchaServiceImpl.java b/src/main/java/xyz/playedu/api/service/impl/ImageCaptchaServiceImpl.java
deleted file mode 100644
index 9b7be22..0000000
--- a/src/main/java/xyz/playedu/api/service/impl/ImageCaptchaServiceImpl.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2023 杭州白书科技有限公司
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package xyz.playedu.api.service.impl;
-
-import cn.hutool.captcha.CaptchaUtil;
-import cn.hutool.captcha.LineCaptcha;
-
-import lombok.extern.slf4j.Slf4j;
-
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-
-import xyz.playedu.api.service.ImageCaptchaService;
-import xyz.playedu.api.types.ImageCaptchaResult;
-import xyz.playedu.api.util.HelperUtil;
-import xyz.playedu.api.util.RedisUtil;
-
-@Slf4j
-@Service
-public class ImageCaptchaServiceImpl implements ImageCaptchaService {
-
- @Value("${playedu.captcha.cache-prefix}")
- private String ConfigCachePrefix;
-
- @Value("${playedu.captcha.expire}")
- private Long ConfigExpire;
-
- @Override
- public ImageCaptchaResult generate() {
- ImageCaptchaResult imageCaptcha = new ImageCaptchaResult();
-
- // 生成验证码
- LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(240, 100, 4, 1);
-
- // 图形验证码的key[api是无状态的需要key来锁定验证码的值]
- String randomKey = HelperUtil.randomString(16);
- imageCaptcha.setKey(randomKey);
- imageCaptcha.setCode(lineCaptcha.getCode());
- imageCaptcha.setImage("data:image/png;base64," + lineCaptcha.getImageBase64());
-
- // 写入到redis中
- RedisUtil.set(getCacheKey(imageCaptcha.getKey()), imageCaptcha.getCode(), ConfigExpire);
-
- return imageCaptcha;
- }
-
- @Override
- public boolean verify(String key, String code) {
- String cacheKey = getCacheKey(key);
- Object queryResult = RedisUtil.get(cacheKey);
- if (queryResult == null) { // 未查找到[已过期 | 不存在]
- return false;
- }
- String cacheValue = (String) queryResult;
- boolean verifyResult = cacheValue.equalsIgnoreCase(code);
-
- if (verifyResult) { // 验证成功删除缓存->防止多次使用
- RedisUtil.del(cacheKey);
- }
-
- return verifyResult;
- }
-
- private String getCacheKey(String val) {
- return ConfigCachePrefix + val;
- }
-}
diff --git a/src/main/java/xyz/playedu/api/bus/AppBus.java b/src/main/java/xyz/playedu/api/service/impl/RateLimiterServiceImpl.java
similarity index 51%
rename from src/main/java/xyz/playedu/api/bus/AppBus.java
rename to src/main/java/xyz/playedu/api/service/impl/RateLimiterServiceImpl.java
index de9dc5f..09648c5 100644
--- a/src/main/java/xyz/playedu/api/bus/AppBus.java
+++ b/src/main/java/xyz/playedu/api/service/impl/RateLimiterServiceImpl.java
@@ -13,25 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package xyz.playedu.api.bus;
+package xyz.playedu.api.service.impl;
+
+import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
-import xyz.playedu.api.config.PlayEduConfig;
-import xyz.playedu.api.constant.SystemConstant;
+import xyz.playedu.api.service.RateLimiterService;
+import xyz.playedu.api.util.RedisUtil;
+
+import java.util.Arrays;
-/**
- * @Author 杭州白书科技有限公司
- *
- * @create 2023/2/19 12:06
- */
@Component
-public class AppBus {
+@Slf4j
+public class RateLimiterServiceImpl implements RateLimiterService {
- @Autowired private PlayEduConfig playEduConfig;
+ @Autowired
+ @Qualifier("rateLimiterScript")
+ private RedisScript redisScript;
- public boolean isDev() {
- return !playEduConfig.getEnv().equals(SystemConstant.ENV_PROD);
+ @Override
+ public Long current(String key, Long seconds) {
+ Long current = RedisUtil.handler().execute(redisScript, Arrays.asList(key, seconds + ""));
+ log.info("key={},count={}", key, current);
+ return current;
}
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 27b21d4..220cd27 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -64,10 +64,7 @@ sa-token:
jwt-secret-key: "playeduxyz"
token-prefix: "Bearer"
-# PlayEdu
playedu:
- # 图形验证码
- captcha:
- expire: 300 #有效期[单位:秒,默认5分钟]
- cache-prefix: "captcha:key:" #存储key的前缀
-
+ limiter:
+ duration: 60
+ limit: 120
diff --git a/src/main/resources/lua/RateLimiterScript.lua b/src/main/resources/lua/RateLimiterScript.lua
new file mode 100644
index 0000000..8c8ba50
--- /dev/null
+++ b/src/main/resources/lua/RateLimiterScript.lua
@@ -0,0 +1,6 @@
+local current
+current = redis.call("incr", KEYS[1])
+if tonumber(current) == 1 then
+ redis.call("expire", KEYS[1], KEYS[2])
+end
+return current
\ No newline at end of file