diff --git a/backend/src/main/java/com/openisle/config/CachingConfig.java b/backend/src/main/java/com/openisle/config/CachingConfig.java index 7497d1024..9094efa1a 100644 --- a/backend/src/main/java/com/openisle/config/CachingConfig.java +++ b/backend/src/main/java/com/openisle/config/CachingConfig.java @@ -46,6 +46,8 @@ public class CachingConfig { public static final String LIMIT_CACHE_NAME="openisle_limit"; // 用户访问统计 public static final String VISIT_CACHE_NAME="openisle_visit"; + // 文章缓存 + public static final String POST_CACHE_NAME="openisle_posts"; /** * 自定义Redis的序列化器 @@ -65,7 +67,10 @@ public class CachingConfig { // Hibernate6Module 可以自动处理懒加载代理对象。 // Tag对象的creator是FetchType.LAZY objectMapper.registerModule(new Hibernate6Module() - .disable(Hibernate6Module.Feature.USE_TRANSIENT_ANNOTATION)); + .disable(Hibernate6Module.Feature.USE_TRANSIENT_ANNOTATION) + // 将 Hibernate 特有的集合类型转换为标准 Java 集合类型 + // 避免序列化时出现 org.hibernate.collection.spi.PersistentSet 这样的类型信息 + .configure(Hibernate6Module.Feature.REPLACE_PERSISTENT_COLLECTIONS, true)); // service的时候带上类型信息 // 启用类型信息,避免 LinkedHashMap 问题 objectMapper.activateDefaultTyping( diff --git a/backend/src/main/java/com/openisle/controller/PostController.java b/backend/src/main/java/com/openisle/controller/PostController.java index 002455900..907358c30 100644 --- a/backend/src/main/java/com/openisle/controller/PostController.java +++ b/backend/src/main/java/com/openisle/controller/PostController.java @@ -27,6 +27,8 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class PostController { private final PostService postService; + private final CategoryService categoryService; + private final TagService tagService; private final LevelService levelService; private final CaptchaService captchaService; private final DraftService draftService; @@ -147,33 +149,16 @@ public class PostController { @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "pageSize", required = false) Integer pageSize, Authentication auth) { - List ids = categoryIds; - if (categoryId != null) { - ids = java.util.List.of(categoryId); - } - List tids = tagIds; - if (tagId != null) { - tids = java.util.List.of(tagId); - } + + List ids = categoryService.getSearchCategoryIds(categoryIds, categoryId); + List tids = tagService.getSearchTagIds(tagIds, tagId); // 只需要在请求的一开始统计一次 // if (auth != null) { // userVisitService.recordVisit(auth.getName()); // } - boolean hasCategories = ids != null && !ids.isEmpty(); - boolean hasTags = tids != null && !tids.isEmpty(); - - if (hasCategories && hasTags) { - return postService.listPostsByCategoriesAndTags(ids, tids, page, pageSize) - .stream().map(postMapper::toSummaryDto).collect(Collectors.toList()); - } - if (hasTags) { - return postService.listPostsByTags(tids, page, pageSize) - .stream().map(postMapper::toSummaryDto).collect(Collectors.toList()); - } - - return postService.listPostsByCategories(ids, page, pageSize) - .stream().map(postMapper::toSummaryDto).collect(Collectors.toList()); + return postService.defaultListPosts(ids,tids,page, pageSize).stream() + .map(postMapper::toSummaryDto).collect(Collectors.toList()); } @GetMapping("/ranking") @@ -187,14 +172,9 @@ public class PostController { @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "pageSize", required = false) Integer pageSize, Authentication auth) { - List ids = categoryIds; - if (categoryId != null) { - ids = java.util.List.of(categoryId); - } - List tids = tagIds; - if (tagId != null) { - tids = java.util.List.of(tagId); - } + + List ids = categoryService.getSearchCategoryIds(categoryIds, categoryId); + List tids = tagService.getSearchTagIds(tagIds, tagId); // 只需要在请求的一开始统计一次 // if (auth != null) { // userVisitService.recordVisit(auth.getName()); @@ -215,14 +195,9 @@ public class PostController { @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "pageSize", required = false) Integer pageSize, Authentication auth) { - List ids = categoryIds; - if (categoryId != null) { - ids = java.util.List.of(categoryId); - } - List tids = tagIds; - if (tagId != null) { - tids = java.util.List.of(tagId); - } + + List ids = categoryService.getSearchCategoryIds(categoryIds, categoryId); + List tids = tagService.getSearchTagIds(tagIds, tagId); // 只需要在请求的一开始统计一次 // if (auth != null) { // userVisitService.recordVisit(auth.getName()); @@ -243,14 +218,9 @@ public class PostController { @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "pageSize", required = false) Integer pageSize, Authentication auth) { - List ids = categoryIds; - if (categoryId != null) { - ids = java.util.List.of(categoryId); - } - List tids = tagIds; - if (tagId != null) { - tids = java.util.List.of(tagId); - } + + List ids = categoryService.getSearchCategoryIds(categoryIds, categoryId); + List tids = tagService.getSearchTagIds(tagIds, tagId); // 只需要在请求的一开始统计一次 // if (auth != null) { // userVisitService.recordVisit(auth.getName()); diff --git a/backend/src/main/java/com/openisle/model/Post.java b/backend/src/main/java/com/openisle/model/Post.java index 3dcbbab3c..7e5df2a4f 100644 --- a/backend/src/main/java/com/openisle/model/Post.java +++ b/backend/src/main/java/com/openisle/model/Post.java @@ -39,19 +39,19 @@ public class Post { columnDefinition = "DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6)") private LocalDateTime createdAt; - @ManyToOne(optional = false, fetch = FetchType.LAZY) + @ManyToOne(optional = false, fetch = FetchType.EAGER) @JoinColumn(name = "author_id") private User author; - @ManyToOne(optional = false, fetch = FetchType.LAZY) + @ManyToOne(optional = false, fetch = FetchType.EAGER) @JoinColumn(name = "category_id") private Category category; - @ManyToMany(fetch = FetchType.LAZY) + @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "post_tags", joinColumns = @JoinColumn(name = "post_id"), inverseJoinColumns = @JoinColumn(name = "tag_id")) - private java.util.Set tags = new java.util.HashSet<>(); + private Set tags = new HashSet<>(); @Column(nullable = false) private long views = 0; diff --git a/backend/src/main/java/com/openisle/service/CategoryService.java b/backend/src/main/java/com/openisle/service/CategoryService.java index 9486bc88c..309ae7f0d 100644 --- a/backend/src/main/java/com/openisle/service/CategoryService.java +++ b/backend/src/main/java/com/openisle/service/CategoryService.java @@ -62,4 +62,18 @@ public class CategoryService { public List listCategories() { return categoryRepository.findAll(); } + + /** + * 获取检索用的分类Id列表 + * @param categoryIds + * @param categoryId + * @return + */ + public List getSearchCategoryIds(List categoryIds, Long categoryId){ + List ids = categoryIds; + if (categoryId != null) { + ids = List.of(categoryId); + } + return ids; + } } diff --git a/backend/src/main/java/com/openisle/service/CommentService.java b/backend/src/main/java/com/openisle/service/CommentService.java index 1076aa5e2..e9456caf7 100644 --- a/backend/src/main/java/com/openisle/service/CommentService.java +++ b/backend/src/main/java/com/openisle/service/CommentService.java @@ -1,5 +1,6 @@ package com.openisle.service; +import com.openisle.config.CachingConfig; import com.openisle.model.Comment; import com.openisle.model.Post; import com.openisle.model.User; @@ -20,6 +21,8 @@ import com.openisle.model.Role; import com.openisle.exception.RateLimitException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.util.List; @@ -47,6 +50,10 @@ public class CommentService { private final PointService pointService; private final ImageUploader imageUploader; + @CacheEvict( + value = CachingConfig.POST_CACHE_NAME, + key = "'latest_reply'" + ) @Transactional public Comment addComment(String username, Long postId, String content) { log.debug("addComment called by user {} for post {}", username, postId); @@ -95,6 +102,10 @@ public class CommentService { return commentRepository.findLastCommentTimeOfUserByUserId(userId); } + @CacheEvict( + value = CachingConfig.POST_CACHE_NAME, + key = "'latest_reply'" + ) @Transactional public Comment addReply(String username, Long parentId, String content) { log.debug("addReply called by user {} for parent comment {}", username, parentId); @@ -228,6 +239,10 @@ public class CommentService { return count; } + @CacheEvict( + value = CachingConfig.POST_CACHE_NAME, + key = "'latest_reply'" + ) @Transactional public void deleteComment(String username, Long id) { log.debug("deleteComment called by user {} for comment {}", username, id); @@ -243,6 +258,10 @@ public class CommentService { log.debug("deleteComment completed for comment {}", id); } + @CacheEvict( + value = CachingConfig.POST_CACHE_NAME, + key = "'latest_reply'" + ) @Transactional public void deleteCommentCascade(Comment comment) { log.debug("deleteCommentCascade called for comment {}", comment.getId()); diff --git a/backend/src/main/java/com/openisle/service/PostService.java b/backend/src/main/java/com/openisle/service/PostService.java index 16162d32a..65092d0b7 100644 --- a/backend/src/main/java/com/openisle/service/PostService.java +++ b/backend/src/main/java/com/openisle/service/PostService.java @@ -1,17 +1,8 @@ package com.openisle.service; import com.openisle.config.CachingConfig; -import com.openisle.model.Post; -import com.openisle.model.PostStatus; -import com.openisle.model.PostType; -import com.openisle.model.PublishMode; -import com.openisle.model.User; -import com.openisle.model.Category; -import com.openisle.model.Comment; -import com.openisle.model.NotificationType; -import com.openisle.model.LotteryPost; -import com.openisle.model.PollPost; -import com.openisle.model.PollVote; +import com.openisle.mapper.PostMapper; +import com.openisle.model.*; import com.openisle.repository.PostRepository; import com.openisle.repository.LotteryPostRepository; import com.openisle.repository.PollPostRepository; @@ -26,11 +17,12 @@ import com.openisle.repository.ReactionRepository; import com.openisle.repository.PostSubscriptionRepository; import com.openisle.repository.NotificationRepository; 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.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.context.ApplicationContext; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -52,6 +44,8 @@ import java.time.LocalDateTime; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledFuture; +import java.util.stream.Collectors; + import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; @@ -195,12 +189,14 @@ public class PostService { pointService.awardForFeatured(saved.getAuthor().getUsername(), saved.getId()); return saved; } - + @CacheEvict( + value = CachingConfig.POST_CACHE_NAME, allEntries = true + ) public Post createPost(String username, Long categoryId, String title, String content, - java.util.List tagIds, + List tagIds, PostType type, String prizeDescription, String prizeIcon, @@ -511,6 +507,10 @@ public class PostService { return listPostsByLatestReply(null, null, page, pageSize); } + @Cacheable( + value = CachingConfig.POST_CACHE_NAME, + key = "'latest_reply'" + ) public List listPostsByLatestReply(java.util.List categoryIds, java.util.List tagIds, Integer page, @@ -538,9 +538,9 @@ public class PostService { posts = postRepository.findByCategoryInAndStatusOrderByCreatedAtDesc(categories, PostStatus.PUBLISHED); } } else { - java.util.List tags = tagRepository.findAllById(tagIds); + List tags = tagRepository.findAllById(tagIds); if (tags.isEmpty()) { - return java.util.List.of(); + return new ArrayList<>(); } posts = postRepository.findByAllTagsOrderByCreatedAtDesc(tags, PostStatus.PUBLISHED, tags.size()); } @@ -638,11 +638,43 @@ public class PostService { return paginate(sortByPinnedAndCreated(posts), page, pageSize); } + /** + * 默认的文章列表 + * @param ids + * @param tids + * @param page + * @param pageSize + * @return + */ + @Cacheable( + value = CachingConfig.POST_CACHE_NAME, + key = "'default'" + ) + public List defaultListPosts(List ids, List tids, Integer page, Integer pageSize){ + boolean hasCategories = !CollectionUtils.isEmpty(ids); + boolean hasTags = !CollectionUtils.isEmpty(tids); + + if (hasCategories && hasTags) { + return listPostsByCategoriesAndTags(ids, tids, page, pageSize) + .stream().collect(Collectors.toList()); + } + if (hasTags) { + return listPostsByTags(tids, page, pageSize) + .stream().collect(Collectors.toList()); + } + + return listPostsByCategories(ids, page, pageSize) + .stream().collect(Collectors.toList()); + } public List listPendingPosts() { return postRepository.findByStatus(PostStatus.PENDING); } + @CacheEvict( + value = CachingConfig.POST_CACHE_NAME, + key = "'default'" + ) public Post approvePost(Long id) { Post post = postRepository.findById(id) .orElseThrow(() -> new com.openisle.exception.NotFoundException("Post not found")); @@ -679,6 +711,10 @@ public class PostService { return post; } + @CacheEvict( + value = CachingConfig.POST_CACHE_NAME, + key = "'default'" + ) public Post pinPost(Long id, String username) { Post post = postRepository.findById(id) .orElseThrow(() -> new com.openisle.exception.NotFoundException("Post not found")); @@ -691,6 +727,10 @@ public class PostService { return saved; } + @CacheEvict( + value = CachingConfig.POST_CACHE_NAME, + key = "'default'" + ) public Post unpinPost(Long id, String username) { Post post = postRepository.findById(id) .orElseThrow(() -> new com.openisle.exception.NotFoundException("Post not found")); @@ -703,6 +743,10 @@ public class PostService { return saved; } + @CacheEvict( + value = CachingConfig.POST_CACHE_NAME, + key = "'default'" + ) public Post closePost(Long id, String username) { Post post = postRepository.findById(id) .orElseThrow(() -> new com.openisle.exception.NotFoundException("Post not found")); @@ -718,6 +762,10 @@ public class PostService { return saved; } + @CacheEvict( + value = CachingConfig.POST_CACHE_NAME, + key = "'default'" + ) public Post reopenPost(Long id, String username) { Post post = postRepository.findById(id) .orElseThrow(() -> new com.openisle.exception.NotFoundException("Post not found")); @@ -733,7 +781,11 @@ public class PostService { return saved; } - @org.springframework.transaction.annotation.Transactional + @CacheEvict( + value = CachingConfig.POST_CACHE_NAME, + key = "'default'" + ) + @Transactional public Post updatePost(Long id, String username, Long categoryId, @@ -786,7 +838,11 @@ public class PostService { return updated; } - @org.springframework.transaction.annotation.Transactional + @CacheEvict( + value = CachingConfig.POST_CACHE_NAME, + key = "'default'" + ) + @Transactional public void deletePost(Long id, String username) { Post post = postRepository.findById(id) .orElseThrow(() -> new com.openisle.exception.NotFoundException("Post not found")); @@ -879,15 +935,17 @@ public class PostService { .toList(); } - private java.util.List paginate(java.util.List posts, Integer page, Integer pageSize) { + private List paginate(List posts, Integer page, Integer pageSize) { if (page == null || pageSize == null) { return posts; } int from = page * pageSize; if (from >= posts.size()) { - return java.util.List.of(); + return new ArrayList<>(); } int to = Math.min(from + pageSize, posts.size()); - return posts.subList(from, to); + // 这里必须将list包装为arrayList类型,否则序列化会有问题 + // list.sublist返回的是内部类 + return new ArrayList<>(posts.subList(from, to)); } } diff --git a/backend/src/main/java/com/openisle/service/TagService.java b/backend/src/main/java/com/openisle/service/TagService.java index eee84121e..02191427d 100644 --- a/backend/src/main/java/com/openisle/service/TagService.java +++ b/backend/src/main/java/com/openisle/service/TagService.java @@ -120,4 +120,18 @@ public class TagService { .orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found")); return tagRepository.findByCreator(user); } + + /** + * 获取检索用的标签Id列表 + * @param tagIds + * @param tagId + * @return + */ + public List getSearchTagIds(List tagIds, Long tagId){ + List ids = tagIds; + if (tagId != null) { + ids = List.of(tagId); + } + return ids; + } }