From ab2bead9b3fa8eaac2507dc672bda1c038f26293 Mon Sep 17 00:00:00 2001 From: xiongxiaoyang <773861846@qq.com> Date: Tue, 24 May 2022 00:44:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20Elasticsearch=208.?= =?UTF-8?q?2.0=20=E9=AB=98=E7=BA=A7=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/es/book.http | 2 +- pom.xml | 4 - .../controller/front/BookController.java | 5 +- .../xxyopen/novel/core/task/BookToEsTask.java | 7 +- .../xxyopen/novel/dto/es/EsBookDto.java | 14 +- .../novel/dto/resp/BookInfoRespDto.java | 5 +- .../xxyopen/novel/service/BookService.java | 10 -- .../xxyopen/novel/service/SearchService.java | 25 ++++ .../novel/service/impl/BookServiceImpl.java | 22 --- .../service/impl/DbSearchServiceImpl.java | 51 +++++++ .../service/impl/EsSearchServiceImpl.java | 141 ++++++++++++++++++ 11 files changed, 235 insertions(+), 51 deletions(-) create mode 100644 src/main/java/io/github/xxyopen/novel/service/SearchService.java create mode 100644 src/main/java/io/github/xxyopen/novel/service/impl/DbSearchServiceImpl.java create mode 100644 src/main/java/io/github/xxyopen/novel/service/impl/EsSearchServiceImpl.java diff --git a/doc/es/book.http b/doc/es/book.http index e80fd3d..45038d3 100644 --- a/doc/es/book.http +++ b/doc/es/book.http @@ -38,7 +38,7 @@ PUT /book "analyzer": "ik_smart" }, "lastChapterUpdateTime" : { - "type": "keyword" + "type": "long" }, "picUrl" : { "type" : "keyword", diff --git a/pom.xml b/pom.xml index 347f5e1..0f462b2 100644 --- a/pom.xml +++ b/pom.xml @@ -109,10 +109,6 @@ com.fasterxml.jackson.core jackson-databind - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - mysql diff --git a/src/main/java/io/github/xxyopen/novel/controller/front/BookController.java b/src/main/java/io/github/xxyopen/novel/controller/front/BookController.java index 79fdf97..46327cd 100644 --- a/src/main/java/io/github/xxyopen/novel/controller/front/BookController.java +++ b/src/main/java/io/github/xxyopen/novel/controller/front/BookController.java @@ -6,6 +6,7 @@ import io.github.xxyopen.novel.core.common.resp.RestResp; import io.github.xxyopen.novel.dto.req.BookSearchReqDto; import io.github.xxyopen.novel.dto.resp.*; import io.github.xxyopen.novel.service.BookService; +import io.github.xxyopen.novel.service.SearchService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -25,6 +26,8 @@ public class BookController { private final BookService bookService; + private final SearchService searchService; + /** * 小说分类列表查询接口 */ @@ -38,7 +41,7 @@ public class BookController { */ @GetMapping("search_list") public RestResp> searchBooks(BookSearchReqDto condition) { - return bookService.searchBooks(condition); + return searchService.searchBooks(condition); } /** diff --git a/src/main/java/io/github/xxyopen/novel/core/task/BookToEsTask.java b/src/main/java/io/github/xxyopen/novel/core/task/BookToEsTask.java index 3c9be46..4a64be1 100644 --- a/src/main/java/io/github/xxyopen/novel/core/task/BookToEsTask.java +++ b/src/main/java/io/github/xxyopen/novel/core/task/BookToEsTask.java @@ -1,6 +1,7 @@ package io.github.xxyopen.novel.core.task; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.Time; import co.elastic.clients.elasticsearch.core.BulkRequest; import co.elastic.clients.elasticsearch.core.BulkResponse; import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem; @@ -17,6 +18,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import java.time.ZoneOffset; import java.util.List; /** @@ -65,7 +67,7 @@ public class BookToEsTask { .id(book.getId().toString()) .document(esBook) ) - ); + ).timeout(Time.of(t -> t.time("10s"))); maxId = book.getId(); } @@ -103,7 +105,8 @@ public class BookToEsTask { .workDirection(book.getWorkDirection()) .lastChapterId(book.getLastChapterId()) .lastChapterName(book.getLastChapterName()) - .lastChapterUpdateTime(book.getLastChapterUpdateTime()) + .lastChapterUpdateTime(book.getLastChapterUpdateTime() + .toInstant(ZoneOffset.ofHours(8)).toEpochMilli()) .build(); } } diff --git a/src/main/java/io/github/xxyopen/novel/dto/es/EsBookDto.java b/src/main/java/io/github/xxyopen/novel/dto/es/EsBookDto.java index 7d77fea..3a714bc 100644 --- a/src/main/java/io/github/xxyopen/novel/dto/es/EsBookDto.java +++ b/src/main/java/io/github/xxyopen/novel/dto/es/EsBookDto.java @@ -1,13 +1,9 @@ package io.github.xxyopen.novel.dto.es; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; - -import java.time.LocalDateTime; +import lombok.NoArgsConstructor; /** * Elasticsearch 存储小说 DTO @@ -15,6 +11,8 @@ import java.time.LocalDateTime; * @date 2022/5/23 */ @Data +@NoArgsConstructor +@AllArgsConstructor @Builder public class EsBookDto { @@ -96,9 +94,7 @@ public class EsBookDto { /** * 最新章节更新时间 */ - @JsonDeserialize(using = LocalDateTimeDeserializer.class) - @JsonSerialize(using = LocalDateTimeSerializer.class) - private LocalDateTime lastChapterUpdateTime; + private Long lastChapterUpdateTime; /** * 是否收费;1-收费 0-免费 diff --git a/src/main/java/io/github/xxyopen/novel/dto/resp/BookInfoRespDto.java b/src/main/java/io/github/xxyopen/novel/dto/resp/BookInfoRespDto.java index 29fc925..55ba809 100644 --- a/src/main/java/io/github/xxyopen/novel/dto/resp/BookInfoRespDto.java +++ b/src/main/java/io/github/xxyopen/novel/dto/resp/BookInfoRespDto.java @@ -1,7 +1,6 @@ package io.github.xxyopen.novel.dto.resp; -import lombok.Builder; -import lombok.Data; +import lombok.*; /** * 小说信息 响应DTO @@ -10,6 +9,8 @@ import lombok.Data; * @date 2022/5/15 */ @Data +@NoArgsConstructor +@AllArgsConstructor @Builder public class BookInfoRespDto { diff --git a/src/main/java/io/github/xxyopen/novel/service/BookService.java b/src/main/java/io/github/xxyopen/novel/service/BookService.java index 634c422..98e9059 100644 --- a/src/main/java/io/github/xxyopen/novel/service/BookService.java +++ b/src/main/java/io/github/xxyopen/novel/service/BookService.java @@ -1,9 +1,7 @@ package io.github.xxyopen.novel.service; -import io.github.xxyopen.novel.core.common.resp.PageRespDto; import io.github.xxyopen.novel.core.common.resp.RestResp; import io.github.xxyopen.novel.dto.req.BookAddReqDto; -import io.github.xxyopen.novel.dto.req.BookSearchReqDto; import io.github.xxyopen.novel.dto.req.ChapterAddReqDto; import io.github.xxyopen.novel.dto.req.UserCommentReqDto; import io.github.xxyopen.novel.dto.resp.*; @@ -19,14 +17,6 @@ import java.util.List; */ public interface BookService { - /** - * 小说搜索 - * - * @param condition 搜索条件 - * @return 搜索结果 - */ - RestResp> searchBooks(BookSearchReqDto condition); - /** * 小说点击榜查询 * diff --git a/src/main/java/io/github/xxyopen/novel/service/SearchService.java b/src/main/java/io/github/xxyopen/novel/service/SearchService.java new file mode 100644 index 0000000..6707ce8 --- /dev/null +++ b/src/main/java/io/github/xxyopen/novel/service/SearchService.java @@ -0,0 +1,25 @@ +package io.github.xxyopen.novel.service; + +import io.github.xxyopen.novel.core.common.resp.PageRespDto; +import io.github.xxyopen.novel.core.common.resp.RestResp; +import io.github.xxyopen.novel.dto.req.BookSearchReqDto; +import io.github.xxyopen.novel.dto.resp.BookInfoRespDto; + +/** + * 搜索 服务类 + * + * @author xiongxiaoyang + * @date 2022/5/23 + */ +public interface SearchService { + + /** + * 小说搜索 + * + * @param condition 搜索条件 + * @return 搜索结果 + */ + RestResp> searchBooks(BookSearchReqDto condition); + + +} diff --git a/src/main/java/io/github/xxyopen/novel/service/impl/BookServiceImpl.java b/src/main/java/io/github/xxyopen/novel/service/impl/BookServiceImpl.java index 073180a..c855bb1 100644 --- a/src/main/java/io/github/xxyopen/novel/service/impl/BookServiceImpl.java +++ b/src/main/java/io/github/xxyopen/novel/service/impl/BookServiceImpl.java @@ -1,10 +1,8 @@ package io.github.xxyopen.novel.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.github.xxyopen.novel.core.auth.UserHolder; import io.github.xxyopen.novel.core.common.constant.ErrorCodeEnum; -import io.github.xxyopen.novel.core.common.resp.PageRespDto; import io.github.xxyopen.novel.core.common.resp.RestResp; import io.github.xxyopen.novel.core.constant.DatabaseConsts; import io.github.xxyopen.novel.dao.entity.*; @@ -14,7 +12,6 @@ import io.github.xxyopen.novel.dao.mapper.BookContentMapper; import io.github.xxyopen.novel.dao.mapper.BookInfoMapper; import io.github.xxyopen.novel.dto.AuthorInfoDto; import io.github.xxyopen.novel.dto.req.BookAddReqDto; -import io.github.xxyopen.novel.dto.req.BookSearchReqDto; import io.github.xxyopen.novel.dto.req.ChapterAddReqDto; import io.github.xxyopen.novel.dto.req.UserCommentReqDto; import io.github.xxyopen.novel.dto.resp.*; @@ -67,25 +64,6 @@ public class BookServiceImpl implements BookService { private static final Integer REC_BOOK_COUNT = 4; - @Override - public RestResp> searchBooks(BookSearchReqDto condition) { - Page page = new Page<>(); - page.setCurrent(condition.getPageNum()); - page.setSize(condition.getPageSize()); - List bookInfos = bookInfoMapper.searchBooks(page, condition); - return RestResp.ok(PageRespDto.of(condition.getPageNum(), condition.getPageSize(), page.getTotal() - , bookInfos.stream().map(v -> BookInfoRespDto.builder() - .id(v.getId()) - .bookName(v.getBookName()) - .categoryId(v.getCategoryId()) - .categoryName(v.getCategoryName()) - .authorId(v.getAuthorId()) - .authorName(v.getAuthorName()) - .wordCount(v.getWordCount()) - .lastChapterName(v.getLastChapterName()) - .build()).toList())); - } - @Override public RestResp> listVisitRankBooks() { return RestResp.ok(bookRankCacheManager.listVisitRankBooks()); diff --git a/src/main/java/io/github/xxyopen/novel/service/impl/DbSearchServiceImpl.java b/src/main/java/io/github/xxyopen/novel/service/impl/DbSearchServiceImpl.java new file mode 100644 index 0000000..4418344 --- /dev/null +++ b/src/main/java/io/github/xxyopen/novel/service/impl/DbSearchServiceImpl.java @@ -0,0 +1,51 @@ +package io.github.xxyopen.novel.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.github.xxyopen.novel.core.common.resp.PageRespDto; +import io.github.xxyopen.novel.core.common.resp.RestResp; +import io.github.xxyopen.novel.dao.entity.BookInfo; +import io.github.xxyopen.novel.dao.mapper.BookInfoMapper; +import io.github.xxyopen.novel.dto.req.BookSearchReqDto; +import io.github.xxyopen.novel.dto.resp.BookInfoRespDto; +import io.github.xxyopen.novel.service.SearchService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 数据库搜索 服务实现类 + * + * @author xiongxiaoyang + * @date 2022/5/23 + */ +@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enable", havingValue = "false") +@Service +@RequiredArgsConstructor +@Slf4j +public class DbSearchServiceImpl implements SearchService { + + private final BookInfoMapper bookInfoMapper; + + @Override + public RestResp> searchBooks(BookSearchReqDto condition) { + Page page = new Page<>(); + page.setCurrent(condition.getPageNum()); + page.setSize(condition.getPageSize()); + List bookInfos = bookInfoMapper.searchBooks(page, condition); + return RestResp.ok(PageRespDto.of(condition.getPageNum(), condition.getPageSize(), page.getTotal() + , bookInfos.stream().map(v -> BookInfoRespDto.builder() + .id(v.getId()) + .bookName(v.getBookName()) + .categoryId(v.getCategoryId()) + .categoryName(v.getCategoryName()) + .authorId(v.getAuthorId()) + .authorName(v.getAuthorName()) + .wordCount(v.getWordCount()) + .lastChapterName(v.getLastChapterName()) + .build()).toList())); + } + +} diff --git a/src/main/java/io/github/xxyopen/novel/service/impl/EsSearchServiceImpl.java b/src/main/java/io/github/xxyopen/novel/service/impl/EsSearchServiceImpl.java new file mode 100644 index 0000000..3eec0d0 --- /dev/null +++ b/src/main/java/io/github/xxyopen/novel/service/impl/EsSearchServiceImpl.java @@ -0,0 +1,141 @@ +package io.github.xxyopen.novel.service.impl; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.SortOrder; +import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.search.Hit; +import co.elastic.clients.elasticsearch.core.search.TotalHits; +import co.elastic.clients.json.JsonData; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import io.github.xxyopen.novel.core.common.resp.PageRespDto; +import io.github.xxyopen.novel.core.common.resp.RestResp; +import io.github.xxyopen.novel.core.constant.EsConsts; +import io.github.xxyopen.novel.dto.es.EsBookDto; +import io.github.xxyopen.novel.dto.req.BookSearchReqDto; +import io.github.xxyopen.novel.dto.resp.BookInfoRespDto; +import io.github.xxyopen.novel.service.SearchService; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Elasticsearch 搜索 服务实现类 + * + * @author xiongxiaoyang + * @date 2022/5/23 + */ +@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enable", havingValue = "true") +@Service +@RequiredArgsConstructor +@Slf4j +public class EsSearchServiceImpl implements SearchService { + + private final ElasticsearchClient esClient; + + @SneakyThrows + @Override + public RestResp> searchBooks(BookSearchReqDto condition) { + + SearchResponse response = esClient.search(s -> { + + SearchRequest.Builder searchBuilder = s.index(EsConsts.IndexEnum.BOOK.getName()); + buildSearchCondition(condition, searchBuilder); + + searchBuilder.sort(o -> + o.field(f -> f.field(StringUtils + .underlineToCamel(condition.getSort().split(" ")[0])) + .order(SortOrder.Desc)) + ) + .from((condition.getPageNum() - 1) * condition.getPageSize()) + .size(condition.getPageSize()); + return searchBuilder; + }, + EsBookDto.class + ); + + TotalHits total = response.hits().total(); + + List list = new ArrayList<>(); + List> hits = response.hits().hits(); + for (Hit hit : hits) { + EsBookDto book = hit.source(); + assert book != null; + list.add(BookInfoRespDto.builder() + .id(book.getId()) + .bookName(book.getBookName()) + .categoryId(book.getCategoryId()) + .categoryName(book.getCategoryName()) + .authorId(book.getAuthorId()) + .authorName(book.getAuthorName()) + .wordCount(book.getWordCount()) + .lastChapterName(book.getLastChapterName()) + .build()); + } + assert total != null; + return RestResp.ok(PageRespDto.of(condition.getPageNum(), condition.getPageSize(), total.value(), list)); + } + + private void buildSearchCondition(BookSearchReqDto condition, SearchRequest.Builder searchBuilder) { + if (!StringUtils.isBlank(condition.getKeyword())) { + searchBuilder.query(q -> q.match(t -> t + .field("bookName") + .query(condition.getKeyword()) + .boost(2.0f) + .field("authorName") + .query(condition.getKeyword()) + .boost(1.8f) + //.field("categoryName") + //.query(condition.getKeyword()) + //.boost(1.0f) + //.field("bookDesc") + //.query(condition.getKeyword()) + //.boost(0.1f) + ) + ); + } + + if (Objects.nonNull(condition.getWorkDirection())) { + searchBuilder.query(MatchQuery.of(m -> m + .field("workDirection") + .query(condition.getWorkDirection()) + )._toQuery()); + } + + if (Objects.nonNull(condition.getCategoryId())) { + searchBuilder.query(MatchQuery.of(m -> m + .field("categoryId") + .query(condition.getCategoryId()) + )._toQuery()); + } + + if (Objects.nonNull(condition.getWordCountMin())) { + searchBuilder.query(RangeQuery.of(m -> m + .field("wordCount") + .gte(JsonData.of(condition.getWordCountMin())) + )._toQuery()); + } + + if (Objects.nonNull(condition.getWordCountMax())) { + searchBuilder.query(RangeQuery.of(m -> m + .field("wordCount") + .lt(JsonData.of(condition.getWordCountMax())) + )._toQuery()); + } + + if (Objects.nonNull(condition.getUpdateTimeMin())) { + searchBuilder.query(RangeQuery.of(m -> m + .field("lastChapterUpdateTime") + .gte(JsonData.of(condition.getUpdateTimeMin().getTime())) + )._toQuery()); + } + } +}