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 d585b27..25179be 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 @@ -25,6 +25,11 @@ public class SearchUserRspVO { */ private String nickname; + /** + * 昵称:关键词高亮 + */ + private String highlightNickname; + /** * 头像 */ @@ -46,6 +51,6 @@ public class SearchUserRspVO { * 粉丝总数 */ @JsonProperty("fans_total") - private Integer fansTotal; + private String 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 22c95d7..7b2f796 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 @@ -5,9 +5,12 @@ 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.HighlightField; import co.elastic.clients.elasticsearch.core.search.Hit; +import co.elastic.clients.util.NamedValue; import com.google.common.collect.Lists; import com.hanserwei.framework.common.response.PageResponse; +import com.hanserwei.framework.common.utils.NumberUtils; import com.hanserwei.hannote.search.index.UserIndex; import com.hanserwei.hannote.search.model.vo.SearchUserReqVO; import com.hanserwei.hannote.search.model.vo.SearchUserRspVO; @@ -17,6 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Map; @Slf4j @Service @@ -25,80 +29,130 @@ 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) { - // 查询关键字 + // 查询关键词 String keyword = searchUserReqVO.getKeyword(); // 当前页码 Integer pageNo = searchUserReqVO.getPageNo(); - int pageSize = 10; + int pageSize = 10; // 每页展示数据量 + int from = (pageNo - 1) * pageSize; // 偏移量 - // 构建SearchRequest,指定索引 - SearchRequest searchRequest = new SearchRequest.Builder() + HighlightField nicknameHighlight = HighlightField.of(hf -> hf + .preTags("") + .postTags("") + ); + + + SearchRequest searchRequest = SearchRequest.of(r -> r .index(UserIndex.NAME) - .query(query -> query - .multiMatch(multiMatch -> multiMatch + + // 1. 构建 Query: multiMatchQuery (RHL 风格的匹配) + .query(q -> q + .multiMatch(m -> m .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) + // 默认使用 MatchQuery 行为,如果要模糊匹配,请添加 .fuzziness("AUTO") + .type(TextQueryType.PhrasePrefix) + ) + ) + + // 2. 构建 Sort + .sort(s -> s + .field(f -> f + .field(UserIndex.FIELD_USER_FANS_TOTAL) + .order(SortOrder.Desc) + ) + ) + + .highlight(h -> h.fields(NamedValue.of(UserIndex.FIELD_USER_NICKNAME, nicknameHighlight))) + + // 3. 分页 from 和 size + .from(from) .size(pageSize) - .build(); + ); + // 返参 VO 集合 - List searchUserRspVOS = null; + List searchUserRspVOS = Lists.newArrayList(); // 总文档数,默认为 0 long total = 0; + try { - log.info("==> SearchRequest:{}", searchRequest); + log.info("==> SearchRequest: {}", searchRequest.toString()); // 执行查询请求 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(); } + searchUserRspVOS = Lists.newArrayList(); + for (Hit hit : hits) { - log.info("==> 文档数据: {}", hit.toString()); - if (hit.source() != null) { - SearchUserRspVO searchUserRspVO = getSearchUserRspVO(hit); + // 1. 获取原始文档数据 (source) + SearchUserRspVO source = hit.source(); + + // 2. 获取高亮数据 (highlight) + Map> highlights = hit.highlight(); + + if (source != null) { + // 3. 调用辅助方法合并数据和高亮 + SearchUserRspVO searchUserRspVO = mergeHitToRspVO(source, highlights); searchUserRspVOS.add(searchUserRspVO); } } - } catch (Exception e) { log.error("==> 查询 Elasticsearch 异常: ", e); } + return PageResponse.success(searchUserRspVOS, pageNo, total); } + + /** + * 将原始文档和高亮数据合并到 SearchUserRspVO + * + * @param source 原始文档数据 (已自动反序列化) + * @param highlights 高亮数据 Map + * @return SearchUserRspVO + */ + private SearchUserRspVO mergeHitToRspVO(SearchUserRspVO source, Map> highlights) { + if (source == null) { + return null; + } + + // 1. 复制原始文档字段 (假设 SearchUserRspVO 使用 Lombok @Data 或 Builder) + SearchUserRspVO searchUserRspVO = SearchUserRspVO.builder() + .userId(source.getUserId()) + .nickname(source.getNickname()) + .avatar(source.getAvatar()) + .hanNoteId(source.getHanNoteId()) // 字段名应与您的 VO 保持一致 + .noteTotal(source.getNoteTotal()) + .build(); + if (source.getFansTotal() != null) { + searchUserRspVO.setFansTotal(NumberUtils.formatNumberString(Long.parseLong(source.getFansTotal()))); + } + + // 2. ⭐️ 核心逻辑:处理并设置高亮字段 + if (highlights != null) { + // 尝试从 highlights Map 中获取 nickname 字段的高亮结果 + List nicknameHighlights = highlights.get(UserIndex.FIELD_USER_NICKNAME); + + if (nicknameHighlights != null && !nicknameHighlights.isEmpty()) { + searchUserRspVO.setHighlightNickname(nicknameHighlights.getFirst()); + } + } + + // 3. 如果高亮字段为空,默认使用原始 nickname + if (searchUserRspVO.getHighlightNickname() == null) { + searchUserRspVO.setHighlightNickname(source.getNickname()); + } + + return searchUserRspVO; + } } diff --git a/hanserwei-framework/hanserwei-common/src/main/java/com/hanserwei/framework/common/utils/NumberUtils.java b/hanserwei-framework/hanserwei-common/src/main/java/com/hanserwei/framework/common/utils/NumberUtils.java new file mode 100644 index 0000000..b43d57f --- /dev/null +++ b/hanserwei-framework/hanserwei-common/src/main/java/com/hanserwei/framework/common/utils/NumberUtils.java @@ -0,0 +1,39 @@ +package com.hanserwei.framework.common.utils; + +import java.math.RoundingMode; +import java.text.DecimalFormat; + +public class NumberUtils { + + /** + * 数字转换字符串 + * + * @param number 数字 + * @return 字符串 + */ + public static String formatNumberString(long number) { + if (number < 10000) { + return String.valueOf(number); // 小于 1 万显示原始数字 + } else if (number < 100000000) { + // 小于 1 亿,显示万单位 + double result = number / 10000.0; + DecimalFormat df = new DecimalFormat("#.#"); // 保留 1 位小数 + df.setRoundingMode(RoundingMode.DOWN); // 禁用四舍五入 + String formatted = df.format(result); + return formatted + "万"; + } else { + return "9999万"; // 超过 1 亿,统一显示 9999万 + } + } + + public static void main(String[] args) { + // 测试 + System.out.println(formatNumberString(1000)); // 1000 + System.out.println(formatNumberString(11130)); // 1.1万 + System.out.println(formatNumberString(26719300)); // 2671.9万 + System.out.println(formatNumberString(10000000)); // 1000万 + System.out.println(formatNumberString(999999)); // 99.9万 + System.out.println(formatNumberString(150000000)); // 超过一亿,展示9999万 + System.out.println(formatNumberString(99999)); // 9.9万 + } +} diff --git a/http-client/gateApi.http b/http-client/gateApi.http index 4727cc8..ec9054b 100644 --- a/http-client/gateApi.http +++ b/http-client/gateApi.http @@ -285,6 +285,6 @@ POST http://localhost:8092/search/user Content-Type: application/json { - "keyword": "憨", + "keyword": "憨憨", "pageNo": 1 }