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}
+