fix(search):修复用户搜索服务中的空指针异常和高亮逻辑
- 修复了likeTotal字段为null时的空指针异常- 重构用户搜索逻辑,优化查询构建和响应处理 - 移除了过时的Guava Lists依赖,使用ArrayList替代 - 改进了高亮字段处理逻辑,确保正确提取高亮内容 - 更新异常处理类型从Exception到具体的IOException-优化代码结构,添加注释分段标识提高可读性- 调整粉丝总数格式化逻辑,增强空值处理能力
This commit is contained in:
@@ -249,7 +249,7 @@ public class NoteServiceImpl implements NoteService {
|
|||||||
.nickname(nickname)
|
.nickname(nickname)
|
||||||
.updateTime(updateTime)
|
.updateTime(updateTime)
|
||||||
.highlightTitle(highlightedTitle)
|
.highlightTitle(highlightedTitle)
|
||||||
.likeTotal(NumberUtils.formatNumberString(Long.parseLong(likeTotal)))
|
.likeTotal(likeTotal == null ? "0" : NumberUtils.formatNumberString(Long.parseLong(likeTotal)))
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
package com.hanserwei.hannote.search.service.impl;
|
package com.hanserwei.hannote.search.service.impl;
|
||||||
|
|
||||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
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.SortOrder;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
|
||||||
import co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType;
|
import co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType;
|
||||||
import co.elastic.clients.elasticsearch.core.SearchRequest;
|
import co.elastic.clients.elasticsearch.core.SearchRequest;
|
||||||
import co.elastic.clients.elasticsearch.core.SearchResponse;
|
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.HighlightField;
|
||||||
import co.elastic.clients.elasticsearch.core.search.Hit;
|
import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||||
import co.elastic.clients.util.NamedValue;
|
import co.elastic.clients.util.NamedValue;
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.hanserwei.framework.common.response.PageResponse;
|
import com.hanserwei.framework.common.response.PageResponse;
|
||||||
import com.hanserwei.framework.common.utils.NumberUtils;
|
import com.hanserwei.framework.common.utils.NumberUtils;
|
||||||
import com.hanserwei.hannote.search.index.UserIndex;
|
import com.hanserwei.hannote.search.index.UserIndex;
|
||||||
@@ -19,6 +21,8 @@ import jakarta.annotation.Resource;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -31,128 +35,83 @@ public class UserServiceImpl implements UserService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResponse<SearchUserRspVO> searchUser(SearchUserReqVO searchUserReqVO) {
|
public PageResponse<SearchUserRspVO> searchUser(SearchUserReqVO searchUserReqVO) {
|
||||||
// 查询关键词
|
// --- 2. 获取请求参数 ---
|
||||||
String keyword = searchUserReqVO.getKeyword();
|
String keyword = searchUserReqVO.getKeyword();
|
||||||
// 当前页码
|
|
||||||
Integer pageNo = searchUserReqVO.getPageNo();
|
Integer pageNo = searchUserReqVO.getPageNo();
|
||||||
|
|
||||||
int pageSize = 10; // 每页展示数据量
|
// --- 3. 设置分页 ---
|
||||||
int from = (pageNo - 1) * pageSize; // 偏移量
|
int pageSize = 10; // 假设每页大小为10
|
||||||
|
int from = (pageNo - 1) * pageSize;
|
||||||
HighlightField nicknameHighlight = HighlightField.of(hf -> hf
|
// --- 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>")
|
.preTags("<strong>")
|
||||||
.postTags("</strong>")
|
.postTags("</strong>")
|
||||||
);
|
);
|
||||||
|
Highlight highlight = Highlight.of(h -> h
|
||||||
|
.fields(NamedValue.of(UserIndex.FIELD_USER_NICKNAME, nikeNameHighlight)));
|
||||||
SearchRequest searchRequest = SearchRequest.of(r -> r
|
// --- 7. 构建 SearchRequest ---
|
||||||
|
SearchRequest searchRequest = SearchRequest.of(s -> s
|
||||||
.index(UserIndex.NAME)
|
.index(UserIndex.NAME)
|
||||||
|
.query(multiMatchQuery)
|
||||||
// 1. 构建 Query: multiMatchQuery (RHL 风格的匹配)
|
.sort(sortOptions)
|
||||||
.query(q -> q
|
.highlight(highlight)
|
||||||
.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
|
|
||||||
.from(from)
|
.from(from)
|
||||||
.size(pageSize)
|
.size(pageSize));
|
||||||
);
|
// --- 8. 执行查询和解析响应 ---
|
||||||
|
List<SearchUserRspVO> searchUserRspVOS = new ArrayList<>();
|
||||||
|
|
||||||
// 返参 VO 集合
|
|
||||||
List<SearchUserRspVO> searchUserRspVOS = Lists.newArrayList();
|
|
||||||
// 总文档数,默认为 0
|
|
||||||
long total = 0;
|
long total = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log.info("==> SearchRequest: {}", searchRequest.toString());
|
log.info("==> searchRequest: {}", searchRequest);
|
||||||
|
|
||||||
// 执行查询请求
|
|
||||||
SearchResponse<SearchUserRspVO> searchResponse = client.search(searchRequest, SearchUserRspVO.class);
|
SearchResponse<SearchUserRspVO> searchResponse = client.search(searchRequest, SearchUserRspVO.class);
|
||||||
|
// 8.2. 处理响应
|
||||||
// 处理搜索结果
|
|
||||||
List<Hit<SearchUserRspVO>> hits = searchResponse.hits().hits();
|
|
||||||
if (searchResponse.hits().total() != null) {
|
if (searchResponse.hits().total() != null) {
|
||||||
total = searchResponse.hits().total().value();
|
total = searchResponse.hits().total().value();
|
||||||
}
|
}
|
||||||
|
log.info("==> 命中文档总数, hits: {}", total);
|
||||||
searchUserRspVOS = Lists.newArrayList();
|
List<Hit<SearchUserRspVO>> hits = searchResponse.hits().hits();
|
||||||
|
|
||||||
for (Hit<SearchUserRspVO> hit : hits) {
|
for (Hit<SearchUserRspVO> hit : hits) {
|
||||||
// 1. 获取原始文档数据 (source)
|
// 获取source
|
||||||
SearchUserRspVO source = hit.source();
|
SearchUserRspVO source = hit.source();
|
||||||
|
// 8.3. 获取高亮字段
|
||||||
// 2. 获取高亮数据 (highlight)
|
String highlightNickname = null;
|
||||||
Map<String, List<String>> highlights = hit.highlight();
|
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) {
|
if (source != null) {
|
||||||
// 3. 调用辅助方法合并数据和高亮
|
Long userId = source.getUserId();
|
||||||
SearchUserRspVO searchUserRspVO = mergeHitToRspVO(source, highlights);
|
String nickname = source.getNickname();
|
||||||
searchUserRspVOS.add(searchUserRspVO);
|
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 (IOException e) {
|
||||||
} catch (Exception e) {
|
log.error("==> search error: {}", e.getMessage());
|
||||||
log.error("==> 查询 Elasticsearch 异常: ", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return PageResponse.success(searchUserRspVOS, pageNo, total);
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user