mirror of
https://github.com/201206030/novel.git
synced 2025-04-26 23:20:51 +00:00
feat: 实现 Elasticsearch 8.2.0 高级搜索功能
This commit is contained in:
parent
4c331224a4
commit
ab2bead9b3
@ -38,7 +38,7 @@ PUT /book
|
||||
"analyzer": "ik_smart"
|
||||
},
|
||||
"lastChapterUpdateTime" : {
|
||||
"type": "keyword"
|
||||
"type": "long"
|
||||
},
|
||||
"picUrl" : {
|
||||
"type" : "keyword",
|
||||
|
4
pom.xml
4
pom.xml
@ -109,10 +109,6 @@
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
|
@ -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<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition) {
|
||||
return bookService.searchBooks(condition);
|
||||
return searchService.searchBooks(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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-免费
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition);
|
||||
|
||||
/**
|
||||
* 小说点击榜查询
|
||||
*
|
||||
|
@ -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<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition);
|
||||
|
||||
|
||||
}
|
@ -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<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition) {
|
||||
Page<BookInfoRespDto> page = new Page<>();
|
||||
page.setCurrent(condition.getPageNum());
|
||||
page.setSize(condition.getPageSize());
|
||||
List<BookInfo> 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<List<BookRankRespDto>> listVisitRankBooks() {
|
||||
return RestResp.ok(bookRankCacheManager.listVisitRankBooks());
|
||||
|
@ -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<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition) {
|
||||
Page<BookInfoRespDto> page = new Page<>();
|
||||
page.setCurrent(condition.getPageNum());
|
||||
page.setSize(condition.getPageSize());
|
||||
List<BookInfo> 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()));
|
||||
}
|
||||
|
||||
}
|
@ -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<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition) {
|
||||
|
||||
SearchResponse<EsBookDto> 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<BookInfoRespDto> list = new ArrayList<>();
|
||||
List<Hit<EsBookDto>> hits = response.hits().hits();
|
||||
for (Hit<EsBookDto> 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());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user