mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-08 16:11:05 +08:00
Compare commits
11 Commits
codex/remo
...
codex/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afa0c7fb8f | ||
|
|
1852f87341 | ||
|
|
7010e8a058 | ||
|
|
38ee37d5be | ||
|
|
e398d8e989 | ||
|
|
85e77c265e | ||
|
|
8abdc73497 | ||
|
|
747d9c07d1 | ||
|
|
09cefbedbf | ||
|
|
d772bc182f | ||
|
|
2110980797 |
@@ -42,6 +42,8 @@ public class CachingConfig {
|
||||
public static final String ONLINE_CACHE_NAME="openisle_online";
|
||||
// 注册验证码
|
||||
public static final String VERIFY_CACHE_NAME="openisle_verify";
|
||||
// 发帖频率限制
|
||||
public static final String LIMIT_CACHE_NAME="openisle_limit";
|
||||
|
||||
/**
|
||||
* 自定义Redis的序列化器
|
||||
|
||||
@@ -8,13 +8,18 @@ import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class OpenApiConfig {
|
||||
|
||||
private final SpringDocProperties springDocProperties;
|
||||
|
||||
@Value("${springdoc.info.title}")
|
||||
private String title;
|
||||
|
||||
@@ -30,20 +35,21 @@ public class OpenApiConfig {
|
||||
@Value("${springdoc.info.header}")
|
||||
private String header;
|
||||
|
||||
@Value("${springdoc.api-docs.server-url}")
|
||||
private String serverUrl;
|
||||
|
||||
@Bean
|
||||
public OpenAPI openAPI() {
|
||||
SecurityScheme securityScheme = new SecurityScheme()
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme(scheme.toLowerCase())
|
||||
.bearerFormat("JWT")
|
||||
.in(SecurityScheme.In.HEADER)
|
||||
.name(header);
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme(scheme.toLowerCase())
|
||||
.bearerFormat("JWT")
|
||||
.in(SecurityScheme.In.HEADER)
|
||||
.name(header);
|
||||
|
||||
List<Server> servers = springDocProperties.getServers().stream()
|
||||
.map(s -> new Server().url(s.getUrl()).description(s.getDescription()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new OpenAPI()
|
||||
.servers(List.of(new Server().url(serverUrl)))
|
||||
.servers(servers)
|
||||
.info(new Info()
|
||||
.title(title)
|
||||
.description(description)
|
||||
|
||||
@@ -90,6 +90,9 @@ public class SecurityConfig {
|
||||
"http://192.168.7.98",
|
||||
"http://192.168.7.98:3000",
|
||||
"https://petstore.swagger.io",
|
||||
// 允许自建OpenAPI地址
|
||||
"https://docs.open-isle.com",
|
||||
"https://www.docs.open-isle.com",
|
||||
websiteUrl,
|
||||
websiteUrl.replace("://www.", "://")
|
||||
));
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.openisle.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "springdoc.api-docs")
|
||||
public class SpringDocProperties {
|
||||
private List<ServerConfig> servers = new ArrayList<>();
|
||||
|
||||
@Data
|
||||
public static class ServerConfig {
|
||||
private String url;
|
||||
private String description;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.openisle.service;
|
||||
|
||||
import com.openisle.config.CachingConfig;
|
||||
import com.openisle.model.Post;
|
||||
import com.openisle.model.PostStatus;
|
||||
import com.openisle.model.PostType;
|
||||
@@ -28,12 +29,15 @@ import com.openisle.repository.PollVoteRepository;
|
||||
import com.openisle.model.Role;
|
||||
import com.openisle.exception.RateLimitException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import com.openisle.service.EmailSender;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.*;
|
||||
@@ -80,6 +84,8 @@ public class PostService {
|
||||
@Value("${app.website-url:https://www.open-isle.com}")
|
||||
private String websiteUrl;
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
@org.springframework.beans.factory.annotation.Autowired
|
||||
public PostService(PostRepository postRepository,
|
||||
UserRepository userRepository,
|
||||
@@ -102,7 +108,8 @@ public class PostService {
|
||||
ApplicationContext applicationContext,
|
||||
PointService pointService,
|
||||
PostChangeLogService postChangeLogService,
|
||||
@Value("${app.post.publish-mode:DIRECT}") PublishMode publishMode) {
|
||||
@Value("${app.post.publish-mode:DIRECT}") PublishMode publishMode,
|
||||
RedisTemplate redisTemplate) {
|
||||
this.postRepository = postRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.categoryRepository = categoryRepository;
|
||||
@@ -125,6 +132,8 @@ public class PostService {
|
||||
this.pointService = pointService;
|
||||
this.postChangeLogService = postChangeLogService;
|
||||
this.publishMode = publishMode;
|
||||
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
@@ -201,9 +210,9 @@ public class PostService {
|
||||
LocalDateTime endTime,
|
||||
java.util.List<String> options,
|
||||
Boolean multiple) {
|
||||
long recent = postRepository.countByAuthorAfter(username,
|
||||
java.time.LocalDateTime.now().minusMinutes(5));
|
||||
if (recent >= 1) {
|
||||
// 限制访问次数
|
||||
boolean limitResult = postRateLimit(username);
|
||||
if (!limitResult) {
|
||||
throw new RateLimitException("Too many posts");
|
||||
}
|
||||
if (tagIds == null || tagIds.isEmpty()) {
|
||||
@@ -300,6 +309,23 @@ public class PostService {
|
||||
return post;
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制发帖频率
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
private boolean postRateLimit(String username){
|
||||
String key = CachingConfig.LIMIT_CACHE_NAME +":posts:"+username;
|
||||
String result = (String)redisTemplate.opsForValue().get(key);
|
||||
//最近没有创建过文章
|
||||
if(StringUtils.isEmpty(result)){
|
||||
// 限制频率为5分钟
|
||||
redisTemplate.opsForValue().set(key,"1", Duration.ofMinutes(5));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void joinLottery(Long postId, String username) {
|
||||
LotteryPost post = lotteryPostRepository.findById(postId)
|
||||
.orElseThrow(() -> new com.openisle.exception.NotFoundException("Post not found"));
|
||||
|
||||
@@ -108,7 +108,10 @@ rabbitmq.sharding.enabled=true
|
||||
# see https://springdoc.org/#springdoc-openapi-core-properties
|
||||
springdoc.api-docs.path=/api/v3/api-docs
|
||||
springdoc.api-docs.enabled=true
|
||||
springdoc.api-docs.server-url=${WEBSITE_URL:https://www.open-isle.com}
|
||||
springdoc.api-docs.servers[0].url=${WEBSITE_URL:https://www.open-isle.com}
|
||||
springdoc.api-docs.servers[0].description=正式环境
|
||||
springdoc.api-docs.servers[1].url=https://www.staging.open-isle.com
|
||||
springdoc.api-docs.servers[1].description=预发环境
|
||||
springdoc.info.title=OpenIsle
|
||||
springdoc.info.description=OpenIsle Open API Documentation
|
||||
springdoc.info.version=0.0.1
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.openisle.exception.RateLimitException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@@ -38,11 +39,12 @@ class PostServiceTest {
|
||||
ApplicationContext context = mock(ApplicationContext.class);
|
||||
PointService pointService = mock(PointService.class);
|
||||
PostChangeLogService postChangeLogService = mock(PostChangeLogService.class);
|
||||
RedisTemplate redisTemplate = mock(RedisTemplate.class);
|
||||
|
||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, postChangeLogService, PublishMode.DIRECT);
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, postChangeLogService, PublishMode.DIRECT, redisTemplate);
|
||||
when(context.getBean(PostService.class)).thenReturn(service);
|
||||
|
||||
Post post = new Post();
|
||||
@@ -88,11 +90,12 @@ class PostServiceTest {
|
||||
ApplicationContext context = mock(ApplicationContext.class);
|
||||
PointService pointService = mock(PointService.class);
|
||||
PostChangeLogService postChangeLogService = mock(PostChangeLogService.class);
|
||||
RedisTemplate redisTemplate = mock(RedisTemplate.class);
|
||||
|
||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, postChangeLogService, PublishMode.DIRECT);
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, postChangeLogService, PublishMode.DIRECT, redisTemplate);
|
||||
when(context.getBean(PostService.class)).thenReturn(service);
|
||||
|
||||
Post post = new Post();
|
||||
@@ -144,11 +147,12 @@ class PostServiceTest {
|
||||
ApplicationContext context = mock(ApplicationContext.class);
|
||||
PointService pointService = mock(PointService.class);
|
||||
PostChangeLogService postChangeLogService = mock(PostChangeLogService.class);
|
||||
RedisTemplate redisTemplate = mock(RedisTemplate.class);
|
||||
|
||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, postChangeLogService, PublishMode.DIRECT);
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, postChangeLogService, PublishMode.DIRECT, redisTemplate);
|
||||
when(context.getBean(PostService.class)).thenReturn(service);
|
||||
|
||||
when(postRepo.countByAuthorAfter(eq("alice"), any())).thenReturn(1L);
|
||||
@@ -181,11 +185,12 @@ class PostServiceTest {
|
||||
ApplicationContext context = mock(ApplicationContext.class);
|
||||
PointService pointService = mock(PointService.class);
|
||||
PostChangeLogService postChangeLogService = mock(PostChangeLogService.class);
|
||||
RedisTemplate redisTemplate = mock(RedisTemplate.class);
|
||||
|
||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, postChangeLogService, PublishMode.DIRECT);
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, postChangeLogService, PublishMode.DIRECT, redisTemplate);
|
||||
when(context.getBean(PostService.class)).thenReturn(service);
|
||||
|
||||
User author = new User();
|
||||
|
||||
Reference in New Issue
Block a user