fix(search):修复用户搜索服务中的空指针异常和高亮逻辑

- 修复了likeTotal字段为null时的空指针异常- 重构用户搜索逻辑,优化查询构建和响应处理
- 移除了过时的Guava Lists依赖,使用ArrayList替代
- 改进了高亮字段处理逻辑,确保正确提取高亮内容
- 更新异常处理类型从Exception到具体的IOException-优化代码结构,添加注释分段标识提高可读性- 调整粉丝总数格式化逻辑,增强空值处理能力
This commit is contained in:
2025-11-02 14:13:10 +08:00
parent 34c7092abc
commit 1335582827
2 changed files with 63 additions and 104 deletions

View File

@@ -249,7 +249,7 @@ public class NoteServiceImpl implements NoteService {
.nickname(nickname)
.updateTime(updateTime)
.highlightTitle(highlightedTitle)
.likeTotal(NumberUtils.formatNumberString(Long.parseLong(likeTotal)))
.likeTotal(likeTotal == null ? "0" : NumberUtils.formatNumberString(Long.parseLong(likeTotal)))
.build());
}

View File

@@ -1,14 +1,16 @@
package com.hanserwei.hannote.search.service.impl;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
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.Highlight;
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;
@@ -19,6 +21,8 @@ import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -31,128 +35,83 @@ public class UserServiceImpl implements UserService {
@Override
public PageResponse<SearchUserRspVO> searchUser(SearchUserReqVO searchUserReqVO) {
// 查询关键词
// --- 2. 获取请求参数 ---
String keyword = searchUserReqVO.getKeyword();
// 当前页码
Integer pageNo = searchUserReqVO.getPageNo();
int pageSize = 10; // 每页展示数据量
int from = (pageNo - 1) * pageSize; // 偏移量
HighlightField nicknameHighlight = HighlightField.of(hf -> hf
// --- 3. 设置分页 ---
int pageSize = 10; // 假设每页大小为10
int from = (pageNo - 1) * pageSize;
// --- 4. 构建 multi_match 查询 ---
Query multiMatchQuery = Query.of(q -> q
.multiMatch(m -> m
.query(keyword)
.type(TextQueryType.PhrasePrefix)
.fields(UserIndex.FIELD_USER_NICKNAME, UserIndex.FIELD_USER_HAN_NOTE_ID)
)
);
// --- 5. 构建排序 ---
SortOptions sortOptions = SortOptions.of(so -> so
.field(f -> f
.field(UserIndex.FIELD_USER_FANS_TOTAL)
.order(SortOrder.Desc)));
// --- 6. 构建高亮 ---
HighlightField nikeNameHighlight = HighlightField.of(hf -> hf
.preTags("<strong>")
.postTags("</strong>")
);
SearchRequest searchRequest = SearchRequest.of(r -> r
Highlight highlight = Highlight.of(h -> h
.fields(NamedValue.of(UserIndex.FIELD_USER_NICKNAME, nikeNameHighlight)));
// --- 7. 构建 SearchRequest ---
SearchRequest searchRequest = SearchRequest.of(s -> s
.index(UserIndex.NAME)
// 1. 构建 Query: multiMatchQuery (RHL 风格的匹配)
.query(q -> q
.multiMatch(m -> m
.query(keyword)
.fields(UserIndex.FIELD_USER_NICKNAME, UserIndex.FIELD_USER_HAN_NOTE_ID)
// 默认使用 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
.query(multiMatchQuery)
.sort(sortOptions)
.highlight(highlight)
.from(from)
.size(pageSize)
);
// 返参 VO 集合
List<SearchUserRspVO> searchUserRspVOS = Lists.newArrayList();
// 总文档数,默认为 0
.size(pageSize));
// --- 8. 执行查询和解析响应 ---
List<SearchUserRspVO> searchUserRspVOS = new ArrayList<>();
long total = 0;
try {
log.info("==> SearchRequest: {}", searchRequest.toString());
// 执行查询请求
log.info("==> searchRequest: {}", searchRequest);
SearchResponse<SearchUserRspVO> searchResponse = client.search(searchRequest, SearchUserRspVO.class);
// 处理搜索结果
List<Hit<SearchUserRspVO>> hits = searchResponse.hits().hits();
// 8.2. 处理响应
if (searchResponse.hits().total() != null) {
total = searchResponse.hits().total().value();
}
searchUserRspVOS = Lists.newArrayList();
log.info("==> 命中文档总数, hits: {}", total);
List<Hit<SearchUserRspVO>> hits = searchResponse.hits().hits();
for (Hit<SearchUserRspVO> hit : hits) {
// 1. 获取原始文档数据 (source)
// 获取source
SearchUserRspVO source = hit.source();
// 2. 获取高亮数据 (highlight)
Map<String, List<String>> highlights = hit.highlight();
// 8.3. 获取高亮字段
String highlightNickname = null;
Map<String, List<String>> highlightFiled = hit.highlight();
if (highlightFiled.containsKey(UserIndex.FIELD_USER_NICKNAME)) {
highlightNickname = highlightFiled.get(UserIndex.FIELD_USER_NICKNAME).getFirst();
}
if (source != null) {
// 3. 调用辅助方法合并数据和高亮
SearchUserRspVO searchUserRspVO = mergeHitToRspVO(source, highlights);
searchUserRspVOS.add(searchUserRspVO);
Long userId = source.getUserId();
String nickname = source.getNickname();
String avatar = source.getAvatar();
String hanNoteId = source.getHanNoteId();
Integer noteTotal = source.getNoteTotal();
String fansTotal = source.getFansTotal();
searchUserRspVOS.add(SearchUserRspVO.builder()
.userId(userId)
.nickname(nickname)
.highlightNickname(highlightNickname)
.avatar(avatar)
.hanNoteId(hanNoteId)
.noteTotal(noteTotal)
.fansTotal(fansTotal == null ? "0" : NumberUtils.formatNumberString(Long.parseLong(fansTotal)))
.build());
}
}
} catch (Exception e) {
log.error("==> 查询 Elasticsearch 异常: ", e);
} catch (IOException e) {
log.error("==> search error: {}", e.getMessage());
}
return PageResponse.success(searchUserRspVOS, pageNo, total);
}
/**
* 将原始文档和高亮数据合并到 SearchUserRspVO
*
* @param source 原始文档数据 (已自动反序列化)
* @param highlights 高亮数据 Map
* @return SearchUserRspVO
*/
private SearchUserRspVO mergeHitToRspVO(SearchUserRspVO source, Map<String, List<String>> 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<String> 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;
}
}