From 23cc2d1606620d7c65399bc28a61cf3fcc56e71f Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 26 Sep 2025 18:19:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=B4=B4=E6=96=87rei?= =?UTF-8?q?ndex?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../openisle/search/OpenSearchProperties.java | 2 + .../search/SearchReindexInitializer.java | 30 ++++++ .../openisle/search/SearchReindexService.java | 94 +++++++++++++++++++ .../src/main/resources/application.properties | 2 + 4 files changed, 128 insertions(+) create mode 100644 backend/src/main/java/com/openisle/search/SearchReindexInitializer.java create mode 100644 backend/src/main/java/com/openisle/search/SearchReindexService.java diff --git a/backend/src/main/java/com/openisle/search/OpenSearchProperties.java b/backend/src/main/java/com/openisle/search/OpenSearchProperties.java index f236abcb5..a334ad8d9 100644 --- a/backend/src/main/java/com/openisle/search/OpenSearchProperties.java +++ b/backend/src/main/java/com/openisle/search/OpenSearchProperties.java @@ -18,6 +18,8 @@ public class OpenSearchProperties { private String indexPrefix = "openisle"; private boolean initialize = true; private int highlightFragmentSize = 200; + private boolean reindexOnStartup = false; + private int reindexBatchSize = 500; private Indices indices = new Indices(); diff --git a/backend/src/main/java/com/openisle/search/SearchReindexInitializer.java b/backend/src/main/java/com/openisle/search/SearchReindexInitializer.java new file mode 100644 index 000000000..5eddf68f5 --- /dev/null +++ b/backend/src/main/java/com/openisle/search/SearchReindexInitializer.java @@ -0,0 +1,30 @@ +package com.openisle.search; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class SearchReindexInitializer implements CommandLineRunner { + + private final OpenSearchProperties properties; + private final SearchReindexService searchReindexService; + + @Override + public void run(String... args) { + if (!properties.isEnabled()) { + log.info("Search indexing disabled, skipping startup reindex."); + return; + } + + if (!properties.isReindexOnStartup()) { + log.debug("Startup reindex disabled by configuration."); + return; + } + + searchReindexService.reindexAll(); + } +} diff --git a/backend/src/main/java/com/openisle/search/SearchReindexService.java b/backend/src/main/java/com/openisle/search/SearchReindexService.java new file mode 100644 index 000000000..1179eadd6 --- /dev/null +++ b/backend/src/main/java/com/openisle/search/SearchReindexService.java @@ -0,0 +1,94 @@ +package com.openisle.search; + +import com.openisle.model.Post; +import com.openisle.model.PostStatus; +import com.openisle.model.Tag; +import com.openisle.repository.CategoryRepository; +import com.openisle.repository.CommentRepository; +import com.openisle.repository.PostRepository; +import com.openisle.repository.TagRepository; +import com.openisle.repository.UserRepository; +import java.util.Objects; +import java.util.function.Function; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Component +@RequiredArgsConstructor +public class SearchReindexService { + + private final SearchIndexer searchIndexer; + private final OpenSearchProperties properties; + private final PostRepository postRepository; + private final CommentRepository commentRepository; + private final UserRepository userRepository; + private final CategoryRepository categoryRepository; + private final TagRepository tagRepository; + + @Transactional(readOnly = true) + public void reindexAll() { + if (!properties.isEnabled()) { + log.info("Search indexing is disabled, skipping reindex operation."); + return; + } + + log.info("Starting full search reindex operation."); + + reindex(properties.postsIndex(), postRepository::findAll, (Post post) -> + post.getStatus() == PostStatus.PUBLISHED ? SearchDocumentFactory.fromPost(post) : null + ); + + reindex( + properties.commentsIndex(), + commentRepository::findAll, + SearchDocumentFactory::fromComment + ); + + reindex(properties.usersIndex(), userRepository::findAll, SearchDocumentFactory::fromUser); + + reindex( + properties.categoriesIndex(), + categoryRepository::findAll, + SearchDocumentFactory::fromCategory + ); + + reindex(properties.tagsIndex(), tagRepository::findAll, (Tag tag) -> + tag.isApproved() ? SearchDocumentFactory.fromTag(tag) : null + ); + + log.info("Completed full search reindex operation."); + } + + private void reindex( + String index, + Function> pageSupplier, + Function mapper + ) { + int batchSize = Math.max(1, properties.getReindexBatchSize()); + int pageNumber = 0; + + Page page; + do { + Pageable pageable = PageRequest.of(pageNumber, batchSize); + page = pageSupplier.apply(pageable); + if (page.isEmpty() && pageNumber == 0) { + log.info("No entities found for index {}.", index); + } + + log.info("Reindexing {} entities for index {}.", page.getTotalElements(), index); + for (T entity : page) { + SearchDocument document = mapper.apply(entity); + if (Objects.nonNull(document)) { + searchIndexer.indexDocument(index, document); + } + } + pageNumber++; + } while (page.hasNext()); + } +} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 44ebbfbfe..479fc683c 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -54,6 +54,8 @@ app.search.username=${SEARCH_USERNAME:} app.search.password=${SEARCH_PASSWORD:} app.search.index-prefix=${SEARCH_INDEX_PREFIX:openisle} app.search.highlight-fragment-size=${SEARCH_HIGHLIGHT_FRAGMENT_SIZE:${SNIPPET_LENGTH:200}} +app.search.reindex-on-startup=${SEARCH_REINDEX_ON_STARTUP:true} +app.search.reindex-batch-size=${SEARCH_REINDEX_BATCH_SIZE:500} # Captcha configuration app.captcha.enabled=${CAPTCHA_ENABLED:false}