Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
basic-api-boot
概览
概览
详情
活动
周期分析
版本库
存储库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
Basic
basic-api-boot
Commits
7496710a
提交
7496710a
authored
12月 19, 2023
作者:
方治民
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 新增 RateLimiter 接口限流注解防刷实现
上级
87c23970
隐藏空白字符变更
内嵌
并排
正在显示
13 个修改的文件
包含
159 行增加
和
9 行删除
+159
-9
ExampleController.java
...in/java/com/yiring/app/web/example/ExampleController.java
+2
-0
AuthController.java
...rc/main/java/com/yiring/auth/web/auth/AuthController.java
+5
-0
status.properties
basic-auth/src/main/resources/i18n/status.properties
+1
-0
status_zh_CN.properties
basic-auth/src/main/resources/i18n/status_zh_CN.properties
+1
-0
build.gradle
basic-common/core/build.gradle
+2
-0
RateLimiter.java
...c/main/java/com/yiring/common/annotation/RateLimiter.java
+36
-0
RateLimiterAspect.java
...main/java/com/yiring/common/aspect/RateLimiterAspect.java
+93
-0
Status.java
...mon/core/src/main/java/com/yiring/common/core/Status.java
+11
-7
I18nConfig.java
...8n/src/main/java/com/yiring/common/config/I18nConfig.java
+1
-1
status.properties
basic-common/i18n/src/main/resources/i18n/status.properties
+1
-0
status_zh_CN.properties
...mmon/i18n/src/main/resources/i18n/status_zh_CN.properties
+1
-0
Redis.java
...mon/redis/src/main/java/com/yiring/common/core/Redis.java
+4
-0
Commons.java
...on/util/src/main/java/com/yiring/common/util/Commons.java
+1
-1
没有找到文件。
app/src/main/java/com/yiring/app/web/example/ExampleController.java
浏览文件 @
7496710a
...
...
@@ -11,6 +11,7 @@ import com.yiring.app.vo.user.UserExtensionVo;
import
com.yiring.auth.domain.user.User
;
import
com.yiring.auth.util.Auths
;
import
com.yiring.common.annotation.DownloadResponse
;
import
com.yiring.common.annotation.RateLimiter
;
import
com.yiring.common.core.I18n
;
import
com.yiring.common.core.Result
;
import
com.yiring.common.core.Status
;
...
...
@@ -61,6 +62,7 @@ public class ExampleController {
final
UserExtensionRepository
userExtensionRepository
;
final
FileManageService
fileManageService
;
@RateLimiter
(
count
=
1
)
@Operation
(
summary
=
"Hello World"
)
@GetMapping
public
Result
<
String
>
hello
()
{
...
...
basic-auth/src/main/java/com/yiring/auth/web/auth/AuthController.java
浏览文件 @
7496710a
...
...
@@ -12,6 +12,7 @@ import com.yiring.auth.param.auth.RegisterParam;
import
com.yiring.auth.param.auth.SafeParam
;
import
com.yiring.auth.util.Auths
;
import
com.yiring.auth.vo.auth.LoginVo
;
import
com.yiring.common.annotation.RateLimiter
;
import
com.yiring.common.core.Result
;
import
com.yiring.common.exception.BusinessException
;
import
com.yiring.common.util.Commons
;
...
...
@@ -54,6 +55,7 @@ public class AuthController {
final
Auths
auths
;
final
UserRepository
userRepository
;
@RateLimiter
(
time
=
1
,
count
=
1
)
@Operation
(
summary
=
"注册"
)
@PostMapping
(
value
=
"register"
)
public
Result
<
String
>
register
(
@ParameterObject
@Validated
RegisterParam
param
)
{
...
...
@@ -91,6 +93,7 @@ public class AuthController {
return
Result
.
ok
();
}
@RateLimiter
(
count
=
3
)
@Operation
(
summary
=
"登录"
)
@PostMapping
(
"login"
)
public
Result
<
LoginVo
>
login
(
@ParameterObject
@Validated
LoginParam
param
,
HttpServletRequest
request
)
{
...
...
@@ -135,6 +138,7 @@ public class AuthController {
return
Result
.
ok
(
StpUtil
.
isLogin
());
}
@RateLimiter
(
count
=
3
)
@Operation
(
summary
=
"登出"
)
@GetMapping
(
"logout"
)
public
Result
<
String
>
logout
()
{
...
...
@@ -149,6 +153,7 @@ public class AuthController {
* @param param 用户密码
* @link { <a href="https://sa-token.dev33.cn/doc.html#/up/safe-auth">...</a> }
*/
@RateLimiter
(
count
=
3
)
@SaCheckLogin
@Operation
(
summary
=
"安全验证"
)
@GetMapping
(
"safe"
)
...
...
basic-auth/src/main/resources/i18n/status.properties
浏览文件 @
7496710a
...
...
@@ -6,6 +6,7 @@ Status.FORBIDDEN=Forbidden
Status.NOT_FOUND
=
Not Found
Status.METHOD_NOT_ALLOWED
=
Method Not Allowed
Status.EXPECTATION_FAILED
=
Expectation Failed
Status.TOO_MANY_REQUESTS
=
Too Many Requests
Status.INTERNAL_SERVER_ERROR
=
Internal Server Error
Status.UNKNOWN_ERROR
=
Unknown Error
Status.NOT_IMPLEMENTED
=
Not Implemented
...
...
basic-auth/src/main/resources/i18n/status_zh_CN.properties
浏览文件 @
7496710a
...
...
@@ -6,6 +6,7 @@ Status.FORBIDDEN=\u7981\u6B62\u8BBF\u95EE
Status.NOT_FOUND
=
\u
627E
\u
4E0D
\u5230\u
8D44
\u
6E90
Status.METHOD_NOT_ALLOWED
=
\u
4E0D
\u
652F
\u6301\u7684\u
8BF7
\u
6C42
\u
7C7B
\u
578B
Status.EXPECTATION_FAILED
=
\u
65E0
\u6548\u
53C2
\u6570
Status.TOO_MANY_REQUESTS
=
\u
8BF7
\u
6C42
\u
8FC7
\u
4E8E
\u9891\u
7E41
Status.INTERNAL_SERVER_ERROR
=
\u
670D
\u
52A1
\u5668\u9519\u
8BEF
Status.UNKNOWN_ERROR
=
\u
672A
\u
77E5
\u9519\u
8BEF
Status.NOT_IMPLEMENTED
=
API
\u
672A
\u
5B9E
\u
73B0
...
...
basic-common/core/build.gradle
浏览文件 @
7496710a
dependencies
{
implementation
project
(
":basic-common:util"
)
implementation
project
(
":basic-common:i18n"
)
implementation
project
(
":basic-common:redis"
)
implementation
'org.springframework.boot:spring-boot-starter-aop'
implementation
'org.springframework.boot:spring-boot-starter-web'
implementation
'org.springframework.boot:spring-boot-starter-data-jpa'
implementation
'org.springframework.boot:spring-boot-starter-data-redis'
implementation
'org.springframework.boot:spring-boot-starter-validation'
// swagger(knife4j)
...
...
basic-common/core/src/main/java/com/yiring/common/annotation/RateLimiter.java
0 → 100644
浏览文件 @
7496710a
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
common
.
annotation
;
import
java.lang.annotation.ElementType
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
import
java.lang.annotation.Target
;
/**
* 流控注解
*
* @author Jim
* @version 0.1
* 2023/12/19 15:20
*/
@SuppressWarnings
({
"unused"
})
@Retention
(
RetentionPolicy
.
RUNTIME
)
@Target
(
ElementType
.
METHOD
)
public
@interface
RateLimiter
{
String
RATE_LIMIT_KEY
=
"rate_limit:"
;
/**
* 限流key
*/
String
key
()
default
RATE_LIMIT_KEY
;
/**
* 限流时间,单位秒
*/
int
time
()
default
5
;
/**
* 限流次数
*/
int
count
()
default
10
;
}
basic-common/core/src/main/java/com/yiring/common/aspect/RateLimiterAspect.java
0 → 100644
浏览文件 @
7496710a
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
common
.
aspect
;
import
com.yiring.common.annotation.RateLimiter
;
import
com.yiring.common.core.Redis
;
import
com.yiring.common.core.Status
;
import
com.yiring.common.util.Commons
;
import
jakarta.servlet.http.HttpServletRequest
;
import
java.lang.reflect.Method
;
import
java.util.Objects
;
import
java.util.concurrent.TimeUnit
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.aspectj.lang.JoinPoint
;
import
org.aspectj.lang.annotation.Aspect
;
import
org.aspectj.lang.annotation.Before
;
import
org.aspectj.lang.reflect.MethodSignature
;
import
org.springframework.data.redis.core.ZSetOperations
;
import
org.springframework.stereotype.Component
;
import
org.springframework.web.context.request.RequestAttributes
;
import
org.springframework.web.context.request.RequestContextHolder
;
/**
* 流控切面
*
* @author Jim
* @version 0.1
* 2023/12/19 15:17
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public
class
RateLimiterAspect
{
final
Redis
redis
;
/**
* 带有注解的方法之前执行
*/
@Before
(
"@annotation(rateLimiter)"
)
public
void
doBefore
(
JoinPoint
point
,
RateLimiter
rateLimiter
)
{
int
time
=
rateLimiter
.
time
();
int
count
=
rateLimiter
.
count
();
// 将接口方法和用户IP构建Redis的key
String
key
=
getRateLimiterKey
(
rateLimiter
.
key
(),
point
);
// 使用 ZSet 的 score 设置成用户访问接口的时间戳
ZSetOperations
<
String
,
Object
>
zSetOperations
=
redis
.
getTemplate
().
opsForZSet
();
// 当前时间戳
long
currentTime
=
System
.
currentTimeMillis
();
zSetOperations
.
add
(
key
,
currentTime
,
currentTime
);
// 设置过期时间防止 key 不消失
redis
.
getTemplate
().
expire
(
key
,
time
,
TimeUnit
.
SECONDS
);
// 移除 time 秒之前的访问记录,动态时间段
zSetOperations
.
removeRangeByScore
(
key
,
0
,
currentTime
-
time
*
1000L
);
// 获得当前时间窗口内的访问记录数
Long
currentCount
=
zSetOperations
.
zCard
(
key
);
// 限流判断
if
(
Objects
.
nonNull
(
currentCount
)
&&
currentCount
>
count
)
{
log
.
warn
(
"[Request RateLimit] 接口限流, Key: {}, count: {}, currentCount: {}"
,
key
,
count
,
currentCount
);
throw
Status
.
TOO_MANY_REQUESTS
.
exception
();
}
}
/**
* 组装 redis 的 key
*/
private
String
getRateLimiterKey
(
String
prefixKey
,
JoinPoint
point
)
{
StringBuilder
sb
=
new
StringBuilder
(
prefixKey
);
HttpServletRequest
request
=
getRequest
();
sb
.
append
(
Commons
.
getClientIpAddress
(
request
));
MethodSignature
signature
=
(
MethodSignature
)
point
.
getSignature
();
Method
method
=
signature
.
getMethod
();
Class
<?>
targetClass
=
method
.
getDeclaringClass
();
return
sb
.
append
(
"_"
).
append
(
targetClass
.
getName
()).
append
(
"_"
).
append
(
method
.
getName
()).
toString
();
}
/**
* 获取 HttpServletRequest
*/
private
HttpServletRequest
getRequest
()
{
RequestAttributes
requestAttributes
=
RequestContextHolder
.
getRequestAttributes
();
assert
requestAttributes
!=
null
;
return
(
HttpServletRequest
)
requestAttributes
.
resolveReference
(
RequestAttributes
.
REFERENCE_REQUEST
);
}
}
basic-common/core/src/main/java/com/yiring/common/core/Status.java
浏览文件 @
7496710a
...
...
@@ -2,6 +2,7 @@
package
com
.
yiring
.
common
.
core
;
import
com.yiring.common.exception.FailStatusException
;
import
lombok.Getter
;
import
org.jetbrains.annotations.PropertyKey
;
import
org.springframework.lang.Nullable
;
...
...
@@ -55,6 +56,11 @@ public enum Status {
EXPECTATION_FAILED
(
417
,
"Status.EXPECTATION_FAILED"
),
/**
* 请求过于频繁
*/
TOO_MANY_REQUESTS
(
429
,
"Status.TOO_MANY_REQUESTS"
),
/**
* 服务器错误
*/
INTERNAL_SERVER_ERROR
(
500
,
"Status.INTERNAL_SERVER_ERROR"
),
...
...
@@ -81,6 +87,11 @@ public enum Status {
private
final
int
value
;
/**
* -- GETTER --
* Return the reason phrase of this status code.
*/
@Getter
private
final
String
reasonPhrase
;
Status
(
int
value
,
@PropertyKey
(
resourceBundle
=
"i18n.status"
)
String
reasonPhrase
)
{
...
...
@@ -128,13 +139,6 @@ public enum Status {
}
/**
* Return the reason phrase of this status code.
*/
public
String
getReasonPhrase
()
{
return
this
.
reasonPhrase
;
}
/**
* Return a string representation of this status code.
*/
@Override
...
...
basic-common/i18n/src/main/java/com/yiring/common/config/I18nConfig.java
浏览文件 @
7496710a
...
...
@@ -36,7 +36,7 @@ public class I18nConfig {
@Bean
public
MessageSource
messageSource
()
{
SmReloadableResourceBundleMessageSource
messageSource
=
new
SmReloadableResourceBundleMessageSource
();
messageSource
.
setBasenames
(
"classpath*:i18n/messages"
,
"classpath:i18n/status"
,
"classpath:i18n/validation"
);
messageSource
.
setBasenames
(
"classpath*:i18n/messages"
,
"classpath
*
:i18n/status"
,
"classpath:i18n/validation"
);
messageSource
.
setDefaultEncoding
(
"UTF-8"
);
messageSource
.
setAlwaysUseMessageFormat
(
true
);
messageSource
.
setDefaultLocale
(
DEFAULT_LOCALE
);
...
...
basic-common/i18n/src/main/resources/i18n/status.properties
浏览文件 @
7496710a
...
...
@@ -6,6 +6,7 @@ Status.FORBIDDEN=Forbidden
Status.NOT_FOUND
=
Not Found
Status.METHOD_NOT_ALLOWED
=
Method Not Allowed
Status.EXPECTATION_FAILED
=
Expectation Failed
Status.TOO_MANY_REQUESTS
=
Too Many Requests
Status.INTERNAL_SERVER_ERROR
=
Internal Server Error
Status.UNKNOWN_ERROR
=
Unknown Error
Status.NOT_IMPLEMENTED
=
Not Implemented
...
...
basic-common/i18n/src/main/resources/i18n/status_zh_CN.properties
浏览文件 @
7496710a
...
...
@@ -6,6 +6,7 @@ Status.FORBIDDEN=\u7981\u6B62\u8BBF\u95EE
Status.NOT_FOUND
=
\u
627E
\u
4E0D
\u5230\u
8D44
\u
6E90
Status.METHOD_NOT_ALLOWED
=
\u
4E0D
\u
652F
\u6301\u7684\u
8BF7
\u
6C42
\u
7C7B
\u
578B
Status.EXPECTATION_FAILED
=
\u
65E0
\u6548\u
53C2
\u6570
Status.TOO_MANY_REQUESTS
=
\u
8BF7
\u
6C42
\u
8FC7
\u
4E8E
\u9891\u
7E41
Status.INTERNAL_SERVER_ERROR
=
\u
670D
\u
52A1
\u5668\u9519\u
8BEF
Status.UNKNOWN_ERROR
=
\u
672A
\u
77E5
\u9519\u
8BEF
Status.NOT_IMPLEMENTED
=
API
\u
672A
\u
5B9E
\u
73B0
...
...
basic-common/redis/src/main/java/com/yiring/common/core/Redis.java
浏览文件 @
7496710a
...
...
@@ -481,4 +481,8 @@ public final class Redis {
.
info
();
return
Convert
.
toStr
(
info
);
}
public
RedisTemplate
<
String
,
Object
>
getTemplate
()
{
return
redisTemplate
;
}
}
basic-common/util/src/main/java/com/yiring/common/util/Commons.java
浏览文件 @
7496710a
...
...
@@ -48,7 +48,7 @@ public class Commons {
public
String
getClientIpAddress
(
HttpServletRequest
request
)
{
for
(
String
header
:
HEADERS_TO_TRY
)
{
String
ip
=
request
.
getHeader
(
header
);
if
(
ip
!=
null
&&
ip
.
length
()
!=
0
&&
!
"unknown"
.
equalsIgnoreCase
(
ip
))
{
if
(
ip
!=
null
&&
!
ip
.
isEmpty
()
&&
!
"unknown"
.
equalsIgnoreCase
(
ip
))
{
return
ip
;
}
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论