Compare commits
2 Commits
4e00542371
...
3d33a73462
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d33a73462 | |||
| 4b13e52a29 |
@@ -5,7 +5,7 @@ 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 jakarta.annotation.Resource;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -19,6 +19,9 @@ public class ElasticsearchConfig {
|
||||
@Value("${elasticsearch.host}")
|
||||
private String host;
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Bean
|
||||
public ElasticsearchClient elasticsearchClient() {
|
||||
// 1. 创建底层 RestClient(低级客户端)
|
||||
@@ -26,12 +29,8 @@ public class ElasticsearchConfig {
|
||||
.builder(HttpHost.create(host))
|
||||
.build();
|
||||
|
||||
// 2. 创建 JSON 映射器
|
||||
ObjectMapper mapper = JsonMapper.builder().build();
|
||||
JacksonJsonpMapper jsonpMapper = new JacksonJsonpMapper(mapper);
|
||||
|
||||
// 3. 构建传输层
|
||||
ElasticsearchTransport transport = new RestClientTransport(restClient, jsonpMapper);
|
||||
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper(objectMapper));
|
||||
|
||||
// 4. 创建高层次的 Elasticsearch 客户端
|
||||
return new ElasticsearchClient(transport);
|
||||
|
||||
@@ -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.SearchNoteReqVO;
|
||||
import com.hanserwei.hannote.search.model.vo.SearchNoteRspVO;
|
||||
import com.hanserwei.hannote.search.service.NoteService;
|
||||
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 NoteController {
|
||||
|
||||
@Resource
|
||||
private NoteService noteService;
|
||||
|
||||
@PostMapping("/note")
|
||||
@ApiOperationLog(description = "搜索笔记")
|
||||
public PageResponse<SearchNoteRspVO> searchNote(@RequestBody @Validated SearchNoteReqVO searchNoteReqVO) {
|
||||
return noteService.searchNote(searchNoteReqVO);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.hanserwei.hannote.search.index;
|
||||
|
||||
public class NoteIndex {
|
||||
|
||||
/**
|
||||
* 索引名称
|
||||
*/
|
||||
public static final String NAME = "note";
|
||||
|
||||
/**
|
||||
* 笔记ID
|
||||
*/
|
||||
public static final String FIELD_NOTE_ID = "id";
|
||||
|
||||
/**
|
||||
* 封面
|
||||
*/
|
||||
public static final String FIELD_NOTE_COVER = "cover";
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
public static final String FIELD_NOTE_TITLE = "title";
|
||||
|
||||
/**
|
||||
* 话题名称
|
||||
*/
|
||||
public static final String FIELD_NOTE_TOPIC = "topic";
|
||||
|
||||
/**
|
||||
* 发布者昵称
|
||||
*/
|
||||
public static final String FIELD_NOTE_NICKNAME = "nickname";
|
||||
|
||||
/**
|
||||
* 发布者头像
|
||||
*/
|
||||
public static final String FIELD_NOTE_AVATAR = "avatar";
|
||||
|
||||
/**
|
||||
* 笔记类型
|
||||
*/
|
||||
public static final String FIELD_NOTE_TYPE = "type";
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
public static final String FIELD_NOTE_CREATE_TIME = "create_time";
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
public static final String FIELD_NOTE_UPDATE_TIME = "update_time";
|
||||
|
||||
/**
|
||||
* 笔记被点赞数
|
||||
*/
|
||||
public static final String FIELD_NOTE_LIKE_TOTAL = "like_total";
|
||||
|
||||
/**
|
||||
* 笔记被收藏数
|
||||
*/
|
||||
public static final String FIELD_NOTE_COLLECT_TOTAL = "collect_total";
|
||||
|
||||
/**
|
||||
* 笔记被评论数
|
||||
*/
|
||||
public static final String FIELD_NOTE_COMMENT_TOTAL = "comment_total";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.hanserwei.hannote.search.model.vo;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class SearchNoteReqVO {
|
||||
|
||||
@NotBlank(message = "搜索关键词不能为空")
|
||||
private String keyword;
|
||||
|
||||
@Min(value = 1, message = "页码不能小于 1")
|
||||
private Integer pageNo = 1; // 默认值为第一页
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.hanserwei.hannote.search.model.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class SearchNoteRspVO {
|
||||
|
||||
/**
|
||||
* 笔记ID
|
||||
*/
|
||||
@JsonAlias("id")
|
||||
private Long noteId;
|
||||
|
||||
/**
|
||||
* 封面
|
||||
*/
|
||||
private String cover;
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 标题:关键词高亮
|
||||
*/
|
||||
private String highlightTitle;
|
||||
|
||||
/**
|
||||
* 发布者头像
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 发布者昵称
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 最后一次编辑时间
|
||||
*/
|
||||
@JsonAlias("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 被点赞总数
|
||||
*/
|
||||
@JsonAlias("like_total")
|
||||
private String likeTotal;
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.hanserwei.hannote.search.model.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -25,6 +26,11 @@ public class SearchUserRspVO {
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 昵称:关键词高亮
|
||||
*/
|
||||
private String highlightNickname;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
@@ -33,19 +39,19 @@ public class SearchUserRspVO {
|
||||
/**
|
||||
* 小憨书ID
|
||||
*/
|
||||
@JsonProperty("han_note_id")
|
||||
@JsonAlias("han_note_id")
|
||||
private String hanNoteId;
|
||||
|
||||
/**
|
||||
* 笔记发布总数
|
||||
*/
|
||||
@JsonProperty("note_total")
|
||||
@JsonAlias("note_total")
|
||||
private Integer noteTotal;
|
||||
|
||||
/**
|
||||
* 粉丝总数
|
||||
*/
|
||||
@JsonProperty("fans_total")
|
||||
private Integer fansTotal;
|
||||
@JsonAlias("fans_total")
|
||||
private String fansTotal;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.hanserwei.hannote.search.service;
|
||||
|
||||
import com.hanserwei.framework.common.response.PageResponse;
|
||||
import com.hanserwei.hannote.search.model.vo.SearchNoteReqVO;
|
||||
import com.hanserwei.hannote.search.model.vo.SearchNoteRspVO;
|
||||
|
||||
public interface NoteService {
|
||||
|
||||
/**
|
||||
* 搜索笔记
|
||||
*
|
||||
* @param searchNoteReqVO 搜索笔记请求
|
||||
* @return 搜索笔记响应
|
||||
*/
|
||||
PageResponse<SearchNoteRspVO> searchNote(SearchNoteReqVO searchNoteReqVO);
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package com.hanserwei.hannote.search.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
import co.elastic.clients.elasticsearch._types.SortOrder;
|
||||
import co.elastic.clients.elasticsearch._types.query_dsl.*;
|
||||
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.hanserwei.framework.common.response.PageResponse;
|
||||
import com.hanserwei.framework.common.utils.NumberUtils;
|
||||
import com.hanserwei.hannote.search.index.NoteIndex;
|
||||
import com.hanserwei.hannote.search.model.vo.SearchNoteReqVO;
|
||||
import com.hanserwei.hannote.search.model.vo.SearchNoteRspVO;
|
||||
import com.hanserwei.hannote.search.service.NoteService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class NoteServiceImpl implements NoteService {
|
||||
|
||||
@Resource
|
||||
private ElasticsearchClient client;
|
||||
|
||||
@Override
|
||||
public PageResponse<SearchNoteRspVO> searchNote(SearchNoteReqVO searchNoteReqVO) {
|
||||
// 查询关键词
|
||||
String keyword = searchNoteReqVO.getKeyword();
|
||||
// 当前页码
|
||||
Integer pageNo = searchNoteReqVO.getPageNo();
|
||||
|
||||
int pageSize = 10; // 每页展示数据量
|
||||
int from = (pageNo - 1) * pageSize; // 偏移量
|
||||
|
||||
// 1. 构建基础 Multi-Match Query (原始查询条件)
|
||||
MultiMatchQuery multiMatchQuery = MultiMatchQuery.of(m -> m
|
||||
.query(keyword)
|
||||
// 字段权重设置,与 RHL 的 .field(field, boost) 对应
|
||||
.fields(NoteIndex.FIELD_NOTE_TITLE + "^2.0", NoteIndex.FIELD_NOTE_TOPIC)
|
||||
);
|
||||
Query functionScoreQuery = Query.of(q -> q
|
||||
.functionScore(fs -> fs
|
||||
// 设置初始查询条件
|
||||
.query(innerQuery -> innerQuery.multiMatch(multiMatchQuery))
|
||||
.scoreMode(FunctionScoreMode.Sum)
|
||||
.boostMode(FunctionBoostMode.Sum)
|
||||
// 设置function数组
|
||||
.functions(List.of(
|
||||
// 评分函数1
|
||||
FunctionScore.of(f -> f.fieldValueFactor(fvf -> fvf
|
||||
.field(NoteIndex.FIELD_NOTE_LIKE_TOTAL)
|
||||
.factor(0.5)
|
||||
.modifier(FieldValueFactorModifier.Sqrt)
|
||||
.missing(0.0))),
|
||||
// 评分函数2
|
||||
FunctionScore.of(f -> f.fieldValueFactor(fvf -> fvf
|
||||
.field(NoteIndex.FIELD_NOTE_COLLECT_TOTAL)
|
||||
.factor(0.3)
|
||||
.modifier(FieldValueFactorModifier.Sqrt)
|
||||
.missing(0.0))),
|
||||
// 评分函数3
|
||||
FunctionScore.of(f -> f.fieldValueFactor(fvf -> fvf
|
||||
.field(NoteIndex.FIELD_NOTE_COMMENT_TOTAL)
|
||||
.factor(0.2)
|
||||
.modifier(FieldValueFactorModifier.Sqrt)
|
||||
.missing(0.0)))
|
||||
))
|
||||
)
|
||||
);
|
||||
// 3. 构建 Highlight 配置
|
||||
HighlightField titleHighlight = HighlightField.of(hf -> hf
|
||||
.preTags("<strong>")
|
||||
.postTags("</strong>")
|
||||
);
|
||||
// 4. 构建最终的 SearchRequest
|
||||
SearchRequest searchRequest = SearchRequest.of(r -> r
|
||||
.index(NoteIndex.NAME)
|
||||
.query(functionScoreQuery) // 设置 function_score 查询
|
||||
|
||||
// 排序:按 _score 降序
|
||||
.sort(s -> s.score(d -> d.order(SortOrder.Desc)))
|
||||
|
||||
// 分页
|
||||
.from(from)
|
||||
.size(pageSize)
|
||||
// 高亮
|
||||
.highlight(h -> h
|
||||
.fields(NamedValue.of(NoteIndex.FIELD_NOTE_TITLE, titleHighlight))
|
||||
)
|
||||
);
|
||||
// 返参 VO 集合
|
||||
List<SearchNoteRspVO> searchNoteRspVOS = null;
|
||||
long total = 0;
|
||||
|
||||
try {
|
||||
log.info("==> NoteSearchRequest: {}", searchRequest.toString());
|
||||
|
||||
// ⭐️ 执行查询请求,并自动反序列化文档源到 SearchNoteRspVO
|
||||
SearchResponse<SearchNoteRspVO> searchResponse =
|
||||
client.search(searchRequest, SearchNoteRspVO.class);
|
||||
|
||||
total = searchResponse.hits().total() != null ? searchResponse.hits().total().value() : 0;
|
||||
log.info("==> 命中文档总数, hits: {}", total);
|
||||
|
||||
// ⭐️ 处理搜索结果:合并原始文档和高亮数据
|
||||
searchNoteRspVOS = searchResponse.hits().hits().stream()
|
||||
.map(this::processNoteHit)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("==> 查询 Elasticsearch 异常: ", e);
|
||||
}
|
||||
|
||||
return PageResponse.success(searchNoteRspVOS, pageNo, total);
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助方法:处理 Hit,合并 Source 和 Highlight 数据
|
||||
*/
|
||||
private SearchNoteRspVO processNoteHit(Hit<SearchNoteRspVO> hit) {
|
||||
SearchNoteRspVO rspVO = hit.source();
|
||||
|
||||
if (rspVO == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. ⭐️ 处理高亮字段
|
||||
Map<String, List<String>> highlights = hit.highlight();
|
||||
|
||||
if (CollUtil.isNotEmpty(highlights)) {
|
||||
List<String> titleHighlights = highlights.get(NoteIndex.FIELD_NOTE_TITLE);
|
||||
if (CollUtil.isNotEmpty(titleHighlights)) {
|
||||
// 设置高亮标题
|
||||
rspVO.setHighlightTitle(titleHighlights.getFirst());
|
||||
}
|
||||
}
|
||||
// 3. 确保 highlightTitle 有值 (如果没有高亮结果,使用原始 title)
|
||||
if (rspVO.getHighlightTitle() == null) {
|
||||
rspVO.setHighlightTitle(rspVO.getTitle());
|
||||
}
|
||||
// 4. 处理特殊格式化(如 RHL 代码中的点赞数格式化)
|
||||
if (rspVO.getLikeTotal() != null) {
|
||||
rspVO.setLikeTotal(NumberUtils.formatNumberString(Long.parseLong(rspVO.getLikeTotal())));
|
||||
}
|
||||
return rspVO;
|
||||
}
|
||||
}
|
||||
@@ -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<SearchUserRspVO> 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<SearchUserRspVO> 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("<strong>")
|
||||
.postTags("</strong>")
|
||||
);
|
||||
|
||||
|
||||
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<SearchUserRspVO> searchUserRspVOS = null;
|
||||
List<SearchUserRspVO> searchUserRspVOS = Lists.newArrayList();
|
||||
// 总文档数,默认为 0
|
||||
long total = 0;
|
||||
|
||||
try {
|
||||
log.info("==> SearchRequest:{}", searchRequest);
|
||||
log.info("==> SearchRequest: {}", searchRequest.toString());
|
||||
|
||||
// 执行查询请求
|
||||
SearchResponse<SearchUserRspVO> searchResponse = client.search(searchRequest, SearchUserRspVO.class);
|
||||
|
||||
searchUserRspVOS = Lists.newArrayList();
|
||||
|
||||
// 处理搜索结果
|
||||
List<Hit<SearchUserRspVO>> hits = searchResponse.hits().hits();
|
||||
if (searchResponse.hits().total() != null) {
|
||||
total = searchResponse.hits().total().value();
|
||||
}
|
||||
|
||||
searchUserRspVOS = Lists.newArrayList();
|
||||
|
||||
for (Hit<SearchUserRspVO> hit : hits) {
|
||||
log.info("==> 文档数据: {}", hit.toString());
|
||||
if (hit.source() != null) {
|
||||
SearchUserRspVO searchUserRspVO = getSearchUserRspVO(hit);
|
||||
// 1. 获取原始文档数据 (source)
|
||||
SearchUserRspVO source = hit.source();
|
||||
|
||||
// 2. 获取高亮数据 (highlight)
|
||||
Map<String, List<String>> 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<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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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万
|
||||
}
|
||||
}
|
||||
@@ -285,6 +285,6 @@ POST http://localhost:8092/search/user
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"keyword": "憨",
|
||||
"keyword": "憨憨",
|
||||
"pageNo": 1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user