libilibi项目总结(17)Elasticsearch 的使用
这段代码定义了一个 EsSearchComponent
类,主要用于与 Elasticsearch 进行交互,执行一些基本的操作,如创建索引、保存、更新和删除文档,及搜索操作。以下是对每部分代码的详细解释:
1. 类的依赖注入
@Resource
private AppConfig appConfig;
@Resource
private RestHighLevelClient restHighLevelClient;
@Resource
private UserInfoMapper userInfoMapper;
AppConfig
:存储应用程序的配置信息,如 Elasticsearch 的索引名称。RestHighLevelClient
:Elasticsearch 官方提供的客户端,用于执行与 Elasticsearch 的交互(如查询、索引等)。UserInfoMapper
:用于操作用户信息数据库的 MyBatis 映射器,用于获取与视频相关的用户信息。
2. 检查索引是否存在
private Boolean isExistIndex() throws IOException {
GetIndexRequest getIndexRequest = new GetIndexRequest(appConfig.getEsIndexVideoName());
return restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
}
此方法检查是否已经存在指定名称的索引。如果存在,返回 true
,否则返回 false
。它通过调用 RestHighLevelClient
的 indices().exists()
方法来执行查询。
3. 创建索引
public void createIndex() {
try {
Boolean existIndex = isExistIndex();
if (existIndex) {
return;
}
CreateIndexRequest request = new CreateIndexRequest(appConfig.getEsIndexVideoName());
request.settings(
"{\"analysis\": {\n" +
" \"analyzer\": {\n" +
" \"comma\": {\n" +
" \"type\": \"pattern\",\n" +
" \"pattern\": \",\"\n" +
" }\n" +
" }\n" +
" }}", XContentType.JSON);
request.mapping(
"{\"properties\": {\n" +
" \"videoId\":{\n" +
" \"type\": \"text\",\n" +
" \"index\": false\n" +
" },\n" +
" \"userId\":{\n" +
" \"type\": \"text\",\n" +
" \"index\": false\n" +
" },\n" +
" \"videoCover\":{\n" +
" \"type\": \"text\",\n" +
" \"index\": false\n" +
" },\n" +
" \"videoName\":{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\"\n" +
" },\n" +
" \"tags\":{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"comma\"\n" +
" },\n" +
" \"playCount\":{\n" +
" \"type\":\"integer\",\n" +
" \"index\":false\n" +
" },\n" +
" \"danmuCount\":{\n" +
" \"type\":\"integer\",\n" +
" \"index\":false\n" +
" },\n" +
" \"collectCount\":{\n" +
" \"type\":\"integer\",\n" +
" \"index\":false\n" +
" },\n" +
" \"createTime\":{\n" +
" \"type\":\"date\",\n" +
" \"format\": \"yyyy-MM-dd HH:mm:ss\",\n" +
" \"index\": false\n" +
" }\n" +
" }}", XContentType.JSON);
CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
boolean acknowledged = createIndexResponse.isAcknowledged();
if (!acknowledged) {
throw new BusinessException("初始化es失败");
}
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
log.error("初始化es失败", e);
throw new BusinessException("初始化es失败");
}
}
createIndex()
方法创建一个 Elasticsearch 索引(如果尚未存在)。它设置索引的配置(如分析器)和映射(字段及类型)。如果索引已存在,则直接返回。
- 设置分析器:如
comma
分析器,用于处理逗号分隔的标签。 - 映射:定义索引中每个字段的类型和属性,如
videoId
不进行索引,videoName
使用 IK 分词器进行分词。
4. 检查文档是否存在
private Boolean docExist(String id) throws IOException {
GetRequest getRequest = new GetRequest(appConfig.getEsIndexVideoName(), id);
GetResponse response = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
return response.isExists();
}
该方法检查 Elasticsearch 中是否存在具有指定 id
的文档。
5. 保存文档
public void saveDoc(VideoInfo videoInfo) {
try {
if (docExist(videoInfo.getVideoId())) {
updateDoc(videoInfo);
} else {
VideoInfoEsDto videoInfoEsDto = CopyTools.copy(videoInfo, VideoInfoEsDto.class);
videoInfoEsDto.setCollectCount(0);
videoInfoEsDto.setPlayCount(0);
videoInfoEsDto.setDanmuCount(0);
IndexRequest request = new IndexRequest(appConfig.getEsIndexVideoName());
request.id(videoInfo.getVideoId()).source(JsonUtils.convertObj2Json(videoInfoEsDto), XContentType.JSON);
restHighLevelClient.index(request, RequestOptions.DEFAULT);
}
} catch (Exception e) {
log.error("新增视频到es失败", e);
throw new BusinessException("保存失败");
}
}
saveDoc()
方法用于将视频信息保存到 Elasticsearch。若文档已存在(通过 docExist()
判断),则调用 updateDoc()
进行更新;否则,创建新的索引文档。它还将一些字段(如播放数、弹幕数等)设置为默认值。
6. 更新文档
private void updateDoc(VideoInfo videoInfo) {
try {
videoInfo.setLastUpdateTime(null);
videoInfo.setCreateTime(null);
Map<String, Object> dataMap = new HashMap<>();
Field[] fields = videoInfo.getClass().getDeclaredFields();
for (Field field : fields) {
String methodName = "get" + StringTools.upperCaseFirstLetter(field.getName());
Method method = videoInfo.getClass().getMethod(methodName);
Object object = method.invoke(videoInfo);
if (object != null && object instanceof String && !StringTools.isEmpty(object.toString()) || object != null && !(object instanceof String)) {
dataMap.put(field.getName(), object);
}
}
if (dataMap.isEmpty()) {
return;
}
UpdateRequest updateRequest = new UpdateRequest(appConfig.getEsIndexVideoName(), videoInfo.getVideoId());
updateRequest.doc(dataMap);
restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
} catch (Exception e) {
log.error("新增视频到es失败", e);
throw new BusinessException("保存失败");
}
}
updateDoc()
方法用于更新 Elasticsearch 中的现有文档。它通过反射获取 videoInfo
对象的字段,并将非空字段值更新到 Elasticsearch 中。注意,lastUpdateTime
和 createTime
字段不会更新。
7. 更新计数字段
public void updateDocCount(String videoId, String fieldName, Integer count) {
try {
UpdateRequest updateRequest = new UpdateRequest(appConfig.getEsIndexVideoName(), videoId);
Script script = new Script(ScriptType.INLINE, "painless", "ctx._source." + fieldName + " += params.count", Collections.singletonMap("count", count));
updateRequest.script(script);
restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
} catch (Exception e) {
log.error("更新数量到es失败", e);
throw new BusinessException("保存失败");
}
}
updateDocCount()
方法更新文档中指定字段的计数值。例如,增加播放数、弹幕数等字段。它使用 Elasticsearch 的脚本语言 Painless
执行字段加法。
8. 删除文档
public void delDoc(String videoId) {
try {
DeleteRequest deleteRequest = new DeleteRequest(appConfig.getEsIndexVideoName(), videoId);
restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
} catch (Exception e) {
log.error("从es删除视频失败", e);
throw new BusinessException("删除视频失败");
}
}
delDoc()
方法用于删除 Elasticsearch 中的文档,通过指定 videoId
删除相应的视频文档。
9. 搜索文档
public PaginationResultVO<VideoInfo> search(Boolean highlight, String keyword, Integer orderType, Integer pageNo, Integer pageSize) {
try {
SearchOrderTypeEnum searchOrderTypeEnum = SearchOrderTypeEnum.getByType(orderType);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.multiMatchQuery(keyword, "videoName", "tags"));
if (highlight) {
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("videoName");
highlightBuilder.preTags("<span class='highlight'>");
highlightBuilder.postTags("</span>");
searchSourceBuilder.highlighter(highlightBuilder);
}
searchSourceBuilder.sort("_score", SortOrder.ASC);
if (orderType != null) {
searchSourceBuilder.sort(searchOrderTypeEnum.getField(), SortOrder.DESC);
}
pageNo = pageNo == null ? 1 : pageNo;
pageSize = pageSize == null ? PageSize.SIZE20.getSize() : pageSize;
searchSourceBuilder.size(pageSize);
searchSourceBuilder.from((pageNo - 1) * pageSize);
SearchRequest searchRequest = new SearchRequest(appConfig.getEsIndexVideoName());
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
Integer totalCount = (int) hits.getTotalHits().value;
List<VideoInfo> videoInfoList = new ArrayList<>();
List<String> userIdList = new ArrayList<>();
for (SearchHit hit : hits.getHits()) {
VideoInfo videoInfo = JsonUtils.convertJson2Obj(hit.getSourceAsString(), VideoInfo.class);
if (hit.getHighlightFields().get("videoName") != null) {
videoInfo.setVideoName(hit.getHighlightFields().get("videoName").fragments()[0].string());
}
videoInfoList.add(videoInfo);
userIdList.add(videoInfo.getUserId());
}
UserInfoQuery userInfoQuery = new UserInfoQuery();
userInfoQuery.setUserIdList(userIdList);
List<UserInfo> userInfoList = userInfoMapper.selectList(userInfoQuery);
Map<String, UserInfo> userInfoMap = userInfoList.stream().collect(Collectors.toMap(item -> item.getUserId(), Function.identity(), (data1, data2) -> data2));
videoInfoList.forEach(item -> {
UserInfo userInfo = userInfoMap.get(item.getUserId());
if (userInfo != null) {
item.setNickName(userInfo.getNickName());
}
});
SimplePage page = new SimplePage(pageNo, totalCount, pageSize);
PaginationResultVO<VideoInfo> result = new PaginationResultVO(totalCount, page.getPageSize(), page.getPageNo(), page.getPageTotal(), videoInfoList);
return result;
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
log.error("查询视频到es失败", e);
throw new BusinessException("查询失败");
}
}
search()
方法用于在 Elasticsearch 中进行视频的搜索,支持关键词搜索、排序、分页以及高亮显示。它通过 SearchSourceBuilder
构建查询,使用 multiMatchQuery()
查询 videoName
和 tags
字段,并在需要时加上高亮。查询结果通过分页封装在 PaginationResultVO
对象中。
总结:
- 索引管理:包括创建索引和检查索引是否存在。
- 文档操作:保存、更新、删除文档。
- 搜索功能:支持关键词搜索、排序、分页和高亮显示。
这个组件是一个集成 Elasticsearch 的基础工具类,便于管理视频数据,并且提供了常用的搜索、更新和删除操作。