mirror of https://github.com/halo-dev/halo
feat: add rate limiter for comment endpoint (#4084)
#### What type of PR is this? /kind feature /kind core #### What this PR does / why we need it: This PR limited comment creation at a rate of 10 per minute. See https://github.com/halo-dev/halo/issues/4044 for more. #### Special notes for your reviewer: 1. Start Halo. 2. Create 11 new comments 3. Check the response. #### Does this PR introduce a user-facing change? ```release-note 增加发表评论频率限制功能 ```pull/4119/head
parent
f37085f5a6
commit
d28f6075c1
|
@ -10,6 +10,8 @@ import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
|||
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
|
||||
import io.github.resilience4j.reactor.ratelimiter.operator.RateLimiterOperator;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
@ -20,6 +22,7 @@ import lombok.RequiredArgsConstructor;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springdoc.core.fn.builders.schema.Builder;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -65,6 +68,8 @@ public class CommentFinderEndpoint implements CustomEndpoint {
|
|||
private final CommentService commentService;
|
||||
private final ReplyService replyService;
|
||||
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||
private final RateLimiterRegistry rateLimiterRegistry;
|
||||
private final MessageSource messageSource;
|
||||
|
||||
@Override
|
||||
public RouterFunction<ServerResponse> endpoint() {
|
||||
|
@ -152,9 +157,17 @@ public class CommentFinderEndpoint implements CustomEndpoint {
|
|||
comment.getSpec().setUserAgent(HaloUtils.userAgentFrom(request));
|
||||
return commentService.create(comment);
|
||||
})
|
||||
.transformDeferred(createIpBasedRateLimiter(request))
|
||||
.flatMap(comment -> ServerResponse.ok().bodyValue(comment));
|
||||
}
|
||||
|
||||
private <T> RateLimiterOperator<T> createIpBasedRateLimiter(ServerRequest request) {
|
||||
var clientIp = IpAddressUtils.getIpAddress(request);
|
||||
var rateLimiter = rateLimiterRegistry.rateLimiter("comment-creation-from-ip-" + clientIp,
|
||||
"comment-creation");
|
||||
return RateLimiterOperator.of(rateLimiter);
|
||||
}
|
||||
|
||||
Mono<ServerResponse> createReply(ServerRequest request) {
|
||||
String commentName = request.pathVariable("name");
|
||||
return request.bodyToMono(ReplyRequest.class)
|
||||
|
|
|
@ -73,3 +73,8 @@ resilience4j.ratelimiter:
|
|||
limitForPeriod: 3
|
||||
limitRefreshPeriod: 1m
|
||||
timeoutDuration: 0
|
||||
comment-creation:
|
||||
limitForPeriod: 10
|
||||
limitRefreshPeriod: 1m
|
||||
timeoutDuration: 10s
|
||||
|
||||
|
|
|
@ -3,12 +3,17 @@ package run.halo.app.theme.endpoint;
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.github.resilience4j.ratelimiter.RateLimiter;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -54,6 +59,9 @@ class CommentFinderEndpointTest {
|
|||
@Mock
|
||||
private ReplyService replyService;
|
||||
|
||||
@Mock
|
||||
private RateLimiterRegistry rateLimiterRegistry;
|
||||
|
||||
@InjectMocks
|
||||
private CommentFinderEndpoint commentFinderEndpoint;
|
||||
|
||||
|
@ -132,6 +140,15 @@ class CommentFinderEndpointTest {
|
|||
void createComment() {
|
||||
when(commentService.create(any())).thenReturn(Mono.empty());
|
||||
|
||||
RateLimiterConfig config = RateLimiterConfig.custom()
|
||||
.limitForPeriod(10)
|
||||
.limitRefreshPeriod(Duration.ofSeconds(1))
|
||||
.timeoutDuration(Duration.ofSeconds(10))
|
||||
.build();
|
||||
RateLimiter rateLimiter = RateLimiter.of("comment-creation-from-ip-" + "0:0:0:0:0:0:0:0",
|
||||
config);
|
||||
when(rateLimiterRegistry.rateLimiter(anyString(), anyString())).thenReturn(rateLimiter);
|
||||
|
||||
final CommentRequest commentRequest = new CommentRequest();
|
||||
Ref ref = new Ref();
|
||||
ref.setGroup("content.halo.run");
|
||||
|
|
Loading…
Reference in New Issue