From 4e0054237134faa229e33de6f3d3a051c63a9639 Mon Sep 17 00:00:00 2001 From: Hanserwei <2628273921@qq.com> Date: Thu, 30 Oct 2025 22:21:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(search):=20=E5=AE=9E=E7=8E=B0=E7=94=A8?= =?UTF-8?q?=E6=88=B7=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 - 添加 Elasticsearch 客户端配置 - 创建用户搜索接口和实现类 - 定义搜索请求和响应 VO 类 - 集成分页查询和关键字匹配逻辑 - 添加 Elasticsearch 测试用例 - 更新 pom.xml 依赖和版本管理 - 添加 HTTP 客户端测试脚本 --- han-note-search/pom.xml | 4 + .../search/config/ElasticsearchConfig.java | 39 +++++--- .../search/controller/UserController.java | 30 ++++++ .../search/model/vo/SearchUserRspVO.java | 7 ++ .../search/service/impl/UserServiceImpl.java | 92 ++++++++++++++++++- .../search/ElasticsearchClientTest.java | 38 ++++++++ http-client/gateApi.http | 9 ++ pom.xml | 6 ++ 8 files changed, 209 insertions(+), 16 deletions(-) create mode 100644 han-note-search/src/main/java/com/hanserwei/hannote/search/controller/UserController.java create mode 100644 han-note-search/src/test/java/com/hanserwei/hannote/search/ElasticsearchClientTest.java diff --git a/han-note-search/pom.xml b/han-note-search/pom.xml index 70aec9b..b3eb16b 100644 --- a/han-note-search/pom.xml +++ b/han-note-search/pom.xml @@ -59,6 +59,10 @@ co.elastic.clients elasticsearch-java + + org.elasticsearch.client + elasticsearch-rest-client + diff --git a/han-note-search/src/main/java/com/hanserwei/hannote/search/config/ElasticsearchConfig.java b/han-note-search/src/main/java/com/hanserwei/hannote/search/config/ElasticsearchConfig.java index 39aed72..2b09948 100644 --- a/han-note-search/src/main/java/com/hanserwei/hannote/search/config/ElasticsearchConfig.java +++ b/han-note-search/src/main/java/com/hanserwei/hannote/search/config/ElasticsearchConfig.java @@ -1,31 +1,40 @@ package com.hanserwei.hannote.search.config; +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; + @Configuration public class ElasticsearchConfig { @Value("${elasticsearch.host}") private String host; - @Value("${elasticsearch.port}") - private int port; + @Bean + public ElasticsearchClient elasticsearchClient() { + // 1. 创建底层 RestClient(低级客户端) + RestClient restClient = RestClient + .builder(HttpHost.create(host)) + .build(); - @Value("${elasticsearch.scheme:http}") - private String scheme; + // 2. 创建 JSON 映射器 + ObjectMapper mapper = JsonMapper.builder().build(); + JacksonJsonpMapper jsonpMapper = new JacksonJsonpMapper(mapper); - @Value("${elasticsearch.username:}") - private String username; - - @Value("${elasticsearch.password:}") - private String password; - - @Value("${elasticsearch.api-key:}") - private String apiKey; - - @Value("${elasticsearch.use-api-key:false}") - private boolean useApiKey; + // 3. 构建传输层 + ElasticsearchTransport transport = new RestClientTransport(restClient, jsonpMapper); + // 4. 创建高层次的 Elasticsearch 客户端 + return new ElasticsearchClient(transport); + } } diff --git a/han-note-search/src/main/java/com/hanserwei/hannote/search/controller/UserController.java b/han-note-search/src/main/java/com/hanserwei/hannote/search/controller/UserController.java new file mode 100644 index 0000000..7b9dfb0 --- /dev/null +++ b/han-note-search/src/main/java/com/hanserwei/hannote/search/controller/UserController.java @@ -0,0 +1,30 @@ +package com.hanserwei.hannote.search.controller; + +import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog; +import com.hanserwei.framework.common.response.PageResponse; +import com.hanserwei.hannote.search.model.vo.SearchUserReqVO; +import com.hanserwei.hannote.search.model.vo.SearchUserRspVO; +import com.hanserwei.hannote.search.service.UserService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/search") +@Slf4j +public class UserController { + + @Resource + private UserService userService; + + @PostMapping("/user") + @ApiOperationLog(description = "搜索用户") + public PageResponse searchUser(@RequestBody @Validated SearchUserReqVO searchUserReqVO) { + return userService.searchUser(searchUserReqVO); + } + +} diff --git a/han-note-search/src/main/java/com/hanserwei/hannote/search/model/vo/SearchUserRspVO.java b/han-note-search/src/main/java/com/hanserwei/hannote/search/model/vo/SearchUserRspVO.java index 85965e5..d585b27 100644 --- a/han-note-search/src/main/java/com/hanserwei/hannote/search/model/vo/SearchUserRspVO.java +++ b/han-note-search/src/main/java/com/hanserwei/hannote/search/model/vo/SearchUserRspVO.java @@ -1,5 +1,7 @@ package com.hanserwei.hannote.search.model.vo; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -9,11 +11,13 @@ import lombok.NoArgsConstructor; @AllArgsConstructor @NoArgsConstructor @Builder +@JsonIgnoreProperties(ignoreUnknown = true) public class SearchUserRspVO { /** * 用户ID */ + @JsonProperty("id") private Long userId; /** @@ -29,16 +33,19 @@ public class SearchUserRspVO { /** * 小憨书ID */ + @JsonProperty("han_note_id") private String hanNoteId; /** * 笔记发布总数 */ + @JsonProperty("note_total") private Integer noteTotal; /** * 粉丝总数 */ + @JsonProperty("fans_total") private Integer fansTotal; } \ No newline at end of file diff --git a/han-note-search/src/main/java/com/hanserwei/hannote/search/service/impl/UserServiceImpl.java b/han-note-search/src/main/java/com/hanserwei/hannote/search/service/impl/UserServiceImpl.java index 4ae366f..22c95d7 100644 --- a/han-note-search/src/main/java/com/hanserwei/hannote/search/service/impl/UserServiceImpl.java +++ b/han-note-search/src/main/java/com/hanserwei/hannote/search/service/impl/UserServiceImpl.java @@ -1,14 +1,104 @@ package com.hanserwei.hannote.search.service.impl; +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.SortOrder; +import co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.search.Hit; +import com.google.common.collect.Lists; import com.hanserwei.framework.common.response.PageResponse; +import com.hanserwei.hannote.search.index.UserIndex; import com.hanserwei.hannote.search.model.vo.SearchUserReqVO; import com.hanserwei.hannote.search.model.vo.SearchUserRspVO; import com.hanserwei.hannote.search.service.UserService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import java.util.List; + +@Slf4j +@Service public class UserServiceImpl implements UserService { + @Resource + private ElasticsearchClient client; + + /** + * 获取 SearchUserRspVO + * + * @param hit 搜索结果 + * @return SearchUserRspVO + */ + private static SearchUserRspVO getSearchUserRspVO(Hit hit) { + SearchUserRspVO searchUserRspVO = new SearchUserRspVO(); + + SearchUserRspVO source = hit.source(); + if (source != null) { + searchUserRspVO.setUserId(source.getUserId()); + searchUserRspVO.setNickname(source.getNickname()); + searchUserRspVO.setAvatar(source.getAvatar()); + searchUserRspVO.setHanNoteId(source.getHanNoteId()); + searchUserRspVO.setNoteTotal(source.getNoteTotal()); + searchUserRspVO.setFansTotal(source.getFansTotal()); + } + return searchUserRspVO; + } + @Override public PageResponse searchUser(SearchUserReqVO searchUserReqVO) { - return null; + // 查询关键字 + String keyword = searchUserReqVO.getKeyword(); + // 当前页码 + Integer pageNo = searchUserReqVO.getPageNo(); + + int pageSize = 10; + + // 构建SearchRequest,指定索引 + SearchRequest searchRequest = new SearchRequest.Builder() + .index(UserIndex.NAME) + .query(query -> query + .multiMatch(multiMatch -> multiMatch + .query(keyword) + .fields(UserIndex.FIELD_USER_NICKNAME, UserIndex.FIELD_USER_HAN_NOTE_ID) + .type(TextQueryType.PhrasePrefix))) + .sort(sort -> sort + .field(filedSort -> filedSort.field(UserIndex.FIELD_USER_FANS_TOTAL).order(SortOrder.Desc))) + .from((pageNo - 1) * pageSize) + .size(pageSize) + .build(); + + // 返参 VO 集合 + List searchUserRspVOS = null; + // 总文档数,默认为 0 + long total = 0; + try { + log.info("==> SearchRequest:{}", searchRequest); + + // 执行查询请求 + SearchResponse searchResponse = client.search(searchRequest, SearchUserRspVO.class); + + searchUserRspVOS = Lists.newArrayList(); + + // 处理搜索结果 + List> hits = searchResponse.hits().hits(); + if (searchResponse.hits().total() != null) { + total = searchResponse.hits().total().value(); + } + + for (Hit hit : hits) { + log.info("==> 文档数据: {}", hit.toString()); + if (hit.source() != null) { + SearchUserRspVO searchUserRspVO = getSearchUserRspVO(hit); + searchUserRspVOS.add(searchUserRspVO); + } + } + + + } catch (Exception e) { + log.error("==> 查询 Elasticsearch 异常: ", e); + } + return PageResponse.success(searchUserRspVOS, pageNo, total); } } diff --git a/han-note-search/src/test/java/com/hanserwei/hannote/search/ElasticsearchClientTest.java b/han-note-search/src/test/java/com/hanserwei/hannote/search/ElasticsearchClientTest.java new file mode 100644 index 0000000..db561e8 --- /dev/null +++ b/han-note-search/src/test/java/com/hanserwei/hannote/search/ElasticsearchClientTest.java @@ -0,0 +1,38 @@ +package com.hanserwei.hannote.search; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.SortOrder; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import com.hanserwei.hannote.search.model.vo.SearchUserRspVO; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class ElasticsearchClientTest { + + @Autowired + private ElasticsearchClient client; + + @Test + @SneakyThrows + public void testClient() { + SearchResponse response = client.search(s -> s + .index("user") + .query(q -> q + .multiMatch(mm -> mm + .query("Han") + .fields("nickname", "han_note_id") + ) + ) + .sort(so -> so + .field(f -> f.field("fans_total").order(SortOrder.Desc)) + ) + .from(0) + .size(10), + SearchUserRspVO.class + ); + response.hits().hits().forEach(hit -> System.out.println(hit.source())); + } +} diff --git a/http-client/gateApi.http b/http-client/gateApi.http index c0a25bc..4727cc8 100644 --- a/http-client/gateApi.http +++ b/http-client/gateApi.http @@ -279,3 +279,12 @@ Content-Type: application/json } } } + +### 搜索用户(搜索服务) +POST http://localhost:8092/search/user +Content-Type: application/json + +{ + "keyword": "憨", + "pageNo": 1 +} diff --git a/pom.xml b/pom.xml index 06f0c8f..05432ad 100755 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,7 @@ 0.2.21 3.2.0 9.2.0 + 9.2.0 @@ -308,6 +309,11 @@ elasticsearch-java ${elasticsearch-java.version} + + org.elasticsearch.client + elasticsearch-rest-client + ${elasticsearch.version} +