Docker+ElasticSearch+Springboot开发高亮搜索

        1.ElasticSearch安装与运行 

2.ElasticSearch中加入中文分词器

3.安装kibana测试并测试中文分词(可跳过)

4.LogStash同步数据库到ElasticSearch(可跳过,使用docker部署失败,用二进制安装包本地同步可以)

5.搜索代码实现 

注:文章默认你已经安装好了docker环境,且了解springboot和mysql的开发。 


1.ElasticSearch安装与运行 

docker search elasticsearch
docker pull elasticsearch:7.8.0
docker images
docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -e "discovery.type=single-node" -d -p 9200:9200 -p 9300:9300 --name es_docker elasticsearch:7.8.0
docker ps -a   #拿到运行容器elasticsearch 的 id
docker exec -it es_docker /bin/bash
cd ./config
vi elasticsearch.yml


在elasticsearch.yml中添加:

http.cors.enabled: true
http.cors.allow-origin: "*"


重启  elasticsearch容器

docker restart  es_docker


2.ElasticSearch中加入中文分词器


安装 IK 分词器
进入到容器内部安装插件

docker exec -it  es_docker/bin/bash


执行命令

/usr/share/elasticsearch/bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.8.0/elasticsearch-analysis-ik-7.8.0.zip


 成功标志:-> Installed analysis-ik
退出容器:exit
重启容器:

docker restart search

打开kibana界面
http://localhost:5601/
测试分词效果

https://blog.csdn.net/eighthroute/article/details/91389011?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param


或者
安装elasticsearch-head 容器 或者 直接谷歌插件elasticsearch-head(推荐)

通过Dockerfile安装elasticsearch-analysis-ik-7.x.x插件

FROM docker.elastic.co/elasticsearch/elasticsearch:7.8.0
ADD elasticsearch-analysis-ik-7.8.0 /usr/share/elasticsearch/plugins/elasticsearch-analysis-ik-7.8.0


生成的docker镜像

docker build -t="elasticsearch-with-ik:7.8.0" /Users/xubh/WorkSpace/DockerFiles


运行刚才生成的docker镜像:
# 开放9200端口,-e中的设置为允许跨域访问,因为我要使用head插件访问

docker run -p 9200:9200 -p 9300:9300-e ES_JAVA_OPTS="-Xms256m -Xmx256m" -e "http.cors.enabled=true" -e "http.cors.allow-origin=*" elasticsearch-with-ik:7.8.0

成功启动后就能使用head插件访问

 

3.安装kibana测试并测试中文分词(可跳过)

    
安装kibana 
https://blog.csdn.net/weixin_38106322/article/details/107828973
3.1.拉取镜像docker pull kibana:7.8.0
3.2.启动容器

docker run --name kibana -e ELASTICSEARCH_HOSTS=http://宿主主机ip:9200-p5601:5601 -d kibana:7.8.0


3.3.配置kibana.yml

docker exec -it  kibana /bin/bash
cd config
vi kibana.yml
server.name: kibana
server.host: "0.0.0.0"
elasticsearch.hosts: [ "http://search:9200" ]
xpack.monitoring.ui.container.elasticsearch.enabled: true
i18n.locale: "zh-CN"


4.LogStash同步数据库到ElasticSearch(可跳过,使用docker部署失败,用二进制安装包本地同步可以)

docker pull logstash:7.8.0
docker run -d --restart=always --log-driver json-file --log-opt max-size=10m --log-opt max-file=2 -p 5044:5044 --name logstash -v /Users/xubh/WorkSpace/DockerSpace/logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml -v /Users/xubh/WorkSpace/DockerSpace/logstash/conf.d/:/usr/share/logstash/conf.d/ logstash:7.8.0


创建容器后,需要安装logstash-input-jdbc插件。

docker exec -it logstash /bin/bash


docker run -d --restart=always --log-driver json-file --log-opt max-size=10m --log-opt max-file=2 -p 5044:5044 --name logstash -v /Users/xubh/WorkSpace/DockerSpace/logstash/logstash.yml:/usr/share/logstash/config/logstash.yml -v /Users/xubh/WorkSpace/DockerSpace/logstash/pipeline:/usr/share/logstash/pipeline logstash:7.8.0

5.搜索代码实现 

springboot

加入依赖包

<!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch -->
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>7.8.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-high-level-client -->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.8.1</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.73</version>
        </dependency>

增加ElasticSearchConfig

package com.example.esdemo.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author xubh
 */
@Configuration
public class ElasticSearchConfig {

    /**
     * 配置RestHighLevelClient对象
     * 将该对象交给Spring容器去管理
     *
     * @return RestHighLevelClient对象
     */
    @Bean
    public RestHighLevelClient restHighLevelClient() {
        return new RestHighLevelClient(
                RestClient.builder(
                        //若有多个,可以传一个数组
                        new HttpHost("127.0.0.1", 9200, "http")));
    }
}

新增业务对象User

package com.example.esdemo.entity;

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class User {
    private Integer id;
    private String username;
    private String remark;
}

 

测试代码ElasticSearchApiTests

package com.example.esdemo;

import com.alibaba.fastjson.JSONObject;
import com.example.esdemo.entity.User;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.query.WildcardQueryBuilder;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@SpringBootTest
public class ElasticSearchApiTests {

    @Autowired
    @Qualifier("restHighLevelClient")
    RestHighLevelClient client;

    private static String INDEX_NAME = "user_index";

    /**
     * 创建索引测试
     */
    @Test
    void createIndex() throws IOException {
        //7.2版本的esString类型分为两种:text,keyword
        //重要:此处字段配置为中文分词,不配置默认是分字
        String createStructName = "{\n"
                + "    \"properties\": {\n"
                + "      \"" + "username" + "\":{\n"
                + "        \"type\":\"text\",\n"
                + "        \"analyzer\":\"ik_max_word\"\n"
                + "         },\n"
                + "      \"" + "remark" + "\":{\n"
                + "        \"type\":\"text\",\n"
                + "        \"analyzer\":\"ik_max_word\"\n"
                + "         }\n"
                + "    }\n"
                + "  }";
        //1、构建 创建索引的请求
        CreateIndexRequest request = new CreateIndexRequest(INDEX_NAME);//索引名
        request.settings(Settings.builder()
                .put("index.number_of_shards", 1)
                .put("index.number_of_replicas", 0)
                .put("max_result_window", 600000000));
        request.mapping(createStructName, XContentType.JSON);
        //2、客户端执行请求,获取响应
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        //3、打印
        System.out.println("创建成功,创建的索引名为:" + response.index());
    }
    /**
     * 获取索引测试
     */
    @Test
    void getIndex() throws IOException {
        //1、构建 获取索引的请求
        GetIndexRequest request = new GetIndexRequest(INDEX_NAME);
        //2、客户端判断该索引是否存在
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        //3、打印
        System.out.println("该索引是否存在:"+exists);
    }
    /**
     * 删除索引测试
     */
    @Test
    void deleteIndex() throws IOException {
        //1、构建 删除索引请求
        DeleteIndexRequest request = new DeleteIndexRequest(INDEX_NAME);
        //2、客户段执行删除的请求
        AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
        //3、打印
        System.out.println("是否删除成功:"+response.isAcknowledged());
    }

    /**
     * 创建文档
     */
    @Test
    void createDocument() throws IOException {
        User user = new User().setId(1).setUsername("张三");

        //1、构建请求
        IndexRequest request = new IndexRequest(INDEX_NAME);

        //2、设置规则  PUT /user_index/user/_doc/1
        request.id("1");//设置id
        request.timeout(TimeValue.timeValueSeconds(1));//设置超时时间

        //3、将数据放入到请求中,以JSON的格式存放
        request.source(JSONObject.toJSONString(user), XContentType.JSON);

        //4、客户端发送请求,获取响应结果
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);

        //5、打印
        System.out.println("响应结果:"+response.toString());
    }

    /**
     * 获取文档
     */
    @Test
    void getDocument() throws IOException {
        //获取id为1的文档的信息
        GetRequest request = new GetRequest(INDEX_NAME,"1");

        boolean exists = client.exists(request, RequestOptions.DEFAULT);
        System.out.println("文档是否存在:"+exists);
        //如果存在,获取文档信息
        if (exists){
            GetResponse response = client.get(request, RequestOptions.DEFAULT);
            System.out.println("文档内容为:"+response.getSourceAsString());
        }
    }

    /**
     * 更新文档
     */
    @Test
    void updateDocument() throws IOException {
        //更新id为1的文档的信息
        UpdateRequest request = new UpdateRequest(INDEX_NAME, "1");

        User user = new User().setUsername("李四");
        request.doc(JSONObject.toJSONString(user), XContentType.JSON);

        //客户端执行更新请求
        UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
        System.out.println("更新状态:" +response.status());
    }

    /**
     * 删除文档
     */
    @Test
    void deleteDocument() throws IOException {
        //构建删除请求
        DeleteRequest request = new DeleteRequest(INDEX_NAME, "1");
        //客户端执行删除请求,并获取响应结果
        DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
        //打印
        System.out.println("删除状态:"+response.status());
    }

    /**
     * 批量插入数据
     */
    @Test
    void createBulkDocument() throws IOException {

        //构建批量插入的请求
        BulkRequest request = new BulkRequest();
        //设置超时时间
        request.timeout("10s");

        //设置数据
        List<User> list = new ArrayList<>();
        list.add(new User().setId(1).setUsername("张三").setRemark("张三"));
        list.add(new User().setId(2).setUsername("李四").setRemark("李四"));
        list.add(new User().setId(3).setUsername("王五").setRemark("王五"));
        list.add(new User().setId(4).setUsername("赵六").setRemark("赵六"));
        list.add(new User().setId(4).setUsername("中国张三").setRemark("中国张三"));
        list.add(new User().setId(4).setUsername("中国李四").setRemark("中国李四"));
        list.add(new User().setId(4).setUsername("王中国").setRemark("王中国"));
        list.add(new User().setId(4).setUsername("王嫌烦").setRemark("王嫌烦"));
        list.add(new User().setId(4).setUsername("赵国").setRemark("赵国"));
        list.add(new User().setId(4).setUsername("赵国中").setRemark("赵国中"));
        list.add(new User().setId(4).setUsername("赵嫌犯").setRemark("赵嫌犯"));


        //批量插入请求设置
        for (int i = 0; i < list.size(); i++) {
            request.add(
                    new IndexRequest(INDEX_NAME)//设置索引
                            .id(String.valueOf(i+1))//设置文档的id,如果没有指定,会随机生成,自己测试
                            .source(JSONObject.toJSONString(list.get(i)), XContentType.JSON)//设置要添加的资源,类型为JSON
            );
        }
        BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
        System.out.println("批量插入是否失败:"+response.hasFailures());
    }


    /**
     * 查询
     */
    @Test
    void query() throws IOException {
        //1、构建搜索请求
        SearchRequest request = new SearchRequest(INDEX_NAME);

        //2、设置搜索条件,使用该构建器进行查询
        SearchSourceBuilder builder = new SearchSourceBuilder();//生成构建器

        //查询条件我们可以用工具类QueryBuilders来构建
        //QueryBuilders.termQuery():精确匹配
        //QueryBuilders.matchAllQuery():全文匹配
        String value = "李四";
        String fieldName = "username";
        String fieldNameKeyword = fieldName+".keyword";

        //构建精确匹配查询条件
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(fieldNameKeyword, value);
//        MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
//        WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery("username", "张");
        builder.query(termQueryBuilder);

        //3、将搜索条件放入搜索请求中
        request.source(builder);
        //4、客户端执行搜索请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //5、打印测试
        SearchHit[] hits = response.getHits().getHits();
        System.out.println("共查询到"+hits.length+"条数据");
        System.out.println("查询结果:");
        for (int i = 0; i < hits.length; i++) {
            System.out.println(hits[i].getSourceAsString());
        }
    }

    /**
     * 一般查询
     */
    @Test
    void query2() throws IOException {
        queryIndexDataByKeyword("index",new String[]{"content"},"中国嫌犯");
    }

    /**
     * 高亮查询
     */
    @Test
    void query3() throws IOException {
        queryHighlightIndexDataByKeyword(new String[]{"index","user_index"},new String[]{"content","username","remark"},"中国",0,2);
    }

    /**
     * 模糊查询
     * @param indexName 索引名称
     * @param keyword 查询关键字
     * @return path list
     */
    public List<String> queryIndexDataByKeyword(String indexName,String[] filedNames,String keyword) {
        try {
            //查询总数
            CountRequest countRequest = new CountRequest();
            SearchSourceBuilder searchCountBuilder = new SearchSourceBuilder();
            /*searchSourceBuilder.query(
                    QueryBuilders.queryStringQuery(keyword).field(config.getFiledName()));*/
            searchCountBuilder.query(
                    QueryBuilders.wildcardQuery(filedNames[0],"*" + keyword + "*"));
            countRequest.source(searchCountBuilder);
            countRequest.indices(indexName);
            CountResponse countResponse = client.count(countRequest, RequestOptions.DEFAULT);
            long count = countResponse.getCount();
            long pageMax = count / 10000;
            long remainder = count % 10000;
            if (remainder > 0) {
                pageMax++;
            }
            List<String> list = new ArrayList<>();
            for (int pageIndex = 0;pageIndex < pageMax;pageIndex++) {
                WildcardQueryBuilder wildcardQueryBuilder =
                        new WildcardQueryBuilder(filedNames[0],"*" + keyword + "*");
                /*builder.query(QueryBuilders
                        .queryStringQuery(keyword).field(config.getFiledName()));*/
                SearchSourceBuilder builder = new SearchSourceBuilder();
                builder.from(pageIndex * 10000);
                builder.size(10000);
                builder.timeout(new TimeValue(60, TimeUnit.MINUTES));
                builder.query(wildcardQueryBuilder);
                SearchRequest request = new SearchRequest(indexName);
                request.source(builder);
                SearchResponse response = client.search(request, RequestOptions.DEFAULT);
                SearchHit[] hits = response.getHits().getHits();
                for (SearchHit hit : hits) {
                    Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                    String sentence = (String)sourceAsMap.get(filedNames[0]);
                    list.add(sentence);
                }
            }
            System.out.println("存入文件数据数量:"+list.size()+"条数据");
            for (String s:
            list) {
                System.out.println(""+s+"");
            }
            return list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public  void queryHighlightIndexDataByKeyword(String[] indexNames,String[] filedNames,String keyword,int from,int size) throws IOException {
            // 1、创建search请求
            SearchRequest searchRequest = new SearchRequest(indexNames);

            // 2、用SearchSourceBuilder来构造查询请求体 ,请仔细查看它的方法,构造各种查询的方法都在这。
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

            //构造QueryBuilder
            QueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery(keyword,filedNames);
            sourceBuilder.query(matchQueryBuilder);

            //分页设置
			sourceBuilder.from(from);
			sourceBuilder.size(size); ;

            // 高亮设置
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.requireFieldMatch(false);
            //不同字段可有不同设置,如不同标签
            for (String filedName:  filedNames) {
                HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field(filedName);
                highlightTitle.preTags("<strong>").postTags("</strong>");
                highlightBuilder.field(highlightTitle);
            }

            sourceBuilder.highlighter(highlightBuilder);
            searchRequest.source(sourceBuilder);

            //3、发送请求
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

            //4、处理响应
            if(RestStatus.OK.equals(searchResponse.status())) {
                //处理搜索命中文档结果
                SearchHits hits = searchResponse.getHits();
                TotalHits totalHits = hits.getTotalHits();
                System.out.println("totalHits.value : " +  totalHits.value);

                SearchHit[] searchHits = hits.getHits();
                for (SearchHit hit : searchHits) {
                    String index = hit.getIndex();
                    String type = hit.getType();
                    String id = hit.getId();
                    float score = hit.getScore();

                    //取_source字段值
                    //String sourceAsString = hit.getSourceAsString(); //取成json串
                    Map<String, Object> sourceAsMap = hit.getSourceAsMap(); // 取成map对象
                    //从map中取字段值
					/*String title = (String) sourceAsMap.get("title");
					String content  = (String) sourceAsMap.get("content"); */
                    System.out.println("index:" + index + "  type:" + type + "  id:" + id);
                    System.out.println("sourceMap : " +  sourceAsMap);
                    //取高亮结果
                    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                    HighlightField highlight = null;
                    for (String filedName:  filedNames) {
                        highlight = highlightFields.get(filedName);
                        if (highlight != null) {
                            if(highlight != null) {
                                Text[] fragments = highlight.fragments();  //多值的字段会有多个值
                                if(fragments != null) {
                                    String fragmentString = fragments[0].string();
                                    System.out.println(filedName+" highlight : " +  fragmentString);
                                    //可用高亮字符串替换上面sourceAsMap中的对应字段返回到上一级调用
                                    sourceAsMap.put(filedName, fragmentString);
                                }
                            }
                        }
                    }
                }
                System.out.println("<===============>");
                SearchHit[] searchHitsWWWW = hits.getHits();
                for (SearchHit hit : searchHitsWWWW) {
                    Map<String, Object> sourceAsMap = hit.getSourceAsMap(); // 取成map对象
                    System.out.println("sourceMap : " + sourceAsMap);
                }

            }
    }
}

 



{
  "query": {
   //"match": {
      //"content": "中国",
      //"username": "中国"
    //},
    "multi_match": {
      "query": "中国",
      "fields": [
        "content",
        "username"
      ]
    }
  },
  "highlight": {
    "pre_tags": [
      "<tag1>",
      "<tag2>"
    ],
    "post_tags": [
      "</tag1>",
      "</tag2>"
    ],
    "fields": {
      "content": {},
      "username": {}
    }
  }
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值