App端文章搜索
文章搜索:用户在app端输入关键字,搜索文章。
一、ElastcSearch环境搭建
(1)拉取ElasticSearch镜像
docker pull elasticsearch:7.4.0
(2)创建ElasticSearch容器
docker run -id --name elasticsearch -d --restart=always -p 9200:9200 -p 9300:9300 -v /usr/share/elasticsearch/plugins:/usr/share/elasticsearch/plugins -e "discovery.type=single-node" elasticsearch:7.4.0
(3)配置中文分词器 ik
把资料中的elasticsearch-analysis-ik-7.4.0.zip上传到服务器上,放到对应目录(plugins)
(4)使用postman测试
向192.168.200.130:9200/_analyze发送post方法
请求体用json传输参数
二、需求说明
(1)用户输入关键字可搜索文章列表
(2)关键字高亮展示
(4)文章列表展示与home展示一样,当用户点击某一篇文章,可查看文章详情
为了加快检索的效率,在查询的时候不会直接从数据库中查询文章,需要在elasticsearch中进行高速检索。
搜索结果页面展示什么内容:标题 布局 封面图片 发布时间 作者名称 文章id 作者id 静态url
哪些需要索引和分词:标题 内容
使用postman添加映射和查询
1.PUT请求添加映射: http://192.168.200.130:9200/app_info_article
{ "mappings":{ "properties":{ "id":{ "type":"long" }, "publishTime":{ "type":"date" }, "layout":{ "type":"integer" }, "images":{ "type":"keyword", "index": false }, "staticUrl":{ "type":"keyword", "index": false }, "authorId": { "type": "long" }, "authorName": { "type": "text"}, "title":{ "type":"text", "analyzer":"ik_smart" }, "content":{ "type":"text", "analyzer":"ik_smart" } } } }
2.其他操作
GET请求查询映射:http://192.168.200.130:9200/app_info_article
DELETE请求删除索引及映射:http://192.168.200.130:9200/app_info_article
GET请求查询所有文档:http://192.168.200.130:9200/app_info_article/_search
三、数据初始化到索引库
注:当postman查询不到时,使用docker logs -f elasticsearch跟踪es日志,提示"flood stage disk watermark [95%] exceeded",得知磁盘使用率超出阈值,es索引模式变为只读,无法写入数据,会造成数据同步失败。
package com.heima.es; import com.alibaba.fastjson.JSON; import com.heima.es.mapper.ApArticleMapper; import com.heima.es.pojo.SearchArticleVo; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.xcontent.XContentType; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @SpringBootTest @RunWith(SpringRunner.class) public class ApArticleTest { @Autowired private ApArticleMapper apArticleMapper; @Autowired private RestHighLevelClient restHighLevelClient; /** * 注意:数据量的导入,如果数据量过大,需要分页导入 * @throws Exception */ @Test public void init() throws Exception { //1、查询所有符合条件的文章数据 List<SearchArticleVo> searchArticleVos = apArticleMapper.loadArticleList(); //2、批量导入到es索引库 BulkRequest bulkRequest = new BulkRequest("app_info_article"); //索引库名称 for (SearchArticleVo searchArticleVo : searchArticleVos) { IndexRequest indexRequest = new IndexRequest().id(searchArticleVo.getId().toString()) .source(JSON.toJSONString(searchArticleVo), XContentType.JSON); //批量添加数据 bulkRequest.add(indexRequest); } restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT); } }
四、app端文章搜索
创建搜索微服务heiama-leadnews-search
package com.heima.search.service.impl; import com.alibaba.fastjson.JSON; import com.heima.model.common.dtos.ResponseResult; import com.heima.model.common.enums.AppHttpCodeEnum; import com.heima.model.search.dtos.UserSearchDto; import com.heima.search.service.ArticleSearchService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.text.Text; import org.elasticsearch.index.query.*; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightField; import org.elasticsearch.search.sort.SortOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.naming.directory.SearchResult; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @Service @Slf4j public class ArticleSearchServiceImpl implements ArticleSearchService { @Autowired private RestHighLevelClient restHighLevelClient; /** * es文章分页检索 * * @param dto * @return */ @Override public ResponseResult search(UserSearchDto dto) throws IOException { //1.检查参数 if(dto == null || StringUtils.isBlank(dto.getSearchWords())){ return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID); } //2.设置查询条件 SearchRequest searchRequest = new SearchRequest("app_info_article"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //布尔查询 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); //关键字的分词之后查询 QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(dto.getSearchWords()).field("title").field("content").defaultOperator(Operator.OR); boolQueryBuilder.must(queryStringQueryBuilder); //查询小于mindate的数据 RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("publishTime").lt(dto.getMinBehotTime().getTime()); boolQueryBuilder.filter(rangeQueryBuilder); //分页查询 searchSourceBuilder.from(0); searchSourceBuilder.size(dto.getPageSize()); //按照发布时间倒序查询 searchSourceBuilder.sort("publishTime", SortOrder.DESC); //设置高亮 title HighlightBuilder highlightBuilder = new HighlightBuilder(); highlightBuilder.field("title"); highlightBuilder.preTags("<font style='color: red; font-size: inherit;'>"); highlightBuilder.postTags("</font>"); searchSourceBuilder.highlighter(highlightBuilder); searchSourceBuilder.query(boolQueryBuilder); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); //3.结果封装返回 List<Map> list = new ArrayList<>(); SearchHit[] hits = searchResponse.getHits().getHits(); for (SearchHit hit : hits) { String json = hit.getSourceAsString(); Map map = JSON.parseObject(json, Map.class); //处理高亮 if(hit.getHighlightFields() != null && hit.getHighlightFields().size() > 0){ Text[] titles = hit.getHighlightFields().get("title").getFragments(); String title = StringUtils.join(titles); //高亮标题 map.put("h_title",title); }else { //原始标题 map.put("h_title",map.get("title")); } list.add(map); } return ResponseResult.okResult(list); } }